Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Filtering over nested arrays of scalars #182

Open
wants to merge 9 commits into
base: daniel/add-arguments-to-places
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Lists all targets
default:
just --list

Expand All @@ -9,14 +10,48 @@ docker +args:
docker compose -f dev.docker-compose.yaml down
exit $exit_code

# Builds in docker
ci-build:
just docker cargo build

# Runs the tests in docker
ci-test:
just docker cargo test

# Runs linting checks in docker
ci-lint:
just docker cargo clippy

# Runs benchmarks in docker
ci-bench:
just docker cargo bench

# Runs the tests
test *ARGS:
#!/usr/bin/env bash
if command -v cargo-nextest; then
COMMAND=(cargo nextest run)
else
COMMAND=(cargo test)
fi
COMMAND+=(--no-fail-fast "$@")
echo "${COMMAND[*]}"
"${COMMAND[@]}"

# Formats all the Markdown, Rust, Nix etc
fix-format: fix-format-prettier
cargo fmt --all
! command -v nix || nix fmt

# Formats Markdown, etc with prettier
fix-format-prettier:
npx --yes prettier --write .

# Runs the tests and updates all goldenfiles with the test output
update-golden-files:
UPDATE_GOLDENFILES=1 just test
just fix-format-prettier

# Starts the ndc-spec documentation webserver
start-docs:
cd specification && mdbook serve
60 changes: 58 additions & 2 deletions ndc-models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pub struct ExistsCapabilities {
#[schemars(title = "Nested Field Capabilities")]
pub struct NestedFieldCapabilities {
/// Does the connector support filtering by values of nested fields
pub filter_by: Option<LeafCapability>,
pub filter_by: Option<NestedFieldFilterByCapabilities>,
/// Does the connector support ordering by values of nested fields
pub order_by: Option<LeafCapability>,
/// Does the connector support aggregating values within nested fields
Expand All @@ -104,7 +104,35 @@ pub struct NestedFieldCapabilities {
/// `NestedField::NestedCollection`
pub nested_collections: Option<LeafCapability>,
}
// ANCHOR_END: NestedCollectionCapabilities
// ANCHOR_END: NestedFieldCapabilities

// ANCHOR: NestedFieldFilterByCapabilities
#[skip_serializing_none]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[schemars(title = "Nested Field Filter By Capabilities")]
pub struct NestedFieldFilterByCapabilities {
/// Does the connector support filtering over nested arrays
pub nested_arrays: Option<NestedArrayFilterByCapabilities>,
}
// ANCHOR_END: NestedFieldFilterByCapabilities

// ANCHOR: NestedArrayFilterByCapabilities
#[skip_serializing_none]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[schemars(title = "Nested Array Filter By Capabilities")]
pub struct NestedArrayFilterByCapabilities {
/// Does the connector support filtering over nested arrays using existential quantification.
/// This means the connector must support ExistsInCollection::NestedScalarCollection.
pub exists: Option<LeafCapability>,
/// Does the connector support filtering over nested arrays by checking if the array contains a value.
/// This must be supported for all types that can be contained in an array that implement an 'eq'
/// comparison operator.
pub contains: Option<LeafCapability>,
/// Does the connector support filtering over nested arrays by checking if the array is empty.
/// This must be supported no matter what type is contained in the array.
pub is_empty: Option<LeafCapability>,
}
// ANCHOR_END: NestedArrayFilterByCapabilities

// ANCHOR: AggregateCapabilities
#[skip_serializing_none]
Expand Down Expand Up @@ -817,13 +845,29 @@ pub enum Expression {
operator: ComparisonOperatorName,
value: ComparisonValue,
},
ArrayComparison {
column: ComparisonTarget,
comparison: ArrayComparison,
},
Exists {
in_collection: ExistsInCollection,
predicate: Option<Box<Expression>>,
},
}
// ANCHOR_END: Expression

// ANCHOR: ArrayComparison
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
#[schemars(title = "Array Comparison")]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ArrayComparison {
/// Check if the array contains the specified value
Contains { value: ComparisonValue },
/// Check is the array is empty
IsEmpty,
}
// ANCHOR_END: ArrayComparison

