Skip to content

Commit ac0dfe1

Browse files
Add codec for "aclitem[]" type
Previously the "aclitem" type specified to be transferred in the text mode because Postgres does not support sending it in the binary mode, but the "aclitem[]" did not have a support at all.
1 parent 3e4e3f1 commit ac0dfe1

File tree

4 files changed

+191
-2
lines changed

4 files changed

+191
-2
lines changed

asyncpg/protocol/codecs/array.pyx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,104 @@ cdef arraytext_decode(ConnectionSettings settings, FastReadBuffer buf):
348348
cdef anyarray_decode(ConnectionSettings settings, FastReadBuffer buf):
349349
return array_decode(settings, buf, NULL, NULL)
350350

351+
cdef arrayaclitemid_decode(ConnectionSettings settings, FastReadBuffer buf):
352+
353+
cdef:
354+
# object array_text
355+
unicode array_text
356+
Py_ssize_t array_textlen
357+
Py_ssize_t bgn_idx
358+
Py_ssize_t cur_idx
359+
list result
360+
list escaped_parts
361+
object elem
362+
363+
# Postgres does not have a function to send aclitems in binary mode
364+
# (aclitem_send) and arrays also must be sent in a text mode.
365+
# Parse it manually with the next convention (since it is system info):
366+
# 1. Array's index must begin from 1;
367+
# 2. Delimeter is ','.
368+
369+
array_text = text_decode(settings, buf)
370+
if array_text is None:
371+
cpython.Py_INCREF(array_text)
372+
return array_text
373+
374+
result = cpython.PyList_New(0)
375+
376+
array_textlen = len(array_text)
377+
if array_textlen == 0 or array_text[1] == '}':
378+
return result
379+
380+
if array_text[0] == '[':
381+
idx = array_text.find('=')
382+
raise ValueError('invalid array bounds (must start with 1); got: {}.'
383+
.format(array_text[:idx]))
384+
385+
assert array_text[0] == '{'
386+
387+
cur_idx = 0
388+
while True:
389+
cur_chr = array_text[cur_idx]
390+
391+
if cur_chr == '}':
392+
assert (cur_idx + 1) == array_textlen
393+
break
394+
395+
assert cur_chr in '{,'
396+
# Here should be a check for a delimeter after a delimeter. Usually
397+
# it (empty string) means NULL, but for aclitem it is not possible.
398+
# So do nothing and get the next char
399+
cur_idx += 1
400+
401+
bgn_idx = cur_idx
402+
cur_chr = array_text[cur_idx]
403+
404+
if cur_chr != '"':
405+
# unquoted element
406+
cur_idx += 1
407+
while array_text[cur_idx] not in ',}':
408+
cur_idx += 1
409+
elem = array_text[bgn_idx:cur_idx]
410+
cpython.Py_INCREF(elem)
411+
cpython.PyList_Append(result, elem)
412+
continue
413+
414+
# quoted element
415+
escaped_parts = cpython.PyList_New(0)
416+
cur_idx += 1
417+
bgn_idx = cur_idx
418+
while True:
419+
while array_text[cur_idx] not in '\\"':
420+
cur_idx += 1
421+
422+
if array_text[cur_idx] == '"':
423+
# Closing quote symbol
424+
elem = array_text[bgn_idx:cur_idx]
425+
cpython.PyList_Append(escaped_parts, elem)
426+
break
427+
428+
# Escape symbol '\'.
429+
# Add previous non-empty block and be ready for the next one.
430+
if bgn_idx != cur_idx:
431+
elem = array_text[bgn_idx:cur_idx]
432+
cpython.PyList_Append(escaped_parts, elem)
433+
434+
cur_idx += 1
435+
bgn_idx = cur_idx # the next block begins from the escaped char
436+
437+
cur_idx += 1
438+
439+
elem = ''.join(escaped_parts)
440+
cpython.Py_INCREF(elem)
441+
cpython.PyList_Append(result, elem)
442+
cur_idx += 1 # skip the last double quote symbol
443+
444+
return result
445+
446+
cdef arrayaclitemid_encode(ConnectionSettings settings, WriteBuffer buf, items):
447+
raise NotImplementedError("use postgresql's conversion "
448+
"from text[] to aclitem[]")
351449

352450
cdef init_array_codecs():
353451
register_core_codec(ANYARRAYOID,
@@ -368,4 +466,9 @@ cdef init_array_codecs():
368466
<decode_func>&arraytext_decode,
369467
PG_FORMAT_BINARY)
370468

469+
register_core_codec(_ACLITEMOID,
470+
<encode_func>&arrayaclitemid_encode,
471+
<decode_func>&arrayaclitemid_decode,
472+
PG_FORMAT_TEXT)
473+
371474
init_array_codecs()

asyncpg/protocol/pgtypes.pxi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ DEF INETOID = 869
5050
DEF _TEXTOID = 1009
5151
DEF _OIDOID = 1028
5252
DEF ACLITEMOID = 1033
53+
DEF _ACLITEMOID = 1034
5354
DEF BPCHAROID = 1042
5455
DEF VARCHAROID = 1043
5556
DEF DATEOID = 1082
@@ -95,7 +96,7 @@ DEF EVENT_TRIGGEROID = 3838
9596
DEF REGNAMESPACEOID = 4089
9697
DEF REGROLEOID = 4096
9798

98-
cdef ARRAY_TYPES = (_TEXTOID, _OIDOID,)
99+
cdef ARRAY_TYPES = (_TEXTOID, _OIDOID, _ACLITEMOID,)
99100

