Skip to content

Add codec for "aclitem[]" type #58

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 105 additions & 2 deletions asyncpg/protocol/codecs/array.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ cdef inline array_decode(ConnectionSettings settings, FastReadBuffer buf,

if ndims > ARRAY_MAXDIM:
raise RuntimeError(
'number of array dimensions exceed the maximum expected ({})'.
format(ARRAY_MAXDIM))
'number of array dimensions ({}) exceed the maximum expected ({})'.
format(ndims, ARRAY_MAXDIM))

if decoder == NULL:
# No decoder is known beforehand, look it up
Expand Down Expand Up @@ -348,6 +348,104 @@ cdef arraytext_decode(ConnectionSettings settings, FastReadBuffer buf):
cdef anyarray_decode(ConnectionSettings settings, FastReadBuffer buf):
return array_decode(settings, buf, NULL, NULL)

cdef arrayaclitemid_decode(ConnectionSettings settings, FastReadBuffer buf):

cdef:
# object array_text
unicode array_text
Py_ssize_t array_textlen
Py_ssize_t bgn_idx
Py_ssize_t cur_idx
list result
list escaped_parts
object elem

# Postgres does not have a function to send aclitems in binary mode
# (aclitem_send) and arrays also must be sent in a text mode.
# Parse it manually with the next convention (since it is system info):
# 1. Array's index must begin from 1;
# 2. Delimeter is ','.

array_text = text_decode(settings, buf)
if array_text is None:
cpython.Py_INCREF(array_text)
return array_text

result = cpython.PyList_New(0)

array_textlen = len(array_text)
if array_textlen == 0 or array_text[1] == '}':
return result

if array_text[0] == '[':
idx = array_text.find('=')
raise ValueError('invalid array bounds (must start with 1); got: {}.'
.format(array_text[:idx]))

assert array_text[0] == '{'

cur_idx = 0
while True:
cur_chr = array_text[cur_idx]

if cur_chr == '}':
assert (cur_idx + 1) == array_textlen
break

assert cur_chr in '{,'
# Here should be a check for a delimeter after a delimeter. Usually
# it (empty string) means NULL, but for aclitem it is not possible.
# So do nothing and get the next char
cur_idx += 1

bgn_idx = cur_idx
cur_chr = array_text[cur_idx]

if cur_chr != '"':
# unquoted element
cur_idx += 1
while array_text[cur_idx] not in ',}':
cur_idx += 1
elem = array_text[bgn_idx:cur_idx]
cpython.Py_INCREF(elem)
cpython.PyList_Append(result, elem)
continue

# quoted element
escaped_parts = cpython.PyList_New(0)
cur_idx += 1
bgn_idx = cur_idx
while True:
while array_text[cur_idx] not in '\\"':
cur_idx += 1

if array_text[cur_idx] == '"':
# Closing quote symbol
elem = array_text[bgn_idx:cur_idx]
cpython.PyList_Append(escaped_parts, elem)
break

# Escape symbol '\'.
# Add previous non-empty block and be ready for the next one.
if bgn_idx != cur_idx:
elem = array_text[bgn_idx:cur_idx]
cpython.PyList_Append(escaped_parts, elem)

cur_idx += 1
bgn_idx = cur_idx # the next block begins from the escaped char

cur_idx += 1

elem = ''.join(escaped_parts)
cpython.Py_INCREF(elem)
cpython.PyList_Append(result, elem)
cur_idx += 1 # skip the last double quote symbol

return result

cdef arrayaclitemid_encode(ConnectionSettings settings, WriteBuffer buf, items):
raise NotImplementedError("use postgresql's conversion "
"from text[] to aclitem[]")

cdef init_array_codecs():
register_core_codec(ANYARRAYOID,
Expand All @@ -368,4 +466,9 @@ cdef init_array_codecs():
<decode_func>&arraytext_decode,
PG_FORMAT_BINARY)

register_core_codec(_ACLITEMOID,
<encode_func>&arrayaclitemid_encode,
<decode_func>&arrayaclitemid_decode,
PG_FORMAT_TEXT)

init_array_codecs()
140 changes: 71 additions & 69 deletions asyncpg/protocol/pgtypes.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ DEF INETOID = 869
DEF _TEXTOID = 1009
DEF _OIDOID = 1028
DEF ACLITEMOID = 1033
DEF _ACLITEMOID = 1034
DEF BPCHAROID = 1042
DEF VARCHAROID = 1043
DEF DATEOID = 1082
Expand Down Expand Up @@ -95,88 +96,89 @@ DEF EVENT_TRIGGEROID = 3838
DEF REGNAMESPACEOID = 4089
DEF REGROLEOID = 4096

cdef ARRAY_TYPES = (_TEXTOID, _OIDOID,)
cdef ARRAY_TYPES = (_TEXTOID, _OIDOID, _ACLITEMOID,)

