Skip to content

Commit

Permalink
Add example script
Browse files Browse the repository at this point in the history
And move the test/demo schemas into the eg directory and add a README.
  • Loading branch information
theory committed Apr 8, 2024
1 parent 4e48f8d commit eaa417b
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 8 deletions.
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions doc/demo.sql
Original file line number Diff line number Diff line change
@@ -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;
68 changes: 68 additions & 0 deletions eg/README.md
Original file line number Diff line number Diff line change
@@ -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/
File renamed without changes.
File renamed without changes.
93 changes: 93 additions & 0 deletions eg/user.sql
Original file line number Diff line number Diff line change
@@ -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;
8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit eaa417b

Please sign in to comment.