From dc8ef3ea3ee32d16daf6cd2e3b165acc616fa4e1 Mon Sep 17 00:00:00 2001 From: Brandon Martin Date: Fri, 25 Oct 2024 08:42:54 -0600 Subject: [PATCH] Make mutation pre/post checks optional (#635) ### What - [ENG-1205](https://linear.app/hasura/issue/ENG-1205/make-mutation-precheck-and-postcheck-arguments-optional) - 60fc380dd5fde6cf8bf4d85cdb81f4ae36abc46a - Change mutation pre/post checks to be optional --------- Co-authored-by: Daniel Harvey --- changelog.md | 2 + .../ndc-postgres/src/schema/mutation/v2.rs | 95 ++- .../src/translation/mutation/v2/common.rs | 5 + .../src/translation/mutation/v2/delete.rs | 63 +- .../src/translation/mutation/v2/insert.rs | 12 +- .../src/translation/mutation/v2/translate.rs | 6 +- .../src/translation/mutation/v2/update.rs | 43 +- ...schema_tests__schema_test__get_schema.snap | 560 +++++++++++++----- 8 files changed, 512 insertions(+), 274 deletions(-) diff --git a/changelog.md b/changelog.md index 9f7769fd6..2950661be 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,8 @@ ### Changed +- Change mutation pre/post checks to be optional + ### Fixed ## [v1.1.2] - 2024-10-07 diff --git a/crates/connectors/ndc-postgres/src/schema/mutation/v2.rs b/crates/connectors/ndc-postgres/src/schema/mutation/v2.rs index ce6dcb647..c04f8365f 100644 --- a/crates/connectors/ndc-postgres/src/schema/mutation/v2.rs +++ b/crates/connectors/ndc-postgres/src/schema/mutation/v2.rs @@ -16,50 +16,42 @@ pub fn delete_to_procedure( object_types: &mut BTreeMap, scalar_types: &mut BTreeMap, ) -> models::ProcedureInfo { - match delete { - mutation::v2::delete::DeleteMutation::DeleteByKey { - by_columns, - pre_check, - description, - collection_name, - columns_prefix, - table_name: _, - schema_name: _, - } => { - let mut arguments = BTreeMap::new(); - - for column in by_columns { - arguments.insert( - format!("{}{}", columns_prefix, column.name).into(), - models::ArgumentInfo { - argument_type: column_to_type(column), - description: column.description.clone(), - }, - ); - } - - arguments.insert( - pre_check.argument_name.clone(), - models::ArgumentInfo { - argument_type: models::Type::Predicate { - object_type_name: collection_name.as_str().into(), - }, - description: Some(pre_check.description.clone()), - }, - ); + let mutation::v2::delete::DeleteMutation::DeleteByKey(delete_by_key) = delete; - make_procedure_type( - name.clone(), - Some(description.to_string()), - arguments, - models::Type::Named { - name: collection_name.as_str().into(), - }, - object_types, - scalar_types, - ) - } + let mut arguments = BTreeMap::new(); + + for column in &delete_by_key.by_columns { + arguments.insert( + format!("{}{}", delete_by_key.columns_prefix, column.name).into(), + models::ArgumentInfo { + argument_type: column_to_type(column), + description: column.description.clone(), + }, + ); } + + arguments.insert( + delete_by_key.pre_check.argument_name.clone(), + models::ArgumentInfo { + argument_type: models::Type::Nullable { + underlying_type: Box::new(models::Type::Predicate { + object_type_name: delete_by_key.collection_name.as_str().into(), + }), + }, + description: Some(delete_by_key.pre_check.description.clone()), + }, + ); + + make_procedure_type( + name.clone(), + Some(delete_by_key.description.to_string()), + arguments, + models::Type::Named { + name: delete_by_key.collection_name.as_str().into(), + }, + object_types, + scalar_types, + ) } /// Given an v2 `UpdateMutation`, turn it into a `ProcedureInfo` to be output in the schema. @@ -88,8 +80,10 @@ pub fn update_to_procedure( arguments.insert( update_by_key.pre_check.argument_name.clone(), models::ArgumentInfo { - argument_type: models::Type::Predicate { - object_type_name: update_by_key.collection_name.as_str().into(), + argument_type: models::Type::Nullable { + underlying_type: Box::new(models::Type::Predicate { + object_type_name: update_by_key.collection_name.as_str().into(), + }), }, description: Some(update_by_key.pre_check.description.clone()), }, @@ -99,8 +93,10 @@ pub fn update_to_procedure( arguments.insert( update_by_key.post_check.argument_name.clone(), models::ArgumentInfo { - argument_type: models::Type::Predicate { - object_type_name: update_by_key.collection_name.as_str().into(), + argument_type: models::Type::Nullable { + underlying_type: Box::new(models::Type::Predicate { + object_type_name: update_by_key.collection_name.as_str().into(), + }), }, description: Some(update_by_key.post_check.description.clone()), }, @@ -209,11 +205,14 @@ pub fn insert_to_procedure( description: None, }, ); + arguments.insert( insert.post_check.argument_name.clone(), models::ArgumentInfo { - argument_type: models::Type::Predicate { - object_type_name: insert.collection_name.as_str().into(), + argument_type: models::Type::Nullable { + underlying_type: Box::new(models::Type::Predicate { + object_type_name: insert.collection_name.as_str().into(), + }), }, description: Some(insert.post_check.description.clone()), }, diff --git a/crates/query-engine/translation/src/translation/mutation/v2/common.rs b/crates/query-engine/translation/src/translation/mutation/v2/common.rs index cbe35ff84..f8edb444b 100644 --- a/crates/query-engine/translation/src/translation/mutation/v2/common.rs +++ b/crates/query-engine/translation/src/translation/mutation/v2/common.rs @@ -102,3 +102,8 @@ pub struct CheckArgument { pub argument_name: models::ArgumentName, pub description: String, } + +// Default check argument/constraint +pub fn default_constraint() -> serde_json::Value { + serde_json::json!({"type": "and", "expressions": []}) +} diff --git a/crates/query-engine/translation/src/translation/mutation/v2/delete.rs b/crates/query-engine/translation/src/translation/mutation/v2/delete.rs index bc3eb3cfa..c5b38fec4 100644 --- a/crates/query-engine/translation/src/translation/mutation/v2/delete.rs +++ b/crates/query-engine/translation/src/translation/mutation/v2/delete.rs @@ -11,22 +11,26 @@ use query_engine_metadata::metadata::database; use query_engine_sql::sql; use std::collections::BTreeMap; -use super::common::{self, CheckArgument}; +use super::common::{self, default_constraint, CheckArgument}; /// A representation of an auto-generated delete mutation. /// /// This can get us `DELETE FROM WHERE column = , ...`. #[derive(Debug, Clone)] pub enum DeleteMutation { - DeleteByKey { - description: String, - collection_name: models::CollectionName, - schema_name: sql::ast::SchemaName, - table_name: sql::ast::TableName, - by_columns: NonEmpty, - columns_prefix: String, - pre_check: CheckArgument, - }, + DeleteByKey(DeleteByKey), +} + +/// A representation of an auto-generated delete mutation by a unique key. +#[derive(Debug, Clone)] +pub struct DeleteByKey { + pub description: String, + pub collection_name: models::CollectionName, + pub schema_name: sql::ast::SchemaName, + pub table_name: sql::ast::TableName, + pub by_columns: NonEmpty, + pub columns_prefix: String, + pub pre_check: CheckArgument, } /// generate a delete for each simple unique constraint on this table @@ -59,7 +63,7 @@ pub fn generate_delete_by_unique( common::description_keys(&keys.0.values().collect()) ); - let delete_mutation = DeleteMutation::DeleteByKey { + let delete_mutation = DeleteMutation::DeleteByKey(DeleteByKey { schema_name: sql::ast::SchemaName(table_info.schema_name.clone()), table_name: sql::ast::TableName(table_info.table_name.clone()), collection_name: collection_name.clone(), @@ -72,7 +76,7 @@ pub fn generate_delete_by_unique( ), }, description, - }; + }); Some((name, delete_mutation)) }) @@ -83,29 +87,21 @@ pub fn generate_delete_by_unique( pub fn translate( env: &crate::translation::helpers::Env, state: &mut crate::translation::helpers::State, - delete: &DeleteMutation, + mutation: &DeleteMutation, arguments: &BTreeMap, ) -> Result<(sql::ast::Delete, sql::ast::ColumnAlias), Error> { - match delete { - DeleteMutation::DeleteByKey { - collection_name, - schema_name, - table_name, - by_columns, - columns_prefix, - pre_check, - description: _, - } => { + match mutation { + DeleteMutation::DeleteByKey(mutation) => { // The root table we are going to be deleting from. let table = sql::ast::TableReference::DBTable { - schema: schema_name.clone(), - table: table_name.clone(), + schema: mutation.schema_name.clone(), + table: mutation.table_name.clone(), }; - let table_alias = state.make_table_alias(table_name.0.clone()); + let table_alias = state.make_table_alias(mutation.table_name.0.clone()); let table_name_and_reference = TableSourceAndReference { - source: helpers::TableSource::Collection(collection_name.clone()), + source: helpers::TableSource::Collection(mutation.collection_name.clone()), reference: sql::ast::TableReference::AliasedTable(table_alias.clone()), }; @@ -115,10 +111,12 @@ pub fn translate( }; // Build the `UNIQUE_KEY = , ...` boolean expression. - let unique_expressions = by_columns + let unique_expressions = mutation + .by_columns .iter() .map(|by_column| { - let argument_name = format!("{}{}", columns_prefix, by_column.name).into(); + let argument_name = + format!("{}{}", mutation.columns_prefix, by_column.name).into(); let unique_key = arguments .get(&argument_name) .ok_or(Error::ArgumentNotFound(argument_name))?; @@ -141,15 +139,16 @@ pub fn translate( .collect::, Error>>()?; // Build the `pre_check` argument boolean expression. + let default_constraint = default_constraint(); let predicate_json = arguments - .get(&pre_check.argument_name) - .ok_or(Error::ArgumentNotFound(pre_check.argument_name.clone()))?; + .get(&mutation.pre_check.argument_name) + .unwrap_or(&default_constraint); let predicate: models::Expression = serde_json::from_value(predicate_json.clone()) .map_err(|_| { Error::UnexpectedStructure(format!( "Argument '{}' should have an ndc-spec Expression structure", - pre_check.argument_name.clone() + mutation.pre_check.argument_name.clone() )) })?; diff --git a/crates/query-engine/translation/src/translation/mutation/v2/insert.rs b/crates/query-engine/translation/src/translation/mutation/v2/insert.rs index e1c8684c6..9a1196f0e 100644 --- a/crates/query-engine/translation/src/translation/mutation/v2/insert.rs +++ b/crates/query-engine/translation/src/translation/mutation/v2/insert.rs @@ -11,7 +11,7 @@ use query_engine_metadata::metadata::database; use query_engine_sql::sql; use std::collections::{BTreeMap, BTreeSet}; -use super::common::CheckArgument; +use super::common::{default_constraint, CheckArgument}; /// A representation of an auto-generated insert mutation. /// @@ -224,12 +224,10 @@ pub fn translate( }; // Build the `post_check` argument boolean expression. - let predicate_json = - arguments - .get(&mutation.post_check.argument_name) - .ok_or(Error::ArgumentNotFound( - mutation.post_check.argument_name.clone(), - ))?; + let default_constraint = default_constraint(); + let predicate_json = arguments + .get(&mutation.post_check.argument_name) + .unwrap_or(&default_constraint); let predicate: models::Expression = serde_json::from_value(predicate_json.clone()).map_err(|_| { diff --git a/crates/query-engine/translation/src/translation/mutation/v2/translate.rs b/crates/query-engine/translation/src/translation/mutation/v2/translate.rs index 1ff289c60..7c4fecbc0 100644 --- a/crates/query-engine/translation/src/translation/mutation/v2/translate.rs +++ b/crates/query-engine/translation/src/translation/mutation/v2/translate.rs @@ -7,6 +7,8 @@ use crate::translation::helpers::{Env, State}; use ndc_models as models; use query_engine_sql::sql; +use super::delete::DeleteByKey; + /// Translate a built-in delete mutation into an ExecutionPlan (SQL) to be run against the database. /// This part is specialized for this mutations versions. /// To be invoke from the main mutations translate function. @@ -28,10 +30,10 @@ pub fn translate( Ok(match mutation { super::generate::Mutation::DeleteMutation(delete) => { let return_collection = match delete { - super::delete::DeleteMutation::DeleteByKey { + super::delete::DeleteMutation::DeleteByKey(DeleteByKey { ref collection_name, .. - } => collection_name.clone(), + }) => collection_name.clone(), }; let (delete_cte, check_constraint_alias) = diff --git a/crates/query-engine/translation/src/translation/mutation/v2/update.rs b/crates/query-engine/translation/src/translation/mutation/v2/update.rs index 8384ddec5..c25cf6e3e 100644 --- a/crates/query-engine/translation/src/translation/mutation/v2/update.rs +++ b/crates/query-engine/translation/src/translation/mutation/v2/update.rs @@ -12,7 +12,7 @@ use query_engine_metadata::metadata::database; use query_engine_sql::sql; use std::collections::BTreeMap; -use super::common; +use super::common::{self, default_constraint, CheckArgument}; /// A representation of an auto-generated update mutation. /// @@ -32,18 +32,11 @@ pub struct UpdateByKey { pub by_columns: NonEmpty, pub columns_prefix: String, pub update_columns_argument_name: models::ArgumentName, - pub pre_check: Constraint, - pub post_check: Constraint, + pub pre_check: CheckArgument, + pub post_check: CheckArgument, pub table_columns: BTreeMap, } -/// The name and description of the constraint input argument. -#[derive(Debug, Clone)] -pub struct Constraint { - pub argument_name: models::ArgumentName, - pub description: String, -} - /// Generate a update for each simple unique constraint on this table. pub fn generate_update_by_unique( collection_name: &models::CollectionName, @@ -81,17 +74,17 @@ pub fn generate_update_by_unique( by_columns: key_columns, columns_prefix: "key_".to_string(), update_columns_argument_name: "update_columns".into(), - pre_check: Constraint { + pre_check: CheckArgument { argument_name: "pre_check".into(), description: format!( - "Update permission pre-condition predicate over the '{collection_name}' collection" - ), + "Update permission pre-condition predicate over the '{collection_name}' collection" + ), }, - post_check: Constraint { + post_check: CheckArgument { argument_name: "post_check".into(), description: format!( - "Update permission post-condition predicate over the '{collection_name}' collection" - ), + "Update permission post-condition predicate over the '{collection_name}' collection" + ), }, table_columns: table_info.columns.clone(), @@ -162,13 +155,13 @@ pub fn translate( current_table: table_name_and_reference, }; + // Set default constrainst + let default_constraint = default_constraint(); + // Build the `pre_constraint` argument boolean expression. - let pre_predicate_json = - arguments - .get(&mutation.pre_check.argument_name) - .ok_or(Error::ArgumentNotFound( - mutation.pre_check.argument_name.clone(), - ))?; + let pre_predicate_json = arguments + .get(&mutation.pre_check.argument_name) + .unwrap_or(&default_constraint); let pre_predicate: models::Expression = serde_json::from_value(pre_predicate_json.clone()).map_err(|_| { @@ -182,9 +175,9 @@ pub fn translate( filtering::translate(env, state, &root_and_current_tables, &pre_predicate)?; // Build the `post_constraint` argument boolean expression. - let post_predicate_json = arguments.get(&mutation.post_check.argument_name).ok_or( - Error::ArgumentNotFound(mutation.post_check.argument_name.clone()), - )?; + let post_predicate_json = arguments + .get(&mutation.post_check.argument_name) + .unwrap_or(&default_constraint); let post_predicate: models::Expression = serde_json::from_value(post_predicate_json.clone()).map_err(|_| { diff --git a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__schema_tests__schema_test__get_schema.snap b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__schema_tests__schema_test__get_schema.snap index dfdcf0787..a4193028e 100644 --- a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__schema_tests__schema_test__get_schema.snap +++ b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__schema_tests__schema_test__get_schema.snap @@ -9891,8 +9891,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'Album' collection", "type": { - "type": "predicate", - "object_type_name": "Album" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Album" + } } } }, @@ -9915,8 +9918,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'Artist' collection", "type": { - "type": "predicate", - "object_type_name": "Artist" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Artist" + } } } }, @@ -9939,8 +9945,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'Customer' collection", "type": { - "type": "predicate", - "object_type_name": "Customer" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Customer" + } } } }, @@ -9962,8 +9971,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'Employee' collection", "type": { - "type": "predicate", - "object_type_name": "Employee" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Employee" + } } } }, @@ -9985,8 +9997,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'Genre' collection", "type": { - "type": "predicate", - "object_type_name": "Genre" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Genre" + } } } }, @@ -10008,8 +10023,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'InvoiceLine' collection", "type": { - "type": "predicate", - "object_type_name": "InvoiceLine" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "InvoiceLine" + } } } }, @@ -10031,8 +10049,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'Invoice' collection", "type": { - "type": "predicate", - "object_type_name": "Invoice" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Invoice" + } } } }, @@ -10054,8 +10075,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'MediaType' collection", "type": { - "type": "predicate", - "object_type_name": "MediaType" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "MediaType" + } } } }, @@ -10083,8 +10107,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'PlaylistTrack' collection", "type": { - "type": "predicate", - "object_type_name": "PlaylistTrack" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "PlaylistTrack" + } } } }, @@ -10106,8 +10133,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'Playlist' collection", "type": { - "type": "predicate", - "object_type_name": "Playlist" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Playlist" + } } } }, @@ -10129,8 +10159,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'Track' collection", "type": { - "type": "predicate", - "object_type_name": "Track" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Track" + } } } }, @@ -10152,8 +10185,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'custom_defaults' collection", "type": { - "type": "predicate", - "object_type_name": "custom_defaults" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "custom_defaults" + } } } }, @@ -10175,8 +10211,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'custom_dog' collection", "type": { - "type": "predicate", - "object_type_name": "custom_dog" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "custom_dog" + } } } }, @@ -10198,8 +10237,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'institution_institution' collection", "type": { - "type": "predicate", - "object_type_name": "institution_institution" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "institution_institution" + } } } }, @@ -10221,8 +10263,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'spatial_ref_sys' collection", "type": { - "type": "predicate", - "object_type_name": "spatial_ref_sys" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "spatial_ref_sys" + } } } }, @@ -10256,8 +10301,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'topology_layer' collection", "type": { - "type": "predicate", - "object_type_name": "topology_layer" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_layer" + } } } }, @@ -10285,8 +10333,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'topology_layer' collection", "type": { - "type": "predicate", - "object_type_name": "topology_layer" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_layer" + } } } }, @@ -10308,8 +10359,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'topology_topology' collection", "type": { - "type": "predicate", - "object_type_name": "topology_topology" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_topology" + } } } }, @@ -10331,8 +10385,11 @@ expression: result "pre_check": { "description": "Delete permission predicate over the 'topology_topology' collection", "type": { - "type": "predicate", - "object_type_name": "topology_topology" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_topology" + } } } }, @@ -10357,8 +10414,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'Album' collection", "type": { - "type": "predicate", - "object_type_name": "Album" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Album" + } } } }, @@ -10383,8 +10443,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'Artist' collection", "type": { - "type": "predicate", - "object_type_name": "Artist" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Artist" + } } } }, @@ -10409,8 +10472,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'Customer' collection", "type": { - "type": "predicate", - "object_type_name": "Customer" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Customer" + } } } }, @@ -10435,8 +10501,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'Employee' collection", "type": { - "type": "predicate", - "object_type_name": "Employee" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Employee" + } } } }, @@ -10461,8 +10530,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'Genre' collection", "type": { - "type": "predicate", - "object_type_name": "Genre" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Genre" + } } } }, @@ -10487,8 +10559,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'Invoice' collection", "type": { - "type": "predicate", - "object_type_name": "Invoice" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Invoice" + } } } }, @@ -10513,8 +10588,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'InvoiceLine' collection", "type": { - "type": "predicate", - "object_type_name": "InvoiceLine" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "InvoiceLine" + } } } }, @@ -10539,8 +10617,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'MediaType' collection", "type": { - "type": "predicate", - "object_type_name": "MediaType" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "MediaType" + } } } }, @@ -10565,8 +10646,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'Playlist' collection", "type": { - "type": "predicate", - "object_type_name": "Playlist" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Playlist" + } } } }, @@ -10591,8 +10675,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'PlaylistTrack' collection", "type": { - "type": "predicate", - "object_type_name": "PlaylistTrack" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "PlaylistTrack" + } } } }, @@ -10617,8 +10704,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'Track' collection", "type": { - "type": "predicate", - "object_type_name": "Track" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Track" + } } } }, @@ -10643,8 +10733,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'custom_defaults' collection", "type": { - "type": "predicate", - "object_type_name": "custom_defaults" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "custom_defaults" + } } } }, @@ -10669,8 +10762,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'custom_dog' collection", "type": { - "type": "predicate", - "object_type_name": "custom_dog" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "custom_dog" + } } } }, @@ -10695,8 +10791,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'custom_test_cidr' collection", "type": { - "type": "predicate", - "object_type_name": "custom_test_cidr" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "custom_test_cidr" + } } } }, @@ -10721,8 +10820,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'deck_of_cards' collection", "type": { - "type": "predicate", - "object_type_name": "deck_of_cards" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "deck_of_cards" + } } } }, @@ -10747,8 +10849,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'discoverable_types_root_occurrence' collection", "type": { - "type": "predicate", - "object_type_name": "discoverable_types_root_occurrence" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "discoverable_types_root_occurrence" + } } } }, @@ -10773,8 +10878,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'even_numbers' collection", "type": { - "type": "predicate", - "object_type_name": "even_numbers" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "even_numbers" + } } } }, @@ -10799,8 +10907,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'group_leader' collection", "type": { - "type": "predicate", - "object_type_name": "group_leader" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "group_leader" + } } } }, @@ -10825,8 +10936,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'institution_institution' collection", "type": { - "type": "predicate", - "object_type_name": "institution_institution" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "institution_institution" + } } } }, @@ -10851,8 +10965,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'phone_numbers' collection", "type": { - "type": "predicate", - "object_type_name": "phone_numbers" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "phone_numbers" + } } } }, @@ -10877,8 +10994,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'spatial_ref_sys' collection", "type": { - "type": "predicate", - "object_type_name": "spatial_ref_sys" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "spatial_ref_sys" + } } } }, @@ -10903,8 +11023,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'topology_layer' collection", "type": { - "type": "predicate", - "object_type_name": "topology_layer" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_layer" + } } } }, @@ -10929,8 +11052,11 @@ expression: result "post_check": { "description": "Insert permission predicate over the 'topology_topology' collection", "type": { - "type": "predicate", - "object_type_name": "topology_topology" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_topology" + } } } }, @@ -10953,15 +11079,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'Album' collection", "type": { - "type": "predicate", - "object_type_name": "Album" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Album" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'Album' collection", "type": { - "type": "predicate", - "object_type_name": "Album" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Album" + } } }, "update_columns": { @@ -10990,15 +11122,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'Artist' collection", "type": { - "type": "predicate", - "object_type_name": "Artist" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Artist" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'Artist' collection", "type": { - "type": "predicate", - "object_type_name": "Artist" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Artist" + } } }, "update_columns": { @@ -11027,15 +11165,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'Customer' collection", "type": { - "type": "predicate", - "object_type_name": "Customer" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Customer" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'Customer' collection", "type": { - "type": "predicate", - "object_type_name": "Customer" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Customer" + } } }, "update_columns": { @@ -11063,15 +11207,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'Employee' collection", "type": { - "type": "predicate", - "object_type_name": "Employee" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Employee" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'Employee' collection", "type": { - "type": "predicate", - "object_type_name": "Employee" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Employee" + } } }, "update_columns": { @@ -11099,15 +11249,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'Genre' collection", "type": { - "type": "predicate", - "object_type_name": "Genre" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Genre" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'Genre' collection", "type": { - "type": "predicate", - "object_type_name": "Genre" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Genre" + } } }, "update_columns": { @@ -11135,15 +11291,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'InvoiceLine' collection", "type": { - "type": "predicate", - "object_type_name": "InvoiceLine" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "InvoiceLine" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'InvoiceLine' collection", "type": { - "type": "predicate", - "object_type_name": "InvoiceLine" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "InvoiceLine" + } } }, "update_columns": { @@ -11171,15 +11333,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'Invoice' collection", "type": { - "type": "predicate", - "object_type_name": "Invoice" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Invoice" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'Invoice' collection", "type": { - "type": "predicate", - "object_type_name": "Invoice" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Invoice" + } } }, "update_columns": { @@ -11207,15 +11375,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'MediaType' collection", "type": { - "type": "predicate", - "object_type_name": "MediaType" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "MediaType" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'MediaType' collection", "type": { - "type": "predicate", - "object_type_name": "MediaType" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "MediaType" + } } }, "update_columns": { @@ -11249,15 +11423,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'PlaylistTrack' collection", "type": { - "type": "predicate", - "object_type_name": "PlaylistTrack" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "PlaylistTrack" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'PlaylistTrack' collection", "type": { - "type": "predicate", - "object_type_name": "PlaylistTrack" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "PlaylistTrack" + } } }, "update_columns": { @@ -11285,15 +11465,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'Playlist' collection", "type": { - "type": "predicate", - "object_type_name": "Playlist" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Playlist" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'Playlist' collection", "type": { - "type": "predicate", - "object_type_name": "Playlist" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Playlist" + } } }, "update_columns": { @@ -11321,15 +11507,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'Track' collection", "type": { - "type": "predicate", - "object_type_name": "Track" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Track" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'Track' collection", "type": { - "type": "predicate", - "object_type_name": "Track" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "Track" + } } }, "update_columns": { @@ -11357,15 +11549,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'custom_defaults' collection", "type": { - "type": "predicate", - "object_type_name": "custom_defaults" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "custom_defaults" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'custom_defaults' collection", "type": { - "type": "predicate", - "object_type_name": "custom_defaults" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "custom_defaults" + } } }, "update_columns": { @@ -11393,15 +11591,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'custom_dog' collection", "type": { - "type": "predicate", - "object_type_name": "custom_dog" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "custom_dog" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'custom_dog' collection", "type": { - "type": "predicate", - "object_type_name": "custom_dog" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "custom_dog" + } } }, "update_columns": { @@ -11429,15 +11633,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'institution_institution' collection", "type": { - "type": "predicate", - "object_type_name": "institution_institution" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "institution_institution" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'institution_institution' collection", "type": { - "type": "predicate", - "object_type_name": "institution_institution" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "institution_institution" + } } }, "update_columns": { @@ -11465,15 +11675,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'spatial_ref_sys' collection", "type": { - "type": "predicate", - "object_type_name": "spatial_ref_sys" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "spatial_ref_sys" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'spatial_ref_sys' collection", "type": { - "type": "predicate", - "object_type_name": "spatial_ref_sys" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "spatial_ref_sys" + } } }, "update_columns": { @@ -11513,15 +11729,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'topology_layer' collection", "type": { - "type": "predicate", - "object_type_name": "topology_layer" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_layer" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'topology_layer' collection", "type": { - "type": "predicate", - "object_type_name": "topology_layer" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_layer" + } } }, "update_columns": { @@ -11555,15 +11777,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'topology_layer' collection", "type": { - "type": "predicate", - "object_type_name": "topology_layer" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_layer" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'topology_layer' collection", "type": { - "type": "predicate", - "object_type_name": "topology_layer" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_layer" + } } }, "update_columns": { @@ -11591,15 +11819,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'topology_topology' collection", "type": { - "type": "predicate", - "object_type_name": "topology_topology" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_topology" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'topology_topology' collection", "type": { - "type": "predicate", - "object_type_name": "topology_topology" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_topology" + } } }, "update_columns": { @@ -11627,15 +11861,21 @@ expression: result "post_check": { "description": "Update permission post-condition predicate over the 'topology_topology' collection", "type": { - "type": "predicate", - "object_type_name": "topology_topology" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_topology" + } } }, "pre_check": { "description": "Update permission pre-condition predicate over the 'topology_topology' collection", "type": { - "type": "predicate", - "object_type_name": "topology_topology" + "type": "nullable", + "underlying_type": { + "type": "predicate", + "object_type_name": "topology_topology" + } } }, "update_columns": {