Skip to content

Commit

Permalink
implement jsonschema
Browse files Browse the repository at this point in the history
  • Loading branch information
attacker0211 committed Aug 10, 2023
1 parent dccbb47 commit e6e4b26
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 0 deletions.
12 changes: 12 additions & 0 deletions examples/rust/jsonschema/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "extension"
version = "0.1.0"
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen.git", rev = "60e3c5b41e616fee239304d92128e117dd9be0a7" }
boon = { version = "0.5.1" }
serde_json = { version = "1" }
158 changes: 158 additions & 0 deletions examples/rust/jsonschema/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# ss_jsonschema: JSON Schema support for SingleStoreDB

**ss_jsonschema** adds [JSON schema](https://json-schema.org/) validation support for json data types. Since this is a Wasm extension, this will take `string` as input and parse as Json in the validation process. See examples below.

## Contents

### `match_schema (schema: string, instance: string) -> bool`
Returns true if the given instance matches the schema provided

### `is_valid_schema (schema: string) -> bool`
Returns true if the given schema is valid

## Building
```
# Install the WASI cargo extension.
cargo install cargo-wasi
# Compile the Wasm module.
cargo wasi build --release
```
## Deployment to SingleStoreDB

To install these functions using the MySQL client, use the following commands. This command assumes you have built the Wasm module and your current directory is the root of this Git repo. Replace `$DBUSER`, `$DBHOST`, `$DBPORT`, and `$DBNAME` with, respectively, your database username, hostname, port, and the name of the database where you want to deploy the functions.

```bash
cat <<EOF | mysql -u $DBUSER -h $DBHOST -P $DBPORT -D $DBNAME -p
CREATE FUNCTION match_schema AS WASM FROM LOCAL INFILE "target/wasm32-wasi/release/extension.wasm" WITH WIT FROM LOCAL INFILE "extension.wit";
CREATE FUNCTION is_valid_schema AS WASM FROM LOCAL INFILE "target/wasm32-wasi/release/extension.wasm" WITH WIT FROM LOCAL INFILE "extension.wit";
```
Alternatively, you can install these functions using [pushwasm](https://github.com/singlestore-labs/pushwasm) with the following command lines. As above, be sure to substitute the environment variables with values of your own.
```bash
pushwasm udf --force --prompt --name match_schema \
--wasm target/wasm32-wasi/release/extension.wasm \
--wit extension.wit \
--abi canonical \
--conn "mysql://$DBUSER@$DBHOST:$DBPORT/$DBNAME"
pushwasm udf --force --prompt --name is_valid_schema \
--wasm target/wasm32-wasi/release/extension.wasm \
--wit extension.wit \
--abi canonical \
--conn "mysql://$DBUSER@$DBHOST:$DBPORT/$DBNAME"
```
## Clean
```
cargo clean
```
## Examples
### Simple example
```sql
SELECT match_schema('{"type": "number"}' , '123');
```
Output:
```
+--------------------------------------------+
| match_schema('{"type": "number"}' , '123') |
+--------------------------------------------+
| 1 |
+--------------------------------------------+
```
```sql
SELECT match_schema('{"type": "number"}' , '"hi"');
```
Output:
```
+---------------------------------------------+
| match_schema('{"type": "number"}' , '"hi"') |
+---------------------------------------------+
| 0 |
+---------------------------------------------+
```
```sql
SELECT is_valid_schema('{"type" : "integer"}');
```
Output:
```
+-----------------------------------------+
| is_valid_schema('{"type" : "integer"}') |
+-----------------------------------------+
| 1 |
+-----------------------------------------+
```
```sql
SELECT is_valid_schema('{"type" : "int"}');
```
Output:
```
+-------------------------------------+
| is_valid_schema('{"type" : "int"}') |
+-------------------------------------+
| 0 |
+-------------------------------------+
```
### More complex example
```sql
set @schema='{
"type": "object",
"properties": {
"foo": {
"type": "string"
}
},
"required": ["foo"],
"additionalProperties": false
}';
SELECT match_schema(@schema, '{"foo" : "bar"}');
```
Output:
```
+------------------------------------------+
| match_schema(@schema, '{"foo" : "bar"}') |
+------------------------------------------+
| 1 |
+------------------------------------------+
```
```sql
SELECT match_schema(@schema, '{"notfoo" : "bar"}');
```
Output:
```
+---------------------------------------------+
| match_schema(@schema, '{"notfoo" : "bar"}') |
+---------------------------------------------+
| 0 |
+---------------------------------------------+
```
```sql
SELECT match_schema(@schema, '{"foo" : "bar", "morefoo": "morebar"}');
```
Output:
```
+----------------------------------------------------------------+
| match_schema(@schema, '{"foo" : "bar", "morefoo": "morebar"}') |
+----------------------------------------------------------------+
| 0 |
+----------------------------------------------------------------+
```
## Acknowledgement
Rust [boon](https://crates.io/crates/boon) library and [JSON Schema](https://json-schema.org/)
2 changes: 2 additions & 0 deletions examples/rust/jsonschema/extension.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
match-schema: func(schema: string, instance: string) -> bool
is-valid-schema: func(schema: string) -> bool
90 changes: 90 additions & 0 deletions examples/rust/jsonschema/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
wit_bindgen_rust::export!("extension.wit");
use boon::{Compiler, Schemas};
use serde_json::Value;

struct Extension;

const DEFAULT_SCHEMA_URL: &str = "http://tmp/schema.json";

impl extension::Extension for Extension {
fn match_schema(schema_str: String, instance_str: String) -> bool {
let schema: Value = serde_json::from_str(schema_str.as_str()).unwrap();
let instance: Value = serde_json::from_str(instance_str.as_str()).unwrap();

let mut schemas = Schemas::new();
let mut compiler = Compiler::new();
compiler.add_resource(DEFAULT_SCHEMA_URL, schema).unwrap();
let sch_index = compiler.compile(DEFAULT_SCHEMA_URL, &mut schemas).unwrap();
let result = schemas.validate(&instance, sch_index);
if result.is_err() {
false
}
else {
true
}
}

fn is_valid_schema(schema_str: String) -> bool {
let schema: Value = serde_json::from_str(schema_str.as_str()).unwrap();
let mut compiler = Compiler::new();
let is_valid = compiler.add_resource(DEFAULT_SCHEMA_URL, schema);
if is_valid.is_err() {
false
}
else {
true
}
}
}

// sanity tests
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_from_string_naive() {
let naive_schema = r#"{"type": "object"}"#.to_string();
let naive_input = r#"{"foo": "bar"}"#.to_string();
assert_eq!(<Extension as extension::Extension>::match_schema(naive_schema, naive_input), true);
}

#[test]
fn test_from_string_number() {
let number_schema = r#"{"type": "number"}"#.to_string();
let number_str_pass = "123".to_string();
let number_str_fail = r#""meow""#.to_string();

assert_eq!(<Extension as extension::Extension>::match_schema(number_schema.clone(), number_str_pass), true);
assert_eq!(<Extension as extension::Extension>::match_schema(number_schema, number_str_fail), false);
}

#[test]
fn test_from_string_required_field() {
let required_schema = r#"{
"type": "object",
"properties": {
"foo": {
"type": "string"
}
},
"required": ["foo"],
"additionalProperties": false
}"#.to_string();
let required_input_pass = r#"{"foo": "bar"}"#.to_string();
let required_input_fail = r#"{"hi" : "bar"}"#.to_string();
let required_input_fail_2 = r#"{"foo": "bar", "hi": "bar"}"#.to_string();
assert_eq!(<Extension as extension::Extension>::match_schema(required_schema.clone(), required_input_pass), true);
assert_eq!(<Extension as extension::Extension>::match_schema(required_schema.clone(), required_input_fail), false);
assert_eq!(<Extension as extension::Extension>::match_schema(required_schema, required_input_fail_2), false);
}

#[test]
fn test_is_valid_schema() {
let valid_schema = r#"{"type": "integer"}"#.to_string();
let not_valid_schema = r#"{"type": "int"}"#.to_string();
assert_eq!(<Extension as extension::Extension>::is_valid_schema(valid_schema), true);
assert_eq!(<Extension as extension::Extension>::is_valid_schema(not_valid_schema), false);
}
}

0 comments on commit e6e4b26

Please sign in to comment.