100101
TYPEMAP = {
101102
ABSTIMEOID: 'abstime',
@@ -178,5 +179,6 @@ TYPEMAP = {
178179
VOIDOID: 'void',
179180
XIDOID: 'xid',
180181
XMLOID: 'xml',
182+
_ACLITEMOID: 'aclitem[]',
181183
_OIDOID: 'oid[]',
182184
_TEXTOID: 'text[]'}

tests/test_codecs.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,3 +916,87 @@ async def test_table_as_composite(self):
916916
await self.con.execute('''
917917
DROP TABLE tab;
918918
''')
919+
920+
async def test_relacl_array_type(self):
921+
await self.con.execute(r'''
922+
CREATE USER """u1'";
923+
CREATE USER "{u2";
924+
CREATE USER ",u3";
925+
CREATE USER "u4}";
926+
CREATE USER "u5""";
927+
CREATE USER "u6\""";
928+
CREATE USER "u7\";
929+
CREATE USER norm1;
930+
CREATE USER norm2;
931+
CREATE TABLE t0 (); GRANT SELECT ON t0 TO norm1;
932+
CREATE TABLE t1 (); GRANT SELECT ON t1 TO """u1'";
933+
CREATE TABLE t2 (); GRANT SELECT ON t2 TO "{u2";
934+
CREATE TABLE t3 (); GRANT SELECT ON t3 TO ",u3";
935+
CREATE TABLE t4 (); GRANT SELECT ON t4 TO "u4}";
936+
CREATE TABLE t5 (); GRANT SELECT ON t5 TO "u5""";
937+
CREATE TABLE t6 (); GRANT SELECT ON t6 TO "u6\""";
938+
CREATE TABLE t7 (); GRANT SELECT ON t7 TO "u7\";
939+
940+
CREATE TABLE a1 ();
941+
GRANT SELECT ON a1 TO """u1'";
942+
GRANT SELECT ON a1 TO "{u2";
943+
GRANT SELECT ON a1 TO ",u3";
944+
GRANT SELECT ON a1 TO "norm1";
945+
GRANT SELECT ON a1 TO "u4}";
946+
GRANT SELECT ON a1 TO "u5""";
947+
GRANT SELECT ON a1 TO "u6\""";
948+
GRANT SELECT ON a1 TO "u7\";
949+
GRANT SELECT ON a1 TO "norm2";
950+
951+
CREATE TABLE a2 ();
952+
GRANT SELECT ON a2 TO """u1'" WITH GRANT OPTION;
953+
GRANT SELECT ON a2 TO "{u2" WITH GRANT OPTION;
954+
GRANT SELECT ON a2 TO ",u3" WITH GRANT OPTION;
955+
GRANT SELECT ON a2 TO "norm1" WITH GRANT OPTION;
956+
GRANT SELECT ON a2 TO "u4}" WITH GRANT OPTION;
957+
GRANT SELECT ON a2 TO "u5""" WITH GRANT OPTION;
958+
GRANT SELECT ON a2 TO "u6\""" WITH GRANT OPTION;
959+
GRANT SELECT ON a2 TO "u7\" WITH GRANT OPTION;
960+
961+
SET SESSION AUTHORIZATION """u1'"; GRANT SELECT ON a2 TO "norm2";
962+
SET SESSION AUTHORIZATION "{u2"; GRANT SELECT ON a2 TO "norm2";
963+
SET SESSION AUTHORIZATION ",u3"; GRANT SELECT ON a2 TO "norm2";
964+
SET SESSION AUTHORIZATION "u4}"; GRANT SELECT ON a2 TO "norm2";
965+
SET SESSION AUTHORIZATION "u5"""; GRANT SELECT ON a2 TO "norm2";
966+
SET SESSION AUTHORIZATION "u6\"""; GRANT SELECT ON a2 TO "norm2";
967+
SET SESSION AUTHORIZATION "u7\"; GRANT SELECT ON a2 TO "norm2";
968+
RESET SESSION AUTHORIZATION;
969+
''')
970+
971+
try:
972+
rows = await self.con.fetch('''
973+
SELECT relacl, relacl::text[] AS chk, relacl::text[]::text AS text_
974+
FROM pg_catalog.pg_class
975+
WHERE relacl IS NOT NULL
976+
''')
977+
978+
for row in rows:
979+
self.assertEqual(row['relacl'], row['chk'],)
980+
981+
finally:
982+
await self.con.execute(r'''
983+
DROP TABLE t0;
984+
DROP TABLE t1;
985+
DROP TABLE t2;
986+
DROP TABLE t3;
987+
DROP TABLE t4;
988+
DROP TABLE t5;
989+
DROP TABLE t6;
990+
DROP TABLE t7;
991+
DROP TABLE a1;
992+
DROP TABLE a2;
993+
DROP USER """u1'";
994+
DROP USER "{u2";
995+
DROP USER ",u3";
996+
DROP USER "u4}";
997+
DROP USER "u5""";
998+
DROP USER "u6\""";
999+
DROP USER "u7\";
1000+
DROP USER norm1;
1001+
DROP USER norm2;
1002+
''')

tools/generate_type_map.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# Array types with builtin codecs, necessary for codec
1717
# bootstrap to work
1818
#
19-
_BUILTIN_ARRAYS = ('_text', '_oid')
19+
_BUILTIN_ARRAYS = ('_text', '_oid', '_aclitem')
2020

2121
_INVALIDOID = 0
2222

0 commit comments

Comments
 (0)