feat: data representations allow custom parsing and formatting of API fields.
See PR #2523. Most notable code changes: - Load data representation casts into schema cache. - Data representations for reads, filters, inserts, updates, views, over joins. - `CoercibleField` represents name references in queries where coercion may be needed. - `ResolverContext` help facilitate field resolution during planning. - Planner 'resolves' names in the API query and pairs them with any implicit conversions to be used in the query builder stage. - Tests for all of the above. - More consistent naming (TypedX -> CoercibleX). New: unit tests for more data representation use cases; helpful as examples as well. New: update CHANGELOG with data representations feature description. Fixed failing idempotence test. New: replace date formatter test with one that does something. Fixup: inadvertent CHANGELOG change after rebase. Cleanup: `tfName` -> `cfName` and related. Document what IRType means. Formatting. New: use a subquery to interpret `IN` literals requiring data rep transformation. - With the previous method, very long queries such as `ANY (ARRAY[test.color('000100'), test.color('CAFE12'), test.color('01E240'), ...` could be generated. Consider the case where the parser function name is 45 characters and there's a hundred literals. That's 4.5kB of SQL just for the function name alone! - New version uses `unnest`: `ANY (SELECT test.color(unnest('{000100,CAFE12,01E240,...}'::text[]))` to produce a much shorter query. - This is likely to be more performant and either way much more readable and debuggable in the logs.
This commit is contained in:
committed by
Steve Chavez
parent
078c6ec08c
commit
0a1564ba5a
Vendored
+142
-1
@@ -2774,9 +2774,20 @@ BEGIN
|
||||
LOAD 'safeupdate';
|
||||
END; $$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
-- This tests data representations over computed joins: even a lower case title should come back title cased.
|
||||
DROP DOMAIN IF EXISTS public.titlecasetext CASCADE;
|
||||
CREATE DOMAIN public.titlecasetext AS text;
|
||||
|
||||
CREATE OR REPLACE FUNCTION json(public.titlecasetext) RETURNS json AS $$
|
||||
SELECT to_json(INITCAP($1::text));
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE CAST (public.titlecasetext AS json) WITH FUNCTION json(public.titlecasetext) AS IMPLICIT;
|
||||
-- End of data representations specific stuff except for where the domain is used in the table.
|
||||
|
||||
CREATE TABLE designers (
|
||||
id int primary key
|
||||
, name text
|
||||
, name public.titlecasetext
|
||||
);
|
||||
|
||||
CREATE TABLE videogames (
|
||||
@@ -3091,6 +3102,136 @@ create table test.subscriptions(
|
||||
primary key(subscriber, subscribed)
|
||||
);
|
||||
|
||||
-- Data representations feature
|
||||
DROP DOMAIN IF EXISTS public.color CASCADE;
|
||||
CREATE DOMAIN public.color AS INTEGER CHECK (VALUE >= 0 AND VALUE <= 16777215);
|
||||
|
||||
CREATE OR REPLACE FUNCTION color(json) RETURNS public.color AS $$
|
||||
SELECT color($1 #>> '{}');
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION color(text) RETURNS public.color AS $$
|
||||
SELECT (('x' || lpad((CASE WHEN SUBSTRING($1::text, 1, 1) = '#' THEN SUBSTRING($1::text, 2) ELSE $1::text END), 8, '0'))::bit(32)::int)::public.color;
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION json(public.color) RETURNS json AS $$
|
||||
SELECT
|
||||
CASE WHEN $1 IS NULL THEN to_json(''::text)
|
||||
ELSE to_json('#' || lpad(upper(to_hex($1)), 6, '0'))
|
||||
END;
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE CAST (public.color AS json) WITH FUNCTION json(public.color) AS IMPLICIT;
|
||||
CREATE CAST (json AS public.color) WITH FUNCTION color(json) AS IMPLICIT;
|
||||
CREATE CAST (text AS public.color) WITH FUNCTION color(text) AS IMPLICIT;
|
||||
|
||||
DROP DOMAIN IF EXISTS public.isodate CASCADE;
|
||||
CREATE DOMAIN public.isodate AS timestamp with time zone;
|
||||
|
||||
CREATE OR REPLACE FUNCTION isodate(json) RETURNS public.isodate AS $$
|
||||
SELECT isodate($1 #>> '{}');
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION isodate(text) RETURNS public.isodate AS $$
|
||||
SELECT (replace($1, 'Z', '+00:00')::timestamp with time zone)::public.isodate;
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION json(public.isodate) RETURNS json AS $$
|
||||
SELECT to_json(replace(to_json($1)#>>'{}', '+00:00', 'Z'));
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE CAST (public.isodate AS json) WITH FUNCTION json(public.isodate) AS IMPLICIT;
|
||||
CREATE CAST (json AS public.isodate) WITH FUNCTION isodate(json) AS IMPLICIT;
|
||||
-- We intentionally don't have this in order to test query string parsing doesn't try to fall back on JSON parsing.
|
||||
-- CREATE CAST (text AS public.isodate) WITH FUNCTION isodate(text) AS IMPLICIT;
|
||||
|
||||
-- bytea_b64 is a base64-encoded binary string
|
||||
DROP DOMAIN IF EXISTS public.bytea_b64 CASCADE;
|
||||
CREATE DOMAIN public.bytea_b64 AS bytea;
|
||||
|
||||
CREATE OR REPLACE FUNCTION bytea_b64(json) RETURNS public.bytea_b64 AS $$
|
||||
SELECT bytea_b64($1 #>> '{}');
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION bytea_b64(text) RETURNS public.bytea_b64 AS $$
|
||||
-- allow unpadded base64
|
||||
SELECT decode($1 || repeat('=', 4 - (length($1) % 4)), 'base64')::public.bytea_b64;
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION json(public.bytea_b64) RETURNS json AS $$
|
||||
SELECT to_json(translate(encode($1, 'base64'), E'\n', ''));
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE CAST (public.bytea_b64 AS json) WITH FUNCTION json(public.bytea_b64) AS IMPLICIT;
|
||||
CREATE CAST (json AS public.bytea_b64) WITH FUNCTION bytea_b64(json) AS IMPLICIT;
|
||||
CREATE CAST (text AS public.bytea_b64) WITH FUNCTION bytea_b64(text) AS IMPLICIT;
|
||||
|
||||
-- unixtz is a timestamptz represented as an integer number of seconds since the Unix epoch
|
||||
DROP DOMAIN IF EXISTS public.unixtz CASCADE;
|
||||
CREATE DOMAIN public.unixtz AS timestamp with time zone;
|
||||
|
||||
CREATE OR REPLACE FUNCTION unixtz(json) RETURNS public.unixtz AS $$
|
||||
SELECT unixtz($1 #>> '{}');
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION unixtz(text) RETURNS public.unixtz AS $$
|
||||
SELECT (to_timestamp($1::numeric)::public.unixtz);
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION json(public.unixtz) RETURNS json AS $$
|
||||
SELECT to_json(extract(epoch from $1)::bigint);
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
|
||||
CREATE CAST (public.unixtz AS json) WITH FUNCTION json(public.unixtz) AS IMPLICIT;
|
||||
CREATE CAST (json AS public.unixtz) WITH FUNCTION unixtz(json) AS IMPLICIT;
|
||||
CREATE CAST (text AS public.unixtz) WITH FUNCTION unixtz(text) AS IMPLICIT;
|
||||
|
||||
DROP DOMAIN IF EXISTS public.monetary CASCADE;
|
||||
CREATE DOMAIN public.monetary AS numeric(17,2);
|
||||
|
||||
CREATE OR REPLACE FUNCTION monetary(json) RETURNS public.monetary AS $$
|
||||
SELECT monetary($1 #>> '{}');
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION monetary(text) RETURNS public.monetary AS $$
|
||||
SELECT ($1::numeric)::public.monetary;
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION json(public.monetary) RETURNS json AS $$
|
||||
SELECT to_json($1::text);
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE CAST (public.monetary AS json) WITH FUNCTION json(public.monetary) AS IMPLICIT;
|
||||
CREATE CAST (json AS public.monetary) WITH FUNCTION monetary(json) AS IMPLICIT;
|
||||
CREATE CAST (text AS public.monetary) WITH FUNCTION monetary(text) AS IMPLICIT;
|
||||
|
||||
CREATE TABLE datarep_todos (
|
||||
id bigint primary key,
|
||||
name text,
|
||||
label_color public.color default 0,
|
||||
due_at public.isodate default '2018-01-01'::date,
|
||||
icon_image public.bytea_b64,
|
||||
created_at public.unixtz default '2017-12-14 01:02:30'::timestamptz,
|
||||
budget public.monetary default 0
|
||||
);
|
||||
|
||||
CREATE TABLE datarep_next_two_todos (
|
||||
id bigint primary key,
|
||||
first_item_id bigint references datarep_todos(id),
|
||||
second_item_id bigint references datarep_todos(id),
|
||||
name text
|
||||
);
|
||||
|
||||
CREATE VIEW datarep_todos_computed as (
|
||||
SELECT id,
|
||||
name,
|
||||
label_color,
|
||||
due_at,
|
||||
(label_color / 2)::public.color as dark_color
|
||||
FROM datarep_todos
|
||||
);
|
||||
|
||||
-- view's name is alphabetically before projects
|
||||
create view test.alpha_projects as
|
||||
select c.id, p.name as pro_name, c.name as cli_name
|
||||
|
||||
Reference in New Issue
Block a user