TYPEMAP = {
NUMERICOID: 'numeric',
INTERVALOID: 'interval',
TIMETZOID: 'timetz',
CHAROID: 'char',
FDW_HANDLEROID: 'fdw_handler',
REGTYPEOID: 'regtype',
REGOPEROID: 'regoper',
ABSTIMEOID: 'abstime',
ACLITEMOID: 'aclitem',
TXID_SNAPSHOTOID: 'txid_snapshot',
REGDICTIONARYOID: 'regdictionary',
POINTOID: 'point',
OIDOID: 'oid',
PG_NODE_TREEOID: 'pg_node_tree',
REFCURSOROID: 'refcursor',
REGNAMESPACEOID: 'regnamespace',
TIMESTAMPOID: 'timestamp',
BYTEAOID: 'bytea',
REGCONFIGOID: 'regconfig',
UUIDOID: 'uuid',
FLOAT4OID: 'float4',
SMGROID: 'smgr',
BOOLOID: 'bool',
INT4OID: 'int4',
MACADDROID: 'macaddr',
TSM_HANDLEROID: 'tsm_handler',
REGPROCEDUREOID: 'regprocedure',
RELTIMEOID: 'reltime',
DATEOID: 'date',
_OIDOID: 'oid[]',
TSQUERYOID: 'tsquery',
LINEOID: 'line',
PG_LSNOID: 'pg_lsn',
JSONOID: 'json',
POLYGONOID: 'polygon',
XMLOID: 'xml',
INT2OID: 'int2',
TINTERVALOID: 'tinterval',
ANYARRAYOID: 'anyarray',
NAMEOID: 'name',
TIDOID: 'tid',
ANYELEMENTOID: 'anyelement',
ANYENUMOID: 'anyenum',
ANYNONARRAYOID: 'anynonarray',
ANYOID: 'any',
ANYRANGEOID: 'anyrange',
BITOID: 'bit',
BOOLOID: 'bool',
BOXOID: 'box',
BPCHAROID: 'bpchar',
BYTEAOID: 'bytea',
CHAROID: 'char',
CIDOID: 'cid',
TIMESTAMPTZOID: 'timestamptz',
CIDROID: 'cidr',
REGCLASSOID: 'regclass',
INT8OID: 'int8',
CIRCLEOID: 'circle',
CSTRINGOID: 'cstring',
DATEOID: 'date',
EVENT_TRIGGEROID: 'event_trigger',
FDW_HANDLEROID: 'fdw_handler',
FLOAT4OID: 'float4',
FLOAT8OID: 'float8',
REGROLEOID: 'regrole',
CIRCLEOID: 'circle',
ANYNONARRAYOID: 'anynonarray',
GTSVECTOROID: 'gtsvector',
ABSTIMEOID: 'abstime',
PATHOID: 'path',
OPAQUEOID: 'opaque',
ANYOID: 'any',
TIMEOID: 'time',
ANYENUMOID: 'anyenum',
VOIDOID: 'void',
ANYELEMENTOID: 'anyelement',
LSEGOID: 'lseg',
LANGUAGE_HANDLEROID: 'language_handler',
INETOID: 'inet',
REGPROCOID: 'regproc',
EVENT_TRIGGEROID: 'event_trigger',
TEXTOID: 'text',
BOXOID: 'box',
INT2OID: 'int2',
INT4OID: 'int4',
INT8OID: 'int8',
INTERNALOID: 'internal',
VARBITOID: 'varbit',
XIDOID: 'xid',
UNKNOWNOID: 'unknown',
PG_DDL_COMMANDOID: 'pg_ddl_command',
BITOID: 'bit',
INTERVALOID: 'interval',
JSONBOID: 'jsonb',
JSONOID: 'json',
LANGUAGE_HANDLEROID: 'language_handler',
LINEOID: 'line',
LSEGOID: 'lseg',
MACADDROID: 'macaddr',
MONEYOID: 'money',
VARCHAROID: 'varchar',
TSVECTOROID: 'tsvector',
_TEXTOID: 'text[]',
NAMEOID: 'name',
NUMERICOID: 'numeric',
OIDOID: 'oid',
OPAQUEOID: 'opaque',
PATHOID: 'path',
PG_DDL_COMMANDOID: 'pg_ddl_command',
PG_LSNOID: 'pg_lsn',
PG_NODE_TREEOID: 'pg_node_tree',
POINTOID: 'point',
POLYGONOID: 'polygon',
RECORDOID: 'record',
JSONBOID: 'jsonb',
REFCURSOROID: 'refcursor',
REGCLASSOID: 'regclass',
REGCONFIGOID: 'regconfig',
REGDICTIONARYOID: 'regdictionary',
REGNAMESPACEOID: 'regnamespace',
REGOPERATOROID: 'regoperator',
REGOPEROID: 'regoper',
REGPROCEDUREOID: 'regprocedure',
REGPROCOID: 'regproc',
REGROLEOID: 'regrole',
REGTYPEOID: 'regtype',
RELTIMEOID: 'reltime',
SMGROID: 'smgr',
TEXTOID: 'text',
TIDOID: 'tid',
TIMEOID: 'time',
TIMESTAMPOID: 'timestamp',
TIMESTAMPTZOID: 'timestamptz',
TIMETZOID: 'timetz',
TINTERVALOID: 'tinterval',
TRIGGEROID: 'trigger',
BPCHAROID: 'bpchar'}
TSM_HANDLEROID: 'tsm_handler',
TSQUERYOID: 'tsquery',
TSVECTOROID: 'tsvector',
TXID_SNAPSHOTOID: 'txid_snapshot',
UNKNOWNOID: 'unknown',
UUIDOID: 'uuid',
VARBITOID: 'varbit',
VARCHAROID: 'varchar',
VOIDOID: 'void',
XIDOID: 'xid',
XMLOID: 'xml',
_ACLITEMOID: 'aclitem[]',
_OIDOID: 'oid[]',
_TEXTOID: 'text[]'}
84 changes: 84 additions & 0 deletions tests/test_codecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -916,3 +916,87 @@ async def test_table_as_composite(self):
await self.con.execute('''
DROP TABLE tab;
''')