// ANCHOR: UnaryComparisonOperator
#[derive(
Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, JsonSchema,
Expand Down Expand Up @@ -931,6 +975,18 @@ pub enum ExistsInCollection {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
field_path: Vec<FieldName>,
},
/// Specifies a column that contains a nested array of scalars. The
/// array will be brought into scope of the nested expression where
/// each element becomes an object with one '__value' column that
/// contains the element value.
NestedScalarCollection {
column_name: FieldName,
#[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
arguments: BTreeMap<ArgumentName, Argument>,
/// Path to a nested collection via object columns
#[serde(skip_serializing_if = "Vec::is_empty", default)]
field_path: Vec<FieldName>,
},
}
// ANCHOR_END: ExistsInCollection

Expand Down
58 changes: 57 additions & 1 deletion ndc-models/tests/json_schema/capabilities_response.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@
"description": "Does the connector support filtering by values of nested fields",
"anyOf": [
{
"$ref": "#/definitions/LeafCapability"
"$ref": "#/definitions/NestedFieldFilterByCapabilities"
},
{
"type": "null"
Expand Down Expand Up @@ -220,6 +220,62 @@
}
}
},
"NestedFieldFilterByCapabilities": {
"title": "Nested Field Filter By Capabilities",
"type": "object",
"properties": {
"nested_arrays": {
"description": "Does the connector support filtering over nested arrays",
"anyOf": [
{
"$ref": "#/definitions/NestedArrayFilterByCapabilities"
},
{
"type": "null"
}
]
}
}
},
"NestedArrayFilterByCapabilities": {
"title": "Nested Array Filter By Capabilities",
"type": "object",
"properties": {
"exists": {
"description": "Does the connector support filtering over nested arrays using existential quantification. This means the connector must support ExistsInCollection::NestedScalarCollection.",
"anyOf": [
{
"$ref": "#/definitions/LeafCapability"
},
{
"type": "null"
}
]
},
"contains": {
"description": "Does the connector support filtering over nested arrays by checking if the array contains a value. This must be supported for all types that can be contained in an array that implement an 'eq' comparison operator.",
"anyOf": [
{
"$ref": "#/definitions/LeafCapability"
},
{
"type": "null"
}
]
},
"is_empty": {
"description": "Does the connector support filtering over nested arrays by checking if the array is empty. This must be supported no matter what type is contained in the array.",
"anyOf": [
{
"$ref": "#/definitions/LeafCapability"
},
{
"type": "null"
}
]
}
}
},
"ExistsCapabilities": {
"title": "Exists Capabilities",
"type": "object",
Expand Down
93 changes: 93 additions & 0 deletions ndc-models/tests/json_schema/mutation_request.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,28 @@
}
}
},
{
"type": "object",
"required": [
"column",
"comparison",
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"array_comparison"
]
},
"column": {
"$ref": "#/definitions/ComparisonTarget"
},
"comparison": {
"$ref": "#/definitions/ArrayComparison"
}
}
},
{
"type": "object",
"required": [
Expand Down Expand Up @@ -942,6 +964,45 @@
}
]
},
"ArrayComparison": {
"title": "Array Comparison",
"oneOf": [
{
"description": "Check if the array contains the specified value",
"type": "object",
"required": [
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"contains"
]
},
"value": {
"$ref": "#/definitions/ComparisonValue"
}
}
},
{
"description": "Check is the array is empty",
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"is_empty"
]
}
}
}
]
},
"ExistsInCollection": {
"title": "Exists In Collection",
"oneOf": [
Expand Down Expand Up @@ -1028,6 +1089,38 @@
}
}
}
},
{
"description": "Specifies a column that contains a nested array of scalars. The array will be brought into scope of the nested expression where each element becomes an object with one '__value' column that contains the element value.",
"type": "object",
"required": [
"column_name",
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"nested_scalar_collection"
]
},
"column_name": {
"type": "string"
},
"arguments": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Argument"
}
},
"field_path": {
"description": "Path to a nested collection via object columns",
"type": "array",
"items": {
"type": "string"
}
}
}
}
]
},
Expand Down
Loading
Loading