├── .dockerignore ├── .envrc ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ └── internal-issues-only.md ├── pull_request_template.md └── workflows │ ├── build-test.yaml │ ├── check-format.yaml │ ├── nix.yaml │ └── release.yaml ├── .gitignore ├── .prettierignore ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── README.md ├── flake.lock ├── flake.nix ├── justfile ├── ndc-models ├── Cargo.toml ├── README.md ├── src │ ├── aggregation.rs │ ├── capabilities.rs │ ├── expression.rs │ ├── fields.rs │ ├── lib.rs │ ├── names.rs │ ├── ordering.rs │ ├── relational_query │ │ ├── capabilities.rs │ │ ├── expression.rs │ │ ├── mod.rs │ │ └── types.rs │ ├── requests.rs │ └── schema.rs └── tests │ ├── json_schema │ ├── capabilities_response.jsonschema │ ├── error_response.jsonschema │ ├── explain_response.jsonschema │ ├── mutation_request.jsonschema │ ├── mutation_response.jsonschema │ ├── query_request.jsonschema │ ├── query_response.jsonschema │ ├── relational_query.jsonschema │ ├── relational_query_response.jsonschema │ └── schema_response.jsonschema │ └── tests.rs ├── ndc-reference ├── Cargo.toml ├── README.md ├── articles.jsonl ├── authors.jsonl ├── bin │ └── reference │ │ └── main.rs ├── countries.jsonl ├── institutions.jsonl └── tests │ ├── capabilities │ └── expected.snap │ ├── mutation │ ├── delete_articles │ │ ├── expected.snap │ │ └── request.json │ ├── upsert_article │ │ ├── expected.snap │ │ └── request.json │ └── upsert_article_with_relationship │ │ ├── expected.snap │ │ └── request.json │ ├── query │ ├── aggregate_function │ │ ├── expected.snap │ │ └── request.json │ ├── aggregate_function_with_nested_field │ │ ├── expected.snap │ │ └── request.json │ ├── authors_with_article_aggregate │ │ ├── expected.snap │ │ └── request.json │ ├── authors_with_articles │ │ ├── expected.snap │ │ └── request.json │ ├── column_count │ │ ├── expected.snap │ │ └── request.json │ ├── get_all_articles │ │ ├── expected.snap │ │ └── request.json │ ├── get_max_article │ │ ├── expected.snap │ │ └── request.json │ ├── get_max_article_id │ │ ├── expected.snap │ │ └── request.json │ ├── group_by │ │ ├── expected.snap │ │ └── request.json │ ├── group_by_with_extraction │ │ ├── expected.snap │ │ └── request.json │ ├── group_by_with_having │ │ ├── expected.snap │ │ └── request.json │ ├── group_by_with_nested_relationship_in_dimension │ │ ├── expected.snap │ │ └── request.json │ ├── group_by_with_order_by │ │ ├── expected.snap │ │ └── request.json │ ├── group_by_with_order_by_dimension │ │ ├── expected.snap │ │ └── request.json │ ├── group_by_with_path_in_dimension │ │ ├── expected.snap │ │ └── request.json │ ├── group_by_with_relationship │ │ ├── expected.snap │ │ └── request.json │ ├── group_by_with_star_count │ │ ├── expected.snap │ │ └── request.json │ ├── group_by_with_where │ │ ├── expected.snap │ │ └── request.json │ ├── named_scopes │ │ ├── expected.snap │ │ └── request.json │ ├── nested_array_select │ │ ├── expected.snap │ │ └── request.json │ ├── nested_array_select_with_limit │ │ ├── expected.snap │ │ └── request.json │ ├── nested_collection_with_aggregates │ │ ├── expected.snap │ │ └── request.json │ ├── nested_collection_with_grouping │ │ ├── expected.snap │ │ └── request.json │ ├── nested_object_select │ │ ├── expected.snap │ │ └── request.json │ ├── nested_object_select_with_relationship │ │ ├── expected.snap │ │ └── request.json │ ├── order_by_aggregate │ │ ├── expected.snap │ │ └── request.json │ ├── order_by_aggregate_function │ │ ├── expected.snap │ │ └── request.json │ ├── order_by_aggregate_nested_relationship │ │ ├── expected.snap │ │ └── request.json │ ├── order_by_aggregate_with_predicate │ │ ├── expected.snap │ │ └── request.json │ ├── order_by_column │ │ ├── expected.snap │ │ └── request.json │ ├── order_by_nested_field │ │ ├── expected.snap │ │ └── request.json │ ├── order_by_nested_relationship │ │ ├── expected.snap │ │ └── request.json │ ├── order_by_relationship │ │ ├── expected.snap │ │ └── request.json │ ├── pagination │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_array_contains │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_array_is_empty │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_column_aggregate │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_eq │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_exists │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_exists_and_in │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_exists_and_relationship │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_exists_from_nested_field │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_exists_in_nested_collection │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_exists_in_nested_scalar_collection │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_in │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_like │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_nested_field_eq │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_star_count │ │ ├── expected.snap │ │ └── request.json │ ├── predicate_with_starts_with │ │ ├── expected.snap │ │ └── request.json │ ├── star_count │ │ ├── expected.snap │ │ └── request.json │ ├── table_argument │ │ ├── expected.snap │ │ └── request.json │ ├── table_argument_aggregate │ │ ├── expected.snap │ │ └── request.json │ ├── table_argument_exists │ │ ├── expected.snap │ │ └── request.json │ ├── table_argument_order_by │ │ ├── expected.snap │ │ └── request.json │ ├── table_argument_relationship_1 │ │ ├── expected.snap │ │ └── request.json │ ├── table_argument_relationship_2 │ │ ├── expected.snap │ │ └── request.json │ ├── table_argument_unrelated_exists │ │ ├── expected.snap │ │ └── request.json │ └── variables │ │ ├── expected.snap │ │ └── request.json │ └── schema │ └── expected.snap ├── ndc-test ├── Cargo.toml ├── README.md └── src │ ├── client │ └── mod.rs │ ├── configuration.rs │ ├── connector.rs │ ├── error.rs │ ├── lib.rs │ ├── main.rs │ ├── reporter.rs │ ├── snapshot.rs │ └── test_cases │ ├── capabilities.rs │ ├── mod.rs │ ├── query │ ├── aggregates │ │ └── mod.rs │ ├── common.rs │ ├── context.rs │ ├── grouping │ │ └── mod.rs │ ├── mod.rs │ ├── relationships │ │ └── mod.rs │ ├── simple_queries │ │ ├── mod.rs │ │ ├── predicates.rs │ │ └── sorting.rs │ └── validate │ │ └── mod.rs │ └── schema.rs ├── rfcs ├── 0001-versioning.md ├── 0002-boolean-expression-types.md ├── 0003-nested-field-selection.md ├── 0004-explain-mutations.md ├── 0005-scalar-type-representation.md ├── 0006-equality.md ├── 0007-additional-type-representations.md ├── 0008-remove-ndc-client.md ├── 0009-filter-and-order-by-nested-fields.md ├── 0010-field-arguments.md ├── 0011-no-paths-in-comparison-target.md ├── 0012-aggregate-nested-fields.md ├── 0013-filter-by-aggregates.md ├── 0014-group-by.md ├── 0015-named-scopes.md ├── 0016-newtypes.md ├── 0017-relationships-from-nested-fields.md ├── 0019-nested-collections.md ├── 0020-exists-in-nested-collection.md ├── 0021-comparison-and-aggregate-meanings.md ├── 0022-runtime-version-checks.md ├── 0023-filtering-over-nested-array-of-scalars.md ├── 0024-nested-relationships.md ├── 0025-additional-field-argument-support.md ├── 0026-string-operators.md └── 0027-extraction-functions.md ├── rust-toolchain.toml └── specification ├── .gitignore ├── Makefile ├── README.md ├── book.toml ├── src ├── SUMMARY.md ├── overview.md ├── reference │ ├── json-schema.md │ └── types.md ├── specification │ ├── README.md │ ├── basics.md │ ├── capabilities.md │ ├── changelog.md │ ├── error-handling.md │ ├── explain.md │ ├── health.md │ ├── metrics.md │ ├── mutations │ │ ├── README.md │ │ └── procedures.md │ ├── queries │ │ ├── README.md │ │ ├── aggregates.md │ │ ├── arguments.md │ │ ├── field-selection.md │ │ ├── filtering.md │ │ ├── functions.md │ │ ├── grouping.md │ │ ├── pagination.md │ │ ├── relationships.md │ │ ├── sorting.md │ │ └── variables.md │ ├── schema │ │ ├── README.md │ │ ├── capabilities.md │ │ ├── collections.md │ │ ├── functions.md │ │ ├── object-types.md │ │ ├── procedures.md │ │ └── scalar-types.md │ ├── telemetry.md │ ├── types.md │ └── versioning.md └── tutorial │ ├── README.md │ ├── capabilities.md │ ├── explain.md │ ├── getting-started.md │ ├── health.md │ ├── mutations │ ├── README.md │ ├── operations.md │ └── procedures.md │ ├── queries │ ├── README.md │ ├── arguments.md │ ├── execute │ │ ├── README.md │ │ ├── aggregates.md │ │ ├── field-selection.md │ │ ├── filtering.md │ │ ├── grouping.md │ │ ├── pagination.md │ │ ├── relationships.md │ │ └── sorting.md │ └── variables.md │ ├── schema.md │ ├── setup.md │ └── testing.md └── theme ├── css └── variables.css ├── pagetoc.css └── pagetoc.js /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | target 3 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source_env_if_exists ./.envrc.local 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @paf31 @codedmart @daniel-chambers @danieljharvey 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Bugs, Feature Requests, Ideas 4 | url: https://github.com/hasura/graphql-engine/issues/new/choose 5 | about: Use hasura/graphql-engine for issues related to ndc-spec. 6 | - name: ☁️ Hasura Cloud Help & Support Center 7 | url: https://cloud.hasura.io/support 8 | about: For Issues related to Hasura Cloud and DDN ⚡️ 9 | - name: ❓ Get Support from Discord Community 10 | url: https://discord.com/invite/hasura 11 | about: Please ask and answer questions here. 🏥 12 | - name: 🙋‍♀️ Have a question on how to achieve something with Hasura? 13 | url: https://github.com/hasura/graphql-engine/discussions 14 | about: Start a GitHub discussion 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/internal-issues-only.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New issues are meant to be created by Hasura collaborators only 3 | about: 4 | DON'T USE THIS. We request you to use hasura/graphql-engine repo to open new 5 | issues related to ndc-spec (bugs, feature requests etc). It's recommended to tag those 6 | issues with label c/ddn-ndc-spec. 7 | title: "" 8 | labels: "" 9 | assignees: 10 | --- 11 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | - [ ] RFC 6 | - [ ] Specification updates 7 | - [ ] Changelog 8 | - [ ] Specification pages 9 | - [ ] Tutorial pages 10 | - [ ] `reference/types.md` 11 | - [ ] Implement your feature in the reference connector 12 | - [ ] Generate test cases in `ndc-test` if appropriate 13 | - [ ] Does your feature add a new capability? 14 | - [ ] Update `specification/capabilities.md` in the specification 15 | - [ ] Check the capability in `ndc-test` 16 | -------------------------------------------------------------------------------- /.github/workflows/check-format.yaml: -------------------------------------------------------------------------------- 1 | name: check formatting 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | cargo-fmt: 8 | name: cargo fmt 9 | runs-on: ubuntu-latest 10 | env: 11 | CARGO_NET_GIT_FETCH_WITH_CLI: "true" 12 | RUSTFLAGS: "-D warnings" # fail on warnings 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: install tools 17 | run: | 18 | rustup show 19 | 20 | - name: check formatting 21 | run: | 22 | cargo fmt --all --check 23 | 24 | nixpkgs-fmt: 25 | name: nixpkgs-fmt 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | 30 | - name: Install Nix 31 | uses: cachix/install-nix-action@V27 32 | with: 33 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 34 | 35 | - name: check formatting 36 | run: | 37 | nix run nixpkgs#nixpkgs-fmt -- --check . 38 | 39 | prettier: 40 | name: prettier 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v4 44 | 45 | - name: Install Nix 46 | uses: cachix/install-nix-action@V27 47 | with: 48 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 49 | 50 | - name: check formatting 51 | run: | 52 | npx prettier --check . 53 | -------------------------------------------------------------------------------- /.github/workflows/nix.yaml: -------------------------------------------------------------------------------- 1 | name: test Nix support 2 | 3 | on: push 4 | 5 | jobs: 6 | build-with-nix: 7 | name: Build with Nix 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 🛎️ 11 | uses: actions/checkout@v3 12 | 13 | - name: Install Nix ❄ 14 | uses: DeterminateSystems/nix-installer-action@v4 15 | 16 | - name: Run the Magic Nix Cache 🔌 17 | uses: DeterminateSystems/magic-nix-cache-action@v2 18 | 19 | - name: Build and test 20 | run: nix build --print-build-logs 21 | 22 | evaluate-nix-shell: 23 | name: Evaluate the Nix shell 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout 🛎️ 27 | uses: actions/checkout@v3 28 | 29 | - name: Install Nix ❄ 30 | uses: DeterminateSystems/nix-installer-action@v4 31 | 32 | - name: Run the Magic Nix Cache 🔌 33 | uses: DeterminateSystems/magic-nix-cache-action@v2 34 | 35 | - name: Evaluate the Nix shell 36 | run: nix develop -c "true" 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust 2 | /target 3 | 4 | # Nix 5 | /result* 6 | 7 | # direnv 8 | /.direnv 9 | /.envrc.local 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /flake.lock 2 | /ndc-reference/*.json 3 | **/expected.json 4 | /specification/book/** 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[json]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | }, 5 | "[jsonc]": { 6 | "editor.defaultFormatter": "esbenp.prettier-vscode" 7 | }, 8 | "[markdown]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | package.version = "0.2.2" 5 | package.edition = "2021" 6 | 7 | members = [ 8 | "ndc-models", 9 | "ndc-reference", 10 | "ndc-test", 11 | ] 12 | 13 | [workspace.lints.clippy] 14 | all = { level = "warn", priority = -1 } 15 | pedantic = { level = "warn", priority = -1 } 16 | # unstable warnings; we might need to suppress them 17 | redundant_clone = "warn" 18 | # disable certain pedantic warnings 19 | doc_markdown = { level = "allow" } 20 | missing_errors_doc = { level = "allow" } 21 | missing_panics_doc = { level = "allow" } 22 | module_name_repetitions = { level = "allow" } 23 | must_use_candidate = { level = "allow" } 24 | return_self_not_must_use = { level = "allow" } 25 | too_many_lines = { level = "allow" } 26 | wildcard_imports = { level = "allow" } 27 | 28 | [workspace.dependencies] 29 | async-trait = "0.1" 30 | axum = "0.7" 31 | clap = "4" 32 | colorful = "0.2" 33 | goldenfile = "1" 34 | indexmap = "2" 35 | insta = { version = "1", features = ["glob", "json"] } 36 | iso8601 = "0.6.1" 37 | itertools = "0.13.0" 38 | prometheus = "0.13" 39 | rand = "0.8" 40 | ref-cast = "1.0" 41 | regex = "1" 42 | reqwest = { version = "0.12", default-features = false } 43 | schemars = "0.8" 44 | semver = "1" 45 | serde = "1" 46 | serde_json = "1" 47 | serde_with = "3" 48 | smol_str = "0.1" 49 | thiserror = "1" 50 | tokio = "1" 51 | tokio-test = "0.4" 52 | url = "2" 53 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # this should match the Rust version in rust-toolchain.yaml and the 2 | FROM rust:1.82.0 AS chef 3 | 4 | WORKDIR app 5 | 6 | RUN apt-get update \ 7 | && DEBIAN_FRONTEND=noninteractive \ 8 | apt-get install --no-install-recommends --assume-yes \ 9 | lld libssl-dev ssh git pkg-config 10 | 11 | ENV CARGO_HOME=/app/.cargo 12 | ENV PATH="$PATH:$CARGO_HOME/bin" 13 | 14 | RUN cargo install cargo-chef 15 | 16 | COPY rust-toolchain.toml . 17 | RUN rustup show 18 | 19 | ### 20 | # Plan recipe 21 | FROM chef AS planner 22 | 23 | COPY . . 24 | RUN cargo chef prepare --recipe-path recipe.json 25 | 26 | ### 27 | # Build recipe 28 | FROM chef AS build 29 | 30 | COPY --from=planner /app/recipe.json recipe.json 31 | 32 | # build dependencies to produce a cached layer 33 | RUN cargo chef cook --release --all-targets --recipe-path recipe.json 34 | RUN cargo chef cook --all-targets --recipe-path recipe.json 35 | 36 | # copy the source after building dependencies to allow caching 37 | COPY . . 38 | 39 | # Build the app 40 | RUN cargo build --release --all-targets 41 | 42 | ### 43 | # Ship the app in an image with very little else 44 | FROM debian:bookworm-slim as ndc-reference 45 | COPY --from=build /app/target/release/ndc-reference /usr/bin/ndc-reference 46 | ENTRYPOINT ["ndc-reference"] 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NDC Specification 2 | 3 | This repository contains the specification for Hasura's Native Data Connectors (NDC), and resources for connector authors. 4 | 5 | ## Resources 6 | 7 | - [Specification](./specification) 8 | - [Rust models](./ndc-models) 9 | - [Reference implementation](./ndc-reference) 10 | - [Test runner](./ndc-test) 11 | 12 | ## Other Links 13 | 14 | - [Rendered Specification](http://hasura.github.io/ndc-spec/) 15 | - [Connector Hub](https://github.com/hasura/ndc-hub) 16 | 17 | ## Getting Started 18 | 19 | To build the Rust code, and run all test suites: 20 | 21 | ```sh 22 | cargo build 23 | cargo test 24 | ``` 25 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # Lists all targets 2 | default: 3 | just --list 4 | 5 | lint: 6 | cargo clippy --all-targets --no-deps 7 | 8 | # Runs the tests 9 | test *ARGS: 10 | #!/usr/bin/env bash 11 | if command -v cargo-nextest; then 12 | COMMAND=(cargo nextest run) 13 | else 14 | COMMAND=(cargo test) 15 | fi 16 | COMMAND+=(--no-fail-fast "$@") 17 | echo "${COMMAND[*]}" 18 | "${COMMAND[@]}" 19 | 20 | # Formats all the Markdown, Rust, Nix etc 21 | fix-format: fix-format-prettier 22 | cargo fmt --all 23 | ! command -v nix || nix fmt 24 | 25 | # Formats Markdown, etc with prettier 26 | fix-format-prettier: 27 | npx --yes prettier --write . 28 | 29 | # Runs the tests and updates all goldenfiles with the test output 30 | update-golden-files: 31 | UPDATE_GOLDENFILES=1 just test 32 | just fix-format-prettier 33 | 34 | build-docs: 35 | #!/usr/bin/env bash 36 | cd specification 37 | BUILD_OUTPUT=$(mdbook build 2>&1) 38 | 39 | if echo "$BUILD_OUTPUT" | grep -q '\[ERROR\]'; then 40 | echo "Build failed with errors:" 41 | echo "$BUILD_OUTPUT" 42 | exit 1 43 | else 44 | echo "Build completed successfully." 45 | fi 46 | 47 | # Starts the ndc-spec documentation webserver 48 | start-docs: 49 | cd specification && mdbook serve 50 | -------------------------------------------------------------------------------- /ndc-models/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ndc-models" 3 | description = "Protocol definitions for the Hasura NDC specification" 4 | version.workspace = true 5 | edition.workspace = true 6 | 7 | [features] 8 | # Use Arc to nest Relations rather than Box 9 | arc-relation = [] 10 | 11 | [lints] 12 | workspace = true 13 | 14 | [dependencies] 15 | indexmap = { workspace = true, features = ["serde"] } 16 | ref-cast = { workspace = true } 17 | schemars = { workspace = true, features = ["indexmap2", "preserve_order", "smol_str"] } 18 | serde = { workspace = true } 19 | serde_json = { workspace = true, features = ["preserve_order"] } 20 | serde_with = { workspace = true } 21 | smol_str = { workspace = true, features = ["serde"] } 22 | 23 | [dev-dependencies] 24 | goldenfile = { workspace = true } 25 | -------------------------------------------------------------------------------- /ndc-models/README.md: -------------------------------------------------------------------------------- 1 | # NDC Rust Client 2 | 3 | This crate contains the type definitions for working with connectors. It can be 4 | used directly from Rust projects, or indirectly via the JSON schema documents, 5 | which are generated as [test artifacts](tests/json_schema) for use in other 6 | clients. 7 | -------------------------------------------------------------------------------- /ndc-models/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod aggregation; 2 | pub use aggregation::*; 3 | mod capabilities; 4 | pub use capabilities::*; 5 | mod expression; 6 | pub use expression::*; 7 | mod fields; 8 | pub use fields::*; 9 | mod names; 10 | pub use names::*; 11 | mod ordering; 12 | pub use ordering::*; 13 | mod relational_query; 14 | pub use relational_query::*; 15 | mod requests; 16 | pub use requests::*; 17 | mod schema; 18 | pub use schema::*; 19 | 20 | pub const VERSION_HEADER_NAME: &str = "X-Hasura-NDC-Version"; 21 | pub const VERSION: &str = env!("CARGO_PKG_VERSION"); 22 | -------------------------------------------------------------------------------- /ndc-models/tests/json_schema/error_response.jsonschema: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Error Response", 4 | "type": "object", 5 | "required": [ 6 | "details", 7 | "message" 8 | ], 9 | "properties": { 10 | "message": { 11 | "description": "A human-readable summary of the error", 12 | "type": "string" 13 | }, 14 | "details": { 15 | "description": "Any additional structured information about the error" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /ndc-models/tests/json_schema/explain_response.jsonschema: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Explain Response", 4 | "type": "object", 5 | "required": [ 6 | "details" 7 | ], 8 | "properties": { 9 | "details": { 10 | "description": "A list of human-readable key-value pairs describing a query execution plan. For example, a connector for a relational database might return the generated SQL and/or the output of the `EXPLAIN` command. An API-based connector might encode a list of statically-known API calls which would be made.", 11 | "type": "object", 12 | "additionalProperties": { 13 | "type": "string" 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /ndc-models/tests/json_schema/mutation_response.jsonschema: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Mutation Response", 4 | "type": "object", 5 | "required": [ 6 | "operation_results" 7 | ], 8 | "properties": { 9 | "operation_results": { 10 | "description": "The results of each mutation operation, in the same order as they were received", 11 | "type": "array", 12 | "items": { 13 | "$ref": "#/definitions/MutationOperationResults" 14 | } 15 | } 16 | }, 17 | "definitions": { 18 | "MutationOperationResults": { 19 | "title": "Mutation Operation Results", 20 | "oneOf": [ 21 | { 22 | "type": "object", 23 | "required": [ 24 | "result", 25 | "type" 26 | ], 27 | "properties": { 28 | "type": { 29 | "type": "string", 30 | "enum": [ 31 | "procedure" 32 | ] 33 | }, 34 | "result": true 35 | } 36 | } 37 | ] 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /ndc-models/tests/json_schema/query_response.jsonschema: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Query Response", 4 | "description": "Query responses may return multiple RowSets when using queries with variables. Else, there should always be exactly one RowSet", 5 | "type": "array", 6 | "items": { 7 | "$ref": "#/definitions/RowSet" 8 | }, 9 | "definitions": { 10 | "Group": { 11 | "title": "Group", 12 | "type": "object", 13 | "required": [ 14 | "aggregates", 15 | "dimensions" 16 | ], 17 | "properties": { 18 | "dimensions": { 19 | "description": "Values of dimensions which identify this group", 20 | "type": "array", 21 | "items": true 22 | }, 23 | "aggregates": { 24 | "description": "Aggregates computed within this group", 25 | "type": "object", 26 | "additionalProperties": true 27 | } 28 | } 29 | }, 30 | "RowFieldValue": { 31 | "title": "Row Field Value" 32 | }, 33 | "RowSet": { 34 | "title": "Row Set", 35 | "type": "object", 36 | "properties": { 37 | "aggregates": { 38 | "description": "The results of the aggregates returned by the query", 39 | "type": [ 40 | "object", 41 | "null" 42 | ], 43 | "additionalProperties": true 44 | }, 45 | "rows": { 46 | "description": "The rows returned by the query, corresponding to the query's fields", 47 | "type": [ 48 | "array", 49 | "null" 50 | ], 51 | "items": { 52 | "type": "object", 53 | "additionalProperties": { 54 | "$ref": "#/definitions/RowFieldValue" 55 | } 56 | } 57 | }, 58 | "groups": { 59 | "description": "The results of any grouping operation", 60 | "type": [ 61 | "array", 62 | "null" 63 | ], 64 | "items": { 65 | "$ref": "#/definitions/Group" 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /ndc-models/tests/json_schema/relational_query_response.jsonschema: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "RelationalQueryResponse", 4 | "type": "object", 5 | "required": [ 6 | "rows" 7 | ], 8 | "properties": { 9 | "rows": { 10 | "type": "array", 11 | "items": { 12 | "type": "array", 13 | "items": true 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /ndc-reference/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ndc-reference" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [lints] 7 | workspace = true 8 | 9 | [[bin]] 10 | name = "ndc-reference" 11 | path = "bin/reference/main.rs" 12 | 13 | [dependencies] 14 | ndc-models = { path = "../ndc-models" } 15 | 16 | axum = { workspace = true } 17 | indexmap = { workspace = true, features = ["serde"] } 18 | prometheus = { workspace = true } 19 | regex = { workspace = true } 20 | semver = { workspace = true } 21 | serde_json = { workspace = true } 22 | tokio = { workspace = true, features = ["macros", "parking_lot", "rt-multi-thread", "signal"] } 23 | itertools = { workspace = true } 24 | iso8601 = { workspace = true } 25 | 26 | [dev-dependencies] 27 | ndc-test = { path = "../ndc-test" } 28 | 29 | async-trait = { workspace = true } 30 | insta = { workspace = true } 31 | tokio-test = { workspace = true } 32 | -------------------------------------------------------------------------------- /ndc-reference/README.md: -------------------------------------------------------------------------------- 1 | # NDC Reference Connector 2 | 3 | The reference connector implements the features in the NDC specification against an in-memory database. It is intended to illustrate the concepts involved, and should be complete, in the sense that every specification feature is covered. It is not optimized and is not intended for production use, but might be useful for testing. 4 | 5 | ## Getting Started 6 | 7 | ### With Cargo 8 | 9 | ```sh 10 | (cd ndc-reference; cargo run) 11 | ``` 12 | 13 | ### With Docker 14 | 15 | ```sh 16 | docker build -t reference_connector . 17 | docker run -it --rm -p 8080:8080 reference_connector 18 | ``` 19 | 20 | ## Using the reference connector 21 | 22 | The reference connector runs on http://localhost:8080 by default: 23 | 24 | ```sh 25 | curl http://localhost:8080/schema | jq . 26 | ``` 27 | -------------------------------------------------------------------------------- /ndc-reference/articles.jsonl: -------------------------------------------------------------------------------- 1 | {"id": 1, "title": "The Next 700 Programming Languages", "published_date": "1966-03-01", "author_id": 1} 2 | {"id": 2, "title": "Why Functional Programming Matters", "published_date": "1989-04-01", "author_id": 2} 3 | {"id": 3, "title": "The Design And Implementation Of Programming Languages", "published_date": "1983-07-01", "author_id": 2} 4 | -------------------------------------------------------------------------------- /ndc-reference/authors.jsonl: -------------------------------------------------------------------------------- 1 | {"id": 1, "first_name": "Peter", "last_name": "Landin"} 2 | {"id": 2, "first_name": "John", "last_name": "Hughes"} 3 | -------------------------------------------------------------------------------- /ndc-reference/countries.jsonl: -------------------------------------------------------------------------------- 1 | {"id": 1, "name": "UK", "area_km2": 244376, "cities": [{"name": "London"}, {"name": "Birmingham"}, {"name": "Manchester"}, {"name": "Glasgow"}, {"name": "Liverpool"}, {"name": "Bristol"}, {"name": "Edinburgh"}, {"name": "Leeds"}, {"name": "Sheffield"}, {"name": "Newcastle"}, {"name": "Nottingham"}, {"name": "Cardiff"}, {"name": "Belfast"}, {"name": "Leicester"}, {"name": "Coventry"}, {"name": "Sunderland"}, {"name": "Brighton"}, {"name": "Hull"}, {"name": "Plymouth"}, {"name": "Derby"}]} 2 | {"id": 2, "name": "Sweden", "area_km2": 450295, "cities": [{"name": "Stockholm"}, {"name": "Gothenburg"}, {"name": "Malmö"}, {"name": "Uppsala"}, {"name": "Västerås"}, {"name": "Örebro"}, {"name": "Linköping"}, {"name": "Helsingborg"}]} 3 | {"id": 3, "name": "Australia", "area_km2": 7688287, "cities": [{"name": "Melbourne"}, {"name": "Sydney"}, {"name": "Brisbane"}, {"name": "Adelaide"}, {"name": "Canberra"}, {"name": "Perth"}, {"name": "Darwin"}, {"name": "Hobart"}]} 4 | {"id": 4, "name": "Mars", "area_km2": 144798500, "cities": []} 5 | -------------------------------------------------------------------------------- /ndc-reference/institutions.jsonl: -------------------------------------------------------------------------------- 1 | { "id": 1, "name": "Queen Mary University of London", "location": { "city": "London", "country": "UK", "country_id": 1, "campuses": ["Mile End", "Whitechapel", "Charterhouse Square", "West Smithfield"] }, "staff": [ { "first_name": "Peter", "last_name": "Landin", "specialities": ["Computer Science", "Education"], "born_country_id": 1 } ], "departments": ["Humanities and Social Sciences", "Science and Engineering", "Medicine and Dentistry"] } 2 | { "id": 2, "name": "Chalmers University of Technology", "location": { "city": "Gothenburg", "country": "Sweden", "country_id": 2, "campuses": ["Johanneberg", "Lindholmen"] }, "staff": [ { "first_name": "John", "last_name": "Hughes", "specialities": ["Computer Science", "Functional Programming", "Software Testing"], "born_country_id": 2 }, { "first_name": "Koen", "last_name": "Claessen", "specialities": ["Computer Science", "Functional Programming", "Automated Reasoning"], "born_country_id": 2 } ], "departments": ["Architecture and Civil Engineering", "Computer Science and Engineering", "Electrical Engineering", "Physics", "Industrial and Materials Science"] } 3 | { "id": 3, "name": "Stockholm University", "location": { "city": "Stockholm", "country": "Sweden", "country_id": 2, "campuses": ["Frescati", "Kista"] }, "staff": [ { "first_name": "Jelena", "last_name": "Zdravkovic", "specialities": ["Requirements Engineering", "Enterprise Modeling", "System Analysis"], "born_country_id": 2 } ], "departments": ["Computer and Systems Sciences", "Physics", "Astronomy"] } 4 | -------------------------------------------------------------------------------- /ndc-reference/tests/capabilities/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response 4 | --- 5 | { 6 | "version": "0.2.2", 7 | "capabilities": { 8 | "query": { 9 | "aggregates": { 10 | "filter_by": {}, 11 | "group_by": { 12 | "filter": {}, 13 | "order": {}, 14 | "paginate": {} 15 | } 16 | }, 17 | "variables": {}, 18 | "nested_fields": { 19 | "filter_by": { 20 | "nested_arrays": { 21 | "contains": {}, 22 | "is_empty": {} 23 | } 24 | }, 25 | "order_by": {}, 26 | "aggregates": {}, 27 | "nested_collections": {} 28 | }, 29 | "exists": { 30 | "named_scopes": {}, 31 | "unrelated": {}, 32 | "nested_collections": {}, 33 | "nested_scalar_collections": {} 34 | } 35 | }, 36 | "mutation": {}, 37 | "relationships": { 38 | "relation_comparisons": {}, 39 | "order_by_aggregate": {}, 40 | "nested": { 41 | "array": {}, 42 | "filtering": {}, 43 | "ordering": {} 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ndc-reference/tests/mutation/delete_articles/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/mutation/delete_articles/request.json 5 | --- 6 | { 7 | "operation_results": [ 8 | { 9 | "type": "procedure", 10 | "result": [ 11 | { 12 | "id": 1, 13 | "title": "The Next 700 Programming Languages" 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /ndc-reference/tests/mutation/delete_articles/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/mutation_request.jsonschema", 3 | "insert_schema": [], 4 | "operations": [ 5 | { 6 | "type": "procedure", 7 | "name": "delete_articles", 8 | "arguments": { 9 | "where": { 10 | "type": "binary_comparison_operator", 11 | "column": { 12 | "type": "column", 13 | "name": "author_id" 14 | }, 15 | "operator": "eq", 16 | "value": { 17 | "type": "scalar", 18 | "value": 1 19 | } 20 | } 21 | }, 22 | "fields": { 23 | "type": "array", 24 | "fields": { 25 | "type": "object", 26 | "fields": { 27 | "id": { 28 | "type": "column", 29 | "column": "id" 30 | }, 31 | "title": { 32 | "type": "column", 33 | "column": "title" 34 | } 35 | } 36 | } 37 | } 38 | } 39 | ], 40 | "collection_relationships": {} 41 | } 42 | -------------------------------------------------------------------------------- /ndc-reference/tests/mutation/upsert_article/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/mutation/upsert_article/request.json 5 | --- 6 | { 7 | "operation_results": [ 8 | { 9 | "type": "procedure", 10 | "result": null 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /ndc-reference/tests/mutation/upsert_article/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/mutation_request.jsonschema", 3 | "insert_schema": [], 4 | "operations": [ 5 | { 6 | "type": "procedure", 7 | "name": "upsert_article", 8 | "arguments": { 9 | "article": { 10 | "id": 4, 11 | "title": "A Novel Representation of Lists and its Application to the Function \"Reverse\"", 12 | "author_id": 2 13 | } 14 | }, 15 | "fields": { 16 | "type": "object", 17 | "fields": { 18 | "id": { 19 | "type": "column", 20 | "column": "id" 21 | } 22 | } 23 | } 24 | } 25 | ], 26 | "collection_relationships": {} 27 | } 28 | -------------------------------------------------------------------------------- /ndc-reference/tests/mutation/upsert_article_with_relationship/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/mutation/upsert_article_with_relationship/request.json 5 | --- 6 | { 7 | "operation_results": [ 8 | { 9 | "type": "procedure", 10 | "result": { 11 | "id": 2, 12 | "author": { 13 | "rows": [ 14 | { 15 | "first_name": "John", 16 | "last_name": "Hughes" 17 | } 18 | ] 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /ndc-reference/tests/mutation/upsert_article_with_relationship/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/mutation_request.jsonschema", 3 | "insert_schema": [], 4 | "operations": [ 5 | { 6 | "type": "procedure", 7 | "name": "upsert_article", 8 | "arguments": { 9 | "article": { 10 | "id": 2, 11 | "title": "QuickCheck: a lightweight tool for random testing of Haskell programs", 12 | "author_id": 2 13 | } 14 | }, 15 | "fields": { 16 | "type": "object", 17 | "fields": { 18 | "id": { 19 | "type": "column", 20 | "column": "id" 21 | }, 22 | "author": { 23 | "type": "relationship", 24 | "arguments": {}, 25 | "relationship": "article_author", 26 | "query": { 27 | "fields": { 28 | "first_name": { 29 | "type": "column", 30 | "column": "first_name" 31 | }, 32 | "last_name": { 33 | "type": "column", 34 | "column": "last_name" 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | ], 43 | "collection_relationships": { 44 | "article_author": { 45 | "arguments": {}, 46 | "column_mapping": { 47 | "author_id": ["id"] 48 | }, 49 | "relationship_type": "object", 50 | "source_collection_or_type": "article", 51 | "target_collection": "authors" 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/aggregate_function/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/aggregate_function/request.json 5 | --- 6 | [ 7 | { 8 | "aggregates": { 9 | "min_id": 1, 10 | "max_id": 3 11 | } 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/aggregate_function/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "aggregates": { 7 | "min_id": { 8 | "type": "single_column", 9 | "column": "id", 10 | "function": "min" 11 | }, 12 | "max_id": { 13 | "type": "single_column", 14 | "column": "id", 15 | "function": "max" 16 | } 17 | } 18 | }, 19 | "collection_relationships": {} 20 | } 21 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/aggregate_function_with_nested_field/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/aggregate_function_with_nested_field/request.json 5 | --- 6 | [ 7 | { 8 | "aggregates": { 9 | "locations": 3 10 | } 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/aggregate_function_with_nested_field/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "aggregates": { 7 | "locations": { 8 | "type": "column_count", 9 | "column": "location", 10 | "field_path": ["city"], 11 | "distinct": true 12 | } 13 | } 14 | }, 15 | "collection_relationships": {} 16 | } 17 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/authors_with_article_aggregate/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/authors_with_article_aggregate/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "Peter", 11 | "last_name": "Landin", 12 | "articles": { 13 | "aggregates": { 14 | "count": 1 15 | } 16 | } 17 | }, 18 | { 19 | "first_name": "John", 20 | "last_name": "Hughes", 21 | "articles": { 22 | "aggregates": { 23 | "count": 2 24 | } 25 | } 26 | } 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/authors_with_article_aggregate/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | }, 15 | "articles": { 16 | "type": "relationship", 17 | "arguments": {}, 18 | "relationship": "author_articles", 19 | "query": { 20 | "aggregates": { 21 | "count": { 22 | "type": "star_count" 23 | } 24 | } 25 | } 26 | } 27 | } 28 | }, 29 | "collection_relationships": { 30 | "author_articles": { 31 | "arguments": {}, 32 | "column_mapping": { 33 | "id": ["author_id"] 34 | }, 35 | "relationship_type": "array", 36 | "source_collection_or_type": "author", 37 | "target_collection": "articles" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/authors_with_articles/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/authors_with_articles/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "Peter", 11 | "last_name": "Landin", 12 | "articles": { 13 | "rows": [ 14 | { 15 | "id": 1, 16 | "title": "The Next 700 Programming Languages" 17 | } 18 | ] 19 | } 20 | }, 21 | { 22 | "first_name": "John", 23 | "last_name": "Hughes", 24 | "articles": { 25 | "rows": [ 26 | { 27 | "id": 2, 28 | "title": "Why Functional Programming Matters" 29 | }, 30 | { 31 | "id": 3, 32 | "title": "The Design And Implementation Of Programming Languages" 33 | } 34 | ] 35 | } 36 | } 37 | ] 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/authors_with_articles/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | }, 15 | "articles": { 16 | "type": "relationship", 17 | "arguments": {}, 18 | "relationship": "author_articles", 19 | "query": { 20 | "fields": { 21 | "id": { 22 | "type": "column", 23 | "column": "id" 24 | }, 25 | "title": { 26 | "type": "column", 27 | "column": "title" 28 | } 29 | } 30 | } 31 | } 32 | } 33 | }, 34 | "collection_relationships": { 35 | "author_articles": { 36 | "arguments": {}, 37 | "column_mapping": { 38 | "id": ["author_id"] 39 | }, 40 | "relationship_type": "array", 41 | "source_collection_or_type": "author", 42 | "target_collection": "articles" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/column_count/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/column_count/request.json 5 | --- 6 | [ 7 | { 8 | "aggregates": { 9 | "distinct_author_count": 2, 10 | "author_count": 3 11 | } 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/column_count/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "aggregates": { 7 | "distinct_author_count": { 8 | "type": "column_count", 9 | "column": "author_id", 10 | "distinct": true 11 | }, 12 | "author_count": { 13 | "type": "column_count", 14 | "column": "author_id", 15 | "distinct": false 16 | } 17 | } 18 | }, 19 | "collection_relationships": {} 20 | } 21 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/get_all_articles/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/get_all_articles/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 1, 11 | "title": "The Next 700 Programming Languages" 12 | }, 13 | { 14 | "id": 2, 15 | "title": "Why Functional Programming Matters" 16 | }, 17 | { 18 | "id": 3, 19 | "title": "The Design And Implementation Of Programming Languages" 20 | } 21 | ] 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/get_all_articles/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "articles", 3 | "arguments": {}, 4 | "query": { 5 | "fields": { 6 | "id": { 7 | "type": "column", 8 | "column": "id" 9 | }, 10 | "title": { 11 | "type": "column", 12 | "column": "title" 13 | } 14 | } 15 | }, 16 | "collection_relationships": {} 17 | } 18 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/get_max_article/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/get_max_article/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "__value": { 11 | "id": 3, 12 | "title": "The Design And Implementation Of Programming Languages" 13 | } 14 | } 15 | ] 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/get_max_article/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "latest_article", 3 | "arguments": {}, 4 | "query": { 5 | "fields": { 6 | "__value": { 7 | "type": "column", 8 | "column": "__value", 9 | "fields": { 10 | "type": "object", 11 | "fields": { 12 | "id": { 13 | "type": "column", 14 | "column": "id" 15 | }, 16 | "title": { 17 | "type": "column", 18 | "column": "title" 19 | } 20 | } 21 | } 22 | } 23 | } 24 | }, 25 | "collection_relationships": {} 26 | } 27 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/get_max_article_id/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/get_max_article_id/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "__value": 3 11 | } 12 | ] 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/get_max_article_id/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "latest_article_id", 3 | "arguments": {}, 4 | "query": { 5 | "fields": { 6 | "__value": { 7 | "type": "column", 8 | "column": "__value" 9 | } 10 | } 11 | }, 12 | "collection_relationships": {} 13 | } 14 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/group_by/request.json 5 | --- 6 | [ 7 | { 8 | "groups": [ 9 | { 10 | "dimensions": [ 11 | 1 12 | ], 13 | "aggregates": { 14 | "min_id": 1, 15 | "max_id": 1 16 | } 17 | }, 18 | { 19 | "dimensions": [ 20 | 2 21 | ], 22 | "aggregates": { 23 | "min_id": 2, 24 | "max_id": 3 25 | } 26 | } 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "groups": { 7 | "aggregates": { 8 | "min_id": { 9 | "type": "single_column", 10 | "column": "id", 11 | "function": "min" 12 | }, 13 | "max_id": { 14 | "type": "single_column", 15 | "column": "id", 16 | "function": "max" 17 | } 18 | }, 19 | "dimensions": [ 20 | { 21 | "type": "column", 22 | "column_name": "author_id", 23 | "path": [] 24 | } 25 | ] 26 | } 27 | }, 28 | "collection_relationships": {} 29 | } 30 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_extraction/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/group_by_with_extraction/request.json 5 | --- 6 | [ 7 | { 8 | "groups": [ 9 | { 10 | "dimensions": [ 11 | 1966 12 | ], 13 | "aggregates": { 14 | "count": 1 15 | } 16 | }, 17 | { 18 | "dimensions": [ 19 | 1989 20 | ], 21 | "aggregates": { 22 | "count": 1 23 | } 24 | }, 25 | { 26 | "dimensions": [ 27 | 1983 28 | ], 29 | "aggregates": { 30 | "count": 1 31 | } 32 | } 33 | ] 34 | } 35 | ] 36 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_extraction/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "groups": { 7 | "aggregates": { 8 | "count": { 9 | "type": "star_count" 10 | } 11 | }, 12 | "dimensions": [ 13 | { 14 | "type": "column", 15 | "column_name": "published_date", 16 | "path": [], 17 | "extraction": "year" 18 | } 19 | ] 20 | } 21 | }, 22 | "collection_relationships": {} 23 | } 24 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_having/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/group_by_with_having/request.json 5 | --- 6 | [ 7 | { 8 | "groups": [ 9 | { 10 | "dimensions": [ 11 | 2 12 | ], 13 | "aggregates": { 14 | "min_id": 2, 15 | "max_id": 3 16 | } 17 | } 18 | ] 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_having/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "groups": { 7 | "aggregates": { 8 | "min_id": { 9 | "type": "single_column", 10 | "column": "id", 11 | "function": "min" 12 | }, 13 | "max_id": { 14 | "type": "single_column", 15 | "column": "id", 16 | "function": "max" 17 | } 18 | }, 19 | "dimensions": [ 20 | { 21 | "type": "column", 22 | "column_name": "author_id", 23 | "path": [] 24 | } 25 | ], 26 | "predicate": { 27 | "type": "binary_comparison_operator", 28 | "target": { 29 | "type": "aggregate", 30 | "aggregate": { 31 | "type": "star_count" 32 | } 33 | }, 34 | "operator": "eq", 35 | "value": { 36 | "type": "scalar", 37 | "value": 2 38 | } 39 | } 40 | } 41 | }, 42 | "collection_relationships": {} 43 | } 44 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_nested_relationship_in_dimension/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/group_by_with_nested_relationship_in_dimension/request.json 5 | --- 6 | [ 7 | { 8 | "groups": [ 9 | { 10 | "dimensions": [ 11 | "UK" 12 | ], 13 | "aggregates": { 14 | "institution_count": 1 15 | } 16 | }, 17 | { 18 | "dimensions": [ 19 | "Sweden" 20 | ], 21 | "aggregates": { 22 | "institution_count": 2 23 | } 24 | } 25 | ] 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_nested_relationship_in_dimension/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "groups": { 7 | "aggregates": { 8 | "institution_count": { 9 | "type": "star_count" 10 | } 11 | }, 12 | "dimensions": [ 13 | { 14 | "type": "column", 15 | "column_name": "name", 16 | "path": [ 17 | { 18 | "field_path": ["location"], 19 | "relationship": "location_country", 20 | "arguments": {} 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | }, 27 | "collection_relationships": { 28 | "location_country": { 29 | "arguments": {}, 30 | "column_mapping": { 31 | "country_id": ["id"] 32 | }, 33 | "relationship_type": "object", 34 | "target_collection": "countries" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_order_by/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/group_by_with_order_by/request.json 5 | --- 6 | [ 7 | { 8 | "groups": [ 9 | { 10 | "dimensions": [ 11 | 2 12 | ], 13 | "aggregates": { 14 | "article_count": 2 15 | } 16 | } 17 | ] 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_order_by/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "groups": { 7 | "aggregates": { 8 | "article_count": { 9 | "type": "star_count" 10 | } 11 | }, 12 | "dimensions": [ 13 | { 14 | "type": "column", 15 | "column_name": "author_id", 16 | "path": [] 17 | } 18 | ], 19 | "limit": 1, 20 | "offset": 0, 21 | "order_by": { 22 | "elements": [ 23 | { 24 | "order_direction": "desc", 25 | "target": { 26 | "type": "aggregate", 27 | "aggregate": { 28 | "type": "star_count" 29 | }, 30 | "path": [] 31 | } 32 | } 33 | ] 34 | } 35 | } 36 | }, 37 | "collection_relationships": {} 38 | } 39 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_order_by_dimension/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/group_by_with_order_by_dimension/request.json 5 | --- 6 | [ 7 | { 8 | "groups": [ 9 | { 10 | "dimensions": [ 11 | 2 12 | ], 13 | "aggregates": { 14 | "article_count": 2 15 | } 16 | }, 17 | { 18 | "dimensions": [ 19 | 1 20 | ], 21 | "aggregates": { 22 | "article_count": 1 23 | } 24 | } 25 | ] 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_order_by_dimension/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "groups": { 7 | "aggregates": { 8 | "article_count": { 9 | "type": "star_count" 10 | } 11 | }, 12 | "dimensions": [ 13 | { 14 | "type": "column", 15 | "column_name": "author_id", 16 | "path": [] 17 | } 18 | ], 19 | "order_by": { 20 | "elements": [ 21 | { 22 | "order_direction": "desc", 23 | "target": { 24 | "type": "dimension", 25 | "index": 0, 26 | "path": [] 27 | } 28 | } 29 | ] 30 | } 31 | } 32 | }, 33 | "collection_relationships": {} 34 | } 35 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_path_in_dimension/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/group_by_with_path_in_dimension/request.json 5 | --- 6 | [ 7 | { 8 | "groups": [ 9 | { 10 | "dimensions": [ 11 | "Landin", 12 | "Peter" 13 | ], 14 | "aggregates": { 15 | "article_count": 1 16 | } 17 | }, 18 | { 19 | "dimensions": [ 20 | "Hughes", 21 | "John" 22 | ], 23 | "aggregates": { 24 | "article_count": 2 25 | } 26 | } 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_path_in_dimension/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "groups": { 7 | "aggregates": { 8 | "article_count": { 9 | "type": "star_count" 10 | } 11 | }, 12 | "dimensions": [ 13 | { 14 | "type": "column", 15 | "column_name": "last_name", 16 | "path": [ 17 | { 18 | "relationship": "article_author", 19 | "arguments": {} 20 | } 21 | ] 22 | }, 23 | { 24 | "type": "column", 25 | "column_name": "first_name", 26 | "path": [ 27 | { 28 | "relationship": "article_author", 29 | "arguments": {} 30 | } 31 | ] 32 | } 33 | ] 34 | } 35 | }, 36 | "collection_relationships": { 37 | "article_author": { 38 | "arguments": {}, 39 | "column_mapping": { 40 | "author_id": ["id"] 41 | }, 42 | "relationship_type": "object", 43 | "source_collection_or_type": "article", 44 | "target_collection": "authors" 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_relationship/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/group_by_with_relationship/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "Peter", 11 | "last_name": "Landin", 12 | "articles": { 13 | "groups": [ 14 | { 15 | "dimensions": [ 16 | "The Next 700 Programming Languages" 17 | ], 18 | "aggregates": { 19 | "min_id": 1, 20 | "max_id": 1 21 | } 22 | } 23 | ] 24 | } 25 | }, 26 | { 27 | "first_name": "John", 28 | "last_name": "Hughes", 29 | "articles": { 30 | "groups": [ 31 | { 32 | "dimensions": [ 33 | "Why Functional Programming Matters" 34 | ], 35 | "aggregates": { 36 | "min_id": 2, 37 | "max_id": 2 38 | } 39 | }, 40 | { 41 | "dimensions": [ 42 | "The Design And Implementation Of Programming Languages" 43 | ], 44 | "aggregates": { 45 | "min_id": 3, 46 | "max_id": 3 47 | } 48 | } 49 | ] 50 | } 51 | } 52 | ] 53 | } 54 | ] 55 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_relationship/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | }, 15 | "articles": { 16 | "type": "relationship", 17 | "arguments": {}, 18 | "relationship": "author_articles", 19 | "query": { 20 | "groups": { 21 | "aggregates": { 22 | "min_id": { 23 | "type": "single_column", 24 | "column": "id", 25 | "function": "min" 26 | }, 27 | "max_id": { 28 | "type": "single_column", 29 | "column": "id", 30 | "function": "max" 31 | } 32 | }, 33 | "dimensions": [ 34 | { 35 | "type": "column", 36 | "column_name": "title", 37 | "path": [] 38 | } 39 | ] 40 | } 41 | } 42 | } 43 | } 44 | }, 45 | "collection_relationships": { 46 | "author_articles": { 47 | "arguments": {}, 48 | "column_mapping": { 49 | "id": ["author_id"] 50 | }, 51 | "relationship_type": "array", 52 | "target_collection": "articles" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_star_count/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/group_by_with_star_count/request.json 5 | --- 6 | [ 7 | { 8 | "groups": [ 9 | { 10 | "dimensions": [ 11 | 1 12 | ], 13 | "aggregates": { 14 | "article_count": 1 15 | } 16 | }, 17 | { 18 | "dimensions": [ 19 | 2 20 | ], 21 | "aggregates": { 22 | "article_count": 2 23 | } 24 | } 25 | ] 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_star_count/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "groups": { 7 | "aggregates": { 8 | "article_count": { 9 | "type": "star_count" 10 | } 11 | }, 12 | "dimensions": [ 13 | { 14 | "type": "column", 15 | "column_name": "author_id", 16 | "path": [] 17 | } 18 | ] 19 | } 20 | }, 21 | "collection_relationships": {} 22 | } 23 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_where/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/group_by_with_where/request.json 5 | --- 6 | [ 7 | { 8 | "groups": [ 9 | { 10 | "dimensions": [ 11 | 1 12 | ], 13 | "aggregates": { 14 | "min_id": 1, 15 | "max_id": 1 16 | } 17 | } 18 | ] 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/group_by_with_where/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "groups": { 7 | "aggregates": { 8 | "min_id": { 9 | "type": "single_column", 10 | "column": "id", 11 | "function": "min" 12 | }, 13 | "max_id": { 14 | "type": "single_column", 15 | "column": "id", 16 | "function": "max" 17 | } 18 | }, 19 | "dimensions": [ 20 | { 21 | "type": "column", 22 | "column_name": "author_id", 23 | "path": [] 24 | } 25 | ] 26 | }, 27 | "predicate": { 28 | "type": "binary_comparison_operator", 29 | "column": { 30 | "type": "column", 31 | "name": "author_id", 32 | "path": [] 33 | }, 34 | "operator": "eq", 35 | "value": { 36 | "type": "scalar", 37 | "value": 1 38 | } 39 | } 40 | }, 41 | "collection_relationships": {} 42 | } 43 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/named_scopes/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/named_scopes/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "John", 11 | "last_name": "Hughes" 12 | } 13 | ] 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/named_scopes/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | } 15 | }, 16 | "predicate": { 17 | "type": "exists", 18 | "in_collection": { 19 | "type": "unrelated", 20 | "arguments": {}, 21 | "collection": "articles" 22 | }, 23 | "predicate": { 24 | "type": "and", 25 | "expressions": [ 26 | { 27 | "type": "binary_comparison_operator", 28 | "column": { 29 | "type": "column", 30 | "name": "author_id" 31 | }, 32 | "operator": "eq", 33 | "value": { 34 | "type": "column", 35 | "path": [], 36 | "name": "id", 37 | "scope": 1 38 | } 39 | }, 40 | { 41 | "type": "binary_comparison_operator", 42 | "column": { 43 | "type": "column", 44 | "name": "title" 45 | }, 46 | "operator": "like", 47 | "value": { 48 | "type": "scalar", 49 | "value": "Functional" 50 | } 51 | } 52 | ] 53 | } 54 | } 55 | }, 56 | "collection_relationships": {} 57 | } 58 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/nested_array_select/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/nested_array_select/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 1, 11 | "staff": [ 12 | { 13 | "last_name": "Landin", 14 | "fields_of_study": [ 15 | "Computer Science", 16 | "Education" 17 | ] 18 | } 19 | ], 20 | "departments": [ 21 | "Humanities and Social Sciences", 22 | "Science and Engineering", 23 | "Medicine and Dentistry" 24 | ] 25 | }, 26 | { 27 | "id": 2, 28 | "staff": [ 29 | { 30 | "last_name": "Hughes", 31 | "fields_of_study": [ 32 | "Computer Science", 33 | "Functional Programming", 34 | "Software Testing" 35 | ] 36 | }, 37 | { 38 | "last_name": "Claessen", 39 | "fields_of_study": [ 40 | "Computer Science", 41 | "Functional Programming", 42 | "Automated Reasoning" 43 | ] 44 | } 45 | ], 46 | "departments": [ 47 | "Architecture and Civil Engineering", 48 | "Computer Science and Engineering", 49 | "Electrical Engineering", 50 | "Physics", 51 | "Industrial and Materials Science" 52 | ] 53 | }, 54 | { 55 | "id": 3, 56 | "staff": [ 57 | { 58 | "last_name": "Zdravkovic", 59 | "fields_of_study": [ 60 | "Requirements Engineering", 61 | "Enterprise Modeling", 62 | "System Analysis" 63 | ] 64 | } 65 | ], 66 | "departments": [ 67 | "Computer and Systems Sciences", 68 | "Physics", 69 | "Astronomy" 70 | ] 71 | } 72 | ] 73 | } 74 | ] 75 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/nested_array_select/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "staff": { 12 | "type": "column", 13 | "column": "staff", 14 | "arguments": { 15 | "limit": { 16 | "type": "literal", 17 | "value": null 18 | } 19 | }, 20 | "fields": { 21 | "type": "array", 22 | "fields": { 23 | "type": "object", 24 | "fields": { 25 | "last_name": { 26 | "type": "column", 27 | "column": "last_name" 28 | }, 29 | "fields_of_study": { 30 | "type": "column", 31 | "column": "specialities", 32 | "arguments": { 33 | "limit": { 34 | "type": "literal", 35 | "value": null 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | }, 43 | "departments": { 44 | "type": "column", 45 | "column": "departments", 46 | "arguments": { 47 | "limit": { 48 | "type": "literal", 49 | "value": null 50 | } 51 | } 52 | } 53 | } 54 | }, 55 | "collection_relationships": {} 56 | } 57 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/nested_array_select_with_limit/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/nested_array_select_with_limit/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 1, 11 | "staff": [ 12 | { 13 | "last_name": "Landin", 14 | "fields_of_study": [ 15 | "Computer Science", 16 | "Education" 17 | ] 18 | } 19 | ], 20 | "departments": [ 21 | "Humanities and Social Sciences", 22 | "Science and Engineering", 23 | "Medicine and Dentistry" 24 | ] 25 | }, 26 | { 27 | "id": 2, 28 | "staff": [ 29 | { 30 | "last_name": "Hughes", 31 | "fields_of_study": [ 32 | "Computer Science", 33 | "Functional Programming" 34 | ] 35 | } 36 | ], 37 | "departments": [ 38 | "Architecture and Civil Engineering", 39 | "Computer Science and Engineering", 40 | "Electrical Engineering", 41 | "Physics", 42 | "Industrial and Materials Science" 43 | ] 44 | }, 45 | { 46 | "id": 3, 47 | "staff": [ 48 | { 49 | "last_name": "Zdravkovic", 50 | "fields_of_study": [ 51 | "Requirements Engineering", 52 | "Enterprise Modeling" 53 | ] 54 | } 55 | ], 56 | "departments": [ 57 | "Computer and Systems Sciences", 58 | "Physics", 59 | "Astronomy" 60 | ] 61 | } 62 | ] 63 | } 64 | ] 65 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/nested_array_select_with_limit/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "staff": { 12 | "type": "column", 13 | "column": "staff", 14 | "arguments": { 15 | "limit": { 16 | "type": "literal", 17 | "value": 1 18 | } 19 | }, 20 | "fields": { 21 | "type": "array", 22 | "fields": { 23 | "type": "object", 24 | "fields": { 25 | "last_name": { 26 | "type": "column", 27 | "column": "last_name" 28 | }, 29 | "fields_of_study": { 30 | "type": "column", 31 | "column": "specialities", 32 | "arguments": { 33 | "limit": { 34 | "type": "literal", 35 | "value": 2 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | }, 43 | "departments": { 44 | "type": "column", 45 | "column": "departments", 46 | "arguments": { 47 | "limit": { 48 | "type": "literal", 49 | "value": null 50 | } 51 | } 52 | } 53 | } 54 | }, 55 | "collection_relationships": {} 56 | } 57 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/nested_collection_with_aggregates/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/nested_collection_with_aggregates/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 1, 11 | "staff_aggregates": { 12 | "aggregates": { 13 | "count": 1 14 | } 15 | }, 16 | "staff": [ 17 | { 18 | "last_name": "Landin", 19 | "first_name": "Peter" 20 | } 21 | ] 22 | }, 23 | { 24 | "id": 2, 25 | "staff_aggregates": { 26 | "aggregates": { 27 | "count": 2 28 | } 29 | }, 30 | "staff": [ 31 | { 32 | "last_name": "Hughes", 33 | "first_name": "John" 34 | }, 35 | { 36 | "last_name": "Claessen", 37 | "first_name": "Koen" 38 | } 39 | ] 40 | }, 41 | { 42 | "id": 3, 43 | "staff_aggregates": { 44 | "aggregates": { 45 | "count": 1 46 | } 47 | }, 48 | "staff": [ 49 | { 50 | "last_name": "Zdravkovic", 51 | "first_name": "Jelena" 52 | } 53 | ] 54 | } 55 | ] 56 | } 57 | ] 58 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/nested_collection_with_aggregates/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "staff_aggregates": { 12 | "type": "column", 13 | "column": "staff", 14 | "arguments": { 15 | "limit": { 16 | "type": "literal", 17 | "value": null 18 | } 19 | }, 20 | "field_path": [], 21 | "fields": { 22 | "type": "collection", 23 | "query": { 24 | "aggregates": { 25 | "count": { 26 | "type": "star_count" 27 | } 28 | } 29 | } 30 | } 31 | }, 32 | "staff": { 33 | "type": "column", 34 | "column": "staff", 35 | "arguments": { 36 | "limit": { 37 | "type": "literal", 38 | "value": null 39 | } 40 | }, 41 | "fields": { 42 | "type": "array", 43 | "fields": { 44 | "type": "object", 45 | "fields": { 46 | "last_name": { 47 | "type": "column", 48 | "column": "last_name" 49 | }, 50 | "first_name": { 51 | "type": "column", 52 | "column": "first_name" 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | }, 60 | "collection_relationships": {} 61 | } 62 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/nested_collection_with_grouping/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/nested_collection_with_grouping/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 1, 11 | "staff_groups": { 12 | "groups": [ 13 | { 14 | "dimensions": [ 15 | "Landin" 16 | ], 17 | "aggregates": { 18 | "count": 1 19 | } 20 | } 21 | ] 22 | }, 23 | "staff": [ 24 | { 25 | "last_name": "Landin", 26 | "first_name": "Peter" 27 | } 28 | ] 29 | }, 30 | { 31 | "id": 2, 32 | "staff_groups": { 33 | "groups": [ 34 | { 35 | "dimensions": [ 36 | "Hughes" 37 | ], 38 | "aggregates": { 39 | "count": 1 40 | } 41 | }, 42 | { 43 | "dimensions": [ 44 | "Claessen" 45 | ], 46 | "aggregates": { 47 | "count": 1 48 | } 49 | } 50 | ] 51 | }, 52 | "staff": [ 53 | { 54 | "last_name": "Hughes", 55 | "first_name": "John" 56 | }, 57 | { 58 | "last_name": "Claessen", 59 | "first_name": "Koen" 60 | } 61 | ] 62 | }, 63 | { 64 | "id": 3, 65 | "staff_groups": { 66 | "groups": [ 67 | { 68 | "dimensions": [ 69 | "Zdravkovic" 70 | ], 71 | "aggregates": { 72 | "count": 1 73 | } 74 | } 75 | ] 76 | }, 77 | "staff": [ 78 | { 79 | "last_name": "Zdravkovic", 80 | "first_name": "Jelena" 81 | } 82 | ] 83 | } 84 | ] 85 | } 86 | ] 87 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/nested_collection_with_grouping/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "staff_groups": { 12 | "type": "column", 13 | "column": "staff", 14 | "arguments": { 15 | "limit": { 16 | "type": "literal", 17 | "value": null 18 | } 19 | }, 20 | "field_path": [], 21 | "fields": { 22 | "type": "collection", 23 | "query": { 24 | "groups": { 25 | "dimensions": [ 26 | { 27 | "type": "column", 28 | "column_name": "last_name", 29 | "path": [] 30 | } 31 | ], 32 | "aggregates": { 33 | "count": { 34 | "type": "star_count" 35 | } 36 | } 37 | } 38 | } 39 | } 40 | }, 41 | "staff": { 42 | "type": "column", 43 | "column": "staff", 44 | "arguments": { 45 | "limit": { 46 | "type": "literal", 47 | "value": null 48 | } 49 | }, 50 | "fields": { 51 | "type": "array", 52 | "fields": { 53 | "type": "object", 54 | "fields": { 55 | "last_name": { 56 | "type": "column", 57 | "column": "last_name" 58 | }, 59 | "first_name": { 60 | "type": "column", 61 | "column": "first_name" 62 | } 63 | } 64 | } 65 | } 66 | } 67 | } 68 | }, 69 | "collection_relationships": {} 70 | } 71 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/nested_object_select/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/nested_object_select/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 1, 11 | "location": { 12 | "city": "London", 13 | "campuses": [ 14 | "Mile End", 15 | "Whitechapel", 16 | "Charterhouse Square", 17 | "West Smithfield" 18 | ] 19 | }, 20 | "location_all": { 21 | "city": "London", 22 | "country": "UK", 23 | "country_id": 1, 24 | "campuses": [ 25 | "Mile End", 26 | "Whitechapel", 27 | "Charterhouse Square", 28 | "West Smithfield" 29 | ] 30 | } 31 | }, 32 | { 33 | "id": 2, 34 | "location": { 35 | "city": "Gothenburg", 36 | "campuses": [ 37 | "Johanneberg", 38 | "Lindholmen" 39 | ] 40 | }, 41 | "location_all": { 42 | "city": "Gothenburg", 43 | "country": "Sweden", 44 | "country_id": 2, 45 | "campuses": [ 46 | "Johanneberg", 47 | "Lindholmen" 48 | ] 49 | } 50 | }, 51 | { 52 | "id": 3, 53 | "location": { 54 | "city": "Stockholm", 55 | "campuses": [ 56 | "Frescati", 57 | "Kista" 58 | ] 59 | }, 60 | "location_all": { 61 | "city": "Stockholm", 62 | "country": "Sweden", 63 | "country_id": 2, 64 | "campuses": [ 65 | "Frescati", 66 | "Kista" 67 | ] 68 | } 69 | } 70 | ] 71 | } 72 | ] 73 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/nested_object_select/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "location": { 12 | "type": "column", 13 | "column": "location", 14 | "fields": { 15 | "type": "object", 16 | "fields": { 17 | "city": { 18 | "type": "column", 19 | "column": "city" 20 | }, 21 | "campuses": { 22 | "type": "column", 23 | "column": "campuses", 24 | "arguments": { 25 | "limit": { 26 | "type": "literal", 27 | "value": null 28 | } 29 | } 30 | } 31 | } 32 | } 33 | }, 34 | "location_all": { 35 | "type": "column", 36 | "column": "location" 37 | } 38 | } 39 | }, 40 | "collection_relationships": {} 41 | } 42 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/nested_object_select_with_relationship/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/nested_object_select_with_relationship/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "name": "Queen Mary University of London", 11 | "staff": [ 12 | { 13 | "first_name": "Peter", 14 | "last_name": "Landin", 15 | "author": { 16 | "rows": [ 17 | { 18 | "id": 1, 19 | "first_name": "Peter", 20 | "last_name": "Landin" 21 | } 22 | ] 23 | } 24 | } 25 | ] 26 | }, 27 | { 28 | "name": "Chalmers University of Technology", 29 | "staff": [ 30 | { 31 | "first_name": "John", 32 | "last_name": "Hughes", 33 | "author": { 34 | "rows": [ 35 | { 36 | "id": 2, 37 | "first_name": "John", 38 | "last_name": "Hughes" 39 | } 40 | ] 41 | } 42 | }, 43 | { 44 | "first_name": "Koen", 45 | "last_name": "Claessen", 46 | "author": { 47 | "rows": [] 48 | } 49 | } 50 | ] 51 | }, 52 | { 53 | "name": "Stockholm University", 54 | "staff": [ 55 | { 56 | "first_name": "Jelena", 57 | "last_name": "Zdravkovic", 58 | "author": { 59 | "rows": [] 60 | } 61 | } 62 | ] 63 | } 64 | ] 65 | } 66 | ] 67 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/nested_object_select_with_relationship/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "name": { 8 | "type": "column", 9 | "column": "name" 10 | }, 11 | "staff": { 12 | "type": "column", 13 | "column": "staff", 14 | "arguments": { 15 | "limit": { 16 | "type": "literal", 17 | "value": null 18 | } 19 | }, 20 | "fields": { 21 | "type": "array", 22 | "fields": { 23 | "type": "object", 24 | "fields": { 25 | "first_name": { 26 | "type": "column", 27 | "column": "first_name" 28 | }, 29 | "last_name": { 30 | "type": "column", 31 | "column": "last_name" 32 | }, 33 | "author": { 34 | "type": "relationship", 35 | "arguments": {}, 36 | "query": { 37 | "aggregates": null, 38 | "fields": { 39 | "id": { 40 | "type": "column", 41 | "column": "id" 42 | }, 43 | "first_name": { 44 | "type": "column", 45 | "column": "first_name" 46 | }, 47 | "last_name": { 48 | "type": "column", 49 | "column": "last_name" 50 | } 51 | } 52 | }, 53 | "relationship": "author_by_first_and_last" 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | }, 61 | "collection_relationships": { 62 | "author_by_first_and_last": { 63 | "arguments": {}, 64 | "column_mapping": { 65 | "first_name": ["first_name"], 66 | "last_name": ["last_name"] 67 | }, 68 | "relationship_type": "object", 69 | "target_collection": "authors" 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/order_by_aggregate/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/order_by_aggregate/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "John", 11 | "last_name": "Hughes", 12 | "articles_aggregate": { 13 | "aggregates": { 14 | "count": 2 15 | } 16 | } 17 | }, 18 | { 19 | "first_name": "Peter", 20 | "last_name": "Landin", 21 | "articles_aggregate": { 22 | "aggregates": { 23 | "count": 1 24 | } 25 | } 26 | } 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/order_by_aggregate/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | }, 15 | "articles_aggregate": { 16 | "type": "relationship", 17 | "arguments": {}, 18 | "relationship": "author_articles", 19 | "query": { 20 | "aggregates": { 21 | "count": { 22 | "type": "star_count" 23 | } 24 | } 25 | } 26 | } 27 | }, 28 | "order_by": { 29 | "elements": [ 30 | { 31 | "order_direction": "desc", 32 | "target": { 33 | "type": "aggregate", 34 | "aggregate": { 35 | "type": "star_count" 36 | }, 37 | "path": [ 38 | { 39 | "arguments": {}, 40 | "relationship": "author_articles", 41 | "predicate": { 42 | "type": "and", 43 | "expressions": [] 44 | } 45 | } 46 | ] 47 | } 48 | } 49 | ] 50 | } 51 | }, 52 | "collection_relationships": { 53 | "author_articles": { 54 | "arguments": {}, 55 | "column_mapping": { 56 | "id": ["author_id"] 57 | }, 58 | "relationship_type": "array", 59 | "source_collection_or_type": "author", 60 | "target_collection": "articles" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/order_by_aggregate_function/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/order_by_aggregate_function/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "Peter", 11 | "last_name": "Landin", 12 | "articles_aggregate": { 13 | "aggregates": { 14 | "max_id": 1 15 | } 16 | } 17 | }, 18 | { 19 | "first_name": "John", 20 | "last_name": "Hughes", 21 | "articles_aggregate": { 22 | "aggregates": { 23 | "max_id": 3 24 | } 25 | } 26 | } 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/order_by_aggregate_function/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | }, 15 | "articles_aggregate": { 16 | "type": "relationship", 17 | "arguments": {}, 18 | "relationship": "author_articles", 19 | "query": { 20 | "aggregates": { 21 | "max_id": { 22 | "type": "single_column", 23 | "column": "id", 24 | "function": "max" 25 | } 26 | } 27 | } 28 | } 29 | }, 30 | "order_by": { 31 | "elements": [ 32 | { 33 | "order_direction": "asc", 34 | "target": { 35 | "type": "aggregate", 36 | "aggregate": { 37 | "type": "single_column", 38 | "column": "id", 39 | "function": "max" 40 | }, 41 | "path": [ 42 | { 43 | "arguments": {}, 44 | "relationship": "author_articles", 45 | "predicate": { 46 | "type": "and", 47 | "expressions": [] 48 | } 49 | } 50 | ] 51 | } 52 | } 53 | ] 54 | } 55 | }, 56 | "collection_relationships": { 57 | "author_articles": { 58 | "arguments": {}, 59 | "column_mapping": { 60 | "id": ["author_id"] 61 | }, 62 | "relationship_type": "array", 63 | "source_collection_or_type": "author", 64 | "target_collection": "articles" 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/order_by_aggregate_with_predicate/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/order_by_aggregate_with_predicate/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "Peter", 11 | "last_name": "Landin", 12 | "articles_aggregate": { 13 | "aggregates": { 14 | "articles_with_numerals_count": 1 15 | } 16 | } 17 | }, 18 | { 19 | "first_name": "John", 20 | "last_name": "Hughes", 21 | "articles_aggregate": { 22 | "aggregates": { 23 | "articles_with_numerals_count": 0 24 | } 25 | } 26 | } 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/order_by_column/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/order_by_column/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 2, 11 | "title": "Why Functional Programming Matters" 12 | }, 13 | { 14 | "id": 1, 15 | "title": "The Next 700 Programming Languages" 16 | }, 17 | { 18 | "id": 3, 19 | "title": "The Design And Implementation Of Programming Languages" 20 | } 21 | ] 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/order_by_column/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "title": { 12 | "type": "column", 13 | "column": "title" 14 | } 15 | }, 16 | "order_by": { 17 | "elements": [ 18 | { 19 | "target": { 20 | "type": "column", 21 | "name": "title", 22 | "path": [] 23 | }, 24 | "order_direction": "desc" 25 | } 26 | ] 27 | } 28 | }, 29 | "collection_relationships": {} 30 | } 31 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/order_by_nested_field/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/order_by_nested_field/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 2, 11 | "location": { 12 | "country": "Sweden" 13 | } 14 | }, 15 | { 16 | "id": 3, 17 | "location": { 18 | "country": "Sweden" 19 | } 20 | }, 21 | { 22 | "id": 1, 23 | "location": { 24 | "country": "UK" 25 | } 26 | } 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/order_by_nested_field/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "location": { 12 | "type": "column", 13 | "column": "location", 14 | "fields": { 15 | "type": "object", 16 | "fields": { 17 | "country": { 18 | "type": "column", 19 | "column": "country" 20 | } 21 | } 22 | } 23 | } 24 | }, 25 | "order_by": { 26 | "elements": [ 27 | { 28 | "target": { 29 | "type": "column", 30 | "name": "location", 31 | "field_path": ["country"], 32 | "path": [] 33 | }, 34 | "order_direction": "asc" 35 | } 36 | ] 37 | } 38 | }, 39 | "collection_relationships": {} 40 | } 41 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/order_by_nested_relationship/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/order_by_nested_relationship/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "name": "Stockholm University", 11 | "location": { 12 | "country_id": 2, 13 | "country": { 14 | "rows": [ 15 | { 16 | "id": 2, 17 | "name": "Sweden", 18 | "area_km2": 450295 19 | } 20 | ] 21 | } 22 | } 23 | }, 24 | { 25 | "name": "Chalmers University of Technology", 26 | "location": { 27 | "country_id": 2, 28 | "country": { 29 | "rows": [ 30 | { 31 | "id": 2, 32 | "name": "Sweden", 33 | "area_km2": 450295 34 | } 35 | ] 36 | } 37 | } 38 | }, 39 | { 40 | "name": "Queen Mary University of London", 41 | "location": { 42 | "country_id": 1, 43 | "country": { 44 | "rows": [ 45 | { 46 | "id": 1, 47 | "name": "UK", 48 | "area_km2": 244376 49 | } 50 | ] 51 | } 52 | } 53 | } 54 | ] 55 | } 56 | ] 57 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/order_by_nested_relationship/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "name": { 8 | "type": "column", 9 | "column": "name" 10 | }, 11 | "location": { 12 | "type": "column", 13 | "column": "location", 14 | "fields": { 15 | "type": "object", 16 | "fields": { 17 | "country_id": { 18 | "type": "column", 19 | "column": "country_id" 20 | }, 21 | "country": { 22 | "type": "relationship", 23 | "arguments": {}, 24 | "relationship": "location_country", 25 | "query": { 26 | "fields": { 27 | "id": { 28 | "type": "column", 29 | "column": "id" 30 | }, 31 | "name": { 32 | "type": "column", 33 | "column": "name" 34 | }, 35 | "area_km2": { 36 | "type": "column", 37 | "column": "area_km2" 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | }, 46 | "order_by": { 47 | "elements": [ 48 | { 49 | "order_direction": "desc", 50 | "target": { 51 | "type": "column", 52 | "path": [ 53 | { 54 | "field_path": ["location"], 55 | "relationship": "location_country", 56 | "arguments": {}, 57 | "predicate": null 58 | } 59 | ], 60 | "name": "area_km2", 61 | "field_path": [] 62 | } 63 | } 64 | ] 65 | } 66 | }, 67 | "collection_relationships": { 68 | "location_country": { 69 | "arguments": {}, 70 | "column_mapping": { 71 | "country_id": ["id"] 72 | }, 73 | "relationship_type": "object", 74 | "target_collection": "countries" 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/order_by_relationship/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/order_by_relationship/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 2, 11 | "title": "Why Functional Programming Matters", 12 | "author": { 13 | "rows": [ 14 | { 15 | "first_name": "John", 16 | "last_name": "Hughes" 17 | } 18 | ] 19 | } 20 | }, 21 | { 22 | "id": 3, 23 | "title": "The Design And Implementation Of Programming Languages", 24 | "author": { 25 | "rows": [ 26 | { 27 | "first_name": "John", 28 | "last_name": "Hughes" 29 | } 30 | ] 31 | } 32 | }, 33 | { 34 | "id": 1, 35 | "title": "The Next 700 Programming Languages", 36 | "author": { 37 | "rows": [ 38 | { 39 | "first_name": "Peter", 40 | "last_name": "Landin" 41 | } 42 | ] 43 | } 44 | } 45 | ] 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/pagination/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/pagination/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 2, 11 | "title": "Why Functional Programming Matters" 12 | }, 13 | { 14 | "id": 3, 15 | "title": "The Design And Implementation Of Programming Languages" 16 | } 17 | ] 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/pagination/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "articles", 3 | "arguments": {}, 4 | "query": { 5 | "fields": { 6 | "id": { 7 | "type": "column", 8 | "column": "id" 9 | }, 10 | "title": { 11 | "type": "column", 12 | "column": "title" 13 | } 14 | }, 15 | "limit": 2, 16 | "offset": 1 17 | }, 18 | "collection_relationships": {} 19 | } 20 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_array_contains/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_array_contains/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 2, 11 | "name": "Chalmers University of Technology", 12 | "location": { 13 | "campuses": [ 14 | "Johanneberg", 15 | "Lindholmen" 16 | ] 17 | } 18 | } 19 | ] 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_array_contains/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "name": { 12 | "type": "column", 13 | "column": "name" 14 | }, 15 | "location": { 16 | "type": "column", 17 | "column": "location", 18 | "fields": { 19 | "type": "object", 20 | "fields": { 21 | "campuses": { 22 | "type": "column", 23 | "column": "campuses", 24 | "arguments": { 25 | "limit": { 26 | "type": "literal", 27 | "value": null 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | }, 35 | "predicate": { 36 | "type": "array_comparison", 37 | "column": { 38 | "type": "column", 39 | "name": "location", 40 | "field_path": ["campuses"] 41 | }, 42 | "comparison": { 43 | "type": "contains", 44 | "value": { 45 | "type": "scalar", 46 | "value": "Lindholmen" 47 | } 48 | } 49 | } 50 | }, 51 | "collection_relationships": {} 52 | } 53 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_array_is_empty/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_array_is_empty/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 4, 11 | "name": "Mars", 12 | "cities": [] 13 | } 14 | ] 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_array_is_empty/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "countries", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "name": { 12 | "type": "column", 13 | "column": "name" 14 | }, 15 | "cities": { 16 | "type": "column", 17 | "column": "cities", 18 | "arguments": { 19 | "limit": { 20 | "type": "literal", 21 | "value": null 22 | } 23 | } 24 | } 25 | }, 26 | "predicate": { 27 | "type": "array_comparison", 28 | "column": { 29 | "type": "column", 30 | "name": "cities", 31 | "arguments": { 32 | "limit": { 33 | "type": "literal", 34 | "value": null 35 | } 36 | } 37 | }, 38 | "comparison": { 39 | "type": "is_empty" 40 | } 41 | } 42 | }, 43 | "collection_relationships": {} 44 | } 45 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_column_aggregate/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_column_aggregate/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "Peter", 11 | "last_name": "Landin" 12 | } 13 | ] 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_column_aggregate/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | } 15 | }, 16 | "predicate": { 17 | "type": "binary_comparison_operator", 18 | "column": { 19 | "type": "aggregate", 20 | "aggregate": { 21 | "type": "single_column", 22 | "column": "id", 23 | "function": "max" 24 | }, 25 | "path": [ 26 | { 27 | "arguments": {}, 28 | "relationship": "author_articles" 29 | } 30 | ] 31 | }, 32 | "operator": "eq", 33 | "value": { 34 | "type": "scalar", 35 | "value": 1 36 | } 37 | } 38 | }, 39 | "collection_relationships": { 40 | "author_articles": { 41 | "arguments": {}, 42 | "column_mapping": { 43 | "id": ["author_id"] 44 | }, 45 | "relationship_type": "array", 46 | "source_collection_or_type": "author", 47 | "target_collection": "articles" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_eq/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_eq/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 1, 11 | "title": "The Next 700 Programming Languages" 12 | } 13 | ] 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_eq/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "title": { 12 | "type": "column", 13 | "column": "title" 14 | } 15 | }, 16 | "predicate": { 17 | "type": "binary_comparison_operator", 18 | "column": { 19 | "type": "column", 20 | "name": "id" 21 | }, 22 | "operator": "eq", 23 | "value": { 24 | "type": "scalar", 25 | "value": 1 26 | } 27 | } 28 | }, 29 | "collection_relationships": {} 30 | } 31 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_exists/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_exists/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "John", 11 | "last_name": "Hughes", 12 | "articles": { 13 | "rows": [ 14 | { 15 | "id": 2, 16 | "title": "Why Functional Programming Matters" 17 | }, 18 | { 19 | "id": 3, 20 | "title": "The Design And Implementation Of Programming Languages" 21 | } 22 | ] 23 | } 24 | } 25 | ] 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_exists/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | }, 15 | "articles": { 16 | "type": "relationship", 17 | "arguments": {}, 18 | "relationship": "author_articles", 19 | "query": { 20 | "fields": { 21 | "id": { 22 | "type": "column", 23 | "column": "id" 24 | }, 25 | "title": { 26 | "type": "column", 27 | "column": "title" 28 | } 29 | } 30 | } 31 | } 32 | }, 33 | "predicate": { 34 | "type": "exists", 35 | "in_collection": { 36 | "type": "related", 37 | "arguments": {}, 38 | "relationship": "author_articles" 39 | }, 40 | "predicate": { 41 | "type": "binary_comparison_operator", 42 | "column": { 43 | "type": "column", 44 | "name": "title" 45 | }, 46 | "operator": "like", 47 | "value": { 48 | "type": "scalar", 49 | "value": "Functional" 50 | } 51 | } 52 | } 53 | }, 54 | "collection_relationships": { 55 | "author_articles": { 56 | "arguments": {}, 57 | "column_mapping": { 58 | "id": ["author_id"] 59 | }, 60 | "relationship_type": "array", 61 | "source_collection_or_type": "author", 62 | "target_collection": "articles" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_exists_and_in/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_exists_and_in/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "Peter", 11 | "last_name": "Landin", 12 | "articles": { 13 | "rows": [ 14 | { 15 | "id": 1, 16 | "title": "The Next 700 Programming Languages" 17 | } 18 | ] 19 | } 20 | }, 21 | { 22 | "first_name": "John", 23 | "last_name": "Hughes", 24 | "articles": { 25 | "rows": [ 26 | { 27 | "id": 2, 28 | "title": "Why Functional Programming Matters" 29 | }, 30 | { 31 | "id": 3, 32 | "title": "The Design And Implementation Of Programming Languages" 33 | } 34 | ] 35 | } 36 | } 37 | ] 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_exists_and_in/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | }, 15 | "articles": { 16 | "type": "relationship", 17 | "arguments": {}, 18 | "relationship": "author_articles", 19 | "query": { 20 | "fields": { 21 | "id": { 22 | "type": "column", 23 | "column": "id" 24 | }, 25 | "title": { 26 | "type": "column", 27 | "column": "title" 28 | } 29 | } 30 | } 31 | } 32 | }, 33 | "predicate": { 34 | "type": "exists", 35 | "in_collection": { 36 | "type": "related", 37 | "arguments": {}, 38 | "relationship": "author_articles" 39 | }, 40 | "predicate": { 41 | "type": "binary_comparison_operator", 42 | "column": { 43 | "type": "column", 44 | "name": "title" 45 | }, 46 | "operator": "in", 47 | "value": { 48 | "type": "scalar", 49 | "value": [ 50 | "The Next 700 Programming Languages", 51 | "Why Functional Programming Matters" 52 | ] 53 | } 54 | } 55 | } 56 | }, 57 | "collection_relationships": { 58 | "author_articles": { 59 | "arguments": {}, 60 | "column_mapping": { 61 | "id": ["author_id"] 62 | }, 63 | "relationship_type": "array", 64 | "source_collection_or_type": "author", 65 | "target_collection": "articles" 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_exists_and_relationship/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_exists_and_relationship/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "title": "The Next 700 Programming Languages", 11 | "author_if_has_functional_articles": { 12 | "rows": [] 13 | } 14 | }, 15 | { 16 | "title": "Why Functional Programming Matters", 17 | "author_if_has_functional_articles": { 18 | "rows": [ 19 | { 20 | "first_name": "John", 21 | "last_name": "Hughes", 22 | "articles": { 23 | "rows": [ 24 | { 25 | "title": "Why Functional Programming Matters" 26 | }, 27 | { 28 | "title": "The Design And Implementation Of Programming Languages" 29 | } 30 | ] 31 | } 32 | } 33 | ] 34 | } 35 | }, 36 | { 37 | "title": "The Design And Implementation Of Programming Languages", 38 | "author_if_has_functional_articles": { 39 | "rows": [ 40 | { 41 | "first_name": "John", 42 | "last_name": "Hughes", 43 | "articles": { 44 | "rows": [ 45 | { 46 | "title": "Why Functional Programming Matters" 47 | }, 48 | { 49 | "title": "The Design And Implementation Of Programming Languages" 50 | } 51 | ] 52 | } 53 | } 54 | ] 55 | } 56 | } 57 | ] 58 | } 59 | ] 60 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_exists_from_nested_field/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_exists_from_nested_field/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "name": "Chalmers University of Technology", 11 | "location": { 12 | "country_id": 2, 13 | "country": { 14 | "rows": [ 15 | { 16 | "id": 2, 17 | "name": "Sweden", 18 | "area_km2": 450295 19 | } 20 | ] 21 | } 22 | } 23 | }, 24 | { 25 | "name": "Stockholm University", 26 | "location": { 27 | "country_id": 2, 28 | "country": { 29 | "rows": [ 30 | { 31 | "id": 2, 32 | "name": "Sweden", 33 | "area_km2": 450295 34 | } 35 | ] 36 | } 37 | } 38 | } 39 | ] 40 | } 41 | ] 42 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_exists_in_nested_collection/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_exists_in_nested_collection/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 2, 11 | "name": "Chalmers University of Technology", 12 | "staff": [ 13 | { 14 | "first_name": "John", 15 | "last_name": "Hughes", 16 | "specialities": [ 17 | "Computer Science", 18 | "Functional Programming", 19 | "Software Testing" 20 | ], 21 | "born_country_id": 2 22 | }, 23 | { 24 | "first_name": "Koen", 25 | "last_name": "Claessen", 26 | "specialities": [ 27 | "Computer Science", 28 | "Functional Programming", 29 | "Automated Reasoning" 30 | ], 31 | "born_country_id": 2 32 | } 33 | ] 34 | } 35 | ] 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_exists_in_nested_collection/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "name": { 12 | "type": "column", 13 | "column": "name" 14 | }, 15 | "staff": { 16 | "type": "column", 17 | "column": "staff", 18 | "arguments": { 19 | "limit": { 20 | "type": "literal", 21 | "value": null 22 | } 23 | } 24 | } 25 | }, 26 | "predicate": { 27 | "type": "exists", 28 | "in_collection": { 29 | "type": "nested_collection", 30 | "arguments": { 31 | "limit": { 32 | "type": "literal", 33 | "value": null 34 | } 35 | }, 36 | "column_name": "staff" 37 | }, 38 | "predicate": { 39 | "type": "binary_comparison_operator", 40 | "column": { 41 | "type": "column", 42 | "name": "last_name" 43 | }, 44 | "operator": "like", 45 | "value": { 46 | "type": "scalar", 47 | "value": "s" 48 | } 49 | } 50 | } 51 | }, 52 | "collection_relationships": {} 53 | } 54 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_exists_in_nested_scalar_collection/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_exists_in_nested_scalar_collection/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 1, 11 | "name": "Queen Mary University of London", 12 | "location": { 13 | "campuses": [ 14 | "Mile End", 15 | "Whitechapel", 16 | "Charterhouse Square", 17 | "West Smithfield" 18 | ] 19 | } 20 | }, 21 | { 22 | "id": 2, 23 | "name": "Chalmers University of Technology", 24 | "location": { 25 | "campuses": [ 26 | "Johanneberg", 27 | "Lindholmen" 28 | ] 29 | } 30 | } 31 | ] 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_exists_in_nested_scalar_collection/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "name": { 12 | "type": "column", 13 | "column": "name" 14 | }, 15 | "location": { 16 | "type": "column", 17 | "column": "location", 18 | "fields": { 19 | "type": "object", 20 | "fields": { 21 | "campuses": { 22 | "type": "column", 23 | "column": "campuses", 24 | "arguments": { 25 | "limit": { 26 | "type": "literal", 27 | "value": null 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | }, 35 | "predicate": { 36 | "type": "exists", 37 | "in_collection": { 38 | "type": "nested_scalar_collection", 39 | "column_name": "location", 40 | "field_path": ["campuses"], 41 | "arguments": {} 42 | }, 43 | "predicate": { 44 | "type": "binary_comparison_operator", 45 | "column": { 46 | "type": "column", 47 | "name": "__value" 48 | }, 49 | "operator": "like", 50 | "value": { 51 | "type": "scalar", 52 | "value": "d" 53 | } 54 | } 55 | } 56 | }, 57 | "collection_relationships": {} 58 | } 59 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_in/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_in/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 1, 11 | "title": "The Next 700 Programming Languages" 12 | }, 13 | { 14 | "id": 2, 15 | "title": "Why Functional Programming Matters" 16 | }, 17 | { 18 | "id": 3, 19 | "title": "The Design And Implementation Of Programming Languages" 20 | } 21 | ] 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_in/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "title": { 12 | "type": "column", 13 | "column": "title" 14 | } 15 | }, 16 | "predicate": { 17 | "type": "binary_comparison_operator", 18 | "column": { 19 | "type": "column", 20 | "name": "author_id" 21 | }, 22 | "operator": "in", 23 | "value": { 24 | "type": "scalar", 25 | "value": [1, 2] 26 | } 27 | } 28 | }, 29 | "collection_relationships": {} 30 | } 31 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_like/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_like/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 2, 11 | "title": "Why Functional Programming Matters" 12 | } 13 | ] 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_like/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "title": { 12 | "type": "column", 13 | "column": "title" 14 | } 15 | }, 16 | "predicate": { 17 | "type": "binary_comparison_operator", 18 | "column": { 19 | "type": "column", 20 | "name": "title" 21 | }, 22 | "operator": "like", 23 | "value": { 24 | "type": "scalar", 25 | "value": "Functional" 26 | } 27 | } 28 | }, 29 | "collection_relationships": {} 30 | } 31 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_nested_field_eq/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_nested_field_eq/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 1, 11 | "location": { 12 | "city": "London", 13 | "campuses": [ 14 | "Mile End", 15 | "Whitechapel", 16 | "Charterhouse Square", 17 | "West Smithfield" 18 | ] 19 | } 20 | } 21 | ] 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_nested_field_eq/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "institutions", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "location": { 12 | "type": "column", 13 | "column": "location", 14 | "fields": { 15 | "type": "object", 16 | "fields": { 17 | "city": { 18 | "type": "column", 19 | "column": "city" 20 | }, 21 | "campuses": { 22 | "type": "column", 23 | "column": "campuses", 24 | "arguments": { 25 | "limit": { 26 | "type": "literal", 27 | "value": null 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | }, 35 | "predicate": { 36 | "type": "binary_comparison_operator", 37 | "column": { 38 | "type": "column", 39 | "name": "location", 40 | "field_path": ["city"] 41 | }, 42 | "operator": "eq", 43 | "value": { 44 | "type": "scalar", 45 | "value": "London" 46 | } 47 | } 48 | }, 49 | "collection_relationships": {} 50 | } 51 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_star_count/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/predicate_with_star_count/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "John", 11 | "last_name": "Hughes" 12 | } 13 | ] 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_star_count/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | } 15 | }, 16 | "predicate": { 17 | "type": "binary_comparison_operator", 18 | "column": { 19 | "type": "aggregate", 20 | "aggregate": { 21 | "type": "star_count" 22 | }, 23 | "path": [ 24 | { 25 | "arguments": {}, 26 | "relationship": "author_articles" 27 | } 28 | ] 29 | }, 30 | "operator": "eq", 31 | "value": { 32 | "type": "scalar", 33 | "value": 2 34 | } 35 | } 36 | }, 37 | "collection_relationships": { 38 | "author_articles": { 39 | "arguments": {}, 40 | "column_mapping": { 41 | "id": ["author_id"] 42 | }, 43 | "relationship_type": "array", 44 | "source_collection_or_type": "author", 45 | "target_collection": "articles" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_starts_with/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | assertion_line: 3432 4 | expression: response.0 5 | input_file: ndc-reference/tests/query/predicate_with_starts_with/request.json 6 | snapshot_kind: text 7 | --- 8 | [ 9 | { 10 | "rows": [ 11 | { 12 | "first_name": "Peter", 13 | "last_name": "Landin" 14 | } 15 | ] 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/predicate_with_starts_with/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | } 15 | }, 16 | "predicate": { 17 | "type": "binary_comparison_operator", 18 | "column": { 19 | "type": "column", 20 | "name": "first_name" 21 | }, 22 | "operator": "istarts_with", 23 | "value": { 24 | "type": "scalar", 25 | "value": "p" 26 | } 27 | } 28 | }, 29 | "collection_relationships": {} 30 | } 31 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/star_count/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/star_count/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "Peter", 11 | "last_name": "Landin", 12 | "articles_aggregate": { 13 | "aggregates": { 14 | "count": 1 15 | } 16 | } 17 | }, 18 | { 19 | "first_name": "John", 20 | "last_name": "Hughes", 21 | "articles_aggregate": { 22 | "aggregates": { 23 | "count": 2 24 | } 25 | } 26 | } 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/star_count/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | }, 15 | "articles_aggregate": { 16 | "type": "relationship", 17 | "arguments": {}, 18 | "relationship": "author_articles", 19 | "query": { 20 | "aggregates": { 21 | "count": { 22 | "type": "star_count" 23 | } 24 | } 25 | } 26 | } 27 | } 28 | }, 29 | "collection_relationships": { 30 | "author_articles": { 31 | "arguments": {}, 32 | "column_mapping": { 33 | "id": ["author_id"] 34 | }, 35 | "relationship_type": "array", 36 | "source_collection_or_type": "author", 37 | "target_collection": "articles" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/table_argument/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 1, 11 | "title": "The Next 700 Programming Languages" 12 | } 13 | ] 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles_by_author", 4 | "arguments": { 5 | "author_id": { 6 | "type": "literal", 7 | "value": 1 8 | } 9 | }, 10 | "query": { 11 | "fields": { 12 | "id": { 13 | "type": "column", 14 | "column": "id" 15 | }, 16 | "title": { 17 | "type": "column", 18 | "column": "title" 19 | } 20 | } 21 | }, 22 | "collection_relationships": {} 23 | } 24 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument_aggregate/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/table_argument_aggregate/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "Peter", 11 | "last_name": "Landin", 12 | "articles_aggregate": { 13 | "aggregates": { 14 | "count": 1 15 | } 16 | } 17 | }, 18 | { 19 | "first_name": "John", 20 | "last_name": "Hughes", 21 | "articles_aggregate": { 22 | "aggregates": { 23 | "count": 2 24 | } 25 | } 26 | } 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument_aggregate/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | }, 15 | "articles_aggregate": { 16 | "type": "relationship", 17 | "arguments": { 18 | "author_id": { 19 | "type": "column", 20 | "name": "id" 21 | } 22 | }, 23 | "relationship": "author_articles", 24 | "query": { 25 | "aggregates": { 26 | "count": { 27 | "type": "star_count" 28 | } 29 | } 30 | } 31 | } 32 | } 33 | }, 34 | "collection_relationships": { 35 | "author_articles": { 36 | "arguments": {}, 37 | "column_mapping": {}, 38 | "relationship_type": "array", 39 | "source_collection_or_type": "author", 40 | "target_collection": "articles_by_author" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument_exists/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/table_argument_exists/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 2 11 | } 12 | ] 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument_exists/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | } 11 | }, 12 | "predicate": { 13 | "type": "exists", 14 | "in_collection": { 15 | "type": "related", 16 | "relationship": "author_articles", 17 | "arguments": {} 18 | }, 19 | "predicate": { 20 | "type": "binary_comparison_operator", 21 | "column": { 22 | "type": "column", 23 | "name": "title" 24 | }, 25 | "operator": "like", 26 | "value": { 27 | "type": "scalar", 28 | "value": "Functional" 29 | } 30 | } 31 | } 32 | }, 33 | "collection_relationships": { 34 | "author_articles": { 35 | "arguments": { 36 | "author_id": { 37 | "type": "column", 38 | "name": "id" 39 | } 40 | }, 41 | "column_mapping": {}, 42 | "relationship_type": "array", 43 | "target_collection": "articles_by_author" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument_order_by/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/table_argument_order_by/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 2 11 | }, 12 | { 13 | "id": 1 14 | } 15 | ] 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument_order_by/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | } 11 | }, 12 | "order_by": { 13 | "elements": [ 14 | { 15 | "order_direction": "desc", 16 | "target": { 17 | "type": "aggregate", 18 | "aggregate": { 19 | "type": "star_count" 20 | }, 21 | "path": [ 22 | { 23 | "arguments": { 24 | "author_id": { 25 | "type": "column", 26 | "name": "id" 27 | } 28 | }, 29 | "relationship": "author_articles", 30 | "predicate": { 31 | "type": "and", 32 | "expressions": [] 33 | } 34 | } 35 | ] 36 | } 37 | } 38 | ] 39 | } 40 | }, 41 | "collection_relationships": { 42 | "author_articles": { 43 | "arguments": {}, 44 | "column_mapping": {}, 45 | "relationship_type": "array", 46 | "source_collection_or_type": "author", 47 | "target_collection": "articles_by_author" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument_relationship_1/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/table_argument_relationship_1/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "Peter", 11 | "last_name": "Landin", 12 | "articles": { 13 | "rows": [ 14 | { 15 | "id": 1, 16 | "title": "The Next 700 Programming Languages" 17 | } 18 | ] 19 | } 20 | }, 21 | { 22 | "first_name": "John", 23 | "last_name": "Hughes", 24 | "articles": { 25 | "rows": [ 26 | { 27 | "id": 2, 28 | "title": "Why Functional Programming Matters" 29 | }, 30 | { 31 | "id": 3, 32 | "title": "The Design And Implementation Of Programming Languages" 33 | } 34 | ] 35 | } 36 | } 37 | ] 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument_relationship_1/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | }, 15 | "articles": { 16 | "type": "relationship", 17 | "arguments": {}, 18 | "relationship": "author_articles", 19 | "query": { 20 | "fields": { 21 | "id": { 22 | "type": "column", 23 | "column": "id" 24 | }, 25 | "title": { 26 | "type": "column", 27 | "column": "title" 28 | } 29 | } 30 | } 31 | } 32 | } 33 | }, 34 | "collection_relationships": { 35 | "author_articles": { 36 | "arguments": { 37 | "author_id": { 38 | "type": "column", 39 | "name": "id" 40 | } 41 | }, 42 | "column_mapping": {}, 43 | "relationship_type": "array", 44 | "source_collection_or_type": "author", 45 | "target_collection": "articles_by_author" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument_relationship_2/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/table_argument_relationship_2/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "first_name": "Peter", 11 | "last_name": "Landin", 12 | "articles": { 13 | "rows": [ 14 | { 15 | "id": 1, 16 | "title": "The Next 700 Programming Languages" 17 | } 18 | ] 19 | } 20 | }, 21 | { 22 | "first_name": "John", 23 | "last_name": "Hughes", 24 | "articles": { 25 | "rows": [ 26 | { 27 | "id": 2, 28 | "title": "Why Functional Programming Matters" 29 | }, 30 | { 31 | "id": 3, 32 | "title": "The Design And Implementation Of Programming Languages" 33 | } 34 | ] 35 | } 36 | } 37 | ] 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument_relationship_2/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "first_name": { 8 | "type": "column", 9 | "column": "first_name" 10 | }, 11 | "last_name": { 12 | "type": "column", 13 | "column": "last_name" 14 | }, 15 | "articles": { 16 | "type": "relationship", 17 | "arguments": { 18 | "author_id": { 19 | "type": "column", 20 | "name": "id" 21 | } 22 | }, 23 | "relationship": "author_articles", 24 | "query": { 25 | "fields": { 26 | "id": { 27 | "type": "column", 28 | "column": "id" 29 | }, 30 | "title": { 31 | "type": "column", 32 | "column": "title" 33 | } 34 | } 35 | } 36 | } 37 | } 38 | }, 39 | "collection_relationships": { 40 | "author_articles": { 41 | "arguments": {}, 42 | "column_mapping": {}, 43 | "relationship_type": "array", 44 | "source_collection_or_type": "author", 45 | "target_collection": "articles_by_author" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument_unrelated_exists/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/table_argument_unrelated_exists/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 2 11 | } 12 | ] 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/table_argument_unrelated_exists/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "authors", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | } 11 | }, 12 | "predicate": { 13 | "type": "exists", 14 | "in_collection": { 15 | "type": "unrelated", 16 | "arguments": { 17 | "author_id": { 18 | "type": "column", 19 | "name": "id" 20 | } 21 | }, 22 | "collection": "articles_by_author" 23 | }, 24 | "predicate": { 25 | "type": "and", 26 | "expressions": [ 27 | { 28 | "type": "binary_comparison_operator", 29 | "column": { 30 | "type": "column", 31 | "name": "title" 32 | }, 33 | "operator": "like", 34 | "value": { 35 | "type": "scalar", 36 | "value": "Functional" 37 | } 38 | } 39 | ] 40 | } 41 | } 42 | }, 43 | "collection_relationships": {} 44 | } 45 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/variables/expected.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: ndc-reference/bin/reference/main.rs 3 | expression: response.0 4 | input_file: ndc-reference/tests/query/variables/request.json 5 | --- 6 | [ 7 | { 8 | "rows": [ 9 | { 10 | "id": 1, 11 | "title": "The Next 700 Programming Languages" 12 | } 13 | ] 14 | }, 15 | { 16 | "rows": [ 17 | { 18 | "id": 2, 19 | "title": "Why Functional Programming Matters" 20 | } 21 | ] 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /ndc-reference/tests/query/variables/request.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", 3 | "collection": "articles", 4 | "arguments": {}, 5 | "query": { 6 | "fields": { 7 | "id": { 8 | "type": "column", 9 | "column": "id" 10 | }, 11 | "title": { 12 | "type": "column", 13 | "column": "title" 14 | } 15 | }, 16 | "predicate": { 17 | "type": "binary_comparison_operator", 18 | "column": { 19 | "type": "column", 20 | "name": "id" 21 | }, 22 | "operator": "eq", 23 | "value": { 24 | "type": "variable", 25 | "name": "$article_id" 26 | } 27 | } 28 | }, 29 | "collection_relationships": {}, 30 | "variables": [ 31 | { 32 | "$article_id": 1 33 | }, 34 | { 35 | "$article_id": 2 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /ndc-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ndc-test" 3 | description = "A tool to verify that a data connector is somewhat compatible with the specification" 4 | version.workspace = true 5 | edition.workspace = true 6 | 7 | [lints] 8 | workspace = true 9 | 10 | [features] 11 | default = ["native-tls"] 12 | 13 | native-tls = ["reqwest/native-tls"] 14 | rustls = ["reqwest/rustls-tls"] 15 | 16 | [dependencies] 17 | ndc-models = { path = "../ndc-models" } 18 | 19 | async-trait = { workspace = true } 20 | clap = { workspace = true, features = ["derive"] } 21 | colorful = { workspace = true } 22 | indexmap = { workspace = true, features = ["serde"] } 23 | rand = { workspace = true, features = ["small_rng"] } 24 | reqwest = { workspace = true, features = ["json", "multipart"] } 25 | semver = { workspace = true } 26 | serde = { workspace = true } 27 | serde_json = { workspace = true, features = ["preserve_order"] } 28 | thiserror = { workspace = true } 29 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "parking_lot"] } 30 | url = { workspace = true } 31 | pretty_assertions = "1.4.1" 32 | -------------------------------------------------------------------------------- /ndc-test/src/configuration.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | #[derive(Debug)] 4 | pub struct TestConfiguration { 5 | pub seed: Option<[u8; 32]>, 6 | pub snapshots_dir: Option, 7 | pub options: TestOptions, 8 | pub gen_config: TestGenerationConfiguration, 9 | } 10 | 11 | #[derive(Debug)] 12 | pub struct TestOptions { 13 | pub validate_responses: bool, 14 | } 15 | 16 | impl Default for TestOptions { 17 | fn default() -> Self { 18 | Self { 19 | validate_responses: true, 20 | } 21 | } 22 | } 23 | 24 | #[derive(Debug)] 25 | pub struct TestGenerationConfiguration { 26 | pub test_cases: u32, 27 | pub sample_size: u32, 28 | pub max_limit: u32, 29 | pub complexity: u8, 30 | } 31 | 32 | impl Default for TestGenerationConfiguration { 33 | fn default() -> Self { 34 | Self { 35 | test_cases: 10, 36 | sample_size: 10, 37 | max_limit: 10, 38 | complexity: 0, 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ndc-test/src/connector.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use ndc_models as models; 3 | 4 | use crate::error::Result; 5 | 6 | #[async_trait(?Send)] 7 | pub trait Connector { 8 | async fn get_capabilities(&self) -> Result; 9 | 10 | async fn get_schema(&self) -> Result; 11 | 12 | async fn query(&self, request: models::QueryRequest) -> Result; 13 | 14 | async fn mutation(&self, request: models::MutationRequest) -> Result; 15 | } 16 | -------------------------------------------------------------------------------- /ndc-test/src/test_cases/capabilities.rs: -------------------------------------------------------------------------------- 1 | use crate::connector::Connector; 2 | use crate::error::{Error, Result}; 3 | use crate::reporter::Reporter; 4 | use crate::test; 5 | use ndc_models as models; 6 | 7 | pub async fn test_capabilities( 8 | connector: &C, 9 | reporter: &mut R, 10 | ) -> Option { 11 | let capabilities = test!( 12 | "Fetching /capabilities", 13 | reporter, 14 | connector.get_capabilities() 15 | )?; 16 | 17 | let _ = test!("Validating capabilities", reporter, { 18 | async { validate_capabilities(&capabilities) } 19 | }); 20 | 21 | Some(capabilities) 22 | } 23 | 24 | pub fn validate_capabilities(capabilities: &models::CapabilitiesResponse) -> Result<()> { 25 | let pkg_version = env!("CARGO_PKG_VERSION"); 26 | let spec_version = semver::VersionReq::parse(format!("^{pkg_version}").as_str())?; 27 | let claimed_version = semver::Version::parse(capabilities.version.as_str())?; 28 | if !spec_version.matches(&claimed_version) { 29 | return Err(Error::IncompatibleSpecification( 30 | claimed_version, 31 | spec_version, 32 | )); 33 | } 34 | 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /ndc-test/src/test_cases/mod.rs: -------------------------------------------------------------------------------- 1 | mod capabilities; 2 | pub mod query; 3 | mod schema; 4 | 5 | use crate::configuration::{TestGenerationConfiguration, TestOptions}; 6 | use crate::connector::Connector; 7 | use crate::nest; 8 | use crate::reporter::Reporter; 9 | use crate::test_cases::query::validate::ValidatingConnector; 10 | 11 | use rand::rngs::SmallRng; 12 | 13 | pub async fn run_all_tests( 14 | gen_config: &TestGenerationConfiguration, 15 | options: &TestOptions, 16 | connector: &C, 17 | reporter: &mut R, 18 | rng: &mut SmallRng, 19 | ) -> Option<()> { 20 | let capabilities = nest!("Capabilities", reporter, { 21 | capabilities::test_capabilities(connector, reporter) 22 | })?; 23 | 24 | let schema = nest!("Schema", reporter, { 25 | schema::test_schema(connector, reporter) 26 | })?; 27 | 28 | nest!("Query", reporter, async { 29 | if options.validate_responses { 30 | query::test_query( 31 | gen_config, 32 | &ValidatingConnector { 33 | connector, 34 | schema: &schema, 35 | }, 36 | reporter, 37 | &capabilities, 38 | &schema, 39 | rng, 40 | ) 41 | .await; 42 | } else { 43 | query::test_query(gen_config, connector, reporter, &capabilities, &schema, rng).await; 44 | } 45 | }); 46 | 47 | Some(()) 48 | } 49 | -------------------------------------------------------------------------------- /ndc-test/src/test_cases/query/context.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use crate::error::Error; 4 | use crate::error::Result; 5 | 6 | use indexmap::IndexMap; 7 | use ndc_models as models; 8 | use rand::rngs::SmallRng; 9 | use rand::seq::SliceRandom; 10 | 11 | #[derive(Clone, Debug)] 12 | pub struct Context<'a> { 13 | pub collection_type: &'a models::ObjectType, 14 | pub values: BTreeMap>, 15 | } 16 | 17 | pub fn make_context( 18 | collection_type: &models::ObjectType, 19 | rows: Vec>, 20 | ) -> Result>> { 21 | let mut values = BTreeMap::new(); 22 | 23 | for row in rows { 24 | for field_name in collection_type.fields.keys() { 25 | if !row.contains_key(field_name) { 26 | return Err(Error::MissingField(field_name.clone())); 27 | } 28 | } 29 | 30 | for (field_name, field_value) in row { 31 | if !field_value.0.is_null() { 32 | values 33 | .entry(field_name.clone()) 34 | .or_insert(vec![]) 35 | .push(field_value.0); 36 | } 37 | } 38 | } 39 | 40 | Ok(if values.is_empty() { 41 | None 42 | } else { 43 | Some(Context { 44 | collection_type, 45 | values, 46 | }) 47 | }) 48 | } 49 | 50 | impl<'a> Context<'a> { 51 | pub fn choose_distinct_fields( 52 | self: &'a Context<'a>, 53 | rng: &mut SmallRng, 54 | amount: usize, 55 | ) -> Vec<(models::FieldName, Vec)> { 56 | self.values 57 | .iter() 58 | .collect::>() 59 | .choose_multiple(rng, amount) 60 | .map(|&(field_name, values)| (field_name.clone(), values.clone())) 61 | .collect::>() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /rfcs/0002-boolean-expression-types.md: -------------------------------------------------------------------------------- 1 | # Boolean Expression Types 2 | 3 | ## Purpose 4 | 5 | In order to implement useful permissions, it is desirable to be able to provide _predicates_ as input arguments to functions and procedures. Predicate types for expressions are already defined by the spec, so the present proposal is simply to reify them as new objects in the type language. 6 | 7 | ## Proposal 8 | 9 | - The `Type` enum will be extended with a new constructor `Predicate { object_type_name: String }`, which will refer to the predicate type for the named object type. The value-level representation of these predicates is already defined by the spec (for collection types, at least), and can be reused. 10 | - An input argument with a predicate type can use the existing client types to parse its input value. 11 | - This extends the usefulness of functions and procedures, but even a function supporting predicate arguments is still more general than a full collection, because a collection requires ordering and pagination, which a source may not support. Thus, functions can now implement a wider variety of sources in a way which supports a useful notion of permissions. 12 | 13 | ## Changes Required 14 | 15 | - Add the new constructor to `Type`. 16 | 17 | ## Questions 18 | 19 | - Predicates are useful as input arguments, but the type language also extends the types of _fields_ and _return types_. Is this useful? 20 | - Maybe, maybe not. But there is no harm in allowing the user to express this. We would most likely not implement field selection and filtering on these new types, so they would be abstract from an output point of view. But it's not inconceivable that a connector might want to return a predicate for some reason. 21 | -------------------------------------------------------------------------------- /rfcs/0004-explain-mutations.md: -------------------------------------------------------------------------------- 1 | # Explain for Mutations 2 | 3 | ## Purpose 4 | 5 | We have `/expain` for queries, but not for mutations. It's equally useful for debugging, but even more useful for testing - while we can execute queries fearlessly, we can't modify the user's data source arbitrarily in `ndc-test`. But we _can_ snapshot test the explain output, for both queries and mutations. 6 | 7 | ## Proposal 8 | 9 | - Rename `/explain` to `/query/explain` 10 | - Add `/mutation/explain` which takes a `MutationRequest` and returns an `ExplainResponse`. 11 | - Rename capabilities accordingly: `query.explain` and `mutation.explain`. 12 | - Later: add explain snapshot testing to `ndc-test`. 13 | -------------------------------------------------------------------------------- /rfcs/0005-scalar-type-representation.md: -------------------------------------------------------------------------------- 1 | # Scalar Type Representations 2 | 3 | ## Purpose 4 | 5 | Several connectors have a useful concept of enumerable input and output types. For example, Postgres and TypeScript both have enum types. On the client side, we can generate GraphQL enum types if we can provide a hint to the client about the representation of such values. 6 | 7 | Right now, we don't have any notion of type representation for scalar types, so we can solve the more general problem at the same time, by adding optional _type representation hints_ to scalar types in the NDC schema. 8 | 9 | ## Proposal 10 | 11 | - Add a `representation` field to `ScalarType`, which is optional with type `TypeRepresentation`: 12 | 13 | ```rust 14 | pub enum TypeRepresentation { 15 | String, 16 | Float, 17 | Boolean, 18 | Enum { one_of: Vec }, 19 | } 20 | ``` 21 | 22 | - In `ndc-test`: 23 | - We don't need to perform any particular validation for these values, except to maybe make sure that `one_of` doesn't include duplicate values. 24 | - We can now synthesize values of some types (boolean, enum) without examples, including function arguments. 25 | - If a `representation` is provided, then we can perform additional response validation in each test case. 26 | 27 | ## Alternative Designs 28 | 29 | ### Extend `Type` to add enums 30 | 31 | An alternative is to add enums to the type language, in the form of a new constructor for `Type`. 32 | 33 | However, we want enum types to be scalar types, because we want to attach comparison and aggregation operators to enums. 34 | -------------------------------------------------------------------------------- /rfcs/0008-remove-ndc-client.md: -------------------------------------------------------------------------------- 1 | # Remove `ndc-client` 2 | 3 | ## Purpose 4 | 5 | `ndc-models` has been extracted from `ndc-client`. `ndc-client` now only contains the HTTP client library for the NDC methods. 6 | 7 | However, there is not a good one-size-fits-all client for all applications. Indeed, the current client library already contains some details which are overfitted to the needs of Hasura V3 engine (e.g. tracing) and we are looking to add more (streaming responses). 8 | 9 | ## Proposal 10 | 11 | - Move the code in `ndc-client` into `ndc-test` as a crate-private module (its only consumer in this repository) 12 | - Remove any tracing-specific code from this client 13 | - V3 engine should build a separate client which implements tracing, streaming, etc. 14 | -------------------------------------------------------------------------------- /rfcs/0009-filter-and-order-by-nested-fields.md: -------------------------------------------------------------------------------- 1 | # Filtering and ordering by nested fields 2 | 3 | ## Purpose 4 | 5 | Some databases, e.g. MongoDB and other document databases, allow collections to contain "columns" with nested structures (i.e. "objects") rather than just scalar values. 6 | It would be useful to allow queries on such collections to be filtered and sorted based on the values in nested fields rather than just top-level scalar column values. 7 | 8 | ## Proposal 9 | 10 | We modify `ComparisonTarget` and `OrderByTarget` to add a `field_path` property, as follows: 11 | 12 | ```rust 13 | pub enum ComparisonTarget { 14 | Column { 15 | name: String, 16 | field_path: Option>, 17 | path: Vec, 18 | }, 19 | RootCollectionColumn { 20 | name: String, 21 | field_path: Option>, 22 | }, 23 | } 24 | 25 | pub enum OrderByTarget { 26 | Column { 27 | name: String, 28 | field_path: Option>, 29 | path: Vec, 30 | }, 31 | SingleColumnAggregate { 32 | column: String, 33 | function: String, 34 | path: Vec, 35 | }, 36 | StarCountAggregate { 37 | path: Vec, 38 | }, 39 | } 40 | ``` 41 | 42 | When `field_path` is present and non-empty it refers to a path to a nested field within the column. 43 | The value of the nested field will be used for comparison or ordering instead of using the full value of the column. 44 | 45 | A connector can declare that it supports filtering and/or ordering by nested fields via two new capabilities: `query.nested_fields.filter_by` and `query.nested_fields.order_by`. 46 | These capabilities declare whether the connector can handle non-empty `field_path` 47 | 48 | ## Future extensions 49 | 50 | Although out of scope for this RFC, in the future we probably want to extend aggregates to allow aggregating on values of nested fields. 51 | This could be achieved by adding a `field_path` property to `Aggregate::ColumnCount` and `Aggregate::SingleColumn`. 52 | We could also order by aggregates on nested fields by adding `field_path` to `OrderByTarget::SingleColumnAggregate`. 53 | -------------------------------------------------------------------------------- /rfcs/0010-field-arguments.md: -------------------------------------------------------------------------------- 1 | # Field Arguments 2 | 3 | The ability to provide arguments to fields broadens the use-cases that NDC can support. 4 | 5 | This can be seen as generalising `fields` to `functions` or, simply as adding context to fields. 6 | 7 | ## Proposal 8 | 9 | This proposes an update to the NDC-Spec to allow arguments to fields. 10 | 11 | The motivation for this change is to generalise the API surface to allow more expressive queries - This should be very similar to collection arguments in practice but can apply to any field (especially nested), not just top-level collections. 12 | 13 | Some examples of why this might be useful: 14 | 15 | - JSON Operations: For a JSON field in a PG table, several JSON functions could be exposed as fields but they require arguments to be useful. Such as [#>](https://www.postgresql.org/docs/9.3/functions-json.html) 16 | - Vector Operations for LLMs etc. 17 | - Advanced Geo Operations 18 | - GraphQL federation: Forwarding Hasura GQL schemas over NDC will require this change if we want to expose root fields as commands instead of collections. 19 | 20 | The schema is extended with: 21 | 22 | ```rust 23 | pub struct ObjectField { 24 | ... 25 | pub arguments: BTreeMap, 26 | } 27 | ``` 28 | 29 | and queries are extended with: 30 | 31 | ```rust 32 | pub enum Field { 33 | Column { 34 | ... 35 | arguments: BTreeMap, 36 | } 37 | ... 38 | } 39 | ``` 40 | 41 | This mirrors the existing implementation for collection arguments. 42 | 43 | ## Implications 44 | 45 | NDC schema and query invocation: 46 | 47 | - When the schema indicates that a field has arguments then they must be provided in a query. 48 | - Optional arguments must be explicitly supplied with `Argument::Literal { Value::Null }`. 49 | - If all arguments are nullable then the field may be referenced without arguments or parenteses 50 | for backwards compatibility purposes, however arguments should be applied going forward. 51 | 52 | Engine interactions: 53 | 54 | - Query field arguments will need to be translated to NDC field arguments 55 | - NDC schema responses will need to be translated into graphql schemas 56 | - Variables need to be bound to field arguments if they are not supplied as scalars 57 | -------------------------------------------------------------------------------- /rfcs/0012-aggregate-nested-fields.md: -------------------------------------------------------------------------------- 1 | # Aggregates for Nested Fields 2 | 3 | ## Purpose 4 | 5 | We can filter and sort by nested fields, but we cannot aggregate their values. This proposal addresses that missing feature. 6 | 7 | ## Proposal 8 | 9 | - Add a new sub-capability `query.nested_fields.aggregates`. 10 | - Add a new field `field_path: Option>` to `Aggregate::ColumnCount` and `Aggregate::SingleColumn`. 11 | - This field may only be non-null and non-empty if the capability is turned on. 12 | - The field path has the same meaning as for filtering and sorting: it is a chain of object properties to traverse to reach the value to aggregate. 13 | -------------------------------------------------------------------------------- /rfcs/0013-filter-by-aggregates.md: -------------------------------------------------------------------------------- 1 | # Filter by Aggregates 2 | 3 | ## Purpose 4 | 5 | We can order by aggregates, but not filter. This proposal adds filtering capabilities based on the values of aggregates on related tables. 6 | 7 | ## Proposal 8 | 9 | - Add a new sub-capability `capabilities.relationships.filter_by_aggregate`. 10 | - Add a new alternative to `ComparisonTarget`: 11 | 12 | ```rust 13 | pub enum ComparisonTarget { 14 | ... 15 | Aggregate { 16 | /// Aggregation method to use 17 | aggregate: Aggregate, 18 | /// Non-empty collection of relationships to traverse 19 | path: Vec, 20 | }, 21 | } 22 | ``` 23 | 24 | - Break `OrderByTarget` while we have the chance as well, to reuse the same structure (and support column count aggregates): 25 | ```rust 26 | pub enum OrderByTarget { 27 | ... 28 | Aggregate { 29 | /// Aggregation method to use 30 | aggregate: Aggregate, 31 | /// Non-empty collection of relationships to traverse 32 | path: Vec, 33 | }, 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /rfcs/0016-newtypes.md: -------------------------------------------------------------------------------- 1 | # Newtypes in `ndc-models` 2 | 3 | ## Purpose 4 | 5 | Most string types in `ndc_models` are raw `String`s. Let's use newtypes to distinguish bewteen different syntactic categories of strings. 6 | 7 | ## Proposal 8 | 9 | We'll start the following list, and then expand later if necessary: 10 | 11 | - `ColumnName` 12 | - `RelationshipName` 13 | - `ArgumentName` 14 | - `CollectionName` 15 | - `ProcedureName` 16 | - `ScalarTypeName` 17 | - `ObjectTypeName` 18 | - `FunctionName` 19 | - `ComparisonOperatorName` 20 | 21 | Since they are all immutable, we will implement them with `SmolStr` underneath, and ensure they have a `Display` method to allow connector writers to turn them into `String` should they need to. 22 | -------------------------------------------------------------------------------- /rfcs/0020-exists-in-nested-collection.md: -------------------------------------------------------------------------------- 1 | # Exists in Nested Collections 2 | 3 | ## Purpose 4 | 5 | [Nested collections](./0019-nested-collections.md) now exist for selection as subfields, as an alternative to relationships. However, we can also extend _exists predicates_ in the same way - with support for both related collections and nested collections. 6 | 7 | ## Proposal 8 | 9 | Add the following to `ExistsInCollection`: 10 | 11 | ```rust 12 | pub enum ExistsInCollection { 13 | ... 14 | NestedCollection { 15 | column_name: FieldName, 16 | #[serde(skip_serializing_if = "BTreeMap::is_empty", default)] 17 | arguments: BTreeMap, 18 | /// Path to a nested collection via object columns 19 | #[serde(skip_serializing_if = "Vec::is_empty", default)] 20 | field_path: Vec, 21 | } 22 | } 23 | ``` 24 | 25 | `ExistsInCollection::NestedCollection` picks out a nested collection (in the same sense as [the original RFC](./0019-nested-collections.md)) as the target of an exists predicate. 26 | 27 | As an example, we can query `institutions` by the existence of a staff member in the reference implementation - [example request](../ndc-reference/tests/query/predicate_with_exists_in_nested_collection/request.json). 28 | -------------------------------------------------------------------------------- /rfcs/0022-runtime-version-checks.md: -------------------------------------------------------------------------------- 1 | # Runtime version checks 2 | 3 | ## Purpose 4 | 5 | It is quite easy to accidentally update a connector but forget to refresh its `DataConnectorLink` (which contains the NDC version, schema and capabilities), which means the engine sends the wrong version of the request to the connector. This can lead to subtle bugs. 6 | 7 | By having an explicit version assertion, connector can immediately reject a request that is for an incorrect version. 8 | 9 | ## Proposal 10 | 11 | Add a `X-Hasura-NDC-Version` HTTP header to all requests, which the client _may_ send in order to clarify the intended protocol version using a semantic version string. The connector should check any provided version and fail fast with a HTTP error code. 12 | -------------------------------------------------------------------------------- /rfcs/0025-additional-field-argument-support.md: -------------------------------------------------------------------------------- 1 | # Additional Field Argument Support 2 | 3 | ## Problem 4 | 5 | Object type fields can declare arguments that must be submitted when the field is evaluated. However, support for using these fields is not universal; there are some features which do not allow the use of fields with arguments, for example in nested field paths, or in relationship column mappings. Those particular examples are harder to address, but there are a few low hanging places where we could easily add support for field arguments in a non-breaking way. 6 | 7 | These places are: 8 | 9 | - `ComparisonTarget::Column` - one cannot compare _against_ column (ie LHS) that takes arguments 10 | - `ComparisonValue::Column` - one cannot compare _to_ a column (ie. RHS) that takes arguments 11 | - `OrderByTarget::Column` - one cannot order by columns that take arguments 12 | - `Aggregate::ColumnCount` - one cannot count by a column that takes arguments 13 | - `Aggregate::SingleColumn` - one cannot aggregate a single column that takes arguments 14 | 15 | ## Solution 16 | 17 | Simply adding: 18 | 19 | ```rust 20 | #[serde(skip_serializing_if = "BTreeMap::is_empty", default)] 21 | arguments: BTreeMap, 22 | ``` 23 | 24 | to all these enum variants resolves the problem in a non-breaking manner. 25 | -------------------------------------------------------------------------------- /rfcs/0026-string-operators.md: -------------------------------------------------------------------------------- 1 | # New String Comparison Operators 2 | 3 | ## Problem 4 | 5 | We want to standardize the `starts_with`, `ends_with` and `contains` operators, and their case-insensitive counterparts. 6 | 7 | ## Solution 8 | 9 | - Add new comparison operator meanings for each of these operator types. 10 | - Specify their semantics in the documentation 11 | - Add operators and tests to the reference implementation 12 | -------------------------------------------------------------------------------- /rfcs/0027-extraction-functions.md: -------------------------------------------------------------------------------- 1 | # Extraction Functions for Grouping 2 | 3 | ## Problem 4 | 5 | We want to be able to support `EXTRACT` (aka `DATE_PART`) in groupings. For example, in Postgres: 6 | 7 | ```sql 8 | SELECT 9 | EXTRACT(MONTH FROM invoice_date) AS month, 10 | SUM(amount) AS total 11 | FROM invoices 12 | GROUP BY EXTRACT(MONTH FROM invoice_date) 13 | ``` 14 | 15 | We want to be able to generalize this later to other hierarchical dimensions, e.g.: 16 | 17 | - Geographical: continent, country, state, region, city, zip code, street, street number 18 | - Categorical: e.g. product category, product subcategory, product ID 19 | - File systems: Drive, folder, subfolder, filename 20 | - Organizational: Department, Team, Employee 21 | 22 | ## Solution 23 | 24 | Add `extraction_functions` to each `ScalarType` in the schema response: 25 | 26 | ```rust 27 | pub struct ScalarType { 28 | // ... 29 | pub extraction_functions: BTreeMap, 30 | } 31 | 32 | pub struct ExtractionFunctionDefinition { 33 | /// The result type, which must be a defined scalar types in the schema response. 34 | result_type: ScalarTypeName, 35 | /// The meaning of this extraction function 36 | r#type: ExtractionFunctionType, 37 | } 38 | 39 | pub enum ExtractionFunctionType { 40 | Nanosecond, 41 | Microsecond, 42 | Millisecond, 43 | Second, 44 | Minute, 45 | Hour, 46 | Day, 47 | Week, 48 | Month, 49 | Quarter, 50 | Year, 51 | DayOfWeek, 52 | DayOfYear, 53 | Custom, 54 | } 55 | ``` 56 | 57 | Add `extraction` to `Dimension`: 58 | 59 | ```rust 60 | pub enum Dimension { 61 | Column { 62 | ... 63 | /// The name of the extraction function to apply to the selected value, if any 64 | extraction: Option, 65 | }, 66 | } 67 | ``` 68 | 69 | If `extraction` is provided, the column value should be computed first, and then the extraction function applied to the value. Data should be grouped by unique extracted values. 70 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.82.0" 3 | profile = "default" # see https://rust-lang.github.io/rustup/concepts/profiles.html 4 | components = ["rust-analyzer", "rust-src"] # see https://rust-lang.github.io/rustup/concepts/components.html 5 | -------------------------------------------------------------------------------- /specification/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /specification/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: deploy 2 | deploy: book 3 | @echo "====> deploying to github" 4 | git worktree add /tmp/book gh-pages 5 | mdbook build 6 | rm -rf /tmp/book/* 7 | cp -rp book/* /tmp/book/ 8 | cd /tmp/book && \ 9 | git add -A && \ 10 | git commit -m "deployed on $(shell date) by ${USER}" && \ 11 | git push origin gh-pages -------------------------------------------------------------------------------- /specification/README.md: -------------------------------------------------------------------------------- 1 | # NDC Specification 2 | 3 | This directory contains source materials for the NDC specification. The specification can be built using `mdbook`, or [read online](http://hasura.github.io/ndc-spec). If building with `mdbook`, you will also need to install [`mdbook-pagetoc`](https://crates.io/crates/mdbook-pagetoc). 4 | -------------------------------------------------------------------------------- /specification/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Phil Freeman"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Hasura Data Connectors Developer's Guide" 7 | 8 | [preprocessor.pagetoc] 9 | [output.html] 10 | additional-css = ["theme/pagetoc.css"] 11 | additional-js = ["theme/pagetoc.js"] 12 | -------------------------------------------------------------------------------- /specification/src/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 |
4 | 5 | NOTE 6 | 7 | This specification contains the low-level details for connector authors, and is intended as a complete reference. 8 | 9 | Users looking to build their own connectors might want to also look at some additional resources: 10 | 11 | - [Hasura Connector Hub](https://hasura.io/connectors) contains a list of currently available connectors 12 | - [Let's Build a Connector](https://hasura.io/learn/graphql/hasura-v3-ts-connector/introduction/) is a step-by-step to creating a connector using TypeScript 13 |
14 | 15 | --- 16 | 17 | Hasura data connectors allow you to extend the functionality of the Hasura server by providing web services which can resolve new sources of data. By following this specification, those sources of data can be added to your Hasura graph, and the usual Hasura features such as relationships and permissions will be supported for your data source. 18 | 19 | This specification is designed to be as general as possible, supporting many different types of data source, while still being targeted enough to provide useful features with high performance guarantees. It is important to note that data connectors are designed for tabular data which supports efficient filtering and sorting. If you are able to model your data source given these constraints, then it will be a good fit for a data connector, but if not, you might like to consider a GraphQL remote source integration with Hasura instead. 20 | -------------------------------------------------------------------------------- /specification/src/reference/json-schema.md: -------------------------------------------------------------------------------- 1 | # JSON Schema 2 | 3 | ## `CapabilitiesResponse` 4 | 5 | ```json 6 | {{#include ../../../ndc-models/tests/json_schema/capabilities_response.jsonschema}} 7 | ``` 8 | 9 | ## `ErrorResponse` 10 | 11 | ```json 12 | {{#include ../../../ndc-models/tests/json_schema/error_response.jsonschema}} 13 | ``` 14 | 15 | ## `ExplainResponse` 16 | 17 | ```json 18 | {{#include ../../../ndc-models/tests/json_schema/explain_response.jsonschema}} 19 | ``` 20 | 21 | ## `MutationRequest` 22 | 23 | ```json 24 | {{#include ../../../ndc-models/tests/json_schema/mutation_request.jsonschema}} 25 | ``` 26 | 27 | ## `MutationResponse` 28 | 29 | ```json 30 | {{#include ../../../ndc-models/tests/json_schema/mutation_response.jsonschema}} 31 | ``` 32 | 33 | ## `QueryRequest` 34 | 35 | ```json 36 | {{#include ../../../ndc-models/tests/json_schema/query_request.jsonschema}} 37 | ``` 38 | 39 | ## `QueryResponse` 40 | 41 | ```json 42 | {{#include ../../../ndc-models/tests/json_schema/query_response.jsonschema}} 43 | ``` 44 | 45 | ## `QueryRequest` 46 | 47 | ```json 48 | {{#include ../../../ndc-models/tests/json_schema/query_request.jsonschema}} 49 | ``` 50 | 51 | ## `SchemaResponse` 52 | 53 | ```json 54 | {{#include ../../../ndc-models/tests/json_schema/schema_response.jsonschema}} 55 | ``` 56 | -------------------------------------------------------------------------------- /specification/src/specification/README.md: -------------------------------------------------------------------------------- 1 | # API Specification 2 | 3 | | Version | 4 | | ------- | 5 | | `0.2.2` | 6 | 7 | A data connector encapsulates a data source by implementing the protocol in this specification. 8 | 9 | A data connector must implement several web service endpoints: 10 | 11 | - A **capabilities** endpoint, which describes which features the data source is capable of implementing. 12 | - A **schema** endpoint, which describes the resources provided by the data source, and the shape of the data they contain. 13 | - A **query** endpoint, which reads data from one of the relations described by the schema endpoint. 14 | - A **query/explain** endpoint, which explains a query plan, without actually executing it. 15 | - A **mutation** endpoint, which modifies the data in one of the relations described by the schema endpoint. 16 | - A **mutation/explain** endpoint, which explains a mutation plan, without actually executing it. 17 | - A **metrics** endpoint, which exposes runtime metrics about the data connector. 18 | - A **health** endpoint, which indicates service health and readiness 19 | -------------------------------------------------------------------------------- /specification/src/specification/basics.md: -------------------------------------------------------------------------------- 1 | # Basics 2 | 3 | Data connectors are implemented as HTTP services. To refer to a running data connector, it suffices to specify its base URL. All required endpoints are specified relative to this base URL. 4 | 5 | All endpoints should accept JSON (in the case of POST request bodies) and return JSON using the `application/json` content type. The particular format of each JSON document will be specified for each endpoint. 6 | -------------------------------------------------------------------------------- /specification/src/specification/explain.md: -------------------------------------------------------------------------------- 1 | # Explain 2 | 3 | There are two endpoints related to explain: 4 | 5 | - The `/query/explain` endpoint, which accepts a [query](./queries/README.md) request. 6 | - The `/mutation/explain` endpoint, which accepts a [mutation](./mutation/README.md) request. 7 | 8 | Both endpoints return a representation of the _execution plan_ without actually executing the query or mutation. 9 | 10 | Connectors that wish to support these endpoints should indicate this in their capabilities; specifically with the `query.explain` capability and the `mutation.explain` capability. 11 | 12 | ## Request 13 | 14 | ``` 15 | POST /query/explain 16 | ``` 17 | 18 | See [`QueryRequest`](../reference/types.md#queryrequest) 19 | 20 | ## Request 21 | 22 | ``` 23 | POST /mutation/explain 24 | ``` 25 | 26 | See [`MutationRequest`](../reference/types.md#mutationrequest) 27 | 28 | ## Response 29 | 30 | See [`ExplainResponse`](../reference/types.md#explainresponse) 31 | -------------------------------------------------------------------------------- /specification/src/specification/health.md: -------------------------------------------------------------------------------- 1 | # Service Health 2 | 3 | Data connectors must provide a **health endpoint** which can be used to indicate service health and readiness to any client applications. 4 | 5 | ## Request 6 | 7 | ``` 8 | GET /health 9 | ``` 10 | 11 | ## Response 12 | 13 | If the data connector is available and ready to accept requests, then the health endpoint should return status code `200 OK`. 14 | 15 | Otherwise, it should ideally return a status code `503 Service Unavailable`, or some other appropriate HTTP error code. 16 | -------------------------------------------------------------------------------- /specification/src/specification/metrics.md: -------------------------------------------------------------------------------- 1 | # Metrics 2 | 3 | Data connectors should provide a **metrics endpoint** which reports relevant metrics in a textual format. Data connectors can report any metrics which are deemed relevant, or none at all, with the exception of any reserved keys. 4 | 5 | ## Request 6 | 7 | ``` 8 | GET /metrics 9 | ``` 10 | 11 | ## Response 12 | 13 | The metrics endpoint should return a content type of `text/plain`, and return any metrics in the [Prometheus textual format](https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format). 14 | 15 | ### Reserved keys 16 | 17 | Metric names prefixed with `hasura_` are reserved for future use, and should not be included in the response. 18 | 19 | ## Example 20 | 21 | ``` 22 | # HELP query_total The number of /query requests served 23 | # TYPE query_total counter 24 | query_total 10000 1685405427000 25 | # HELP mutation_total The number of /mutation requests served 26 | # TYPE mutation_total counter 27 | mutation_total 5000 1685405427000 28 | ``` 29 | -------------------------------------------------------------------------------- /specification/src/specification/mutations/procedures.md: -------------------------------------------------------------------------------- 1 | # Procedures 2 | 3 | A procedure which is [described in the schema](../schema/procedures.md) can be invoked using a [`MutationOperation`](../../reference/types.md#mutationoperation). 4 | 5 | The operation should specify the procedure name, any arguments, and a list of [`Field`](../../reference/types.md#field)s to be returned. 6 | 7 | _Note_: just as for [functions](../queries/functions.md), fields to return can include [relationships](../queries/relationships.md) or [nested fields](../queries/field-selection.md#nested-fields). However, unlike functions, procedures do not need to wrap their result in a `__value` field, so top-level fields can be extracted without use of nested field queries. 8 | 9 | ## Requirements 10 | 11 | - The [`MutationResponse`](../../reference/types.md#mutationresponse) structure will contain a [`MutationOperationResults`](../../reference/types.md#mutationoperationresults) structure for the procedure response. This structure should have type `procedure` and contain a `result` field with a result of the type indicated in the [schema response](../schema/procedures.md). 12 | -------------------------------------------------------------------------------- /specification/src/specification/queries/functions.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | A [function](../schema/functions.md) is invoked in a query request in exactly the same way as any other collection - recall that a function is simply a collection which returns a single row, and a single column, named `__value`. 4 | 5 | Because a function returns a single row, many query capabilities are limited in their usefulness: 6 | 7 | - It would not make sense to specify `limit` or `offset`, 8 | - Sorting has no effect 9 | - Filtering can only remove the whole result row, based on some condition expressed in terms of the _result_. 10 | 11 | However, some query functions are still useful in the context of functions: 12 | 13 | - The caller can request a subset of the full result, by using [nested field queries](./field-selection.md#nested-fields), 14 | - A function can be the source or target of a [relationship](./relationships.md), 15 | - Function [arguments](./arguments.md) are specified in the same way as collection arguments, and can also be specified using [variables](./variables.md). 16 | 17 | ## Examples 18 | 19 | ### A function returning a scalar value 20 | 21 | This example uses the `latest_article_id` function, which returns a scalar type: 22 | 23 | ```json 24 | {{#include ../../../../ndc-reference/tests/query/get_max_article_id/request.json:1 }} 25 | {{#include ../../../../ndc-reference/tests/query/get_max_article_id/request.json:3: }} 26 | ``` 27 | 28 | The response JSON includes the requested data in the special `__value` field: 29 | 30 | ```json 31 | {{#include ../../../../ndc-reference/tests/query/get_max_article_id/expected.snap:6: }} 32 | ``` 33 | 34 | ### A function returning an object type 35 | 36 | This example uses the `latest_article` function instead, which returns the full `article` object. To query the object structure, it uses a [nested field request](./field-selection.md): 37 | 38 | ```json 39 | {{#include ../../../../ndc-reference/tests/query/get_max_article/request.json:1 }} 40 | {{#include ../../../../ndc-reference/tests/query/get_max_article/request.json:3: }} 41 | ``` 42 | 43 | Again, the response is sent in the `__value` field: 44 | 45 | ```json 46 | {{#include ../../../../ndc-reference/tests/query/get_max_article/expected.snap:6: }} 47 | ``` 48 | -------------------------------------------------------------------------------- /specification/src/specification/queries/pagination.md: -------------------------------------------------------------------------------- 1 | # Pagination 2 | 3 | The `limit` and `offset` parameters on the [`Query`](../../reference/types.md#query) object control pagination: 4 | 5 | - `limit` specifies the maximum number of rows that are considered during field selection and before aggregates and grouping are applied. 6 | - `offset`: The index of the first row to consider during field selection and before aggregates and grouping are applied. 7 | 8 | `limit` and `offset` are applied after the [predicate filter from the Query](../filtering.md) is applied and after [sorting from the Query](../sorting.md) is applied, but before [aggregates](../aggregates.md) and [grouping](../grouping.md) are applied. Both `limit` and `offset` affect the rows returned by field selection. 9 | 10 | ## Requirements 11 | 12 | - If `limit` is specified, the response should contain at most that many rows, and aggregates and grouping should be applied to at most that many rows. 13 | 14 | ## See also 15 | 16 | - Type [`Query`](../../reference/types.md#query) 17 | -------------------------------------------------------------------------------- /specification/src/specification/queries/variables.md: -------------------------------------------------------------------------------- 1 | # Variables 2 | 3 | A [`QueryRequest`](../../reference/types.md#queryrequest) can optionally specify one or more sets of variables which can be referenced throughout the [`Query`](../../reference/types.md#query) object. 4 | 5 | Query variables will only be provided if the `query.variables` [capability](../capabilities.md) is advertised in the capabilities response. 6 | 7 | The intent is that the data connector should attempt to perform multiple versions of the query in parallel - one instance of the query for each set of variables. For each set of variables, each variable value should be substituted wherever it is referenced in the query - for example in a [`ComparisonValue`](../../reference/types.md#comparisonvalue). 8 | 9 | ## Example 10 | 11 | In the following query, we fetch two rowsets of article data. In each rowset, the rows are filtered based on the `author_id` column, and the prescribed `author_id` is determined by a variable. The choice of `author_id` varies between rowsets. 12 | 13 | The result contains one rowset containing articles from the author with ID `1`, and a second for the author with ID `2`. 14 | 15 | ```json 16 | {{#include ../../../../ndc-reference/tests/query/variables/request.json:1 }} 17 | {{#include ../../../../ndc-reference/tests/query/variables/request.json:3: }} 18 | ``` 19 | 20 | ## Requirements 21 | 22 | - If `variables` are provided in the [`QueryRequest`](../../reference/types.md#queryrequest), then the [`QueryResponse`](../../reference/types.md#queryresponse) should contain one [`RowSet`](../../reference/types.md#rowset) for each set of variables, in the same order. 23 | - If `variables` are not provided, the data connector should return a single [`RowSet`](../../reference/types.md#rowset). 24 | -------------------------------------------------------------------------------- /specification/src/specification/schema/README.md: -------------------------------------------------------------------------------- 1 | # Schema 2 | 3 | The schema endpoint defines any types used by the data connector, and describes the collections and their columns, functions, and any procedures. 4 | 5 | The schema endpoint is used to specify the behavior of a data connector, so that it can be tested, verified, and used by tools such as code generators. It is primarily provided by data connector implementors as a development and specification tool, and it is not expected to be used at "runtime", in the same sense that the `/query` and `/mutation` endpoints would be. 6 | 7 | ## Request 8 | 9 | ``` 10 | GET /schema 11 | ``` 12 | 13 | ## Response 14 | 15 | See [`SchemaResponse`](../../reference/types.md#schemaresponse) 16 | 17 | ### Example 18 | 19 | ```json 20 | {{#include ../../../../ndc-reference/tests/schema/expected.snap:5:}} 21 | ``` 22 | 23 | ## Response Fields 24 | 25 | | Name | Description | 26 | | -------------- | -------------------------------------------------- | 27 | | `scalar_types` | [Scalar types](scalar-types.md) | 28 | | `object_types` | [Object types](object-types.md) | 29 | | `collections` | [Collection](collections.md) | 30 | | `functions` | [Functions](functions.md) | 31 | | `procedures` | [Procedures](procedures.md) | 32 | | `capabilities` | [Capability-specific information](capabilities.md) | 33 | -------------------------------------------------------------------------------- /specification/src/specification/schema/capabilities.md: -------------------------------------------------------------------------------- 1 | # Capabilities 2 | 3 | The schema response should also provide any capability-specific data, based on the set of enabled [capabilities](../capabilities.md). 4 | 5 | ## Requirements 6 | 7 | - If the `query.aggregates` capability is enabled, then the schema response should include the `capabilities.query.aggregates` object, which has type [`AggregateCapabilitiesSchemaInfo`](../../reference/types.md#aggregatecapabilitiesschemainfo). 8 | - This object should indicate the scalar type used as count aggregate result type, in order to implement [aggregates](../queries/aggregates.md). 9 | 10 | ## Example 11 | 12 | ```json 13 | { 14 | ... 15 | "capabilities": { 16 | "query": { 17 | "aggregates": { 18 | "count_scalar_type": "Int" 19 | } 20 | } 21 | } 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /specification/src/specification/schema/collections.md: -------------------------------------------------------------------------------- 1 | # Collections 2 | 3 | The schema should define the metadata for any _collections_ which can be queried using the query endpoint, or mutated using the mutation endpoint. 4 | 5 | Each collection is defined by its name, any collection [arguments](../queries/arguments.md), the [object type](./object-types.md) of its rows, and some additional metadata related to permissions and constraints. 6 | 7 | To describe a collection, add a [`CollectionInfo`](../../reference/types.md#collectioninfo) structure to the `collections` field of the schema response. 8 | 9 | ## Requirements 10 | 11 | - The `type` field should name an object type which is defined in the schema response. 12 | 13 | ## Example 14 | 15 | ```json 16 | { 17 | "collections": [ 18 | { 19 | "name": "articles", 20 | "description": "A collection of articles", 21 | "arguments": {}, 22 | "type": "article", 23 | "deletable": false, 24 | "uniqueness_constraints": { 25 | "ArticleByID": { 26 | "unique_columns": [ 27 | "id" 28 | ] 29 | } 30 | } 31 | }, 32 | { 33 | "name": "authors", 34 | "description": "A collection of authors", 35 | "arguments": {}, 36 | "type": "author", 37 | "deletable": false, 38 | "uniqueness_constraints": { 39 | "AuthorByID": { 40 | "unique_columns": [ 41 | "id" 42 | ] 43 | } 44 | } 45 | } 46 | ], 47 | ... 48 | } 49 | ``` 50 | 51 | ## See also 52 | 53 | - Type [`CollectionInfo`](../../reference/types.md#collectioninfo) 54 | -------------------------------------------------------------------------------- /specification/src/specification/schema/functions.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | Functions are a special case of [collections](./collections.md), which are identified separately in the schema for convenience. 4 | 5 | A function is a collection which returns a single row and a single column, named `__value`. Like collections, functions can have arguments. Unlike collections, functions cannot be used by the mutations endpoint, do not describe constraints, and only provide a type for the `__value` column, not the name of an object type. 6 | 7 | _Note_: even though a function acts like a collection returning a row type with a single column, there is no need to define and name such a type in the `object_types` section of the schema response. 8 | 9 | To describe a function, add a [`FunctionInfo`](../../reference/types.md#FunctionInfo) structure to the `functions` field of the schema response. 10 | 11 | ## Example 12 | 13 | ```json 14 | { 15 | "functions": [ 16 | { 17 | "name": "latest_article_id", 18 | "description": "Get the ID of the most recent article", 19 | "arguments": {}, 20 | "result_type": { 21 | "type": "nullable", 22 | "underlying_type": { 23 | "type": "named", 24 | "name": "Int" 25 | } 26 | } 27 | } 28 | ], 29 | ... 30 | } 31 | ``` 32 | 33 | ## See also 34 | 35 | - Type [`FunctionInfo`](../../reference/types.md#FunctionInfo) 36 | -------------------------------------------------------------------------------- /specification/src/specification/schema/procedures.md: -------------------------------------------------------------------------------- 1 | # Procedures 2 | 3 | The schema should define metadata for each _procedure_ which the data connector implements. 4 | 5 | Each procedure is defined by its name, any arguments types and a result type. 6 | 7 | To describe a procedure, add a [`ProcedureInfo`](../../reference/types.md#procedureinfo) structure to the `procedure` field of the schema response. 8 | 9 | ## Example 10 | 11 | ```json 12 | { 13 | "procedures": [ 14 | { 15 | "name": "upsert_article", 16 | "description": "Insert or update an article", 17 | "arguments": { 18 | "article": { 19 | "description": "The article to insert or update", 20 | "type": { 21 | "type": "named", 22 | "name": "article" 23 | } 24 | } 25 | }, 26 | "result_type": { 27 | "type": "named", 28 | "name": "article" 29 | } 30 | } 31 | ], 32 | ... 33 | } 34 | ``` 35 | 36 | ## See also 37 | 38 | - Type [`ProcedureInfo`](../../reference/types.md#procedureinfo) 39 | -------------------------------------------------------------------------------- /specification/src/specification/telemetry.md: -------------------------------------------------------------------------------- 1 | # Telemetry 2 | 3 | Hasura uses OpenTelemetry to coordinate the collection of traces and metrics with data connectors. 4 | 5 | ## Trace Collection 6 | 7 | Trace collection is out of the scope of this specification currently. This may change in a future revision. 8 | 9 | ## Trace Propagation 10 | 11 | Hasura uses the [W3C TraceContext specification](https://www.w3.org/TR/trace-context/) to implement trace propagation. Data connectors should propagate tracing headers in this format to any downstream services. 12 | -------------------------------------------------------------------------------- /specification/src/specification/versioning.md: -------------------------------------------------------------------------------- 1 | # Versioning 2 | 3 | This specification is versioned using semantic versioning, and a data connector declares the [semantic version](https://semver.org) of the specification that it implements via its [capabilities](capabilities.md) endpoint. 4 | 5 | Non-breaking changes to the specification may be achieved via the addition of new capabilities, which a connector will be assumed not to implement if the corresponding field is not present in its capabilities endpoint. 6 | 7 | ## Requirements 8 | 9 | The client _may_ send a semantic version string in the `X-Hasura-NDC-Version` HTTP header to any of the HTTP endpoints described by this specification. This header communicates the version of this specification that the client intends to use. Typically this should be the minimum non-breaking version of the specification that is supported by the client, so that the widest range of connectors can be used. For example, if a client sends supports sending v0.1.6 requests, then it technically is sending requests that are compatible with v0.1.0 clients because non-breaking additions are gated behind capabilities and would be disabled for older connectors. In this case, the client should send `0.1.0` as its version in the header. 10 | 11 | _If_ the client sends this header, the connector should check compatibility with the requested version, and return an appropriate HTTP error code (e.g. `400 Bad Request`) if it is not capable of providing an implementation. Compatibility is defined as the semver range: `^{requested-version}`. For example, if the client sends `0.2.0`, then the compatible semver range is `^0.2.0`. If the connector implemented spec version `0.1.6`, this would be incompatible, but if it implemented spec version `0.2.1`, this would be compatible. 12 | 13 | _Note_: the `/capabilities` endpoint also indicates the implemented specification version for any connector, but it may not be practical for a client to check the capabilities endpoint before issuing a new request, so this provides a way to check compatibility in the course of a normal request. 14 | -------------------------------------------------------------------------------- /specification/src/tutorial/README.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | In this tutorial, we will walk through the _reference implementation_ of the specification, which will illustrate how to implement data connectors from scratch. 4 | 5 | The reference implementation is written in Rust, but it should be possible to follow along using any language of your choice, as long as you can implement a basic web server and implement serializers and deserializers for the data formats involved. 6 | 7 | It is recommended that you follow along chapter-by-chapter, as each will build on the last. 8 | -------------------------------------------------------------------------------- /specification/src/tutorial/capabilities.md: -------------------------------------------------------------------------------- 1 | # Capabilities 2 | 3 | The [capabilities endpoint](../specification/capabilities.md) should return data describing which features the data connector can implement, along with the version of this specification that the data connector claims to implement. 4 | 5 | The reference implementation returns a static `CapabilitiesResponse`: 6 | 7 | ```rust,no_run,noplayground 8 | {{#include ../../../ndc-reference/bin/reference/main.rs:capabilities}} 9 | ``` 10 | 11 | _Note_: the reference implementation supports all capabilities with the exception of `query.explain` and `mutation.explain`. This is because all queries are run in memory by naively interpreting the query request - there is no better description of the query plan than the raw query request itself! 12 | -------------------------------------------------------------------------------- /specification/src/tutorial/explain.md: -------------------------------------------------------------------------------- 1 | # Explain 2 | 3 | The `/query/explain` and `/mutation/explain` endpoints are not implemented in the reference implementation, because their respective request objects are interpreted directly. There is no intermediate representation (such as SQL) which could be described as an "execution plan". 4 | 5 | The `query.explain` and `mutation.explain` capabilities are turned off in the [capabilities endpoint](./capabilities.md), 6 | and the `/query/explain` and `/mutation/explain` endpoints throw an error: 7 | 8 | ```rust,no_run,noplayground 9 | {{#include ../../../ndc-reference/bin/reference/main.rs:query_explain}} 10 | ``` 11 | -------------------------------------------------------------------------------- /specification/src/tutorial/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | The reference implementation will serve queries and mutations based on in-memory data read from newline-delimited JSON files. 4 | 5 | First, we will define some types to represent the data in the newline-delimited JSON files. Rows of JSON data will be stored in memory as ordered maps: 6 | 7 | ```rust,no_run,noplayground 8 | {{#include ../../../ndc-reference/bin/reference/main.rs:row-type}} 9 | ``` 10 | 11 | Our application state will consist of collections of various types of rows: 12 | 13 | ```rust,no_run,noplayground 14 | {{#include ../../../ndc-reference/bin/reference/main.rs:app-state}} 15 | ``` 16 | 17 | In our `main` function, the data connector reads the initial data from the newline-delimited JSON files, and creates the `AppState`: 18 | 19 | ```rust,no-run,noplayground 20 | {{#include ../../../ndc-reference/bin/reference/main.rs:init_app_state}} 21 | ``` 22 | 23 | Finally, we start a web server with the endpoints which are required by this specification: 24 | 25 | ```rust,no_run,noplayground 26 | {{#include ../../../ndc-reference/bin/reference/main.rs:main}} 27 | ``` 28 | 29 | _Note_: the application state is stored in an `Arc>`, so that we can perform locking reads and writes in multiple threads. 30 | 31 | In the next chapters, we will look at the implementation of each of these endpoints in turn. 32 | -------------------------------------------------------------------------------- /specification/src/tutorial/health.md: -------------------------------------------------------------------------------- 1 | # Health and Metrics 2 | 3 | ## Service Health 4 | 5 | The `/health` endpoint has nothing to check, because the reference implementation does not need to connect to any other services. Therefore, once the reference implementation is running, it can always report a healthy status: 6 | 7 | ```rust,no_run,noplayground 8 | {{#include ../../../ndc-reference/bin/reference/main.rs:health}} 9 | ``` 10 | 11 | In practice, a connector should make sure that any upstream services can be successfully contacted, and respond accordingly. 12 | 13 | ## Metrics 14 | 15 | The reference implementation maintains some generic access metrics in its application state: 16 | 17 | - `metrics.total_requests` counts the number of requests ever served, and 18 | - `metrics.active_requests` counts the number of requests _currently_ being served. 19 | 20 | The [metrics endpoint](../specification/metrics.md) reports these metrics using the Rust [prometheus](https://docs.rs/prometheus/latest/prometheus/) crate: 21 | 22 | ```rust,no_run,noplayground 23 | {{#include ../../../ndc-reference/bin/reference/main.rs:metrics}} 24 | ``` 25 | 26 | To maintain these metrics, it uses a simple metrics middleware: 27 | 28 | ```rust,no_run,noplayground 29 | {{#include ../../../ndc-reference/bin/reference/main.rs:metrics_middleware}} 30 | ``` 31 | -------------------------------------------------------------------------------- /specification/src/tutorial/mutations/README.md: -------------------------------------------------------------------------------- 1 | # Mutations 2 | 3 | In this section, we will break down the implementation of the `/mutation` endpoint. 4 | 5 | The mutation endpoint is handled by the `post_mutation` function: 6 | 7 | ```rust,no_run,noplayground 8 | {{#include ../../../../ndc-reference/bin/reference/main.rs:post_mutation_signature}} 9 | ``` 10 | 11 | This function receives the application state, and the [`MutationRequest`](../../reference/types.md#mutationrequest) structure. 12 | 13 | The function iterates over the collection of requested [`MutationOperation`](../../reference/types.md#mutationoperation) structures, and handles each one in turn, adding each result to the `operation_results` field in the response: 14 | 15 | ```rust,no_run,noplayground 16 | {{#include ../../../../ndc-reference/bin/reference/main.rs:post_mutation}} 17 | ``` 18 | 19 | The `execute_mutation_operation` function is responsible for executing an individual operation. In the next section, we'll break that function down. 20 | -------------------------------------------------------------------------------- /specification/src/tutorial/mutations/operations.md: -------------------------------------------------------------------------------- 1 | # Handling Operations 2 | 3 | The `execute_mutation_operation` function is responsible for handling a single [`MutationOperation`](../../reference/types.md#mutationoperation), and returning the corresponding [`MutationOperationResults`](../../reference/types.md#mutationoperationresults): 4 | 5 | ```rust,no_run,noplayground 6 | {{#include ../../../../ndc-reference/bin/reference/main.rs:execute_mutation_operation}} 7 | ``` 8 | 9 | The function matches on the type of the operation, and delegates to the appropriate function. Currently, the only type of operation is `Procedure`, so the function delegates to the `execute_procedure` function. In the next section, we will break down the implementation of that function. 10 | -------------------------------------------------------------------------------- /specification/src/tutorial/queries/README.md: -------------------------------------------------------------------------------- 1 | # Queries 2 | 3 | The reference implementation of the `/query` endpoint may seem complicated, because there is a lot of functionality packed into a single endpoint. However, we will break the implementation down into small sections, each of which should be easily understood. 4 | 5 | We start by looking at the type signature of the `post_query` function, which is the top-level function implementing the query endpoint: 6 | 7 | ```rust,no_run,noplayground 8 | {{#include ../../../../ndc-reference/bin/reference/main.rs:post_query_signature}} 9 | ``` 10 | 11 | This function accepts a [`QueryRequest`](../../reference/types.md#queryrequest) and must produce a [`QueryResponse`](../../reference/types.md#queryresponse). 12 | 13 | In the next section, we will start to break down this problem step-by-step. 14 | -------------------------------------------------------------------------------- /specification/src/tutorial/queries/arguments.md: -------------------------------------------------------------------------------- 1 | # Evaluating Arguments 2 | 3 | Now that we have reduced the problem to a single set of query variables, we must evaluate any [collection arguments](../../specification/queries/arguments.md), and in turn, evaluate the _collection_ of rows that we will be working with. 4 | 5 | From there, we will be able to apply predicates, sort and paginate rows. But one step at a time! 6 | 7 | The first step is to evaluate each argument, which the `execute_query_with_variables` function does by delegating to the `eval_argument` function: 8 | 9 | ```rust,no_run,noplayground 10 | {{#include ../../../../ndc-reference/bin/reference/main.rs:execute_query_with_variables}} 11 | ``` 12 | 13 | Once this is complete, and we have a collection of evaluated `argument_values`, we can delegate to the `get_collection_by_name` function. This function peforms the work of computing the full collection, by pattern matching on the name of the collection: 14 | 15 | ```rust,no_run,noplayground 16 | {{#include ../../../../ndc-reference/bin/reference/main.rs:get_collection_by_name}} 17 | ``` 18 | 19 | _Note 1_: the `articles_by_author` collection is the only example here which has to apply any arguments. It is provided as an example of a collection which accepts an `author_id` argument, and it must validate that the argument is present, and that it is an integer. 20 | 21 | _Note 2_: the `latest_article_id` collection is provided as an example of a [function](../../specification/schema/functions.md). It is a collection like all the others, but must follow the rules for functions: it must consist of a single row, with a single column named `__value`. 22 | 23 | In the next section, we will break down the implementation of `execute_query`. 24 | Once we have computed the full collection, we can move onto evaluating the query in the context of that collection, using the `execute_query` function: 25 | 26 | ```rust,no_run,noplayground 27 | {{#include ../../../../ndc-reference/bin/reference/main.rs:execute_query_signature}} 28 | ``` 29 | 30 | In the next section, we will break down the implementation of `execute_query`. 31 | -------------------------------------------------------------------------------- /specification/src/tutorial/queries/execute/README.md: -------------------------------------------------------------------------------- 1 | # Executing Queries 2 | 3 | In this section, we will break down the implementation of the `execute_query` function: 4 | 5 | ```rust,no_run,noplayground 6 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:execute_query_signature}} 7 | ``` 8 | 9 | At this point, we have already computed the full collection, which is passed via the `collection` argument. Now, we need to evaluate the [`Query`](../../../reference/types.md#query) in the context of this collection. 10 | 11 | The `Query` describes the predicate which should be applied to all rows, the sort order, pagination options, along with any aggregates to compute and fields to return. 12 | 13 | The first step is to sort the collection. 14 | 15 | _Note_: we could also start by filtering, and then sort the filtered rows. Which is more efficient depends on the data and the query, and choosing between these approaches would be the job of a _query planner_ in a real database engine. However, this is out of scope here, so we make an arbitrary choice, and sort the data first. 16 | -------------------------------------------------------------------------------- /specification/src/tutorial/queries/execute/aggregates.md: -------------------------------------------------------------------------------- 1 | # Aggregates 2 | 3 | Now that we have computed the sorted, filtered, and paginated rows of the original collection, we can compute any aggregates over those rows. 4 | 5 | Each aggregate is computed in turn by the `eval_aggregate` function, and added to the list of all aggregates to return: 6 | 7 | ```rust,no_run,noplayground 8 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:execute_query_aggregates}} 9 | ``` 10 | 11 | The `eval_aggregate` function works by pattern matching on the type of the aggregate being computed: 12 | 13 | - A `star_count` aggregate simply counts all rows, 14 | - A `column_count` aggregate computes the subset of rows where the named column is non-null, and returns the count of only those rows, 15 | - A `single_column` aggregate is computed by delegating to the `eval_aggregate_function` function, which computes a custom aggregate operator over the values of the selected column taken from all rows. 16 | 17 | ```rust,no_run,noplayground 18 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:eval_aggregate}} 19 | ``` 20 | 21 | The `eval_aggregate_function` function discovers the type of data being aggregated and then dispatches to a specific function that implements aggregation for that type. 22 | 23 | ```rust,no_run,noplayground 24 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:eval_aggregate_function_snippet}} 25 | ... 26 | ``` 27 | 28 | For example, integer aggregation is implemented by `eval_integer_aggregate_function`. In it, the `min`, `max`, `sum`, and `avg` functions are implemented. 29 | 30 | ```rust,no_run,noplayground 31 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:eval_integer_aggregate_function}} 32 | ``` 33 | -------------------------------------------------------------------------------- /specification/src/tutorial/queries/execute/field-selection.md: -------------------------------------------------------------------------------- 1 | # Field Selection 2 | 3 | In addition to computing aggregates, we can also return fields selected directly from the rows themselves. 4 | 5 | This is done by mapping over the computed rows, and using the `eval_field` function to evaluate each selected field in turn: 6 | 7 | ```rust,no_run,noplayground 8 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:execute_query_fields}} 9 | ``` 10 | 11 | The `eval_field` function works by pattern matching on the field type: 12 | 13 | - A `column` is selected using the `eval_column` function (or `eval_nested_field` if there are nested fields to fetch) 14 | - A `relationship` field is selected by evaluating the related collection using `eval_path_element` (we will cover this in the next section), and then recursively executing a query using `execute_query`: 15 | 16 | ```rust,no_run,noplayground 17 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:eval_field}} 18 | ``` 19 | -------------------------------------------------------------------------------- /specification/src/tutorial/queries/execute/grouping.md: -------------------------------------------------------------------------------- 1 | # Grouping 2 | 3 | In addition to [field selection](./field-selection.md) and [computing aggregates](./aggregates.md), we also need to return the results of any requested [grouping operations](../../../specification/queries/grouping.md). 4 | 5 | This is done by delegating to the `eval_groups` function: 6 | 7 | ```rust,no_run,noplayground 8 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:execute_query_groups}} 9 | ``` 10 | 11 | `eval_groups` takes a set of rows, and proceeds largely like `execute_query` itself. 12 | 13 | First, rows are partitioned into groups: 14 | 15 | ```rust,no_run,noplayground 16 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:eval_groups_partition}} 17 | ``` 18 | 19 | The `eval_dimensions` function computes a vector of dimensions for each row: 20 | 21 | ```rust,no_run,noplayground 22 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:eval_dimensions}} 23 | ``` 24 | 25 | The only type of dimension we need to handle is a column. First the value of the column is computed by delegating to `eval_column_field_path`, and then any [extraction function](../../../specification/schema/scalar-types.md#extraction-functions) is evaluated using the `eval_extraction` function: 26 | 27 | ```rust,no_run,noplayground 28 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:eval_dimension}} 29 | ``` 30 | 31 | Next, the partitions are sorted, using the `group_sort` function which is very similar to its row-based counterpart `sort`: 32 | 33 | ```rust,no_run,noplayground 34 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:eval_groups_sort}} 35 | ``` 36 | 37 | Next, groups are aggregated and filtered: 38 | 39 | ```rust,no_run,noplayground 40 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:eval_groups_filter}} 41 | ``` 42 | 43 | The `eval_group_expression` function is also very similar to the `eval_expression` function which performs a similar operation on rows. 44 | 45 | Finally, the groups are paginated and returned: 46 | 47 | ```rust,no_run,noplayground 48 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:eval_groups_paginate}} 49 | ``` 50 | -------------------------------------------------------------------------------- /specification/src/tutorial/queries/execute/pagination.md: -------------------------------------------------------------------------------- 1 | # Pagination 2 | 3 | Once the irrelevant rows have been filtered out, the `execute_query` function applies the `limit` and `offset` arguments by calling the `paginate function: 4 | 5 | ```rust,no_run,noplayground 6 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:execute_query_paginate}} 7 | ``` 8 | 9 | The `paginate` function is implemented using the `skip` and `take` functions on iterators: 10 | 11 | ```rust,no_run,noplayground 12 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:paginate}} 13 | ``` 14 | -------------------------------------------------------------------------------- /specification/src/tutorial/queries/execute/relationships.md: -------------------------------------------------------------------------------- 1 | # Relationships 2 | 3 | Relationships appear in many places in the [`QueryRequest`](../../../reference/types.md#queryrequest), but are always computed using the `eval_path` function. 4 | 5 | `eval_path` accepts a list of [`PathElement`](../../../reference/types.md#pathelement)s, each of which describes the traversal of a single edge of the collection-relationship graph. `eval_path` computes the collection at the final node of this path through the graph. 6 | 7 | It does this by successively evaluating each edge in turn using the `eval_path_element` function: 8 | 9 | ```rust,no_run,noplayground 10 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:eval_path}} 11 | ``` 12 | 13 | The `eval_path_element` function computes a collection from a single relationship, one source row at a time. If a `field_path` exists, the source row is replaced by descending through the nested objects as specified by the field path (using `eval_row_field_path`). Once this is done, all relationship arguments are evaluated, and the target collection is computed by using `get_collection_by_name`. Finally the column mapping is evaluated on any resulting rows. 14 | 15 | ```rust,no_run,noplayground 16 | {{#include ../../../../../ndc-reference/bin/reference/main.rs:eval_path_element}} 17 | ``` 18 | -------------------------------------------------------------------------------- /specification/src/tutorial/queries/variables.md: -------------------------------------------------------------------------------- 1 | # Query Variables 2 | 3 | The first step in `post_query` is to reduce the problem from a query with multiple sets of [query variables](../../specification/queries/variables.md) to only a single set. 4 | 5 | The `post_query` function iterates over all variable sets, and for each one, produces a [`RowSet`](../../reference/types.md#rowset) of rows corresponding to that set of variables. Each `RowSet` is then added to the final `QueryResponse`: 6 | 7 | ```rust,no_run,noplayground 8 | {{#include ../../../../ndc-reference/bin/reference/main.rs:post_query}} 9 | ``` 10 | 11 | In order to compute the `RowSet` for a given set of variables, the function delegates to a function named `execute_query_with_variables`: 12 | 13 | ```rust,no_run,noplayground 14 | {{#include ../../../../ndc-reference/bin/reference/main.rs:execute_query_with_variables_signature}} 15 | ``` 16 | 17 | In the next section, we will break down the implementation of this function. 18 | -------------------------------------------------------------------------------- /specification/src/tutorial/setup.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | To compile and run the reference implementation, you will need to install a Rust toolchain, and then run: 4 | 5 | ```bash 6 | git clone git@github.com:hasura/ndc-spec.git 7 | cd ndc-spec/ndc-reference 8 | cargo build 9 | cargo run 10 | ``` 11 | 12 | Alternatively, you can run the reference implementation entirely inside a Docker container: 13 | 14 | ```bash 15 | git clone git@github.com:hasura/ndc-spec.git 16 | cd ndc-spec 17 | docker build -t reference_connector . 18 | docker run -it reference_connector 19 | ``` 20 | 21 | Either way, you should have a working data connector running on , which you can test as follows: 22 | 23 | ```bash 24 | curl http://localhost:8100/schema 25 | ``` 26 | -------------------------------------------------------------------------------- /specification/src/tutorial/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | Testing tools are provided in the specification repository to aid in the development of connectors. 4 | 5 | ## `ndc-test` 6 | 7 | The `ndc-test` executable performs basic validation of the data returned by the capabilities and schema endpoints, and performs some basic queries. 8 | 9 | To test a connector, provide its endpoint to `ndc-test` on the command line: 10 | 11 | ```sh 12 | ndc-test --endpoint 13 | ``` 14 | 15 | For example, running the reference connector and passing its URL to `ndc-test`, we will see that it issues test queries against the `articles` and `authors` collections: 16 | 17 | ```text 18 | ndc-test test --endpoint http://localhost:8100 19 | 20 | Capabilities 21 | ├ Fetching /capabilities ... ... OK 22 | ├ Validating capabilities ... OK 23 | Schema 24 | ├ Fetching /schema ... OK 25 | ├ Validating schema ... 26 | │ ├ object_types ... OK 27 | │ ├ Collections ... 28 | │ │ ├ articles ... 29 | │ │ │ ├ Arguments ... OK 30 | │ │ │ ├ Collection type ... OK 31 | │ │ ├ authors ... 32 | │ │ │ ├ Arguments ... OK 33 | │ │ │ ├ Collection type ... OK 34 | │ │ ├ articles_by_author ... 35 | │ │ │ ├ Arguments ... OK 36 | │ │ │ ├ Collection type ... OK 37 | │ ├ Functions ... 38 | │ │ ├ latest_article_id ... 39 | │ │ │ ├ Result type ... OK 40 | │ │ │ ├ Arguments ... OK 41 | │ │ ├ Procedures ... 42 | │ │ │ ├ upsert_article ... 43 | │ │ │ │ ├ Result type ... OK 44 | │ │ │ │ ├ Arguments ... OK 45 | Query 46 | ├ articles ... 47 | │ ├ Simple queries ... 48 | │ │ ├ Select top N ... OK 49 | │ │ ├ Predicates ... OK 50 | │ ├ Aggregate queries ... 51 | │ │ ├ star_count ... OK 52 | ├ authors ... 53 | │ ├ Simple queries ... 54 | │ │ ├ Select top N ... OK 55 | │ │ ├ Predicates ... OK 56 | │ ├ Aggregate queries ... 57 | │ │ ├ star_count ... OK 58 | ├ articles_by_author ... 59 | ``` 60 | 61 | However, `ndc-test` cannot validate the entire schema. For example, it will not issue queries against the `articles_by_author` collection, because it does not have any way to synthesize inputs for its required collection argument. 62 | -------------------------------------------------------------------------------- /specification/theme/pagetoc.css: -------------------------------------------------------------------------------- 1 | @media only screen and (max-width: 1439px) { 2 | .sidetoc { 3 | display: none; 4 | } 5 | } 6 | 7 | @media only screen and (min-width: 1440px) { 8 | main { 9 | position: relative; 10 | } 11 | .sidetoc { 12 | margin-left: auto; 13 | margin-right: auto; 14 | left: calc(100% + (var(--content-max-width)) / 4 - 140px); 15 | position: absolute; 16 | font-size: 0.875em; 17 | } 18 | .pagetoc { 19 | position: fixed; 20 | width: 250px; 21 | height: calc(100vh - var(--menu-bar-height) - 0.67em * 4); 22 | overflow: auto; 23 | } 24 | .pagetoc a { 25 | border-left: 1px solid var(--sidebar-bg); 26 | color: var(--fg) !important; 27 | display: block; 28 | padding-bottom: 5px; 29 | padding-top: 5px; 30 | padding-left: 10px; 31 | text-align: left; 32 | text-decoration: none; 33 | } 34 | .pagetoc a:hover, 35 | .pagetoc a.active { 36 | background: var(--sidebar-bg); 37 | color: var(--sidebar-fg) !important; 38 | } 39 | .pagetoc .active { 40 | background: var(--sidebar-bg); 41 | color: var(--sidebar-fg); 42 | } 43 | .pagetoc .pagetoc-H2 { 44 | padding-left: 20px; 45 | } 46 | .pagetoc .pagetoc-H3 { 47 | padding-left: 40px; 48 | } 49 | .pagetoc .pagetoc-H4 { 50 | padding-left: 60px; 51 | } 52 | .pagetoc .pagetoc-H5 { 53 | display: none; 54 | } 55 | .pagetoc .pagetoc-H6 { 56 | display: none; 57 | } 58 | } 59 | --------------------------------------------------------------------------------