@@ -20,6 +20,113 @@ type queryResponseExpectation struct {
2020 values []string
2121}
2222
23+ func TestHandleQueryExpandArray (t * testing.T ) {
24+ queryHandler := initQueryHandlerForTest (t )
25+ tests := []struct {
26+ query string
27+ columns []string
28+ rows [][]string
29+ }{
30+ {
31+ query : "SELECT information_schema._pg_expandarray(ARRAY[1,2])" ,
32+ columns : []string {"_pg_expandarray" },
33+ rows : [][]string {{"(1,1)" }, {"(2,2)" }},
34+ },
35+ {
36+ query : "SELECT (information_schema._pg_expandarray(ARRAY[10,NULL,30])).x AS x, (information_schema._pg_expandarray(ARRAY[10,NULL,30])).n AS n" ,
37+ columns : []string {"x" , "n" },
38+ rows : [][]string {{"10" , "1" }, {"" , "2" }, {"30" , "3" }},
39+ },
40+ {
41+ query : "SELECT * FROM information_schema._pg_expandarray(ARRAY[10,20])" ,
42+ columns : []string {"x" , "n" },
43+ rows : [][]string {{"10" , "1" }, {"20" , "2" }},
44+ },
45+ {
46+ query : "SELECT information_schema._pg_expandarray(ARRAY[]::integer[])" ,
47+ columns : []string {"_pg_expandarray" },
48+ },
49+ {
50+ query : "SELECT information_schema._pg_expandarray(NULL::integer[])" ,
51+ columns : []string {"_pg_expandarray" },
52+ },
53+ {
54+ query : "SELECT information_schema._pg_expandarray(ARRAY[NULL,1])" ,
55+ columns : []string {"_pg_expandarray" },
56+ rows : [][]string {{"(,1)" }, {"(1,2)" }},
57+ },
58+ {
59+ query : `SELECT information_schema._pg_expandarray(ARRAY['', 'a,b', 'a"b', 'a\b'])` ,
60+ columns : []string {"_pg_expandarray" },
61+ rows : [][]string {{`("",1)` }, {`("a,b",2)` }, {`("a""b",3)` }, {`("a\\b",4)` }},
62+ },
63+ {
64+ // JDBC expands the same array twice and accesses the composite in an outer query.
65+ query : `SELECT result.key_seq, (result.keys).x AS column_number FROM (
66+ SELECT (information_schema._pg_expandarray(i.indkey)).n AS key_seq,
67+ information_schema._pg_expandarray(i.indkey) AS keys
68+ FROM (SELECT ARRAY[3,1]::smallint[] AS indkey) i
69+ ) result ORDER BY result.key_seq` ,
70+ columns : []string {"key_seq" , "column_number" },
71+ rows : [][]string {{"1" , "3" }, {"2" , "1" }},
72+ },
73+ }
74+ for _ , test := range tests {
75+ t .Run (test .query , func (t * testing.T ) {
76+ messages , err := queryHandler .HandleSimpleQuery (test .query )
77+ if err != nil {
78+ t .Fatal (err )
79+ }
80+ if len (messages ) != len (test .rows )+ 2 {
81+ t .Fatalf ("message count = %d, want %d" , len (messages ), len (test .rows )+ 2 )
82+ }
83+ description := messages [0 ].(* pgproto3.RowDescription )
84+ columns := make ([]string , len (description .Fields ))
85+ for i , field := range description .Fields {
86+ columns [i ] = string (field .Name )
87+ }
88+ if ! slices .Equal (columns , test .columns ) {
89+ t .Fatalf ("columns = %v, want %v" , columns , test .columns )
90+ }
91+ if test .columns [0 ] == "_pg_expandarray" && description .Fields [0 ].DataTypeOID != pgtype .RecordOID {
92+ t .Fatalf ("record OID = %d, want %d" , description .Fields [0 ].DataTypeOID , pgtype .RecordOID )
93+ }
94+ for i , row := range test .rows {
95+ assertDataRow (t , messages [i + 1 ], row )
96+ }
97+ })
98+ }
99+ }
100+
101+ func TestHandleQueryJDBCPrimaryKeysWithoutPrimaryKey (t * testing.T ) {
102+ queryHandler := initQueryHandlerForTest (t )
103+ if _ , err := queryHandler .ServerDuckdbClient .ExecContext (context .Background (), "CREATE SCHEMA audit; CREATE TABLE audit.changes(id INTEGER)" ); err != nil {
104+ t .Fatal (err )
105+ }
106+ // pgjdbc getPrimaryKeys query, including the composite field access used by Airbyte.
107+ assertQueryResponse (t , queryHandler , queryResponseExpectation {
108+ query : `SELECT result.TABLE_CAT AS "TABLE_CAT", result.TABLE_SCHEM AS "TABLE_SCHEM",
109+ result.TABLE_NAME AS "TABLE_NAME", result.COLUMN_NAME AS "COLUMN_NAME",
110+ result.KEY_SEQ AS "KEY_SEQ", result.PK_NAME AS "PK_NAME"
111+ FROM (
112+ SELECT current_database() AS TABLE_CAT, n.nspname AS TABLE_SCHEM,
113+ ct.relname AS TABLE_NAME, a.attname AS COLUMN_NAME,
114+ (information_schema._pg_expandarray(i.indkey)).n AS KEY_SEQ, ci.relname AS PK_NAME,
115+ information_schema._pg_expandarray(i.indkey) AS KEYS, a.attnum AS A_ATTNUM,
116+ i.indnkeyatts AS NUM_KEYS
117+ FROM pg_catalog.pg_class ct
118+ JOIN pg_catalog.pg_attribute a ON (ct.oid = a.attrelid)
119+ JOIN pg_catalog.pg_namespace n ON (ct.relnamespace = n.oid)
120+ JOIN pg_catalog.pg_index i ON (a.attrelid = i.indrelid)
121+ JOIN pg_catalog.pg_class ci ON (ci.oid = i.indexrelid)
122+ WHERE true AND n.nspname = 'audit' AND ct.relname = 'changes' AND i.indisprimary
123+ ) result
124+ WHERE result.A_ATTNUM = (result.KEYS).x AND result.KEY_SEQ <= result.NUM_KEYS
125+ ORDER BY result.table_name, result.pk_name, result.key_seq` ,
126+ columnNames : []string {"TABLE_CAT" , "TABLE_SCHEM" , "TABLE_NAME" , "COLUMN_NAME" , "KEY_SEQ" , "PK_NAME" },
127+ })
128+ }
129+
23130func TestHandleQueryFixedSizeFloatArray (t * testing.T ) {
24131 queryHandler := initQueryHandlerForTest (t )
25132 if _ , err := queryHandler .ServerDuckdbClient .ExecContext (
@@ -488,18 +595,16 @@ func TestHandleQueryExpressions(t *testing.T) {
488595 columnNames : []string {"index" },
489596 values : []string {"1" },
490597 },
491- // Temporarily disabled: _pg_expandarray is registered as a DuckDB table
492- // function, but PostgreSQL permits this composite-returning scalar syntax.
493- // {
494- // query: "SELECT (information_schema._pg_expandarray(ARRAY[10])).n",
495- // columnNames: []string{"n"},
496- // values: []string{"1"},
497- // },
498- // {
499- // query: "SELECT (information_schema._pg_expandarray(ARRAY[10])).x AS value",
500- // columnNames: []string{"value"},
501- // values: []string{"10"},
502- // },
598+ {
599+ query : "SELECT (information_schema._pg_expandarray(ARRAY[10])).n" ,
600+ columnNames : []string {"n" },
601+ values : []string {"1" },
602+ },
603+ {
604+ query : "SELECT (information_schema._pg_expandarray(ARRAY[10])).x AS value" ,
605+ columnNames : []string {"value" },
606+ values : []string {"10" },
607+ },
503608 {
504609 query : "SELECT s.usename, r.rolconfig FROM pg_catalog.pg_shadow s LEFT JOIN pg_catalog.pg_roles r ON s.usename = r.rolname" ,
505610 columnNames : []string {"usename" , "rolconfig" },
0 commit comments