text: CREATE OR REPLACE FUNCTION _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 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(@extschema@._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 @extschema@.validate_json_schema(schema->'properties'->prop, data->prop, root_schema) THEN
          RETURN false;
        END IF;
      END LOOP;
    END IF;
  
    IF schema ? 'propertyNames' AND jsonb_typeof(schema->'propertyNames') = 'object' THEN 
    	FOR prop IN SELECT jsonb_object_keys(data) LOOP
    	  IF NOT validate_json_schema(schema->'propertyNames', to_jsonb(prop)) 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 @extschema@.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 @extschema@.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(@extschema@.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(@extschema@.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(@extschema@.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 @extschema@.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(@extschema@.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 @extschema@.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 @extschema@.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 @extschema@.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 @extschema@.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;
  