Skip to content

Commit 4e0e5e3

Browse files
committed
Move some code around...almost ready for FKs...
1 parent 1b6860d commit 4e0e5e3

File tree

1 file changed

+106
-43
lines changed

1 file changed

+106
-43
lines changed

graphql.sql

Lines changed: 106 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ RETURNS text AS $$
7272
DECLARE
7373
q text;
7474
tab regclass = selector::regclass; -- We need a table, to select from
75-
cols text[];
76-
col text;
75+
cols name[];
76+
col name;
7777
sub record;
7878
pk text = NULL;
79-
fk record;
79+
fks record[];
8080
subselects text[];
8181
predicates text[];
8282
BEGIN
@@ -92,60 +92,32 @@ BEGIN
9292
RAISE EXCEPTION 'Unhandled nested selector %(%)',
9393
sub.selector, sub.predicate;
9494
END IF;
95-
SELECT col FROM cols(tab) WHERE col = sub.selector INTO col;
95+
SELECT col FROM cols(tab) WHERE cols.col = sub.selector INTO col;
9696
CASE
9797
WHEN FOUND AND sub.body IS NULL THEN -- A simple column reference
9898
SELECT * FROM graphql.fk(tab)
9999
WHERE cardinality(cols) = 1 AND cols[1] = col
100-
INTO fk; -- TODO: If there's more than one, emit a clear message.
100+
INTO fks;
101101
IF FOUND THEN
102+
IF cardinality(fks) > 1 THEN
103+
RAISE EXCEPTION 'Multiple candidate foreign keys for %(%)', tab, col;
104+
END IF;
102105
subselects := subselects
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',
106-
fk.other, fk.refs[1], tab, col);
106+
|| format(E'SELECT to_json(%1$I) AS %4$I FROM %1$I\n'
107+
' WHERE %1$I.%2$I = %3$I.%4$I',
108+
fks[1].other, fks[1].refs[1], tab, col);
107109
cols := cols || format('%I.%I', 'sub/'||cardinality(subselects), col);
108110
ELSE
109111
cols := cols || format('%I', col);
110112
END IF;
111113
WHEN FOUND AND sub.body IS NOT NULL THEN -- Index into a column
114+
subselects := subselects || graphql.to_sql(sub.*, tab);
112115
--- TODO: Handle nested lookup into JSON, HStore, RECORD
113116
--- TODO: If col REFERENCES something, push lookup down to it
117+
cols := cols || format('%I.%I', 'sub/'||cardinality(subselects), col);
114118
WHEN NOT FOUND THEN -- It might be a reference to another table
115-
SELECT fk.*
116-
FROM graphql.fk(sub.selector),
117-
LATERAL (SELECT num FROM graphql.cols(tab)
118-
WHERE col = fk.cols[1]) AS _
119-
WHERE cardinality(fk.cols) = 1 AND fk.tab = to_sql.tab
120-
ORDER BY _.num LIMIT 1 INTO fk;
121-
IF NOT FOUND THEN
122-
RAISE EXCEPTION 'Not able to construct a JOIN for missing column: %',
123-
sub.selector;
124-
END IF;
125-
--- If:
126-
--- * Thare are two and only two foreign keys for the other table, and
127-
--- * All the columns of the table participate in one or the other
128-
--- foreign key, then
129-
--- * We can treat the table as a JOIN table and follow the keys.
130-
--- Otherwise:
131-
--- * We use the existence of the foreign key to look up the record in
132-
--- the table that JOINs with us.
133-
---
134-
--- Whenever we are looking at a table that REFERENCES us, we assume it
135-
--- is a many-to-one relationship; and expect to return an array-valued
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.
139-
IF FALSE THEN
140-
subquery := subquery
141-
|| graphql.to_sql(sub.selector, sub.predicate, sub.body);
142-
--- Recursion happens in here
143-
ELSE
144-
--- Recursion happens in here
145-
subquery := subquery
146-
|| graphql.to_sql(sub.selector, sub.predicate, sub.body);
147-
END IF;
148-
--
119+
subselects := subselects || graphql.to_sql(sub.*, tab, sub.selector);
120+
cols := cols || format('%I.%I', 'sub/'||cardinality(subselects), col);
149121
ELSE
150122
RAISE EXCEPTION 'Not able to interpret this selector: %', sub.selector;
151123
END CASE;
@@ -186,6 +158,97 @@ BEGIN
186158
END
187159
$$ LANGUAGE plpgsql STABLE STRICT;
188160

