chatai/postgrest_v12.2.8/test/spec/fixtures/jsonschema.sql

281 lines
9.4 KiB
PL/PgSQL

-- from gavinwahl/postgres-json-schema commit 5a257e19a1569a77b82e9182b0b7d9fc8b6f6382
/*
Copyright (c) 2016, Gavin Wahl
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose, without fee, and without a written agreement is
hereby granted, provided that the above copyright notice and this paragraph and
the following two paragraphs appear in all copies.
IN NO EVENT SHALL GAVIN WAHL BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING
OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF GAVIN WAHL HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
GAVIN WAHL SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND GAVIN WAHL
HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
MODIFICATIONS.
*/
CREATE OR REPLACE FUNCTION public._validate_json_schema_type(type text, data jsonb) RETURNS boolean AS $f$
BEGIN
IF type = 'integer' THEN
IF jsonb_typeof(data) != 'number' THEN
RETURN false;
END IF;
IF trunc(data::text::numeric) != data::text::numeric THEN
RETURN false;
END IF;
ELSE
IF type != jsonb_typeof(data) THEN
RETURN false;
END IF;
END IF;
RETURN true;
END;
$f$ LANGUAGE 'plpgsql' IMMUTABLE;
CREATE OR REPLACE FUNCTION test.validate_json_schema(schema jsonb, data jsonb, root_schema jsonb DEFAULT NULL) RETURNS boolean AS $f$
DECLARE
prop text;
item jsonb;
path text[];
types text[];
pattern text;
props text[];
BEGIN
IF root_schema IS NULL THEN
root_schema = schema;
END IF;
IF schema ? 'type' THEN
IF jsonb_typeof(schema->'type') = 'array' THEN
types = ARRAY(SELECT jsonb_array_elements_text(schema->'type'));
ELSE
types = ARRAY[schema->>'type'];
END IF;
IF (SELECT NOT bool_or(public._validate_json_schema_type(type, data)) FROM unnest(types) type) THEN
RETURN false;
END IF;
END IF;
IF schema ? 'properties' THEN
FOR prop IN SELECT jsonb_object_keys(schema->'properties') LOOP
IF data ? prop AND NOT validate_json_schema(schema->'properties'->prop, data->prop, root_schema) THEN
RETURN false;
END IF;
END LOOP;
END IF;
IF schema ? 'required' AND jsonb_typeof(data) = 'object' THEN
IF NOT ARRAY(SELECT jsonb_object_keys(data)) @>
ARRAY(SELECT jsonb_array_elements_text(schema->'required')) THEN
RETURN false;
END IF;
END IF;
IF schema ? 'items' AND jsonb_typeof(data) = 'array' THEN
IF jsonb_typeof(schema->'items') = 'object' THEN
FOR item IN SELECT jsonb_array_elements(data) LOOP
IF NOT validate_json_schema(schema->'items', item, root_schema) THEN
RETURN false;
END IF;
END LOOP;
ELSE
IF NOT (
SELECT bool_and(i > jsonb_array_length(schema->'items') OR validate_json_schema(schema->'items'->(i::int - 1), elem, root_schema))
FROM jsonb_array_elements(data) WITH ORDINALITY AS t(elem, i)
) THEN
RETURN false;
END IF;
END IF;
END IF;
IF jsonb_typeof(schema->'additionalItems') = 'boolean' and NOT (schema->'additionalItems')::text::boolean AND jsonb_typeof(schema->'items') = 'array' THEN
IF jsonb_array_length(data) > jsonb_array_length(schema->'items') THEN
RETURN false;
END IF;
END IF;
IF jsonb_typeof(schema->'additionalItems') = 'object' THEN
IF NOT (
SELECT bool_and(validate_json_schema(schema->'additionalItems', elem, root_schema))
FROM jsonb_array_elements(data) WITH ORDINALITY AS t(elem, i)
WHERE i > jsonb_array_length(schema->'items')
) THEN
RETURN false;
END IF;
END IF;
IF schema ? 'minimum' AND jsonb_typeof(data) = 'number' THEN
IF data::text::numeric < (schema->>'minimum')::numeric THEN
RETURN false;
END IF;
END IF;
IF schema ? 'maximum' AND jsonb_typeof(data) = 'number' THEN
IF data::text::numeric > (schema->>'maximum')::numeric THEN
RETURN false;
END IF;
END IF;
IF COALESCE((schema->'exclusiveMinimum')::text::bool, FALSE) THEN
IF data::text::numeric = (schema->>'minimum')::numeric THEN
RETURN false;
END IF;
END IF;
IF COALESCE((schema->'exclusiveMaximum')::text::bool, FALSE) THEN
IF data::text::numeric = (schema->>'maximum')::numeric THEN
RETURN false;
END IF;
END IF;
IF schema ? 'anyOf' THEN
IF NOT (SELECT bool_or(validate_json_schema(sub_schema, data, root_schema)) FROM jsonb_array_elements(schema->'anyOf') sub_schema) THEN
RETURN false;
END IF;
END IF;
IF schema ? 'allOf' THEN
IF NOT (SELECT bool_and(validate_json_schema(sub_schema, data, root_schema)) FROM jsonb_array_elements(schema->'allOf') sub_schema) THEN
RETURN false;
END IF;
END IF;
IF schema ? 'oneOf' THEN
IF 1 != (SELECT COUNT(*) FROM jsonb_array_elements(schema->'oneOf') sub_schema WHERE validate_json_schema(sub_schema, data, root_schema)) THEN
RETURN false;
END IF;
END IF;
IF COALESCE((schema->'uniqueItems')::text::boolean, false) THEN
IF (SELECT COUNT(*) FROM jsonb_array_elements(data)) != (SELECT count(DISTINCT val) FROM jsonb_array_elements(data) val) THEN
RETURN false;
END IF;
END IF;
IF schema ? 'additionalProperties' AND jsonb_typeof(data) = 'object' THEN
props := ARRAY(
SELECT key
FROM jsonb_object_keys(data) key
WHERE key NOT IN (SELECT jsonb_object_keys(schema->'properties'))
AND NOT EXISTS (SELECT * FROM jsonb_object_keys(schema->'patternProperties') pat WHERE key ~ pat)
);
IF jsonb_typeof(schema->'additionalProperties') = 'boolean' THEN
IF NOT (schema->'additionalProperties')::text::boolean AND jsonb_typeof(data) = 'object' AND NOT props <@ ARRAY(SELECT jsonb_object_keys(schema->'properties')) THEN
RETURN false;
END IF;
ELSEIF NOT (
SELECT bool_and(validate_json_schema(schema->'additionalProperties', data->key, root_schema))
FROM unnest(props) key
) THEN
RETURN false;
END IF;
END IF;
IF schema ? '$ref' THEN
path := ARRAY(
SELECT regexp_replace(regexp_replace(path_part, '~1', '/'), '~0', '~')
FROM UNNEST(regexp_split_to_array(schema->>'$ref', '/')) path_part
);
-- ASSERT path[1] = '#', 'only refs anchored at the root are supported';
IF NOT validate_json_schema(root_schema #> path[2:array_length(path, 1)], data, root_schema) THEN
RETURN false;
END IF;
END IF;
IF schema ? 'enum' THEN
IF NOT EXISTS (SELECT * FROM jsonb_array_elements(schema->'enum') val WHERE val = data) THEN
RETURN false;
END IF;
END IF;
IF schema ? 'minLength' AND jsonb_typeof(data) = 'string' THEN
IF char_length(data #>> '{}') < (schema->>'minLength')::numeric THEN
RETURN false;
END IF;
END IF;
IF schema ? 'maxLength' AND jsonb_typeof(data) = 'string' THEN
IF char_length(data #>> '{}') > (schema->>'maxLength')::numeric THEN
RETURN false;
END IF;
END IF;
IF schema ? 'not' THEN
IF validate_json_schema(schema->'not', data, root_schema) THEN
RETURN false;
END IF;
END IF;
IF schema ? 'maxProperties' AND jsonb_typeof(data) = 'object' THEN
IF (SELECT count(*) FROM jsonb_object_keys(data)) > (schema->>'maxProperties')::numeric THEN
RETURN false;
END IF;
END IF;
IF schema ? 'minProperties' AND jsonb_typeof(data) = 'object' THEN
IF (SELECT count(*) FROM jsonb_object_keys(data)) < (schema->>'minProperties')::numeric THEN
RETURN false;
END IF;
END IF;
IF schema ? 'maxItems' AND jsonb_typeof(data) = 'array' THEN
IF (SELECT count(*) FROM jsonb_array_elements(data)) > (schema->>'maxItems')::numeric THEN
RETURN false;
END IF;
END IF;
IF schema ? 'minItems' AND jsonb_typeof(data) = 'array' THEN
IF (SELECT count(*) FROM jsonb_array_elements(data)) < (schema->>'minItems')::numeric THEN
RETURN false;
END IF;
END IF;
IF schema ? 'dependencies' THEN
FOR prop IN SELECT jsonb_object_keys(schema->'dependencies') LOOP
IF data ? prop THEN
IF jsonb_typeof(schema->'dependencies'->prop) = 'array' THEN
IF NOT (SELECT bool_and(data ? dep) FROM jsonb_array_elements_text(schema->'dependencies'->prop) dep) THEN
RETURN false;
END IF;
ELSE
IF NOT validate_json_schema(schema->'dependencies'->prop, data, root_schema) THEN
RETURN false;
END IF;
END IF;
END IF;
END LOOP;
END IF;
IF schema ? 'pattern' AND jsonb_typeof(data) = 'string' THEN
IF (data #>> '{}') !~ (schema->>'pattern') THEN
RETURN false;
END IF;
END IF;
IF schema ? 'patternProperties' AND jsonb_typeof(data) = 'object' THEN
FOR prop IN SELECT jsonb_object_keys(data) LOOP
FOR pattern IN SELECT jsonb_object_keys(schema->'patternProperties') LOOP
RAISE NOTICE 'prop %s, pattern %, schema %', prop, pattern, schema->'patternProperties'->pattern;
IF prop ~ pattern AND NOT validate_json_schema(schema->'patternProperties'->pattern, data->prop, root_schema) THEN
RETURN false;
END IF;
END LOOP;
END LOOP;
END IF;
IF schema ? 'multipleOf' AND jsonb_typeof(data) = 'number' THEN
IF data::text::numeric % (schema->>'multipleOf')::numeric != 0 THEN
RETURN false;
END IF;
END IF;
RETURN true;
END;
$f$ LANGUAGE 'plpgsql' IMMUTABLE;