From e0ff3e8aedd03ba99abc3581509e77e6650fd261 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 8 Apr 2024 18:01:02 -0400 Subject: [PATCH] Add example script And move the test/demo schemas into the eg directory and add a README. --- README.md | 45 +++++++++++- doc/demo.sql | 15 ++++ eg/README.md | 68 +++++++++++++++++ {schemas => eg}/address.schema.json | 0 {schemas => eg}/user-profile.schema.json | 0 eg/user.sql | 93 ++++++++++++++++++++++++ src/lib.rs | 8 +- 7 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 doc/demo.sql create mode 100644 eg/README.md rename {schemas => eg}/address.schema.json (100%) rename {schemas => eg}/user-profile.schema.json (100%) create mode 100644 eg/user.sql diff --git a/README.md b/README.md index 8620b0f..6bc56c6 100644 --- a/README.md +++ b/README.md @@ -75,12 +75,48 @@ At build time it requires [Rust] and [pgrx]. Prior Art --------- -* [postgres-json-schema]: JSON Schema Postgres extension written with pgrx + - jsonschema +* [pg_jsonschema]: JSON Schema Postgres extension written with pgrx + + the [jsonschema crate] +* [pgx_json_schema]: Slightly older JSON Schema Postgres extension written + with pgrx + the [jsonschema crate] * [postgres-json-schema]: JSON Schema Postgres extension written in PL/pgSQL * [is_jsonb_valid]: JSON Schema Postgres extension written in C -* [pgx_json_schema]: JSON Schema Postgres extension written with pgrx + - jsonschema + +Benchmark +--------- + +A quick benchmark in [`eg/bench.sql](eg/bench.sql) compares the performance +for a simple validation a check constraint between the jsonschema and +[pg_jsonschema]. Example running PostgreSQL 16 on an M3 Max MacBook Pro with +32G of RAM: + +``` console +$ psql -Xf eg/bench.sql --set extension=pg_jsonschema + +###################################################################### +# Test pg_jsonschema JSON validation for 200_000 iterations +###################################################################### +Time: 8170.906 ms (00:08.171) + + +###################################################################### +# Test pg_jsonschema JSONB validation for 200_000 iterations +###################################################################### +Time: 8215.921 ms (00:08.216) + + +$ psql -Xf eg/bench.sql --set extension=jsonschema + +###################################################################### +# Test jsonschema JSON validation for 200_000 iterations +###################################################################### +Time: 3356.828 ms (00:03.357) + +###################################################################### +# Test jsonschema JSONB validation for 200_000 iterations +###################################################################### +Time: 3428.245 ms (00:03.428) +``` Copyright and License --------------------- @@ -126,6 +162,7 @@ SOFTWARE. [pg_config]: https://www.postgresql.org/docs/current/app-pgconfig.html "PostgreSQL Docs: pg_config" [CREATE EXTENSION]: https://www.postgresql.org/docs/current/sql-createextension.html "PostgreSQL Docs: CREATE EXTENSION" + [jsonschema crate]: https://docs.rs/jsonschema/latest/jsonschema/ [pg_jsonschema]: https://github.com/supabase/pg_jsonschema [postgres-json-schema]: https://github.com/gavinwahl/postgres-json-schema [is_jsonb_valid]: https://github.com/furstenheim/is_jsonb_valid diff --git a/doc/demo.sql b/doc/demo.sql new file mode 100644 index 0000000..a084c37 --- /dev/null +++ b/doc/demo.sql @@ -0,0 +1,15 @@ +\timing off + + +BEGIN; + +CREATE EXTENSION IF NOT EXISTS jsonschema; + +SELECT ' +\ir schemas/address.schema.json +' AS address_schema \gset + +\echo :address_schema + + +ROLLBACK; \ No newline at end of file diff --git a/eg/README.md b/eg/README.md new file mode 100644 index 0000000..99408f6 --- /dev/null +++ b/eg/README.md @@ -0,0 +1,68 @@ +jsonschema Examples +=================== + +This directory contains example use cases for the PostgreSQL jsonschema +extension. + +Schemas +------- + +The following schema files are used in one or more example scripts (detailed +below) and in the unit test suite (run by `make test`). + +### `address.schema.json` + +A sample JSON schema for validating mail addresses. Borrowed from the [Address +Example]. + +### `user-profile.schema.json` + +A sample JSON schema for validating user profiles. Borrowed from the [User +Profile Example], but augmented with an `address` property that references +`address.schema.json`. + +Use Cases +--------- + +### `bench.sql` + +This file contains a simple benchmark SQL script that demonstrates the +performance of jsonschema vs [pg_jsonschema]. It requires that each be built +and installed. To test jsonschema, run: + +```sh +psql -Xf eg/bench.sql +``` + +And to test [pg_jsonschema]: + +```sh +psql -Xf eg/bench.sql --set extension=pg_jsonschema +``` + +Set `iterations` to change the number of iterations (defaults to 200,000): + +```sh +psql -Xf eg/bench.sql --set iterations=500_000 +``` + +### `user.sql` + +This SQL script demonstrates the use of composed schemas to validate records +in a table. It uses [jq] to load [address.schema.json`](#addressschemajson) +and [user-profile.schema.json](#user-profileschemajson) into a table, +validates them both, then constructs a function that uses them to validate a +user profile. It then creates a table for users with a check constraint using +that function. It demonstrates inserting valid and invalid user objects. + +The script must be run from the project root directory so that it can find and +load the schema files. Run it like so: + +``` sh +psql -Xf eg/user.sql +``` + + [Address Example]: https://json-schema.org/learn/json-schema-examples#address + [User Profile Example]: https://json-schema.org/learn/json-schema-examples#user-profile + [pg_jsonschema]: https://github.com/supabase/pg_jsonschema + [jq]: https://jqlang.github.io/jq/manual/ \ No newline at end of file diff --git a/schemas/address.schema.json b/eg/address.schema.json similarity index 100% rename from schemas/address.schema.json rename to eg/address.schema.json diff --git a/schemas/user-profile.schema.json b/eg/user-profile.schema.json similarity index 100% rename from schemas/user-profile.schema.json rename to eg/user-profile.schema.json diff --git a/eg/user.sql b/eg/user.sql new file mode 100644 index 0000000..9c5bcec --- /dev/null +++ b/eg/user.sql @@ -0,0 +1,93 @@ +\set QUIET +\pset pager off +SET client_min_messages TO WARNING; +\set ECHO queries + +\unset QUIET + +CREATE EXTENSION jsonschema; +\echo +\prompt xxx + +SET jsonschema.default_draft TO 'V2020'; +\echo +\prompt xxx + +CREATE TEMPORARY TABLE json_schemas( + schema JSON +); +\echo +\prompt xxx + +\set ECHO ALL +\copy json_schemas FROM PROGRAM 'jq -c . eg/*.schema.json'; +\set ECHO queries +\echo +\prompt xxx + +SELECT jsonschema_is_valid( + 'https://example.com/user-profile.schema.json', + VARIADIC ARRAY(SELECT schema from json_schemas) +); +\prompt xxx + +SELECT jsonschema_is_valid( + 'https://example.com/address.schema.json', + VARIADIC ARRAY(SELECT schema from json_schemas) +); +\prompt xxx + +CREATE OR REPLACE FUNCTION validate_user( + data json +) RETURNS BOOLEAN LANGUAGE SQL STABLE AS $$ + SELECT jsonschema_validates( + data, 'https://example.com/user-profile.schema.json', + VARIADIC ARRAY(SELECT schema from json_schemas) + ); +$$; +\echo +\prompt xxx + +CREATE TEMPORARY TABLE json_users ( + id SERIAL PRIMARY KEY, + body JSON CHECK (validate_user(body)) +); +\echo +\prompt xxx + +INSERT INTO json_users (body) VALUES(json_build_object( + 'username', 'theory', + 'email', 'theory@example.com' +)); +\echo +\prompt xxx + +SELECT body FROM json_users WHERE body->>'username' = 'theory'; +\prompt xxx + +INSERT INTO json_users (body) VALUES(json_build_object( + 'username', 'naomi', + 'email', 'nagata@rocinante.ship', + 'address', json_build_object( + 'locality', 'Series', + 'region', 'The Belt', + 'countryName', 'Sol System' + ) +)); +\echo +\prompt xxx + +SELECT body FROM json_users WHERE body->>'username' = 'naomi'; +\prompt xxx + +INSERT INTO json_users (body) VALUES(json_build_object( + 'username', 42, + 'email', 'hhgttg@example.com' +)); +\echo +\prompt xxx + +\set ECHO none +\set QUIET +DROP FUNCTION validate_user(json) CASCADE; +DROP EXTENSION jsonschema; diff --git a/src/lib.rs b/src/lib.rs index e39140b..135c105 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -221,9 +221,9 @@ fn init_guc() { ); } -// Executes when Postgres loads the extension shared object library, which it -// does the first time it's used (and in the session where its loaded by -// `CREATE EXTENSION`). +/// _PG_init executes when Postgres loads the extension shared object library, +/// which it does the first time it's used, either by `CREATE EXTENSION` and +/// `UPDATE EXTENSION` or when one of its functions is called. #[pg_guard] extern "C" fn _PG_init() { init_guc(); @@ -311,7 +311,7 @@ mod tests { fn load_json(name: &str) -> Value { let root_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let root = Path::new(&root_dir); - serde_json::from_reader(File::open(root.join("schemas").join(name)).unwrap()).unwrap() + serde_json::from_reader(File::open(root.join("eg").join(name)).unwrap()).unwrap() } // Make sure our Draft enum converts properly into boon's.