161+
CREATE FUNCTION to_sql(selector text, predicate text, body text, tab regclass)
162+
RETURNS text AS $$
163+
DECLARE
164+
q text;
165+
col name;
166+
typ regtype;
167+
lookups text[];
168+
labels text[];
169+
BEGIN
170+
SELECT col, typ FROM cols(tab) WHERE cols.col = selector INTO col, typ;
171+
IF NOT FOUND THEN
172+
RAISE EXCEPTION 'Did not find column % on table %', col, tab;
173+
END IF;
174+
FOR sub IN SELECT * FROM graphql.parse_many(body) LOOP
175+
IF sub.predicate IS NOT NULL THEN
176+
RAISE EXCEPTION 'Not able to handle predicates when following lookups '
177+
'into columns (for field % under %.%)',
178+
sub.selector, tab, col;
179+
END IF;
180+
CASE typ
181+
WHEN regtype('jsonb'), regtype('json') THEN
182+
IF sub.body IS NOT NULL THEN -- TODO: Nested JSON lookups
183+
RAISE EXCEPTION 'Nested JSON lookup is as yet unimplemented';
184+
END IF;
185+
WHEN regtype('hstore') THEN
186+
IF sub.body IS NOT NULL THEN
187+
RAISE EXCEPTION 'No fields below this level (column % is hstore)',
188+
tab, col;
189+
END IF;
190+
ELSE
191+
RAISE EXCEPTION 'Unhandled nested type %s for %s.%s', typ, tab, col;
192+
END CASE;
193+
lookups := lookups || format('->%L', sub.selector);
194+
labels := labels || format('%I', sub.selector);
195+
END IF;
196+
q := format(E'SELECT to_json(_) AS %I\n'
197+
' FROM (VALUES (%s)) AS _(%s)',
198+
col,
199+
array_to_string(lookups, ', '),
200+
array_to_string(labels, ', '));
201+
END
202+
$$ LANGUAGE plpgsql STABLE STRICT;
203+
204+
CREATE FUNCTION to_sql(selector text,
205+
predicate text,
206+
body text,
207+
tab regclass,
208+
other regclass)
209+
RETURNS text AS $$
210+
DECLARE
211+
BEGIN
212+
END
213+
$$ LANGUAGE plpgsql STABLE STRICT;
214+
215+
SELECT fk.*
216+
FROM graphql.fk(sub.selector),
217+
LATERAL (SELECT num FROM graphql.cols(tab)
218+
WHERE col = fk.cols[1]) AS _
219+
WHERE cardinality(fk.cols) = 1 AND fk.tab = to_sql.tab
220+
ORDER BY _.num LIMIT 1 INTO fk;
221+
IF NOT FOUND THEN
222+
RAISE EXCEPTION 'Not able to construct a JOIN for missing column: %',
223+
sub.selector;
224+
END IF;
225+
--- If:
226+
--- * Thare are two and only two foreign keys for the other table, and
227+
--- * All the columns of the table participate in one or the other
228+
--- foreign key, then
229+
--- * We can treat the table as a JOIN table and follow the keys.
230+
--- Otherwise:
231+
--- * We use the existence of the foreign key to look up the record in
232+
--- the table that JOINs with us.
233+
---
234+
--- Whenever we are looking at a table that REFERENCES us, we assume it
235+
--- is a many-to-one relationship; and expect to return an array-valued
236+
--- result. However, it should be possible to recognize a one-to-one
237+
--- relationship via the presence of a UNIQUE constraint, in which case
238+
--- we ought to return a scalar result.
239+
IF FALSE THEN
240+
subselects := subselects || graphql.to_sql(sub.*, tab);
241+
SELECT json_agg(other.*)
242+
FROM tab
243+
JOIN sub.selector ON (pk) = (fk.cols)
244+
JOIN fks[2].tab ON (fk2.cols) = (pk(fks[2].tab));
245+
--- Recursion happens in here
246+
ELSE
247+
subselects := subselects || graphql.to_sql(sub.*, tab, fk.others);
248+
--- Recursion happens in here
249+
subquery := subquery
250+
|| graphql.to_sql(sub.selector, sub.predicate, sub.body);
251+
END IF;
189252
CREATE FUNCTION parse_many(expr text)
190253
RETURNS TABLE (selector text, predicate text, body text) AS $$
191254
DECLARE

0 commit comments

Comments
 (0)