async def test_relacl_array_type(self):
await self.con.execute(r'''
CREATE USER """u1'";
CREATE USER "{u2";
CREATE USER ",u3";
CREATE USER "u4}";
CREATE USER "u5""";
CREATE USER "u6\""";
CREATE USER "u7\";
CREATE USER norm1;
CREATE USER norm2;
CREATE TABLE t0 (); GRANT SELECT ON t0 TO norm1;
CREATE TABLE t1 (); GRANT SELECT ON t1 TO """u1'";
CREATE TABLE t2 (); GRANT SELECT ON t2 TO "{u2";
CREATE TABLE t3 (); GRANT SELECT ON t3 TO ",u3";
CREATE TABLE t4 (); GRANT SELECT ON t4 TO "u4}";
CREATE TABLE t5 (); GRANT SELECT ON t5 TO "u5""";
CREATE TABLE t6 (); GRANT SELECT ON t6 TO "u6\""";
CREATE TABLE t7 (); GRANT SELECT ON t7 TO "u7\";

CREATE TABLE a1 ();
GRANT SELECT ON a1 TO """u1'";
GRANT SELECT ON a1 TO "{u2";
GRANT SELECT ON a1 TO ",u3";
GRANT SELECT ON a1 TO "norm1";
GRANT SELECT ON a1 TO "u4}";
GRANT SELECT ON a1 TO "u5""";
GRANT SELECT ON a1 TO "u6\""";
GRANT SELECT ON a1 TO "u7\";
GRANT SELECT ON a1 TO "norm2";

CREATE TABLE a2 ();
GRANT SELECT ON a2 TO """u1'" WITH GRANT OPTION;
GRANT SELECT ON a2 TO "{u2" WITH GRANT OPTION;
GRANT SELECT ON a2 TO ",u3" WITH GRANT OPTION;
GRANT SELECT ON a2 TO "norm1" WITH GRANT OPTION;
GRANT SELECT ON a2 TO "u4}" WITH GRANT OPTION;
GRANT SELECT ON a2 TO "u5""" WITH GRANT OPTION;
GRANT SELECT ON a2 TO "u6\""" WITH GRANT OPTION;
GRANT SELECT ON a2 TO "u7\" WITH GRANT OPTION;

SET SESSION AUTHORIZATION """u1'"; GRANT SELECT ON a2 TO "norm2";
SET SESSION AUTHORIZATION "{u2"; GRANT SELECT ON a2 TO "norm2";
SET SESSION AUTHORIZATION ",u3"; GRANT SELECT ON a2 TO "norm2";
SET SESSION AUTHORIZATION "u4}"; GRANT SELECT ON a2 TO "norm2";
SET SESSION AUTHORIZATION "u5"""; GRANT SELECT ON a2 TO "norm2";
SET SESSION AUTHORIZATION "u6\"""; GRANT SELECT ON a2 TO "norm2";
SET SESSION AUTHORIZATION "u7\"; GRANT SELECT ON a2 TO "norm2";
RESET SESSION AUTHORIZATION;
''')

try:
rows = await self.con.fetch('''
SELECT relacl, relacl::text[] AS chk, relacl::text[]::text AS text_
FROM pg_catalog.pg_class
WHERE relacl IS NOT NULL
''')

for row in rows:
self.assertEqual(row['relacl'], row['chk'],)

finally:
await self.con.execute(r'''
DROP TABLE t0;
DROP TABLE t1;
DROP TABLE t2;
DROP TABLE t3;
DROP TABLE t4;
DROP TABLE t5;
DROP TABLE t6;
DROP TABLE t7;
DROP TABLE a1;
DROP TABLE a2;
DROP USER """u1'";
DROP USER "{u2";
DROP USER ",u3";
DROP USER "u4}";
DROP USER "u5""";
DROP USER "u6\""";
DROP USER "u7\";
DROP USER norm1;
DROP USER norm2;
''')
Loading