Skip to content

Commit

Permalink
experimental mutations version with boolean expressions (#393)
Browse files Browse the repository at this point in the history
### What

We introduce a new experimental mutations version. This version is
intended to be used as a WIP version not for public consumption.
It also introduces an update delete mutation that contains a `filter`
predicate field.

<!-- Consider: do we need to add a changelog entry? -->

### How

1. Introduce a new mutations version: `VeryExperimentalWip`.
2. Take the existing `insert`, `delete`, `translate` and `generate`
modules, duplicate them, and place them in `v1/` and `experimental/`
directories.
3. Add and modify existing code to dispatch on operations from the right
module according to the mutations version in the config.
4. Add a new `filter` field for the delete operation which will be
interpreted as a boolean expression and added as a `WHERE` clause in the
delete sql.
5. Expose this new field as part of the schema as a boolean expression
over the collection.
  • Loading branch information
Gil Mizrahi authored Apr 1, 2024
1 parent 5ec39c0 commit 8ee6d48
Show file tree
Hide file tree
Showing 27 changed files with 1,305 additions and 492 deletions.
114 changes: 101 additions & 13 deletions crates/connectors/ndc-postgres/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use ndc_sdk::models;

use ndc_postgres_configuration as configuration;
use query_engine_metadata::metadata;
use query_engine_translation::translation::mutation::{delete, generate, insert};
use query_engine_translation::translation::mutation;

/// Get the connector's schema.
///
Expand Down Expand Up @@ -355,29 +355,35 @@ fn type_to_type(typ: &metadata::Type) -> models::Type {
/// Turn our different `Mutation` items into `ProcedureInfo`s to be output in the schema
fn mutation_to_procedure(
name: &String,
mutation: &generate::Mutation,
mutation: &mutation::generate::Mutation,
object_types: &mut BTreeMap<String, models::ObjectType>,
scalar_types: &mut BTreeMap<String, models::ScalarType>,
) -> models::ProcedureInfo {
match mutation {
generate::Mutation::DeleteMutation(delete) => {
delete_to_procedure(name, delete, object_types, scalar_types)
mutation::generate::Mutation::V1(mutation::v1::Mutation::DeleteMutation(delete)) => {
v1_delete_to_procedure(name, delete, object_types, scalar_types)
}
generate::Mutation::InsertMutation(insert) => {
insert_to_procedure(name, insert, object_types, scalar_types)
mutation::generate::Mutation::V1(mutation::v1::Mutation::InsertMutation(insert)) => {
v1_insert_to_procedure(name, insert, object_types, scalar_types)
}
mutation::generate::Mutation::Experimental(
mutation::experimental::Mutation::DeleteMutation(delete),
) => experimental_delete_to_procedure(name, delete, object_types, scalar_types),
mutation::generate::Mutation::Experimental(
mutation::experimental::Mutation::InsertMutation(insert),
) => experimental_insert_to_procedure(name, insert, object_types, scalar_types),
}
}

/// given a `DeleteMutation`, turn it into a `ProcedureInfo` to be output in the schema
fn delete_to_procedure(
/// given a v1 `DeleteMutation`, turn it into a `ProcedureInfo` to be output in the schema
fn v1_delete_to_procedure(
name: &String,
delete: &delete::DeleteMutation,
delete: &mutation::v1::delete::DeleteMutation,
object_types: &mut BTreeMap<String, models::ObjectType>,
scalar_types: &mut BTreeMap<String, models::ScalarType>,
) -> models::ProcedureInfo {
match delete {
delete::DeleteMutation::DeleteByKey {
mutation::v1::delete::DeleteMutation::DeleteByKey {
by_column,
description,
collection_name,
Expand Down Expand Up @@ -407,6 +413,56 @@ fn delete_to_procedure(
}
}

/// given an experimental `DeleteMutation`, turn it into a `ProcedureInfo` to be output in the schema
fn experimental_delete_to_procedure(
name: &String,
delete: &mutation::experimental::delete::DeleteMutation,
object_types: &mut BTreeMap<String, models::ObjectType>,
scalar_types: &mut BTreeMap<String, models::ScalarType>,
) -> models::ProcedureInfo {
match delete {
mutation::experimental::delete::DeleteMutation::DeleteByKey {
by_column,
filter,
description,
collection_name,
table_name: _,
schema_name: _,
} => {
let mut arguments = BTreeMap::new();

arguments.insert(
by_column.name.clone(),
models::ArgumentInfo {
argument_type: column_to_type(by_column),
description: by_column.description.clone(),
},
);

arguments.insert(
filter.argument_name.clone(),
models::ArgumentInfo {
argument_type: models::Type::Predicate {
object_type_name: collection_name.clone(),
},
description: Some(filter.description.clone()),
},
);

make_procedure_type(
name.to_string(),
Some(description.to_string()),
arguments,
models::Type::Named {
name: collection_name.to_string(),
},
object_types,
scalar_types,
)
}
}
}

/// Create an ObjectType out of columns metadata.
fn make_object_type(
columns: &BTreeMap<String, metadata::database::ColumnInfo>,
Expand Down Expand Up @@ -440,10 +496,42 @@ fn make_object_type(
}
}

/// Given an `InsertMutation`, turn it into a `ProcedureInfo` to be output in the schema.
fn insert_to_procedure(
/// Given a v1 `InsertMutation`, turn it into a `ProcedureInfo` to be output in the schema.
fn v1_insert_to_procedure(
name: &String,
insert: &mutation::v1::insert::InsertMutation,
object_types: &mut BTreeMap<String, models::ObjectType>,
scalar_types: &mut BTreeMap<String, models::ScalarType>,
) -> models::ProcedureInfo {
let mut arguments = BTreeMap::new();
let object_type = make_object_type(&insert.columns);
let object_name = format!("{name}_object").to_string();
object_types.insert(object_name.clone(), object_type);

arguments.insert(
"_object".to_string(),
models::ArgumentInfo {
argument_type: models::Type::Named { name: object_name },
description: None,
},
);

make_procedure_type(
name.to_string(),
Some(insert.description.to_string()),
arguments,
models::Type::Named {
name: insert.collection_name.to_string(),
},
object_types,
scalar_types,
)
}

/// Given an experimental `InsertMutation`, turn it into a `ProcedureInfo` to be output in the schema.
fn experimental_insert_to_procedure(
name: &String,
insert: &insert::InsertMutation,
insert: &mutation::experimental::insert::InsertMutation,
object_types: &mut BTreeMap<String, models::ObjectType>,
scalar_types: &mut BTreeMap<String, models::ScalarType>,
) -> models::ProcedureInfo {
Expand Down
1 change: 1 addition & 0 deletions crates/query-engine/metadata/src/metadata/mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ use serde::{Deserialize, Serialize};
#[serde(rename_all = "camelCase")]
pub enum MutationsVersion {
V1,
VeryExperimentalWip,
}
24 changes: 2 additions & 22 deletions crates/query-engine/translation/src/translation/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ use query_engine_sql::sql;
#[derive(Debug)]
/// Static information from the query and metadata.
pub struct Env<'request> {
metadata: &'request metadata::Metadata,
pub(crate) metadata: &'request metadata::Metadata,
relationships: BTreeMap<String, models::Relationship>,
mutations_version: Option<metadata::mutations::MutationsVersion>,
pub(crate) mutations_version: Option<metadata::mutations::MutationsVersion>,
variables_table: Option<sql::ast::TableReference>,
}

Expand Down Expand Up @@ -142,26 +142,6 @@ impl<'request> Env<'request> {
.ok_or(Error::ProcedureNotFound(procedure_name.to_string()))
}

/// Auto-generate mutation procedures return the generated procedure
/// that matches the procedure name.
pub fn lookup_generated_mutation(
&self,
procedure_name: &str,
) -> Result<crate::translation::mutation::generate::Mutation, Error> {
// this means we generate them on every mutation request
// i don't think this is optimal but I'd like to get this working before working out
// where best to store these
let generated = crate::translation::mutation::generate::generate(
&self.metadata.tables,
self.mutations_version,
);

generated
.get(procedure_name)
.cloned()
.ok_or(Error::ProcedureNotFound(procedure_name.to_string()))
}

pub fn lookup_relationship(&self, name: &str) -> Result<&models::Relationship, Error> {
self.relationships
.get(name)
Expand Down
Loading

0 comments on commit 8ee6d48

Please sign in to comment.