diff --git a/ndc-models/src/lib.rs b/ndc-models/src/lib.rs index 8a8ef6a6..a0c8dfa9 100644 --- a/ndc-models/src/lib.rs +++ b/ndc-models/src/lib.rs @@ -219,6 +219,9 @@ pub struct ObjectField { /// The type of this field #[serde(rename = "type")] pub r#type: Type, + /// The arguments available to the field - Matches implementation from CollectionInfo + #[serde(skip_serializing_if = "BTreeMap::is_empty", default)] + pub arguments: BTreeMap, } // ANCHOR_END: ObjectField @@ -478,6 +481,8 @@ pub enum Field { /// by specifying fields to fetch here. /// If omitted, the column data will be fetched in full. fields: Option, + #[serde(skip_serializing_if = "BTreeMap::is_empty", default)] + arguments: BTreeMap, }, Relationship { query: Box, diff --git a/ndc-models/tests/json_schema/mutation_request.jsonschema b/ndc-models/tests/json_schema/mutation_request.jsonschema index 7a0bd092..70422346 100644 --- a/ndc-models/tests/json_schema/mutation_request.jsonschema +++ b/ndc-models/tests/json_schema/mutation_request.jsonschema @@ -139,6 +139,12 @@ "type": "null" } ] + }, + "arguments": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Argument" + } } } }, @@ -175,6 +181,47 @@ } ] }, + "Argument": { + "title": "Argument", + "oneOf": [ + { + "description": "The argument is provided by reference to a variable", + "type": "object", + "required": [ + "name", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "variable" + ] + }, + "name": { + "type": "string" + } + } + }, + { + "description": "The argument is provided as a literal value", + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "literal" + ] + }, + "value": true + } + } + ] + }, "Query": { "title": "Query", "type": "object", diff --git a/ndc-models/tests/json_schema/query_request.jsonschema b/ndc-models/tests/json_schema/query_request.jsonschema index 33732b1c..62652b9f 100644 --- a/ndc-models/tests/json_schema/query_request.jsonschema +++ b/ndc-models/tests/json_schema/query_request.jsonschema @@ -209,6 +209,12 @@ "type": "null" } ] + }, + "arguments": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Argument" + } } } }, @@ -291,6 +297,47 @@ } ] }, + "Argument": { + "title": "Argument", + "oneOf": [ + { + "description": "The argument is provided by reference to a variable", + "type": "object", + "required": [ + "name", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "variable" + ] + }, + "name": { + "type": "string" + } + } + }, + { + "description": "The argument is provided as a literal value", + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "literal" + ] + }, + "value": true + } + } + ] + }, "RelationshipArgument": { "title": "Relationship Argument", "oneOf": [ @@ -852,47 +899,6 @@ } ] }, - "Argument": { - "title": "Argument", - "oneOf": [ - { - "description": "The argument is provided by reference to a variable", - "type": "object", - "required": [ - "name", - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "variable" - ] - }, - "name": { - "type": "string" - } - } - }, - { - "description": "The argument is provided as a literal value", - "type": "object", - "required": [ - "type", - "value" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "literal" - ] - }, - "value": true - } - } - ] - }, "Relationship": { "title": "Relationship", "type": "object", diff --git a/ndc-models/tests/json_schema/schema_response.jsonschema b/ndc-models/tests/json_schema/schema_response.jsonschema index 6b8adb73..8dfbe809 100644 --- a/ndc-models/tests/json_schema/schema_response.jsonschema +++ b/ndc-models/tests/json_schema/schema_response.jsonschema @@ -628,6 +628,37 @@ "$ref": "#/definitions/Type" } ] + }, + "arguments": { + "description": "The arguments available to the field - Matches implementation from CollectionInfo", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ArgumentInfo" + } + } + } + }, + "ArgumentInfo": { + "title": "Argument Info", + "type": "object", + "required": [ + "type" + ], + "properties": { + "description": { + "description": "Argument description", + "type": [ + "string", + "null" + ] + }, + "type": { + "description": "The name of the type of this argument", + "allOf": [ + { + "$ref": "#/definitions/Type" + } + ] } } }, @@ -680,30 +711,6 @@ } } }, - "ArgumentInfo": { - "title": "Argument Info", - "type": "object", - "required": [ - "type" - ], - "properties": { - "description": { - "description": "Argument description", - "type": [ - "string", - "null" - ] - }, - "type": { - "description": "The name of the type of this argument", - "allOf": [ - { - "$ref": "#/definitions/Type" - } - ] - } - } - }, "UniquenessConstraint": { "title": "Uniqueness Constraint", "type": "object", diff --git a/ndc-reference/bin/reference/main.rs b/ndc-reference/bin/reference/main.rs index f93a54f5..8d4be99b 100644 --- a/ndc-reference/bin/reference/main.rs +++ b/ndc-reference/bin/reference/main.rs @@ -15,7 +15,7 @@ use axum::{ }; use indexmap::IndexMap; -use models::NestedFieldCapabilities; +use models::{ArgumentInfo, NestedFieldCapabilities}; use ndc_models::{self as models, LeafCapability, RelationshipCapabilities}; use prometheus::{Encoder, IntCounter, IntGauge, Opts, Registry, TextEncoder}; use regex::Regex; @@ -238,6 +238,17 @@ async fn get_capabilities() -> Json { // ANCHOR: schema1 async fn get_schema() -> Json { // ANCHOR_END: schema1 + let array_arguments: BTreeMap = vec![( + "limit".to_string(), + ArgumentInfo { + description: None, + argument_type: models::Type::Nullable { + underlying_type: Box::new(models::Type::Named { name: "Int".into() }), + }, + }, + )] + .into_iter() + .collect(); // ANCHOR: schema_scalar_types let scalar_types = BTreeMap::from_iter([ ( @@ -302,6 +313,7 @@ async fn get_schema() -> Json { models::ObjectField { description: Some("The article's primary key".into()), r#type: models::Type::Named { name: "Int".into() }, + arguments: BTreeMap::new(), }, ), ( @@ -311,6 +323,7 @@ async fn get_schema() -> Json { r#type: models::Type::Named { name: "String".into(), }, + arguments: BTreeMap::new(), }, ), ( @@ -318,6 +331,7 @@ async fn get_schema() -> Json { models::ObjectField { description: Some("The article's author ID".into()), r#type: models::Type::Named { name: "Int".into() }, + arguments: BTreeMap::new(), }, ), ]), @@ -332,6 +346,7 @@ async fn get_schema() -> Json { models::ObjectField { description: Some("The author's primary key".into()), r#type: models::Type::Named { name: "Int".into() }, + arguments: BTreeMap::new(), }, ), ( @@ -341,6 +356,7 @@ async fn get_schema() -> Json { r#type: models::Type::Named { name: "String".into(), }, + arguments: BTreeMap::new(), }, ), ( @@ -350,6 +366,7 @@ async fn get_schema() -> Json { r#type: models::Type::Named { name: "String".into(), }, + arguments: BTreeMap::new(), }, ), ]), @@ -364,6 +381,7 @@ async fn get_schema() -> Json { models::ObjectField { description: Some("The institution's primary key".into()), r#type: models::Type::Named { name: "Int".into() }, + arguments: BTreeMap::new(), }, ), ( @@ -373,6 +391,7 @@ async fn get_schema() -> Json { r#type: models::Type::Named { name: "String".into(), }, + arguments: BTreeMap::new(), }, ), ( @@ -382,6 +401,7 @@ async fn get_schema() -> Json { r#type: models::Type::Named { name: "location".into(), }, + arguments: BTreeMap::new(), }, ), ( @@ -393,6 +413,7 @@ async fn get_schema() -> Json { name: "staff_member".into(), }), }, + arguments: array_arguments.clone(), }, ), ( @@ -404,6 +425,7 @@ async fn get_schema() -> Json { name: "String".into(), }), }, + arguments: array_arguments.clone(), }, ), ]), @@ -420,6 +442,7 @@ async fn get_schema() -> Json { r#type: models::Type::Named { name: "String".into(), }, + arguments: BTreeMap::new(), }, ), ( @@ -429,6 +452,7 @@ async fn get_schema() -> Json { r#type: models::Type::Named { name: "String".into(), }, + arguments: BTreeMap::new(), }, ), ( @@ -440,6 +464,7 @@ async fn get_schema() -> Json { name: "String".into(), }), }, + arguments: array_arguments.clone(), }, ), ]), @@ -456,6 +481,7 @@ async fn get_schema() -> Json { r#type: models::Type::Named { name: "String".into(), }, + arguments: BTreeMap::new(), }, ), ( @@ -465,6 +491,7 @@ async fn get_schema() -> Json { r#type: models::Type::Named { name: "String".into(), }, + arguments: BTreeMap::new(), }, ), ( @@ -476,6 +503,7 @@ async fn get_schema() -> Json { name: "String".into(), }), }, + arguments: array_arguments.clone(), }, ), ]), @@ -1258,7 +1286,7 @@ fn eval_column_field_path( column_name: &str, field_path: &Option>, ) -> Result { - let column_value = eval_column(row, column_name)?; + let column_value = eval_column(&BTreeMap::default(), row, column_name, &BTreeMap::default())?; match field_path { None => Ok(column_value), Some(path) => path @@ -1476,7 +1504,9 @@ fn eval_relationship_argument( Ok(value) } models::RelationshipArgument::Literal { value } => Ok(value.clone()), - models::RelationshipArgument::Column { name } => eval_column(row, name), + models::RelationshipArgument::Column { name } => { + eval_column(&BTreeMap::default(), row, name, &BTreeMap::default()) + } } } // ANCHOR_END: eval_relationship_argument @@ -1785,14 +1815,46 @@ fn eval_comparison_target( } // ANCHOR_END: eval_comparison_target // ANCHOR: eval_column -fn eval_column(row: &Row, column_name: &str) -> Result { - row.get(column_name).cloned().ok_or(( +fn eval_column( + variables: &BTreeMap, + row: &Row, + column_name: &str, + arguments: &BTreeMap, +) -> Result { + let column = row.get(column_name).cloned().ok_or(( StatusCode::BAD_REQUEST, Json(models::ErrorResponse { message: "invalid column name".into(), details: serde_json::Value::Null, }), - )) + ))?; + + if let Some(array) = column.as_array() { + let limit_argument = arguments.get("limit").ok_or(( + StatusCode::BAD_REQUEST, + Json(models::ErrorResponse { + message: format!("Expected argument 'limit' in column {column_name}"), + details: serde_json::Value::Null, + }), + ))?; + let limit = + serde_json::from_value::>(eval_argument(variables, limit_argument)?) + .map_err(|_| { + ( + StatusCode::BAD_REQUEST, + Json(models::ErrorResponse { + message: "limit must be null or an integer".into(), + details: serde_json::Value::Null, + }), + ) + })?; + + let result_array = array[0..limit.unwrap_or(array.len())].to_vec(); + + Ok(serde_json::Value::Array(result_array)) + } else { + Ok(column) + } } // ANCHOR_END: eval_column // ANCHOR: eval_comparison_value @@ -1878,10 +1940,17 @@ fn eval_nested_field( }), ) })?; + let result_array = array .into_iter() .map(|value| { - eval_nested_field(collection_relationships, variables, state, value, fields) + eval_nested_field( + collection_relationships, + variables, + state, + value.clone(), + fields, + ) }) .collect::>>()?; Ok(models::RowFieldValue( @@ -1908,8 +1977,12 @@ fn eval_field( item: &Row, ) -> Result { match field { - models::Field::Column { column, fields } => { - let col_val = eval_column(item, column.as_str())?; + models::Field::Column { + column, + fields, + arguments, + } => { + let col_val = eval_column(variables, item, column.as_str(), arguments)?; match fields { None => Ok(models::RowFieldValue(col_val)), Some(nested_field) => eval_nested_field( @@ -2225,8 +2298,18 @@ fn eval_column_mapping( tgt_row: &Row, ) -> Result { for (src_column, tgt_column) in &relationship.column_mapping { - let src_value = eval_column(src_row, src_column)?; - let tgt_value = eval_column(tgt_row, tgt_column)?; + let src_value = eval_column( + &BTreeMap::default(), + src_row, + src_column, + &BTreeMap::default(), + )?; + let tgt_value = eval_column( + &BTreeMap::default(), + tgt_row, + tgt_column, + &BTreeMap::default(), + )?; if src_value != tgt_value { return Ok(false); } diff --git a/ndc-reference/tests/query/nested_array_select/request.json b/ndc-reference/tests/query/nested_array_select/request.json index 4455655d..ac415cbb 100644 --- a/ndc-reference/tests/query/nested_array_select/request.json +++ b/ndc-reference/tests/query/nested_array_select/request.json @@ -11,6 +11,12 @@ "staff": { "type": "column", "column": "staff", + "arguments": { + "limit": { + "type": "literal", + "value": null + } + }, "fields": { "type": "array", "fields": { @@ -22,15 +28,27 @@ }, "fields_of_study": { "type": "column", - "column": "specialities" + "column": "specialities", + "arguments": { + "limit": { + "type": "literal", + "value": null + } + } } } } } }, "departments": { - "type":"column", - "column": "departments" + "type": "column", + "column": "departments", + "arguments": { + "limit": { + "type": "literal", + "value": null + } + } } } }, diff --git a/ndc-reference/tests/query/nested_array_select_with_limit/expected.json b/ndc-reference/tests/query/nested_array_select_with_limit/expected.json new file mode 100644 index 00000000..3bcf2631 --- /dev/null +++ b/ndc-reference/tests/query/nested_array_select_with_limit/expected.json @@ -0,0 +1,42 @@ +[ + { + "rows": [ + { + "id": 1, + "staff": [ + { + "last_name": "Landin", + "fields_of_study": [ + "Computer Science", + "Education" + ] + } + ], + "departments": [ + "Humanities and Social Sciences", + "Science and Engineering", + "Medicine and Dentistry" + ] + }, + { + "id": 2, + "staff": [ + { + "last_name": "Hughes", + "fields_of_study": [ + "Computer Science", + "Functional Programming" + ] + } + ], + "departments": [ + "Architecture and Civil Engineering", + "Computer Science and Engineering", + "Electrical Engineering", + "Physics", + "Industrial and Materials Science" + ] + } + ] + } +] \ No newline at end of file diff --git a/ndc-reference/tests/query/nested_array_select_with_limit/request.json b/ndc-reference/tests/query/nested_array_select_with_limit/request.json new file mode 100644 index 00000000..1683da90 --- /dev/null +++ b/ndc-reference/tests/query/nested_array_select_with_limit/request.json @@ -0,0 +1,56 @@ +{ + "$schema": "../../../../ndc-models/tests/json_schema/query_request.jsonschema", + "collection": "institutions", + "arguments": {}, + "query": { + "fields": { + "id": { + "type": "column", + "column": "id" + }, + "staff": { + "type": "column", + "column": "staff", + "arguments": { + "limit": { + "type": "literal", + "value": 1 + } + }, + "fields": { + "type": "array", + "fields": { + "type": "object", + "fields": { + "last_name": { + "type": "column", + "column": "last_name" + }, + "fields_of_study": { + "type": "column", + "column": "specialities", + "arguments": { + "limit": { + "type": "literal", + "value": 2 + } + } + } + } + } + } + }, + "departments": { + "type":"column", + "column": "departments", + "arguments": { + "limit": { + "type": "literal", + "value": null + } + } + } + } + }, + "collection_relationships": {} +} \ No newline at end of file diff --git a/ndc-reference/tests/query/nested_object_select/request.json b/ndc-reference/tests/query/nested_object_select/request.json index 847f1f8e..b9316462 100644 --- a/ndc-reference/tests/query/nested_object_select/request.json +++ b/ndc-reference/tests/query/nested_object_select/request.json @@ -20,7 +20,13 @@ }, "campuses": { "type": "column", - "column": "campuses" + "column": "campuses", + "arguments": { + "limit": { + "type": "literal", + "value": null + } + } } } } diff --git a/ndc-reference/tests/query/predicate_with_nested_field_eq/request.json b/ndc-reference/tests/query/predicate_with_nested_field_eq/request.json index d3160d01..9b3bede7 100644 --- a/ndc-reference/tests/query/predicate_with_nested_field_eq/request.json +++ b/ndc-reference/tests/query/predicate_with_nested_field_eq/request.json @@ -20,7 +20,13 @@ }, "campuses": { "type": "column", - "column": "campuses" + "column": "campuses", + "arguments": { + "limit": { + "type": "literal", + "value": null + } + } } } } diff --git a/ndc-reference/tests/schema/expected.json b/ndc-reference/tests/schema/expected.json index 1e2b2827..62530fc1 100644 --- a/ndc-reference/tests/schema/expected.json +++ b/ndc-reference/tests/schema/expected.json @@ -119,6 +119,17 @@ "type": "named", "name": "String" } + }, + "arguments": { + "limit": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Int" + } + } + } } }, "id": { @@ -150,6 +161,17 @@ "type": "named", "name": "staff_member" } + }, + "arguments": { + "limit": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Int" + } + } + } } } } @@ -165,6 +187,17 @@ "type": "named", "name": "String" } + }, + "arguments": { + "limit": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Int" + } + } + } } }, "city": { @@ -208,6 +241,17 @@ "type": "named", "name": "String" } + }, + "arguments": { + "limit": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Int" + } + } + } } } } diff --git a/ndc-test/src/test_cases/query/common.rs b/ndc-test/src/test_cases/query/common.rs index f5ea6219..745aa542 100644 --- a/ndc-test/src/test_cases/query/common.rs +++ b/ndc-test/src/test_cases/query/common.rs @@ -1,4 +1,5 @@ use indexmap::IndexMap; +use models::Type; use ndc_models as models; use rand::{rngs::SmallRng, seq::IteratorRandom, Rng}; @@ -6,14 +7,35 @@ pub fn select_all_columns(collection_type: &models::ObjectType) -> IndexMap>() } @@ -27,6 +49,11 @@ pub fn select_columns( collection_type .fields .iter() + .filter(|f| { + f.1.arguments + .iter() + .all(|(_, v)| matches!(v.argument_type, Type::Nullable { underlying_type: _ })) + }) .choose_multiple(rng, amount) .iter() .map(|f| { @@ -35,6 +62,19 @@ pub fn select_columns( models::Field::Column { column: f.0.clone(), fields: None, + arguments: f + .1 + .arguments + .keys() + .map(|k| { + ( + k.to_owned(), + models::Argument::Literal { + value: serde_json::Value::Null, + }, + ) + }) + .collect(), }, ) }) diff --git a/ndc-test/src/test_cases/query/validate/mod.rs b/ndc-test/src/test_cases/query/validate/mod.rs index 06c2b458..a4b7eb71 100644 --- a/ndc-test/src/test_cases/query/validate/mod.rs +++ b/ndc-test/src/test_cases/query/validate/mod.rs @@ -134,6 +134,7 @@ fn find_collection_type_by_name( models::ObjectField { description: None, r#type: function.result_type.clone(), + arguments: BTreeMap::default(), }, )]), }) @@ -199,7 +200,11 @@ pub fn validate_field( json_path: Vec, ) -> Result<()> { match field { - models::Field::Column { column, fields } => { + models::Field::Column { + column, + fields, + arguments: _, + } => { let object_field = object_type .fields .get(column) diff --git a/rfcs/0010-field-arguments.md b/rfcs/0010-field-arguments.md new file mode 100644 index 00000000..150adeff --- /dev/null +++ b/rfcs/0010-field-arguments.md @@ -0,0 +1,56 @@ +# Field Arguments + +The ability to provide arguments to fields broadens the use-cases that NDC can support. + +This can be seen as generalising `fields` to `functions` or, simply as adding context to fields. + +## Proposal + +This proposes an update to the NDC-Spec to allow arguments to fields. + +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. + +Some examples of why this might be useful: + +* 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) +* Vector Operations for LLMs etc. +* Advanced Geo Operations +* GraphQL federation: Forwarding Hasura GQL schemas over NDC will require this change if we want to expose root fields as commands instead of collections. + +The schema is extended with: + +```rust +pub struct ObjectField { + ... + pub arguments: BTreeMap, +} +``` + +and queries are extended with: + +```rust +pub enum Field { + Column { + ... + arguments: BTreeMap, + } + ... +} +``` + +This mirrors the existing implementation for collection arguments. + +## Implications + +NDC schema and query invocation: + +* When the schema indicates that a field has arguments then they must be provided in a query. +* Optional arguments must be explicitly supplied with `Argument::Literal { Value::Null }`. +* If all arguments are nullable then the field may be referenced without arguments or parenteses + for backwards compatibility purposes, however arguments should be applied going forward. + +Engine interactions: + +* Query field arguments will need to be translated to NDC field arguments +* NDC schema responses will need to be translated into graphql schemas +* Variables need to be bound to field arguments if they are not supplied as scalars diff --git a/specification/src/specification/queries/arguments.md b/specification/src/specification/queries/arguments.md index 3e529ed7..e70a62ea 100644 --- a/specification/src/specification/queries/arguments.md +++ b/specification/src/specification/queries/arguments.md @@ -1,6 +1,8 @@ # Arguments -_Collection arguments_ parameterize an entire collection, and must be provided in queries wherever the collection is referenced, either directly, or via relationships, +_Collection arguments_ parameterize an entire collection, and must be provided in queries wherever the collection is referenced, either directly, or via relationships. + +_Field_ arguments parameterize a single field, and must be provided wherever that field is referenced. ## Collection Arguments @@ -58,4 +60,9 @@ For example, when ordering by an aggregate of rows in a related collection, and ```json {{#include ../../../../ndc-reference/tests/query/table_argument_order_by/request.json:1 }} {{#include ../../../../ndc-reference/tests/query/table_argument_order_by/request.json:3: }} -``` \ No newline at end of file +``` + +## Field Arguments + +Field arguments can be provided to any field requested (in addition to those described for top-level collections). +These are specified in the [schema response](../schema/object-types.md) and their use is described in [field selection](./field-selection.md). Their specification and usage matches that of collection arguments above. \ No newline at end of file diff --git a/specification/src/specification/queries/field-selection.md b/specification/src/specification/queries/field-selection.md index 42713818..847f08a3 100644 --- a/specification/src/specification/queries/field-selection.md +++ b/specification/src/specification/queries/field-selection.md @@ -7,6 +7,14 @@ A [`Query`](../../reference/types.md#query) can specify which fields to fetch. T The requested fields are specified as a collection of [`Field`](../../reference/types.md#field) structures in the `field` property on the [`Query`](../../reference/types.md#query). +## Field Arguments + +Arguments can be supplied to fields via the `arguments` key. These match the format described in [the arguments documentation](../arguments.md). + +The [schema response](../schema/object-types.md) will specify which fields take arguments via its respective `arguments` key. + +If a field has any arguments defined, then the `arguments` field must be provided wherever that field is referenced. All fields are required, including nullable fields. + ## Nested Fields Queries can specify nested field selections for columns which have structured types (that is, not simply a scalar type or a nullable scalar type). @@ -52,6 +60,13 @@ Here is an example of a query which selects some columns from a nested array ins Notice that the `staff` column is fetched using a `fields` property of type `array`. For each staff member in each institution row, we apply the selection function denoted by its `fields` property (of type `object`). Specifically, the `last_name` and `specialities` properties are selected for each staff member. +### Example with Field Arguments + +Here is an example of a query which selects some columns from a nested array inside the rows of the `institutions` collection of the reference data connector and uses the `limit` field argument to limit the number of items returned: + +```json +{{#include ../../../../ndc-reference/tests/query/nested_array_select_with_limit/request.json}} +``` ## Requirements diff --git a/specification/src/specification/schema/object-types.md b/specification/src/specification/schema/object-types.md index a30e2f79..18fa267c 100644 --- a/specification/src/specification/schema/object-types.md +++ b/specification/src/specification/schema/object-types.md @@ -4,7 +4,7 @@ The schema should define any named _object types_ which will be used as the type An object type consists of a name and a collection of named fields. Each field is defined by its [type](../types.md), and any [arguments](../queries/arguments.md). -_Note_: field arguments are only used in a query context, and ignored when an object type is used in other contexts (such as the input type of a _procedure_). +_Note_: field arguments are only used in a query context. Objects with field arguments cannot be used as input types, and fields with arguments cannot be used to define [column mappings](../queries/relationships.md#column-mappings), or in [nested field references](../queries/filtering.md#referencing-nested-fields-within-columns). To define an object type, add an [`ObjectType` ](../../reference/types.md#objecttype) to the `object_types` field of the schema response. diff --git a/specification/src/tutorial/queries/execute/field-selection.md b/specification/src/tutorial/queries/execute/field-selection.md index 1e7d54a9..900ac911 100644 --- a/specification/src/tutorial/queries/execute/field-selection.md +++ b/specification/src/tutorial/queries/execute/field-selection.md @@ -10,7 +10,7 @@ This is done by mapping over the computed rows, and using the `eval_field` funct The `eval_field` function works by pattern matching on the field type: -- A `column` is selected using the `eval_column` function, +- A `column` is selected using the `eval_column` function (or `eval_nested_field` if there are nested fields to fetch) - 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`: ```rust,no_run,noplayground diff --git a/specification/src/tutorial/schema.md b/specification/src/tutorial/schema.md index f88875ee..9fc1e3e5 100644 --- a/specification/src/tutorial/schema.md +++ b/specification/src/tutorial/schema.md @@ -40,6 +40,8 @@ For each collection, we define an object type for its rows. In addition, we defi ### Institution +_Note_: the fields with array types have field-level arguments (`array_arguments`) in order to support nested array operations. + ```rust,no_run,noplayground {{#include ../../../ndc-reference/bin/reference/main.rs:schema_object_type_institution}} ```