@@ -71,22 +71,20 @@ CREATE FUNCTION to_sql(selector text, predicate text, body text)
71
71
RETURNS text AS $$
72
72
DECLARE
73
73
q text ;
74
- tab regclass = selector::regclass; -- Without a parent, we need a table
74
+ tab regclass = selector::regclass; -- We need a table, to select from
75
75
cols text [];
76
76
col text ;
77
77
sub record;
78
78
pk text = NULL ;
79
79
fk record;
80
80
subselects text [];
81
+ predicates text [];
81
82
BEGIN
82
- q := ' FROM ' || tab; -- Regclass is auto-escaped
83
83
body := substr(body, 2 , length(body)- 2 );
84
84
IF predicate IS NOT NULL THEN
85
85
SELECT array_to_string(array_agg(format(' %I' , col)), ' , ' )
86
86
FROM unnest(graphql .pk (tab)) INTO pk;
87
- SELECT array_to_string(array_agg(format(' %I' , col)), ' , ' )
88
- FROM graphql .pk (tab) INTO pk;
89
- q := q || E' \n WHERE (' || pk || ' ) = (' || predicate || ' )' ;
87
+ predicates := predicates || format(' (%s) = (%s)' , pk, predicate);
90
88
-- - Compound primary keys are okay, since we naively trust the input...
91
89
END IF;
92
90
FOR sub IN SELECT * FROM graphql .parse_many (body) LOOP
@@ -98,12 +96,13 @@ BEGIN
98
96
CASE
99
97
WHEN FOUND AND sub .body IS NULL THEN -- A simple column reference
100
98
SELECT * FROM graphql .fk (tab)
101
- WHERE cols[ 1 ] = col AND cardinality( cols) = 1
99
+ WHERE cardinality( cols) = 1 AND cols[ 1 ] = col
102
100
INTO fk; -- TODO: If there's more than one, emit a clear message.
103
101
IF FOUND THEN
104
102
subselects := subselects
105
- || format(E' SELECT to_json(%1$I) AS %4$I FROM %1$I\n '
106
- ' WHERE %1$I.%2$I = %3$I.%4$I' ,
103
+ || format(' SELECT to_json(%1$I) AS %4$I FROM %1$I'
104
+ E' \n '
105
+ ' WHERE %1$I.%2$I = %3$I.%4$I' ,
107
106
fk .other , fk .refs [1 ], tab, col);
108
107
cols := cols || format(' %I.%I' , ' sub/' || cardinality(subselects), col);
109
108
ELSE
@@ -131,14 +130,22 @@ BEGIN
131
130
-- - Otherwise:
132
131
-- - * We use the existence of the foreign key to look up the record in
133
132
-- - the table that JOINs with us.
133
+ -- -
134
134
-- - Whenever we are looking at a table that REFERENCES us, we assume it
135
135
-- - is a many-to-one relationship; and expect to return an array-valued
136
- -- - result.
136
+ -- - result. However, it should be possible to recognize a one-to-one
137
+ -- - relationship via the presence of a UNIQUE constraint, in which case
138
+ -- - we ought to return a scalar result.
137
139
IF FALSE THEN
140
+ subquery := subquery
141
+ || graphql .to_sql (sub .selector , sub .predicate , sub .body );
138
142
-- - Recursion happens in here
139
143
ELSE
140
144
-- - Recursion happens in here
145
+ subquery := subquery
146
+ || graphql .to_sql (sub .selector , sub .predicate , sub .body );
141
147
END IF;
148
+ --
142
149
ELSE
143
150
RAISE EXCEPTION ' Not able to interpret this selector: %' , sub .selector ;
144
151
END CASE;
@@ -157,6 +164,22 @@ BEGIN
157
164
q := ' SELECT json_agg(' || column_expression || E' )\n ' || q;
158
165
END IF;
159
166
END;
167
+ q := q || format(E' \n FROM %I' , tab);
168
+ FOR n IN 1 ..cardinality(subselects) LOOP
169
+ q := q || E' ,\n '
170
+ || E' LATERAL (\n '
171
+ || graphql .indent (subselects[i])
172
+ || E' \n ) AS ' || format(' %I' , ' sub/' || i);
173
+ -- - TODO: Switch to abstract representation of subqueries so we don't end
174
+ -- - up reindenting the same lines multiple times.
175
+ END LOOP;
176
+ FOR n IN 1 ..cardinality(predicates) LOOP
177
+ IF n = 1 THEN
178
+ q := q || E' \n WHERE (' || predicates[i] || ' )' ;
179
+ ELSE
180
+ q := q || E' \n AND (' || predicates[i] || ' )' ;
181
+ END IF;
182
+ END LOOP;
160
183
RETURN q;
161
184
END
162
185
$$ LANGUAGE plpgsql STABLE STRICT;
@@ -237,11 +260,22 @@ BEGIN
237
260
END
238
261
$$ LANGUAGE plpgsql IMMUTABLE STRICT;
239
262
263
+
264
+ /* * * * * * * * * * * * * * * * Text utilities * * * * * * * * * * * * * * */
265
+
240
266
CREATE FUNCTION excerpt (str text , start integer , length integer )
241
267
RETURNS text AS $$
242
268
SELECT substr(regexp_replace(str, ' [ \n\t ]+' , ' ' , ' g' ), start, length);
243
269
$$ LANGUAGE sql IMMUTABLE STRICT;
244
270
271
+ CREATE FUNCTION indent (str text )
272
+ RETURNS text AS $$
273
+ SELECT array_to_string(array_agg(s), E' \n ' )
274
+ FROM unnest(string_to_array(str, E' \n ' )) AS _(ln),
275
+ LATERAL (SELECT CASE ln WHEN ' ' THEN ln ELSE ' ' || ln)
276
+ AS indented(s)
277
+ $$ LANGUAGE sql IMMUTABLE STRICT;
278
+
245
279
246
280
/* * * * * * * * * * * * * Table inspection functions * * * * * * * * * * * */
247
281
0 commit comments