From ad80c16e4fc5f725fd94f48474b83cfed5e19aa4 Mon Sep 17 00:00:00 2001 From: Gil Mizrahi Date: Thu, 8 Aug 2024 15:25:01 +0300 Subject: [PATCH 1/6] fix nested field relationships --- crates/query-engine/sql/src/sql/ast.rs | 9 +- crates/query-engine/sql/src/sql/convert.rs | 24 ++- .../translation/src/translation/helpers.rs | 137 ++++++++++++------ .../src/translation/mutation/v2/delete.rs | 2 +- .../src/translation/mutation/v2/insert.rs | 2 +- .../src/translation/mutation/v2/update.rs | 2 +- .../src/translation/query/fields.rs | 56 +++++-- .../src/translation/query/filtering.rs | 30 ++-- .../src/translation/query/relationships.rs | 3 +- .../translation/src/translation/query/root.rs | 9 +- .../src/translation/query/sorting.rs | 20 ++- 11 files changed, 199 insertions(+), 95 deletions(-) diff --git a/crates/query-engine/sql/src/sql/ast.rs b/crates/query-engine/sql/src/sql/ast.rs index 0175ba64f..2eea2a276 100644 --- a/crates/query-engine/sql/src/sql/ast.rs +++ b/crates/query-engine/sql/src/sql/ast.rs @@ -139,7 +139,7 @@ pub enum From { Unnest { expression: Expression, alias: TableAlias, - column: ColumnAlias, + columns: Vec, }, GenerateSeries { from: usize, @@ -306,7 +306,7 @@ pub enum Expression { } /// Represents the name of a field in a nested object. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NestedField(pub String); /// An unary operator @@ -409,6 +409,11 @@ pub enum TableReference { }, /// refers to an alias we created AliasedTable(TableAlias), + /// refers to a nested field + NestedField { + source: Box, + field: NestedField, + }, } /// A database table's column name diff --git a/crates/query-engine/sql/src/sql/convert.rs b/crates/query-engine/sql/src/sql/convert.rs index 88cec0187..6683b0ad8 100644 --- a/crates/query-engine/sql/src/sql/convert.rs +++ b/crates/query-engine/sql/src/sql/convert.rs @@ -315,7 +315,7 @@ impl From { From::Unnest { expression, alias, - column, + columns, } => { sql.append_syntax("UNNEST"); sql.append_syntax("("); @@ -323,9 +323,18 @@ impl From { sql.append_syntax(")"); sql.append_syntax(" AS "); alias.to_sql(sql); - sql.append_syntax("("); - column.to_sql(sql); - sql.append_syntax(")"); + if !columns.is_empty() { + sql.append_syntax("("); + + for (index, column) in columns.iter().enumerate() { + column.to_sql(sql); + if index < (columns.len() - 1) { + sql.append_syntax(", "); + } + } + + sql.append_syntax(")"); + } } From::GenerateSeries { from, to } => { sql.append_syntax("generate_series"); @@ -701,6 +710,13 @@ impl TableReference { table.to_sql(sql); } TableReference::AliasedTable(alias) => alias.to_sql(sql), + TableReference::NestedField { source, field } => { + sql.append_syntax("("); + source.to_sql(sql); + sql.append_syntax(")"); + sql.append_syntax("."); + field.to_sql(sql); + } }; } } diff --git a/crates/query-engine/translation/src/translation/helpers.rs b/crates/query-engine/translation/src/translation/helpers.rs index e739683d0..c06007de6 100644 --- a/crates/query-engine/translation/src/translation/helpers.rs +++ b/crates/query-engine/translation/src/translation/helpers.rs @@ -64,11 +64,35 @@ pub struct RootAndCurrentTables { #[derive(Debug, Clone, PartialEq, Eq)] pub struct TableNameAndReference { /// Table name for column lookup - pub name: models::CollectionName, + pub source: TableSource, /// Table alias to query from pub reference: sql::ast::TableReference, } +/// How to find the relevant information about a table. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TableSource { + /// Using the collection name. + Collection(models::CollectionName), + /// Using the nested field path. + NestedField { + collection_name: models::CollectionName, + type_name: models::TypeName, + field_path: FieldPath, + }, +} + +impl TableSource { + pub fn name(&self) -> String { + match self { + TableSource::Collection(collection_name) + | TableSource::NestedField { + collection_name, .. + } => collection_name.to_string(), + } + } +} + #[derive(Debug)] /// Information about columns pub struct ColumnInfo { @@ -108,11 +132,11 @@ pub enum CompositeTypeInfo<'env> { /// Metadata information about any object that can have fields pub enum FieldsInfo<'env> { Table { - name: &'env models::CollectionName, + name: models::CollectionName, info: &'env metadata::TableInfo, }, NativeQuery { - name: &'env models::CollectionName, + name: models::CollectionName, info: &'env metadata::NativeQueryInfo, }, CompositeType { @@ -124,7 +148,10 @@ pub enum FieldsInfo<'env> { impl<'a> From<&'a CompositeTypeInfo<'a>> for FieldsInfo<'a> { fn from(value: &'a CompositeTypeInfo<'a>) -> Self { match value { - CompositeTypeInfo::Table { name, info } => FieldsInfo::Table { name, info }, + CompositeTypeInfo::Table { name, info } => FieldsInfo::Table { + name: name.clone(), + info, + }, CompositeTypeInfo::CompositeType { name, info } => FieldsInfo::CompositeType { name: name.clone(), info, @@ -136,8 +163,14 @@ impl<'a> From<&'a CompositeTypeInfo<'a>> for FieldsInfo<'a> { impl<'a> From<&'a CollectionInfo<'a>> for FieldsInfo<'a> { fn from(value: &'a CollectionInfo<'a>) -> Self { match value { - CollectionInfo::Table { name, info } => FieldsInfo::Table { name, info }, - CollectionInfo::NativeQuery { name, info } => FieldsInfo::NativeQuery { name, info }, + CollectionInfo::Table { name, info } => FieldsInfo::Table { + name: (*name).clone(), + info, + }, + CollectionInfo::NativeQuery { name, info } => FieldsInfo::NativeQuery { + name: (*name).clone(), + info, + }, } } } @@ -182,55 +215,63 @@ impl<'request> Env<'request> { /// Queries, and Composite Types. /// /// This is used to translate field selection, where any of these may occur. - pub fn lookup_fields_info( - &self, - type_name: &'request models::CollectionName, - ) -> Result, Error> { - // Lookup the fields of a type name in a specific order: - // tables, then composite types, then native queries. - let info = self - .metadata - .tables - .0 - .get(type_name) - .map(|t| FieldsInfo::Table { - name: type_name, - info: t, - }) - .or_else(|| { - self.metadata + pub fn lookup_fields_info(&self, source: &TableSource) -> Result, Error> { + match source { + TableSource::NestedField { + collection_name: _, + type_name, + field_path: _, + } => { + let info = self + .metadata .composite_types .0 .get(type_name.as_str()) .map(|t| FieldsInfo::CompositeType { name: t.type_name.clone().into(), info: t, - }) - }) - .or_else(|| { - self.metadata - .native_operations - .queries + }); + + info.ok_or(Error::ScalarTypeNotFound(type_name.as_str().into())) + } + TableSource::Collection(collection_name) => { + // Lookup the fields of a type name in a specific order: + // tables, then composite types, then native queries. + let info = self + .metadata + .tables .0 - .get(type_name) - .map(|nq| FieldsInfo::NativeQuery { - name: type_name, - info: nq, + .get(collection_name) + .map(|t| FieldsInfo::Table { + name: collection_name.clone(), + info: t, }) - }) - .or_else(|| { - self.metadata - .native_operations - .mutations - .0 - .get(type_name.as_str()) - .map(|nq| FieldsInfo::NativeQuery { - name: type_name, - info: nq, + .or_else(|| { + self.metadata + .native_operations + .queries + .0 + .get(collection_name) + .map(|nq| FieldsInfo::NativeQuery { + name: collection_name.clone(), + info: nq, + }) }) - }); - - info.ok_or(Error::CollectionNotFound(type_name.as_str().into())) + .or_else(|| { + self.metadata + .native_operations + .mutations + .0 + .get(collection_name.as_str()) + .map(|nq| FieldsInfo::NativeQuery { + name: collection_name.clone(), + info: nq, + }) + }); + + info.ok_or(Error::CollectionNotFound(collection_name.as_str().into())) + } + } } /// Lookup a metadata object which can be described by a Composite Type. This can be any of @@ -265,7 +306,7 @@ impl<'request> Env<'request> { }) }); - info.ok_or(Error::CollectionNotFound(type_name.as_str().into())) + info.ok_or(Error::ScalarTypeNotFound(type_name.as_str().into())) } /// Lookup a collection's information in the metadata. @@ -591,7 +632,7 @@ impl NativeQueries { } /// A newtype wrapper around an ndc-spec type which represents accessing a nested field. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct FieldPath(pub Vec); impl From<&Option>> for FieldPath { 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 e87d1ab23..595774971 100644 --- a/crates/query-engine/translation/src/translation/mutation/v2/delete.rs +++ b/crates/query-engine/translation/src/translation/mutation/v2/delete.rs @@ -105,7 +105,7 @@ pub fn translate( let table_alias = state.make_table_alias(table_name.0.clone()); let table_name_and_reference = TableNameAndReference { - name: collection_name.clone(), + source: helpers::TableSource::Collection(collection_name.clone()), reference: sql::ast::TableReference::AliasedTable(table_alias.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 aff3c3c12..3d8b2d109 100644 --- a/crates/query-engine/translation/src/translation/mutation/v2/insert.rs +++ b/crates/query-engine/translation/src/translation/mutation/v2/insert.rs @@ -216,7 +216,7 @@ pub fn translate( let (columns, from) = translate_objects_to_columns_and_values(env, state, mutation, object)?; let table_name_and_reference = TableNameAndReference { - name: mutation.collection_name.clone(), + source: helpers::TableSource::Collection(mutation.collection_name.clone()), reference: sql::ast::TableReference::DBTable { schema: mutation.schema_name.clone(), table: mutation.table_name.clone(), 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 3e43e8d83..70ba6f84a 100644 --- a/crates/query-engine/translation/src/translation/mutation/v2/update.rs +++ b/crates/query-engine/translation/src/translation/mutation/v2/update.rs @@ -122,7 +122,7 @@ pub fn translate( let set = parse_update_columns(env, state, mutation, object)?; let table_name_and_reference = TableNameAndReference { - name: mutation.collection_name.clone(), + source: helpers::TableSource::Collection(mutation.collection_name.clone()), reference: sql::ast::TableReference::DBTable { schema: mutation.schema_name.clone(), table: mutation.table_name.clone(), diff --git a/crates/query-engine/translation/src/translation/query/fields.rs b/crates/query-engine/translation/src/translation/query/fields.rs index 51f203b53..b8c5616b8 100644 --- a/crates/query-engine/translation/src/translation/query/fields.rs +++ b/crates/query-engine/translation/src/translation/query/fields.rs @@ -10,7 +10,9 @@ use ndc_models as models; use super::relationships; use crate::translation::error::Error; use crate::translation::error::UnsupportedCapabilities; +use crate::translation::helpers::FieldPath; use crate::translation::helpers::FieldsInfo; +use crate::translation::helpers::TableSource; use crate::translation::helpers::{ColumnInfo, Env, State, TableNameAndReference}; use query_engine_metadata::metadata::{Type, TypeRepresentation}; use query_engine_sql::sql; @@ -27,7 +29,7 @@ pub(crate) fn translate( join_relationship_fields: &mut Vec, ) -> Result { // find the table according to the metadata. - let fields_info = env.lookup_fields_info(¤t_table.name)?; + let fields_info = env.lookup_fields_info(¤t_table.source)?; // Each nested field is computed in one joined-on sub query. let mut nested_field_joins: Vec = vec![]; @@ -43,7 +45,6 @@ pub(crate) fn translate( env, state, current_table, - join_relationship_fields, &column, sql::helpers::make_column_alias(alias.to_string()), &fields_info, @@ -62,7 +63,6 @@ pub(crate) fn translate( &column, &column_info, nested_field, - join_relationship_fields, )?; nested_field_joins.push(nested_field_join); @@ -118,6 +118,7 @@ pub(crate) fn translate( /// This type collects the salient parts of joined-on subqueries that compute the result of a /// nested field selection. +#[derive(Debug)] struct JoinNestedFieldInfo { select: sql::ast::Select, alias: sql::ast::TableAlias, @@ -179,7 +180,6 @@ fn translate_nested_field( current_column_name: &models::FieldName, current_column: &ColumnInfo, field: models::NestedField, - join_relationship_fields: &mut Vec, ) -> Result<(JoinNestedFieldInfo, sql::ast::ColumnReference), Error> { let nested_field_column_collect_alias = sql::helpers::make_column_alias("collected".to_string()); @@ -292,20 +292,57 @@ fn translate_nested_field( }; // The recursive call to the next layer of fields - let nested_field_table_reference = TableNameAndReference { - name: nested_field_type_name.as_str().into(), - reference: sql::ast::TableReference::AliasedTable(nested_field_binding_alias), + let nested_field_table_reference = { + match ¤t_table.source { + TableSource::Collection(collection_name) => TableNameAndReference { + source: TableSource::NestedField { + collection_name: collection_name.clone(), + type_name: nested_field_type_name, + field_path: FieldPath(vec![]), + }, + reference: sql::ast::TableReference::AliasedTable(nested_field_binding_alias), + }, + TableSource::NestedField { + collection_name, + type_name: _, + field_path, + } => { + let mut field_path = field_path.0.clone(); + field_path.push(current_column_name.clone()); + TableNameAndReference { + source: TableSource::NestedField { + collection_name: collection_name.clone(), + type_name: nested_field_type_name, + field_path: FieldPath(field_path), + }, + reference: sql::ast::TableReference::AliasedTable(nested_field_binding_alias), + } + } + } }; - let fields_select = translate( + // join aliases + let mut join_relationship_fields: Vec = vec![]; + + let mut fields_select = translate( env, state, fields, &nested_field_table_reference, nested_field_from, + &mut join_relationship_fields, + )?; + + // collect any joins for relationships from fields selection. + let relationship_joins = relationships::translate( + env, + state, + &nested_field_table_reference, join_relationship_fields, )?; + fields_select.joins.extend(relationship_joins); + // The top-level select statement which collects the fields at the next level of nesting into a // single json object. let mut collect_select = sql::helpers::simple_select(vec![( @@ -345,7 +382,6 @@ fn unpack_and_wrap_fields( env: &Env, state: &mut State, current_table: &TableNameAndReference, - join_relationship_fields: &mut Vec, column: &models::FieldName, alias: sql::ast::ColumnAlias, fields_info: &FieldsInfo<'_>, @@ -385,7 +421,6 @@ fn unpack_and_wrap_fields( column, &column_info, nested_field, - join_relationship_fields, )?; nested_field_joins.push(nested_field_join); @@ -412,7 +447,6 @@ fn unpack_and_wrap_fields( column, &column_info, nested_field, - join_relationship_fields, )?; nested_field_joins.push(nested_field_join); diff --git a/crates/query-engine/translation/src/translation/query/filtering.rs b/crates/query-engine/translation/src/translation/query/filtering.rs index ee09d9519..1e4b36a18 100644 --- a/crates/query-engine/translation/src/translation/query/filtering.rs +++ b/crates/query-engine/translation/src/translation/query/filtering.rs @@ -12,6 +12,7 @@ use super::values; use super::variables; use crate::translation::error::Error; use crate::translation::helpers::wrap_in_field_path; +use crate::translation::helpers::TableSource; use crate::translation::helpers::{ ColumnInfo, CompositeTypeInfo, Env, RootAndCurrentTables, State, TableNameAndReference, }; @@ -355,7 +356,7 @@ fn translate_comparison_pathelements( root_table: root_and_current_tables.root_table.clone(), current_table: TableNameAndReference { reference: table.reference.clone(), - name: table.name.clone(), + source: table.source.clone(), }, }; // relationship-specfic filter @@ -406,13 +407,13 @@ fn translate_comparison_pathelements( outer_select.from = Some(sql::ast::From::Select { select, alias }); outer_select.joins = joins.into(); - let alias = state.make_boolean_expression_table_alias(final_ref.name.as_str()); + let alias = state.make_boolean_expression_table_alias(final_ref.source.name().as_str()); let reference = sql::ast::TableReference::AliasedTable(alias.clone()); Ok(( TableNameAndReference { reference, - name: final_ref.name.clone(), + source: final_ref.source, }, // create a join from the select. // We use a full outer join so even if one of the sides does not contain rows, @@ -446,7 +447,7 @@ fn translate_comparison_target( translate_comparison_pathelements(env, state, root_and_current_tables, path)?; // get the unrelated table information from the metadata. - let collection_info = env.lookup_collection(&table_ref.name)?; + let collection_info = env.lookup_fields_info(&table_ref.source)?; let ColumnInfo { name, .. } = collection_info.lookup_column(name)?; Ok(( @@ -465,7 +466,7 @@ fn translate_comparison_target( models::ComparisonTarget::RootCollectionColumn { name, field_path } => { let RootAndCurrentTables { root_table, .. } = root_and_current_tables; // get the unrelated table information from the metadata. - let collection_info = env.lookup_collection(&root_table.name)?; + let collection_info = env.lookup_fields_info(&root_table.source)?; // find the requested column in the tables columns. let ColumnInfo { name, .. } = collection_info.lookup_column(name)?; @@ -548,7 +549,7 @@ pub fn translate_exists_in_collection( root_table: root_and_current_tables.root_table.clone(), current_table: TableNameAndReference { reference: table.reference, - name: table.name, + source: table.source, }, }; @@ -609,7 +610,7 @@ pub fn translate_exists_in_collection( root_table: root_and_current_tables.root_table.clone(), current_table: TableNameAndReference { reference: table.reference.clone(), - name: table.name, + source: table.source, }, }; @@ -651,7 +652,7 @@ fn get_comparison_target_type( match column { models::ComparisonTarget::RootCollectionColumn { name, field_path } => { let column = env - .lookup_collection(&root_and_current_tables.root_table.name)? + .lookup_fields_info(&root_and_current_tables.root_table.source)? .lookup_column(name)?; let mut field_path = match field_path { @@ -672,17 +673,18 @@ fn get_comparison_target_type( match path.last() { None => { let column = env - .lookup_collection(&root_and_current_tables.current_table.name)? + .lookup_fields_info(&root_and_current_tables.current_table.source)? .lookup_column(name)?; get_column_scalar_type_name(env, &column.r#type, &mut field_path) } Some(last) => { let column = env - .lookup_collection( - &env.lookup_relationship(&last.relationship)? - .target_collection, - )? + .lookup_fields_info(&TableSource::Collection( + env.lookup_relationship(&last.relationship)? + .target_collection + .clone(), + ))? .lookup_column(name)?; get_column_scalar_type_name(env, &column.r#type, &mut field_path) @@ -754,7 +756,7 @@ fn make_unnest_subquery( let subquery_reference = sql::ast::TableReference::AliasedTable(subquery_alias.clone()); let subquery_from = sql::ast::From::Unnest { expression, - column: sql::helpers::make_column_alias("value".to_string()), + columns: vec![sql::helpers::make_column_alias("value".to_string())], alias: subquery_alias, }; let mut subquery = sql::helpers::simple_select(vec![sql::helpers::make_column( diff --git a/crates/query-engine/translation/src/translation/query/relationships.rs b/crates/query-engine/translation/src/translation/query/relationships.rs index ae068b17a..4e39ad472 100644 --- a/crates/query-engine/translation/src/translation/query/relationships.rs +++ b/crates/query-engine/translation/src/translation/query/relationships.rs @@ -9,6 +9,7 @@ use crate::translation::error::Error; use crate::translation::helpers::{Env, State, TableNameAndReference}; use query_engine_sql::sql; +#[derive(Debug)] pub struct JoinFieldInfo { pub table_alias: sql::ast::TableAlias, pub column_alias: sql::ast::ColumnAlias, @@ -88,7 +89,7 @@ pub fn translate_column_mapping( expr: sql::ast::Expression, relationship: &models::Relationship, ) -> Result { - let table_info = env.lookup_collection(¤t_table.name)?; + let table_info = env.lookup_fields_info(¤t_table.source)?; let target_collection_info = env.lookup_collection(&relationship.target_collection)?; diff --git a/crates/query-engine/translation/src/translation/query/root.rs b/crates/query-engine/translation/src/translation/query/root.rs index 487e5a57d..d6012a61f 100644 --- a/crates/query-engine/translation/src/translation/query/root.rs +++ b/crates/query-engine/translation/src/translation/query/root.rs @@ -12,6 +12,7 @@ use super::filtering; use super::relationships; use super::sorting; use crate::translation::error::Error; +use crate::translation::helpers::TableSource; use crate::translation::helpers::{ CollectionInfo, Env, RootAndCurrentTables, State, TableNameAndReference, }; @@ -76,13 +77,13 @@ fn translate_aggregates( // So we wrap this query part in another query that performs the aggregation. // Create a from clause selecting from the inner query. - let from_alias = state.make_table_alias(table.name.to_string()); + let from_alias = state.make_table_alias(table.source.name()); let from = sql::ast::From::Select { select: Box::new(inner_query), alias: from_alias.clone(), }; let current_table = TableNameAndReference { - name: table.name, + source: table.source, reference: sql::ast::TableReference::AliasedTable(from_alias), }; @@ -237,7 +238,7 @@ pub fn make_from_clause_and_reference( let collection_alias_name = sql::ast::TableReference::AliasedTable(collection_alias); let current_table = TableNameAndReference { - name: collection_name.clone(), + source: TableSource::Collection(collection_name.clone()), reference: collection_alias_name, }; Ok((current_table, from_clause)) @@ -317,7 +318,7 @@ fn make_reference_and_from_clause( let reference = sql::ast::TableReference::AliasedTable(table_alias); Ok(( TableNameAndReference { - name: name.clone(), + source: TableSource::Collection(name.clone()), reference, }, from_clause, diff --git a/crates/query-engine/translation/src/translation/query/sorting.rs b/crates/query-engine/translation/src/translation/query/sorting.rs index b29ce232c..b014c8f7e 100644 --- a/crates/query-engine/translation/src/translation/query/sorting.rs +++ b/crates/query-engine/translation/src/translation/query/sorting.rs @@ -10,8 +10,8 @@ use super::relationships; use super::root; use crate::translation::error::Error; use crate::translation::helpers::{ - wrap_in_field_path, CollectionInfo, Env, FieldPath, RootAndCurrentTables, State, - TableNameAndReference, + wrap_in_field_path, Env, FieldPath, FieldsInfo, RootAndCurrentTables, State, + TableNameAndReference, TableSource, }; use query_engine_sql::sql; @@ -274,8 +274,9 @@ fn translate_order_by_target_group( // The column is from a relationship table, we need to join with this select query. ColumnsOrSelect::Select { columns, select } => { // Give it a nice unique alias. - let table_alias = state - .make_order_by_table_alias(root_and_current_tables.current_table.name.as_str()); + let table_alias = state.make_order_by_table_alias( + root_and_current_tables.current_table.source.name().as_str(), + ); // Build a join and push it to the accumulated joins. let new_join = sql::ast::LeftOuterJoinLateral { @@ -382,7 +383,8 @@ fn build_select_and_joins_for_order_by_group( } OrderByElementGroup::Columns { .. } => { // If the path is empty, we don't need to build a query, just return the columns. - let table = env.lookup_collection(&root_and_current_tables.current_table.name)?; + let table = + env.lookup_fields_info(&root_and_current_tables.current_table.source)?; let columns = translate_targets( &table, &root_and_current_tables.current_table, @@ -577,7 +579,7 @@ fn process_path_element_for_order_by_targets( .column_mapping .keys() .map(|source_col| { - let collection = env.lookup_collection(&table.name)?; + let collection = env.lookup_fields_info(&table.source)?; let selected_column = collection.lookup_column(source_col)?; // we are going to deliberately use the table column name and not an alias we get from // the query request because this is internal to the sorting mechanism. @@ -609,7 +611,9 @@ fn process_path_element_for_order_by_targets( OrderByElementGroup::Aggregates { .. } => Ok(()), }?; - let target_collection = env.lookup_collection(&relationship.target_collection)?; + let target_collection = env.lookup_fields_info(&TableSource::Collection( + relationship.target_collection.clone(), + ))?; Ok(PathElementSelectColumns::OrderBySelectExpressions( translate_targets(&target_collection, &table, element_group)?, )) @@ -646,7 +650,7 @@ fn process_path_element_for_order_by_targets( /// to aliases and expressions, along with their order by direction and their index /// in the order by list. fn translate_targets( - target_collection: &CollectionInfo, + target_collection: &FieldsInfo<'_>, table: &TableNameAndReference, element_group: &OrderByElementGroup, ) -> Result, Error> { From 88d44b19afd4c6e81883d94e12eb8df2389bfb4b Mon Sep 17 00:00:00 2001 From: Gil Mizrahi Date: Thu, 8 Aug 2024 16:32:21 +0300 Subject: [PATCH 2/6] test nested field relationship --- .../src/postgres/query_tests.rs | 6 + ...re_initial_configuration_is_unchanged.snap | 174 + ...re_initial_configuration_is_unchanged.snap | 174 + ...ationships__nested_field_relationship.snap | 50 + ...schema_tests__schema_test__get_schema.snap | 532 ++ .../nested_field_relationship.json | 54 + docker-compose.yaml | 6 +- static/institution.sql | 80 + .../configuration.json | 4261 +++++++++++++++++ .../summarize_organizations.sql | 22 + .../v5-configuration/configuration.json | 172 + 11 files changed, 5530 insertions(+), 1 deletion(-) create mode 100644 crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__query_tests__relationships__nested_field_relationship.snap create mode 100644 crates/tests/tests-common/goldenfiles/nested_field_relationship.json create mode 100644 static/institution.sql create mode 100644 static/ndc-metadata-snapshots/c982ed0bbd9938aebd5a2dfc9298bb8221ece869da550dba1a24d733a007b7af/configuration.json create mode 100644 static/ndc-metadata-snapshots/c982ed0bbd9938aebd5a2dfc9298bb8221ece869da550dba1a24d733a007b7af/native_queries/summarize_organizations.sql diff --git a/crates/tests/databases-tests/src/postgres/query_tests.rs b/crates/tests/databases-tests/src/postgres/query_tests.rs index 09d9ca6f9..d937b4d79 100644 --- a/crates/tests/databases-tests/src/postgres/query_tests.rs +++ b/crates/tests/databases-tests/src/postgres/query_tests.rs @@ -516,6 +516,12 @@ mod relationships { let result = run_query(create_router().await, "very_nested_recursive_relationship").await; insta::assert_json_snapshot!(result); } + + #[tokio::test] + async fn nested_field_relationship() { + let result = run_query(create_router().await, "nested_field_relationship").await; + insta::assert_json_snapshot!(result); + } } #[cfg(test)] diff --git a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__cli_version4_tests__postgres_current_only_configure_initial_configuration_is_unchanged.snap b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__cli_version4_tests__postgres_current_only_configure_initial_configuration_is_unchanged.snap index 01142d324..4d5d443f1 100644 --- a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__cli_version4_tests__postgres_current_only_configure_initial_configuration_is_unchanged.snap +++ b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__cli_version4_tests__postgres_current_only_configure_initial_configuration_is_unchanged.snap @@ -1000,6 +1000,71 @@ expression: default_configuration "foreignRelations": {}, "description": null }, + "institution_institution": { + "schemaName": "institution", + "tableName": "institution", + "columns": { + "departments": { + "name": "departments", + "type": { + "arrayType": { + "scalarType": "text" + } + }, + "nullable": "nullable", + "description": null + }, + "id": { + "name": "id", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "location": { + "name": "location", + "type": { + "compositeType": "institution_location" + }, + "nullable": "nullable", + "description": null + }, + "name": { + "name": "name", + "type": { + "scalarType": "text" + }, + "nullable": "nullable", + "description": null + }, + "songs": { + "name": "songs", + "type": { + "compositeType": "institution_institution_songs" + }, + "nullable": "nullable", + "description": null + }, + "staff": { + "name": "staff", + "type": { + "arrayType": { + "compositeType": "institution_staff" + } + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": { + "institution_pkey": [ + "id" + ] + }, + "foreignRelations": {}, + "description": null + }, "phone_numbers": { "schemaName": "public", "tableName": "phone_numbers", @@ -2641,6 +2706,115 @@ expression: default_configuration } }, "description": null + }, + "institution_country": { + "typeName": "country", + "schemaName": "institution", + "fields": { + "continent": { + "fieldName": "continent", + "type": { + "scalarType": "text" + }, + "description": null + }, + "name": { + "fieldName": "name", + "type": { + "scalarType": "text" + }, + "description": null + } + }, + "description": null + }, + "institution_institution_songs": { + "typeName": "institution_songs", + "schemaName": "institution", + "fields": { + "primary_anthem_track_id": { + "fieldName": "primary_anthem_track_id", + "type": { + "scalarType": "int4" + }, + "description": null + }, + "secondary_anthem_track_id": { + "fieldName": "secondary_anthem_track_id", + "type": { + "scalarType": "int4" + }, + "description": null + } + }, + "description": null + }, + "institution_location": { + "typeName": "location", + "schemaName": "institution", + "fields": { + "campuses": { + "fieldName": "campuses", + "type": { + "arrayType": { + "scalarType": "text" + } + }, + "description": null + }, + "city": { + "fieldName": "city", + "type": { + "scalarType": "text" + }, + "description": null + }, + "country": { + "fieldName": "country", + "type": { + "compositeType": "institution_country" + }, + "description": null + } + }, + "description": null + }, + "institution_staff": { + "typeName": "staff", + "schemaName": "institution", + "fields": { + "favourite_artist_id": { + "fieldName": "favourite_artist_id", + "type": { + "scalarType": "int4" + }, + "description": null + }, + "first_name": { + "fieldName": "first_name", + "type": { + "scalarType": "text" + }, + "description": null + }, + "last_name": { + "fieldName": "last_name", + "type": { + "scalarType": "text" + }, + "description": null + }, + "specialities": { + "fieldName": "specialities", + "type": { + "arrayType": { + "scalarType": "text" + } + }, + "description": null + } + }, + "description": null } }, "nativeQueries": {} diff --git a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__cli_version5_tests__postgres_current_only_configure_initial_configuration_is_unchanged.snap b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__cli_version5_tests__postgres_current_only_configure_initial_configuration_is_unchanged.snap index 738f6b5b6..a18a0087a 100644 --- a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__cli_version5_tests__postgres_current_only_configure_initial_configuration_is_unchanged.snap +++ b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__cli_version5_tests__postgres_current_only_configure_initial_configuration_is_unchanged.snap @@ -1000,6 +1000,71 @@ expression: default_configuration "foreignRelations": {}, "description": null }, + "institution_institution": { + "schemaName": "institution", + "tableName": "institution", + "columns": { + "departments": { + "name": "departments", + "type": { + "arrayType": { + "scalarType": "text" + } + }, + "nullable": "nullable", + "description": null + }, + "id": { + "name": "id", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "location": { + "name": "location", + "type": { + "compositeType": "institution_location" + }, + "nullable": "nullable", + "description": null + }, + "name": { + "name": "name", + "type": { + "scalarType": "text" + }, + "nullable": "nullable", + "description": null + }, + "songs": { + "name": "songs", + "type": { + "compositeType": "institution_institution_songs" + }, + "nullable": "nullable", + "description": null + }, + "staff": { + "name": "staff", + "type": { + "arrayType": { + "compositeType": "institution_staff" + } + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": { + "institution_pkey": [ + "id" + ] + }, + "foreignRelations": {}, + "description": null + }, "phone_numbers": { "schemaName": "public", "tableName": "phone_numbers", @@ -2666,6 +2731,115 @@ expression: default_configuration } }, "description": null + }, + "institution_country": { + "typeName": "country", + "schemaName": "institution", + "fields": { + "continent": { + "fieldName": "continent", + "type": { + "scalarType": "text" + }, + "description": null + }, + "name": { + "fieldName": "name", + "type": { + "scalarType": "text" + }, + "description": null + } + }, + "description": null + }, + "institution_institution_songs": { + "typeName": "institution_songs", + "schemaName": "institution", + "fields": { + "primary_anthem_track_id": { + "fieldName": "primary_anthem_track_id", + "type": { + "scalarType": "int4" + }, + "description": null + }, + "secondary_anthem_track_id": { + "fieldName": "secondary_anthem_track_id", + "type": { + "scalarType": "int4" + }, + "description": null + } + }, + "description": null + }, + "institution_location": { + "typeName": "location", + "schemaName": "institution", + "fields": { + "campuses": { + "fieldName": "campuses", + "type": { + "arrayType": { + "scalarType": "text" + } + }, + "description": null + }, + "city": { + "fieldName": "city", + "type": { + "scalarType": "text" + }, + "description": null + }, + "country": { + "fieldName": "country", + "type": { + "compositeType": "institution_country" + }, + "description": null + } + }, + "description": null + }, + "institution_staff": { + "typeName": "staff", + "schemaName": "institution", + "fields": { + "favourite_artist_id": { + "fieldName": "favourite_artist_id", + "type": { + "scalarType": "int4" + }, + "description": null + }, + "first_name": { + "fieldName": "first_name", + "type": { + "scalarType": "text" + }, + "description": null + }, + "last_name": { + "fieldName": "last_name", + "type": { + "scalarType": "text" + }, + "description": null + }, + "specialities": { + "fieldName": "specialities", + "type": { + "arrayType": { + "scalarType": "text" + } + }, + "description": null + } + }, + "description": null } } }, diff --git a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__query_tests__relationships__nested_field_relationship.snap b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__query_tests__relationships__nested_field_relationship.snap new file mode 100644 index 000000000..cb532f4d4 --- /dev/null +++ b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__query_tests__relationships__nested_field_relationship.snap @@ -0,0 +1,50 @@ +--- +source: crates/tests/databases-tests/src/postgres/query_tests.rs +expression: result +--- +[ + { + "rows": [ + { + "name": "Queen Mary University of London", + "staff": [ + { + "favourite_artist": { + "rows": [ + { + "artist_id": 1, + "name": "AC/DC" + } + ] + } + } + ] + }, + { + "name": "Chalmers University of Technology", + "staff": [ + { + "favourite_artist": { + "rows": [ + { + "artist_id": 2, + "name": "Accept" + } + ] + } + }, + { + "favourite_artist": { + "rows": [ + { + "artist_id": 3, + "name": "Aerosmith" + } + ] + } + } + ] + } + ] + } +] 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 bf658091b..4ed72e23d 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 @@ -3684,6 +3684,155 @@ expression: result } } }, + "institution_country": { + "fields": { + "continent": { + "type": { + "type": "named", + "name": "text" + } + }, + "name": { + "type": { + "type": "named", + "name": "text" + } + } + } + }, + "institution_institution": { + "fields": { + "departments": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "text" + } + } + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "location": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "institution_location" + } + } + }, + "name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + }, + "songs": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "institution_institution_songs" + } + } + }, + "staff": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "institution_staff" + } + } + } + } + } + }, + "institution_institution_songs": { + "fields": { + "primary_anthem_track_id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "secondary_anthem_track_id": { + "type": { + "type": "named", + "name": "int4" + } + } + } + }, + "institution_location": { + "fields": { + "campuses": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "text" + } + } + }, + "city": { + "type": { + "type": "named", + "name": "text" + } + }, + "country": { + "type": { + "type": "named", + "name": "institution_country" + } + } + } + }, + "institution_staff": { + "fields": { + "favourite_artist_id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "first_name": { + "type": { + "type": "named", + "name": "text" + } + }, + "last_name": { + "type": { + "type": "named", + "name": "text" + } + }, + "specialities": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "text" + } + } + } + } + }, "make_person": { "description": "A native query used to test support for composite types", "fields": { @@ -4904,6 +5053,99 @@ expression: result } } }, + "update_column_institution_institution_departments": { + "description": "Update the 'departments' column in the 'institution_institution' collection", + "fields": { + "_set": { + "description": "Set the column to this value", + "type": { + "type": "nullable", + "underlying_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "text" + } + } + } + } + } + }, + "update_column_institution_institution_id": { + "description": "Update the 'id' column in the 'institution_institution' collection", + "fields": { + "_set": { + "description": "Set the column to this value", + "type": { + "type": "named", + "name": "int4" + } + } + } + }, + "update_column_institution_institution_location": { + "description": "Update the 'location' column in the 'institution_institution' collection", + "fields": { + "_set": { + "description": "Set the column to this value", + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "institution_location" + } + } + } + } + }, + "update_column_institution_institution_name": { + "description": "Update the 'name' column in the 'institution_institution' collection", + "fields": { + "_set": { + "description": "Set the column to this value", + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + } + } + }, + "update_column_institution_institution_songs": { + "description": "Update the 'songs' column in the 'institution_institution' collection", + "fields": { + "_set": { + "description": "Set the column to this value", + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "institution_institution_songs" + } + } + } + } + }, + "update_column_institution_institution_staff": { + "description": "Update the 'staff' column in the 'institution_institution' collection", + "fields": { + "_set": { + "description": "Set the column to this value", + "type": { + "type": "nullable", + "underlying_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "institution_staff" + } + } + } + } + } + }, "update_column_spatial_ref_sys_auth_name": { "description": "Update the 'auth_name' column in the 'spatial_ref_sys' collection", "fields": { @@ -5421,6 +5663,28 @@ expression: result } } }, + "v2_delete_institution_institution_by_id_response": { + "description": "Responses from the 'v2_delete_institution_institution_by_id' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "institution_institution" + } + } + } + } + }, "v2_delete_spatial_ref_sys_by_srid_response": { "description": "Responses from the 'v2_delete_spatial_ref_sys_by_srid' procedure", "fields": { @@ -6622,6 +6886,89 @@ expression: result } } }, + "v2_insert_institution_institution_object": { + "fields": { + "departments": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "text" + } + } + } + }, + "id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "location": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "institution_location" + } + } + }, + "name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "text" + } + } + }, + "songs": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "institution_institution_songs" + } + } + }, + "staff": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "institution_staff" + } + } + } + } + } + }, + "v2_insert_institution_institution_response": { + "description": "Responses from the 'v2_insert_institution_institution' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "institution_institution" + } + } + } + } + }, "v2_insert_phone_numbers_object": { "fields": { "the_number": { @@ -7925,6 +8272,93 @@ expression: result } } }, + "v2_update_institution_institution_by_id_response": { + "description": "Responses from the 'v2_update_institution_institution_by_id' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "institution_institution" + } + } + } + } + }, + "v2_update_institution_institution_by_id_update_columns": { + "description": "Update the columns of the 'institution_institution' collection", + "fields": { + "departments": { + "description": "Update the 'departments' column in the 'institution_institution' collection.", + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "update_column_institution_institution_departments" + } + } + }, + "id": { + "description": "Update the 'id' column in the 'institution_institution' collection.", + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "update_column_institution_institution_id" + } + } + }, + "location": { + "description": "Update the 'location' column in the 'institution_institution' collection.", + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "update_column_institution_institution_location" + } + } + }, + "name": { + "description": "Update the 'name' column in the 'institution_institution' collection.", + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "update_column_institution_institution_name" + } + } + }, + "songs": { + "description": "Update the 'songs' column in the 'institution_institution' collection.", + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "update_column_institution_institution_songs" + } + } + }, + "staff": { + "description": "Update the 'staff' column in the 'institution_institution' collection.", + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "update_column_institution_institution_staff" + } + } + } + } + }, "v2_update_spatial_ref_sys_by_srid_response": { "description": "Responses from the 'v2_update_spatial_ref_sys_by_srid' procedure", "fields": { @@ -8801,6 +9235,19 @@ expression: result "uniqueness_constraints": {}, "foreign_keys": {} }, + { + "name": "institution_institution", + "arguments": {}, + "type": "institution_institution", + "uniqueness_constraints": { + "institution_pkey": { + "unique_columns": [ + "id" + ] + } + }, + "foreign_keys": {} + }, { "name": "phone_numbers", "arguments": {}, @@ -9612,6 +10059,29 @@ expression: result "name": "v2_delete_custom_dog_by_id_response" } }, + { + "name": "v2_delete_institution_institution_by_id", + "description": "Delete any row on the 'institution_institution' collection using the 'id' key", + "arguments": { + "key_id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "pre_check": { + "description": "Delete permission predicate over the 'institution_institution' collection", + "type": { + "type": "predicate", + "object_type_name": "institution_institution" + } + } + }, + "result_type": { + "type": "named", + "name": "v2_delete_institution_institution_by_id_response" + } + }, { "name": "v2_delete_spatial_ref_sys_by_srid", "description": "Delete any row on the 'spatial_ref_sys' collection using the 'srid' key", @@ -10213,6 +10683,32 @@ expression: result "name": "v2_insert_group_leader_response" } }, + { + "name": "v2_insert_institution_institution", + "description": "Insert into the institution_institution table", + "arguments": { + "objects": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "v2_insert_institution_institution_object" + } + } + }, + "post_check": { + "description": "Insert permission predicate over the 'institution_institution' collection", + "type": { + "type": "predicate", + "object_type_name": "institution_institution" + } + } + }, + "result_type": { + "type": "named", + "name": "v2_insert_institution_institution_response" + } + }, { "name": "v2_insert_phone_numbers", "description": "Insert into the phone_numbers table", @@ -10794,6 +11290,42 @@ expression: result "name": "v2_update_custom_dog_by_id_response" } }, + { + "name": "v2_update_institution_institution_by_id", + "description": "Update any row on the 'institution_institution' collection using the 'id' key", + "arguments": { + "key_id": { + "type": { + "type": "named", + "name": "int4" + } + }, + "post_check": { + "description": "Update permission post-condition predicate over the 'institution_institution' collection", + "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" + } + }, + "update_columns": { + "type": { + "type": "named", + "name": "v2_update_institution_institution_by_id_update_columns" + } + } + }, + "result_type": { + "type": "named", + "name": "v2_update_institution_institution_by_id_response" + } + }, { "name": "v2_update_spatial_ref_sys_by_srid", "description": "Update any row on the 'spatial_ref_sys' collection using the 'srid' key", diff --git a/crates/tests/tests-common/goldenfiles/nested_field_relationship.json b/crates/tests/tests-common/goldenfiles/nested_field_relationship.json new file mode 100644 index 000000000..50e1be3b2 --- /dev/null +++ b/crates/tests/tests-common/goldenfiles/nested_field_relationship.json @@ -0,0 +1,54 @@ +{ + "collection": "institution_institution", + "query": { + "fields": { + "name": { + "type": "column", + "column": "name", + "fields": null + }, + "staff": { + "type": "column", + "column": "staff", + "fields": { + "type": "array", + "fields": { + "type": "object", + "fields": { + "favourite_artist": { + "type": "relationship", + "query": { + "fields": { + "artist_id": { + "type": "column", + "column": "ArtistId", + "fields": null + }, + "name": { + "type": "column", + "column": "Name", + "fields": null + } + } + }, + "relationship": "default___staff_member__favourite_artist", + "arguments": {} + } + } + } + } + } + } + }, + "arguments": {}, + "collection_relationships": { + "default___staff_member__favourite_artist": { + "column_mapping": { + "favourite_artist_id": "ArtistId" + }, + "relationship_type": "object", + "target_collection": "Artist", + "arguments": {} + } + } +} diff --git a/docker-compose.yaml b/docker-compose.yaml index 63b007740..cea1e6896 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -44,11 +44,15 @@ services: source: ./static/enum-types.sql target: /docker-entrypoint-initdb.d/08-enum-types.sql read_only: true + - type: bind + source: ./static/institution.sql + target: /docker-entrypoint-initdb.d/09-institution.sql + read_only: true # The script to copy the database template for mutations tests should # come in last in order to capture all aspects. - type: bind source: ./static/copy-chinook.sql - target: /docker-entrypoint-initdb.d/09-copy-chinook.sql + target: /docker-entrypoint-initdb.d/10-copy-chinook.sql read_only: true healthcheck: test: diff --git a/static/institution.sql b/static/institution.sql new file mode 100644 index 000000000..266df365a --- /dev/null +++ b/static/institution.sql @@ -0,0 +1,80 @@ +-- `institution` table for testing queries into JSON objects + +create schema institution; + +CREATE TYPE institution.country AS (name text, continent text); + +CREATE TYPE institution.location AS (city text, country institution.country, campuses text []); + +CREATE TYPE institution.staff AS ( + first_name text, + last_name text, + specialities text [], + favourite_artist_id int +); + +CREATE TYPE institution.institution_songs AS ( + primary_anthem_track_id int, + secondary_anthem_track_id int +); + +CREATE TABLE institution.institution ( + id integer NOT NULL, + name text, + location institution.location, + staff institution.staff [], + departments text [], + songs institution.institution_songs, + CONSTRAINT institution_pkey PRIMARY KEY (id) +); + +INSERT INTO institution.institution (id, name, location, staff, departments, songs) +VALUES ( + 1, + 'Queen Mary University of London', + ROW( + 'London', + ROW('UK','Europe') :: institution.country, + ARRAY ['Mile End','Whitechapel','Charterhouse Square','West Smithfield'] + )::institution.location, + ARRAY [ROW('Peter','Landin',ARRAY['Computer Science','Education'], + 1 + )::institution.staff ], + ARRAY ['Humanities and Social Sciences','Science and Engineering','Medicine and Dentistry'], + ROW( + 2270, + 2271 + )::institution.institution_songs +), +( + 2, + 'Chalmers University of Technology', + ROW( + 'Gothenburg', + ROW('Sweden','Europe') :: institution.country, + ARRAY ['Johanneberg','Lindholmen'] + )::institution.location, + ARRAY [ROW('John','Hughes',ARRAY['Computer Science','Functional Programming','Software Testing'], + 2 +)::institution.staff, +ROW( + 'Koen', + 'Claessen', + ARRAY ['Computer Science','Functional Programming','Automated Reasoning'], + 3 +)::institution.staff ], +ARRAY ['Architecture and Civil Engineering','Computer Science and Engineering','Electrical Engineering','Physics','Industrial and Materials Science'], +ROW( + 3421, + 3421 -- NULL + )::institution.institution_songs +) +; +-- ,( +-- 3, +-- 'University of Nowhere', +-- null, +-- null, +-- ARRAY ['nothing',null], +-- NULL +-- ); diff --git a/static/ndc-metadata-snapshots/c982ed0bbd9938aebd5a2dfc9298bb8221ece869da550dba1a24d733a007b7af/configuration.json b/static/ndc-metadata-snapshots/c982ed0bbd9938aebd5a2dfc9298bb8221ece869da550dba1a24d733a007b7af/configuration.json new file mode 100644 index 000000000..61372d7ba --- /dev/null +++ b/static/ndc-metadata-snapshots/c982ed0bbd9938aebd5a2dfc9298bb8221ece869da550dba1a24d733a007b7af/configuration.json @@ -0,0 +1,4261 @@ +{ + "version": "5", + "$schema": "../../schema.json", + "connectionSettings": { + "connectionUri": { + "variable": "CONNECTION_URI" + }, + "poolSettings": { + "maxConnections": 50, + "poolTimeout": 30, + "idleTimeout": 180, + "checkConnectionAfterIdle": 60, + "connectionLifetime": 600 + }, + "isolationLevel": "ReadCommitted" + }, + "metadata": { + "tables": { + "Album": { + "schemaName": "public", + "tableName": "Album", + "columns": { + "AlbumId": { + "name": "AlbumId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": "The identifier of an album" + }, + "ArtistId": { + "name": "ArtistId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": "The id of the artist that authored the album" + }, + "Title": { + "name": "Title", + "type": { + "scalarType": "varchar" + }, + "nullable": "nonNullable", + "description": "The title of an album" + } + }, + "uniquenessConstraints": { + "PK_Album": ["AlbumId"] + }, + "foreignRelations": { + "FK_AlbumArtistId": { + "foreignSchema": "public", + "foreignTable": "Artist", + "columnMapping": { + "ArtistId": "ArtistId" + } + } + }, + "description": "The record of all albums" + }, + "Artist": { + "schemaName": "public", + "tableName": "Artist", + "columns": { + "ArtistId": { + "name": "ArtistId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": "The identifier of an artist" + }, + "Name": { + "name": "Name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": "The name of an artist" + } + }, + "uniquenessConstraints": { + "PK_Artist": ["ArtistId"] + }, + "foreignRelations": {}, + "description": "The record of all artists" + }, + "Customer": { + "schemaName": "public", + "tableName": "Customer", + "columns": { + "Address": { + "name": "Address", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "City": { + "name": "City", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "Company": { + "name": "Company", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "Country": { + "name": "Country", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "CustomerId": { + "name": "CustomerId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": "The identifier of customer" + }, + "Email": { + "name": "Email", + "type": { + "scalarType": "varchar" + }, + "nullable": "nonNullable", + "description": null + }, + "Fax": { + "name": "Fax", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "FirstName": { + "name": "FirstName", + "type": { + "scalarType": "varchar" + }, + "nullable": "nonNullable", + "description": "The first name of a customer" + }, + "LastName": { + "name": "LastName", + "type": { + "scalarType": "varchar" + }, + "nullable": "nonNullable", + "description": "The last name of a customer" + }, + "Phone": { + "name": "Phone", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "PostalCode": { + "name": "PostalCode", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "State": { + "name": "State", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "SupportRepId": { + "name": "SupportRepId", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": { + "PK_Customer": ["CustomerId"] + }, + "foreignRelations": { + "FK_CustomerSupportRepId": { + "foreignSchema": "public", + "foreignTable": "Employee", + "columnMapping": { + "SupportRepId": "EmployeeId" + } + } + }, + "description": "The record of all customers" + }, + "Employee": { + "schemaName": "public", + "tableName": "Employee", + "columns": { + "Address": { + "name": "Address", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "BirthDate": { + "name": "BirthDate", + "type": { + "scalarType": "timestamp" + }, + "nullable": "nullable", + "description": null + }, + "City": { + "name": "City", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "Country": { + "name": "Country", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "Email": { + "name": "Email", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "EmployeeId": { + "name": "EmployeeId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "Fax": { + "name": "Fax", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "FirstName": { + "name": "FirstName", + "type": { + "scalarType": "varchar" + }, + "nullable": "nonNullable", + "description": null + }, + "HireDate": { + "name": "HireDate", + "type": { + "scalarType": "timestamp" + }, + "nullable": "nullable", + "description": null + }, + "LastName": { + "name": "LastName", + "type": { + "scalarType": "varchar" + }, + "nullable": "nonNullable", + "description": null + }, + "Phone": { + "name": "Phone", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "PostalCode": { + "name": "PostalCode", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "ReportsTo": { + "name": "ReportsTo", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "State": { + "name": "State", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "Title": { + "name": "Title", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": { + "PK_Employee": ["EmployeeId"] + }, + "foreignRelations": { + "FK_EmployeeReportsTo": { + "foreignSchema": "public", + "foreignTable": "Employee", + "columnMapping": { + "ReportsTo": "EmployeeId" + } + } + }, + "description": null + }, + "Genre": { + "schemaName": "public", + "tableName": "Genre", + "columns": { + "GenreId": { + "name": "GenreId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "Name": { + "name": "Name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": { + "PK_Genre": ["GenreId"] + }, + "foreignRelations": {}, + "description": null + }, + "Invoice": { + "schemaName": "public", + "tableName": "Invoice", + "columns": { + "BillingAddress": { + "name": "BillingAddress", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "BillingCity": { + "name": "BillingCity", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "BillingCountry": { + "name": "BillingCountry", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "BillingPostalCode": { + "name": "BillingPostalCode", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "BillingState": { + "name": "BillingState", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "CustomerId": { + "name": "CustomerId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "InvoiceDate": { + "name": "InvoiceDate", + "type": { + "scalarType": "timestamp" + }, + "nullable": "nonNullable", + "description": null + }, + "InvoiceId": { + "name": "InvoiceId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "Total": { + "name": "Total", + "type": { + "scalarType": "numeric" + }, + "nullable": "nonNullable", + "description": null + } + }, + "uniquenessConstraints": { + "PK_Invoice": ["InvoiceId"] + }, + "foreignRelations": { + "FK_InvoiceCustomerId": { + "foreignSchema": "public", + "foreignTable": "Customer", + "columnMapping": { + "CustomerId": "CustomerId" + } + } + }, + "description": null + }, + "InvoiceLine": { + "schemaName": "public", + "tableName": "InvoiceLine", + "columns": { + "InvoiceId": { + "name": "InvoiceId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "InvoiceLineId": { + "name": "InvoiceLineId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "Quantity": { + "name": "Quantity", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "TrackId": { + "name": "TrackId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "UnitPrice": { + "name": "UnitPrice", + "type": { + "scalarType": "numeric" + }, + "nullable": "nonNullable", + "description": null + } + }, + "uniquenessConstraints": { + "PK_InvoiceLine": ["InvoiceLineId"] + }, + "foreignRelations": { + "FK_InvoiceLineInvoiceId": { + "foreignSchema": "public", + "foreignTable": "Invoice", + "columnMapping": { + "InvoiceId": "InvoiceId" + } + }, + "FK_InvoiceLineTrackId": { + "foreignSchema": "public", + "foreignTable": "Track", + "columnMapping": { + "TrackId": "TrackId" + } + } + }, + "description": null + }, + "MediaType": { + "schemaName": "public", + "tableName": "MediaType", + "columns": { + "MediaTypeId": { + "name": "MediaTypeId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "Name": { + "name": "Name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": { + "PK_MediaType": ["MediaTypeId"] + }, + "foreignRelations": {}, + "description": null + }, + "Playlist": { + "schemaName": "public", + "tableName": "Playlist", + "columns": { + "Name": { + "name": "Name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "PlaylistId": { + "name": "PlaylistId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + } + }, + "uniquenessConstraints": { + "PK_Playlist": ["PlaylistId"] + }, + "foreignRelations": {}, + "description": null + }, + "PlaylistTrack": { + "schemaName": "public", + "tableName": "PlaylistTrack", + "columns": { + "PlaylistId": { + "name": "PlaylistId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "TrackId": { + "name": "TrackId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + } + }, + "uniquenessConstraints": { + "PK_PlaylistTrack": ["PlaylistId", "TrackId"] + }, + "foreignRelations": { + "FK_PlaylistTrackPlaylistId": { + "foreignSchema": "public", + "foreignTable": "Playlist", + "columnMapping": { + "PlaylistId": "PlaylistId" + } + }, + "FK_PlaylistTrackTrackId": { + "foreignSchema": "public", + "foreignTable": "Track", + "columnMapping": { + "TrackId": "TrackId" + } + } + }, + "description": null + }, + "Track": { + "schemaName": "public", + "tableName": "Track", + "columns": { + "AlbumId": { + "name": "AlbumId", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "Bytes": { + "name": "Bytes", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "Composer": { + "name": "Composer", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "GenreId": { + "name": "GenreId", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "MediaTypeId": { + "name": "MediaTypeId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "Milliseconds": { + "name": "Milliseconds", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "Name": { + "name": "Name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nonNullable", + "description": null + }, + "TrackId": { + "name": "TrackId", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "UnitPrice": { + "name": "UnitPrice", + "type": { + "scalarType": "numeric" + }, + "nullable": "nonNullable", + "description": null + } + }, + "uniquenessConstraints": { + "PK_Track": ["TrackId"] + }, + "foreignRelations": { + "FK_TrackAlbumId": { + "foreignSchema": "public", + "foreignTable": "Album", + "columnMapping": { + "AlbumId": "AlbumId" + } + }, + "FK_TrackGenreId": { + "foreignSchema": "public", + "foreignTable": "Genre", + "columnMapping": { + "GenreId": "GenreId" + } + }, + "FK_TrackMediaTypeId": { + "foreignSchema": "public", + "foreignTable": "MediaType", + "columnMapping": { + "MediaTypeId": "MediaTypeId" + } + } + }, + "description": null + }, + "custom_defaults": { + "schemaName": "custom", + "tableName": "defaults", + "columns": { + "birthday": { + "name": "birthday", + "type": { + "scalarType": "date" + }, + "nullable": "nonNullable", + "hasDefault": "hasDefault", + "description": null + }, + "height_cm": { + "name": "height_cm", + "type": { + "scalarType": "numeric" + }, + "nullable": "nonNullable", + "hasDefault": "hasDefault", + "description": null + }, + "height_in": { + "name": "height_in", + "type": { + "scalarType": "numeric" + }, + "nullable": "nullable", + "hasDefault": "hasDefault", + "isGenerated": "stored", + "description": null + }, + "id": { + "name": "id", + "type": { + "scalarType": "int8" + }, + "nullable": "nonNullable", + "isIdentity": "identityAlways", + "description": null + }, + "name": { + "name": "name", + "type": { + "scalarType": "text" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": { + "defaults_pkey": ["id"] + }, + "foreignRelations": {}, + "description": null + }, + "custom_dog": { + "schemaName": "custom", + "tableName": "dog", + "columns": { + "adopter_name": { + "name": "adopter_name", + "type": { + "scalarType": "text" + }, + "nullable": "nullable", + "description": null + }, + "birthday": { + "name": "birthday", + "type": { + "scalarType": "date" + }, + "nullable": "nonNullable", + "hasDefault": "hasDefault", + "description": null + }, + "height_cm": { + "name": "height_cm", + "type": { + "scalarType": "numeric" + }, + "nullable": "nonNullable", + "description": null + }, + "height_in": { + "name": "height_in", + "type": { + "scalarType": "numeric" + }, + "nullable": "nullable", + "hasDefault": "hasDefault", + "isGenerated": "stored", + "description": null + }, + "id": { + "name": "id", + "type": { + "scalarType": "int8" + }, + "nullable": "nonNullable", + "isIdentity": "identityAlways", + "description": null + }, + "name": { + "name": "name", + "type": { + "scalarType": "text" + }, + "nullable": "nonNullable", + "description": null + } + }, + "uniquenessConstraints": { + "dog_pkey": ["id"] + }, + "foreignRelations": {}, + "description": null + }, + "custom_test_cidr": { + "schemaName": "custom", + "tableName": "test_cidr", + "columns": { + "ip": { + "name": "ip", + "type": { + "scalarType": "cidr" + }, + "nullable": "nullable", + "description": null + }, + "service": { + "name": "service", + "type": { + "scalarType": "text" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": {}, + "foreignRelations": {}, + "description": null + }, + "deck_of_cards": { + "schemaName": "public", + "tableName": "deck_of_cards", + "columns": { + "pips": { + "name": "pips", + "type": { + "scalarType": "int2" + }, + "nullable": "nonNullable", + "description": null + }, + "suit": { + "name": "suit", + "type": { + "scalarType": "card_suit" + }, + "nullable": "nonNullable", + "description": null + } + }, + "uniquenessConstraints": {}, + "foreignRelations": {}, + "description": null + }, + "discoverable_types_root_occurrence": { + "schemaName": "public", + "tableName": "discoverable_types_root_occurrence", + "columns": { + "col": { + "name": "col", + "type": { + "compositeType": "discoverable_types" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": {}, + "foreignRelations": {}, + "description": null + }, + "even_numbers": { + "schemaName": "public", + "tableName": "even_numbers", + "columns": { + "the_number": { + "name": "the_number", + "type": { + "scalarType": "even_number" + }, + "nullable": "nonNullable", + "description": null + } + }, + "uniquenessConstraints": {}, + "foreignRelations": {}, + "description": null + }, + "group_leader": { + "schemaName": "public", + "tableName": "group_leader", + "columns": { + "characters": { + "name": "characters", + "type": { + "compositeType": "characters" + }, + "nullable": "nullable", + "description": null + }, + "id": { + "name": "id", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "name": { + "name": "name", + "type": { + "compositeType": "chara" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": {}, + "foreignRelations": {}, + "description": null + }, + "phone_numbers": { + "schemaName": "public", + "tableName": "phone_numbers", + "columns": { + "the_number": { + "name": "the_number", + "type": { + "scalarType": "Phone" + }, + "nullable": "nonNullable", + "description": null + } + }, + "uniquenessConstraints": {}, + "foreignRelations": {}, + "description": null + }, + "spatial_ref_sys": { + "schemaName": "public", + "tableName": "spatial_ref_sys", + "columns": { + "auth_name": { + "name": "auth_name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "auth_srid": { + "name": "auth_srid", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "proj4text": { + "name": "proj4text", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + }, + "srid": { + "name": "srid", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "srtext": { + "name": "srtext", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": { + "spatial_ref_sys_pkey": ["srid"] + }, + "foreignRelations": {}, + "description": null + }, + "topology_layer": { + "schemaName": "topology", + "tableName": "layer", + "columns": { + "child_id": { + "name": "child_id", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "feature_column": { + "name": "feature_column", + "type": { + "scalarType": "varchar" + }, + "nullable": "nonNullable", + "description": null + }, + "feature_type": { + "name": "feature_type", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "layer_id": { + "name": "layer_id", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "level": { + "name": "level", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "hasDefault": "hasDefault", + "description": null + }, + "schema_name": { + "name": "schema_name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nonNullable", + "description": null + }, + "table_name": { + "name": "table_name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nonNullable", + "description": null + }, + "topology_id": { + "name": "topology_id", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + } + }, + "uniquenessConstraints": { + "layer_pkey": ["layer_id", "topology_id"], + "layer_schema_name_table_name_feature_column_key": [ + "feature_column", + "schema_name", + "table_name" + ] + }, + "foreignRelations": { + "layer_topology_id_fkey": { + "foreignSchema": "topology", + "foreignTable": "topology", + "columnMapping": { + "topology_id": "id" + } + } + }, + "description": null + }, + "topology_topology": { + "schemaName": "topology", + "tableName": "topology", + "columns": { + "hasz": { + "name": "hasz", + "type": { + "scalarType": "bool" + }, + "nullable": "nonNullable", + "hasDefault": "hasDefault", + "description": null + }, + "id": { + "name": "id", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "hasDefault": "hasDefault", + "description": null + }, + "name": { + "name": "name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nonNullable", + "description": null + }, + "precision": { + "name": "precision", + "type": { + "scalarType": "float8" + }, + "nullable": "nonNullable", + "description": null + }, + "srid": { + "name": "srid", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + } + }, + "uniquenessConstraints": { + "topology_name_key": ["name"], + "topology_pkey": ["id"] + }, + "foreignRelations": {}, + "description": null + } + }, + "types": { + "scalar": { + "Phone": { + "typeName": "Phone", + "schemaName": "public", + "description": null, + "aggregateFunctions": { + "max": { + "returnType": "text" + }, + "min": { + "returnType": "text" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "Phone", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "_ilike": { + "operatorName": "~~*", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "Phone", + "isInfix": true + }, + "_iregex": { + "operatorName": "~*", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "_like": { + "operatorName": "~~", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "_nilike": { + "operatorName": "!~~*", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "_niregex": { + "operatorName": "!~*", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "_nlike": { + "operatorName": "!~~", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "_nregex": { + "operatorName": "!~", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "_regex": { + "operatorName": "~", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": true + }, + "st_coveredby": { + "operatorName": "st_coveredby", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": false + }, + "st_covers": { + "operatorName": "st_covers", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": false + }, + "st_intersects": { + "operatorName": "st_intersects", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": false + }, + "st_relatematch": { + "operatorName": "st_relatematch", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": false + }, + "starts_with": { + "operatorName": "starts_with", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": false + }, + "ts_match_tt": { + "operatorName": "ts_match_tt", + "operatorKind": "custom", + "argumentType": "Phone", + "isInfix": false + } + }, + "typeRepresentation": "string" + }, + "bool": { + "typeName": "bool", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "bool_and": { + "returnType": "bool" + }, + "bool_or": { + "returnType": "bool" + }, + "every": { + "returnType": "bool" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "bool", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "bool", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "bool", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "bool", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "bool", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "bool", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "bool", + "isInfix": true + } + }, + "typeRepresentation": "boolean" + }, + "card_suit": { + "typeName": "card_suit", + "schemaName": "public", + "description": null, + "aggregateFunctions": { + "max": { + "returnType": "card_suit" + }, + "min": { + "returnType": "card_suit" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "card_suit", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "card_suit", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "card_suit", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "card_suit", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "card_suit", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "card_suit", + "isInfix": true + }, + "_neq": { + "operatorName": "!=", + "operatorKind": "custom", + "argumentType": "card_suit", + "isInfix": true + } + }, + "typeRepresentation": { + "enum": ["hearts", "clubs", "diamonds", "spades"] + } + }, + "char": { + "typeName": "char", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "max": { + "returnType": "text" + }, + "min": { + "returnType": "text" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "char", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "_ilike": { + "operatorName": "~~*", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "char", + "isInfix": true + }, + "_iregex": { + "operatorName": "~*", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "_like": { + "operatorName": "~~", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "_nilike": { + "operatorName": "!~~*", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "_niregex": { + "operatorName": "!~*", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "_nlike": { + "operatorName": "!~~", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "_nregex": { + "operatorName": "!~", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "_regex": { + "operatorName": "~", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": true + }, + "st_coveredby": { + "operatorName": "st_coveredby", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": false + }, + "st_covers": { + "operatorName": "st_covers", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": false + }, + "st_intersects": { + "operatorName": "st_intersects", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": false + }, + "st_relatematch": { + "operatorName": "st_relatematch", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": false + }, + "starts_with": { + "operatorName": "starts_with", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": false + }, + "ts_match_tt": { + "operatorName": "ts_match_tt", + "operatorKind": "custom", + "argumentType": "char", + "isInfix": false + } + }, + "typeRepresentation": "string" + }, + "cidr": { + "typeName": "cidr", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "max": { + "returnType": "inet" + }, + "min": { + "returnType": "inet" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "cidr", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "cidr", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "cidr", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "cidr", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "cidr", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "cidr", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "cidr", + "isInfix": true + }, + "inet_same_family": { + "operatorName": "inet_same_family", + "operatorKind": "custom", + "argumentType": "cidr", + "isInfix": false + }, + "network_overlap": { + "operatorName": "network_overlap", + "operatorKind": "custom", + "argumentType": "cidr", + "isInfix": false + }, + "network_sub": { + "operatorName": "network_sub", + "operatorKind": "custom", + "argumentType": "cidr", + "isInfix": false + }, + "network_subeq": { + "operatorName": "network_subeq", + "operatorKind": "custom", + "argumentType": "cidr", + "isInfix": false + }, + "network_sup": { + "operatorName": "network_sup", + "operatorKind": "custom", + "argumentType": "cidr", + "isInfix": false + }, + "network_supeq": { + "operatorName": "network_supeq", + "operatorKind": "custom", + "argumentType": "cidr", + "isInfix": false + } + }, + "typeRepresentation": null + }, + "date": { + "typeName": "date", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "max": { + "returnType": "date" + }, + "min": { + "returnType": "date" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "date", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "date", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "date", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "date", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "date", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "date", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "date", + "isInfix": true + } + }, + "typeRepresentation": "date" + }, + "even_number": { + "typeName": "even_number", + "schemaName": "public", + "description": null, + "aggregateFunctions": { + "avg": { + "returnType": "numeric" + }, + "bit_and": { + "returnType": "int4" + }, + "bit_or": { + "returnType": "int4" + }, + "bit_xor": { + "returnType": "int4" + }, + "max": { + "returnType": "int4" + }, + "min": { + "returnType": "int4" + }, + "stddev": { + "returnType": "numeric" + }, + "stddev_pop": { + "returnType": "numeric" + }, + "stddev_samp": { + "returnType": "numeric" + }, + "sum": { + "returnType": "int8" + }, + "var_pop": { + "returnType": "numeric" + }, + "var_samp": { + "returnType": "numeric" + }, + "variance": { + "returnType": "numeric" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "even_number", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "even_number", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "even_number", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "even_number", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "even_number", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "even_number", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "even_number", + "isInfix": true + } + }, + "typeRepresentation": "int32" + }, + "float4": { + "typeName": "float4", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "avg": { + "returnType": "float8" + }, + "max": { + "returnType": "float4" + }, + "min": { + "returnType": "float4" + }, + "stddev": { + "returnType": "float8" + }, + "stddev_pop": { + "returnType": "float8" + }, + "stddev_samp": { + "returnType": "float8" + }, + "sum": { + "returnType": "float4" + }, + "var_pop": { + "returnType": "float8" + }, + "var_samp": { + "returnType": "float8" + }, + "variance": { + "returnType": "float8" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "float4", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "float4", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "float4", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "float4", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "float4", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "float4", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "float4", + "isInfix": true + } + }, + "typeRepresentation": "float32" + }, + "float8": { + "typeName": "float8", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "avg": { + "returnType": "float8" + }, + "max": { + "returnType": "float8" + }, + "min": { + "returnType": "float8" + }, + "stddev": { + "returnType": "float8" + }, + "stddev_pop": { + "returnType": "float8" + }, + "stddev_samp": { + "returnType": "float8" + }, + "sum": { + "returnType": "float8" + }, + "var_pop": { + "returnType": "float8" + }, + "var_samp": { + "returnType": "float8" + }, + "variance": { + "returnType": "float8" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "float8", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "float8", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "float8", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "float8", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "float8", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "float8", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "float8", + "isInfix": true + } + }, + "typeRepresentation": "float64" + }, + "inet": { + "typeName": "inet", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "max": { + "returnType": "inet" + }, + "min": { + "returnType": "inet" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "inet", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "inet", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "inet", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "inet", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "inet", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "inet", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "inet", + "isInfix": true + }, + "inet_same_family": { + "operatorName": "inet_same_family", + "operatorKind": "custom", + "argumentType": "inet", + "isInfix": false + }, + "network_overlap": { + "operatorName": "network_overlap", + "operatorKind": "custom", + "argumentType": "inet", + "isInfix": false + }, + "network_sub": { + "operatorName": "network_sub", + "operatorKind": "custom", + "argumentType": "inet", + "isInfix": false + }, + "network_subeq": { + "operatorName": "network_subeq", + "operatorKind": "custom", + "argumentType": "inet", + "isInfix": false + }, + "network_sup": { + "operatorName": "network_sup", + "operatorKind": "custom", + "argumentType": "inet", + "isInfix": false + }, + "network_supeq": { + "operatorName": "network_supeq", + "operatorKind": "custom", + "argumentType": "inet", + "isInfix": false + } + }, + "typeRepresentation": null + }, + "int2": { + "typeName": "int2", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "avg": { + "returnType": "numeric" + }, + "bit_and": { + "returnType": "int2" + }, + "bit_or": { + "returnType": "int2" + }, + "bit_xor": { + "returnType": "int2" + }, + "max": { + "returnType": "int2" + }, + "min": { + "returnType": "int2" + }, + "stddev": { + "returnType": "numeric" + }, + "stddev_pop": { + "returnType": "numeric" + }, + "stddev_samp": { + "returnType": "numeric" + }, + "sum": { + "returnType": "int8" + }, + "var_pop": { + "returnType": "numeric" + }, + "var_samp": { + "returnType": "numeric" + }, + "variance": { + "returnType": "numeric" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "int2", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "int2", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "int2", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "int2", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "int2", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "int2", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "int2", + "isInfix": true + } + }, + "typeRepresentation": "int16" + }, + "int4": { + "typeName": "int4", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "avg": { + "returnType": "numeric" + }, + "bit_and": { + "returnType": "int4" + }, + "bit_or": { + "returnType": "int4" + }, + "bit_xor": { + "returnType": "int4" + }, + "max": { + "returnType": "int4" + }, + "min": { + "returnType": "int4" + }, + "stddev": { + "returnType": "numeric" + }, + "stddev_pop": { + "returnType": "numeric" + }, + "stddev_samp": { + "returnType": "numeric" + }, + "sum": { + "returnType": "int8" + }, + "var_pop": { + "returnType": "numeric" + }, + "var_samp": { + "returnType": "numeric" + }, + "variance": { + "returnType": "numeric" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "int4", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "int4", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "int4", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "int4", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "int4", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "int4", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "int4", + "isInfix": true + } + }, + "typeRepresentation": "int32" + }, + "int8": { + "typeName": "int8", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "avg": { + "returnType": "numeric" + }, + "bit_and": { + "returnType": "int8" + }, + "bit_or": { + "returnType": "int8" + }, + "bit_xor": { + "returnType": "int8" + }, + "max": { + "returnType": "int8" + }, + "min": { + "returnType": "int8" + }, + "stddev": { + "returnType": "numeric" + }, + "stddev_pop": { + "returnType": "numeric" + }, + "stddev_samp": { + "returnType": "numeric" + }, + "sum": { + "returnType": "numeric" + }, + "var_pop": { + "returnType": "numeric" + }, + "var_samp": { + "returnType": "numeric" + }, + "variance": { + "returnType": "numeric" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "int8", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "int8", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "int8", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "int8", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "int8", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "int8", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "int8", + "isInfix": true + } + }, + "typeRepresentation": "int64AsString" + }, + "interval": { + "typeName": "interval", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "avg": { + "returnType": "interval" + }, + "max": { + "returnType": "interval" + }, + "min": { + "returnType": "interval" + }, + "sum": { + "returnType": "interval" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "interval", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "interval", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "interval", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "interval", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "interval", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "interval", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "interval", + "isInfix": true + } + }, + "typeRepresentation": null + }, + "numeric": { + "typeName": "numeric", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "avg": { + "returnType": "numeric" + }, + "max": { + "returnType": "numeric" + }, + "min": { + "returnType": "numeric" + }, + "stddev": { + "returnType": "numeric" + }, + "stddev_pop": { + "returnType": "numeric" + }, + "stddev_samp": { + "returnType": "numeric" + }, + "sum": { + "returnType": "numeric" + }, + "var_pop": { + "returnType": "numeric" + }, + "var_samp": { + "returnType": "numeric" + }, + "variance": { + "returnType": "numeric" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "numeric", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "numeric", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "numeric", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "numeric", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "numeric", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "numeric", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "numeric", + "isInfix": true + } + }, + "typeRepresentation": "bigDecimalAsString" + }, + "text": { + "typeName": "text", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "max": { + "returnType": "text" + }, + "min": { + "returnType": "text" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "text", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "_ilike": { + "operatorName": "~~*", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "text", + "isInfix": true + }, + "_iregex": { + "operatorName": "~*", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "_like": { + "operatorName": "~~", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "_nilike": { + "operatorName": "!~~*", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "_niregex": { + "operatorName": "!~*", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "_nlike": { + "operatorName": "!~~", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "_nregex": { + "operatorName": "!~", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "_regex": { + "operatorName": "~", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": true + }, + "st_coveredby": { + "operatorName": "st_coveredby", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": false + }, + "st_covers": { + "operatorName": "st_covers", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": false + }, + "st_intersects": { + "operatorName": "st_intersects", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": false + }, + "st_relatematch": { + "operatorName": "st_relatematch", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": false + }, + "starts_with": { + "operatorName": "starts_with", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": false + }, + "ts_match_tt": { + "operatorName": "ts_match_tt", + "operatorKind": "custom", + "argumentType": "text", + "isInfix": false + } + }, + "typeRepresentation": "string" + }, + "time": { + "typeName": "time", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "avg": { + "returnType": "interval" + }, + "max": { + "returnType": "time" + }, + "min": { + "returnType": "time" + }, + "sum": { + "returnType": "interval" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "time", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "time", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "time", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "time", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "time", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "time", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "time", + "isInfix": true + } + }, + "typeRepresentation": "time" + }, + "timestamp": { + "typeName": "timestamp", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "max": { + "returnType": "timestamp" + }, + "min": { + "returnType": "timestamp" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "timestamp", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "timestamp", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "timestamp", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "timestamp", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "timestamp", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "timestamp", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "timestamp", + "isInfix": true + } + }, + "typeRepresentation": "timestamp" + }, + "timestamptz": { + "typeName": "timestamptz", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "max": { + "returnType": "timestamptz" + }, + "min": { + "returnType": "timestamptz" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "timestamptz", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "timestamptz", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "timestamptz", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "timestamptz", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "timestamptz", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "timestamptz", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "timestamptz", + "isInfix": true + } + }, + "typeRepresentation": "timestamptz" + }, + "timetz": { + "typeName": "timetz", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "max": { + "returnType": "timetz" + }, + "min": { + "returnType": "timetz" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "timetz", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "timetz", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "timetz", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "timetz", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "timetz", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "timetz", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "timetz", + "isInfix": true + } + }, + "typeRepresentation": "timetz" + }, + "uuid": { + "typeName": "uuid", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": {}, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "uuid", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "uuid", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "uuid", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "uuid", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "uuid", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "uuid", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "uuid", + "isInfix": true + } + }, + "typeRepresentation": "uUID" + }, + "varchar": { + "typeName": "varchar", + "schemaName": "pg_catalog", + "description": null, + "aggregateFunctions": { + "max": { + "returnType": "text" + }, + "min": { + "returnType": "text" + } + }, + "comparisonOperators": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "varchar", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "_ilike": { + "operatorName": "~~*", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "varchar", + "isInfix": true + }, + "_iregex": { + "operatorName": "~*", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "_like": { + "operatorName": "~~", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "_nilike": { + "operatorName": "!~~*", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "_niregex": { + "operatorName": "!~*", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "_nlike": { + "operatorName": "!~~", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "_nregex": { + "operatorName": "!~", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "_regex": { + "operatorName": "~", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": true + }, + "st_coveredby": { + "operatorName": "st_coveredby", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": false + }, + "st_covers": { + "operatorName": "st_covers", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": false + }, + "st_intersects": { + "operatorName": "st_intersects", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": false + }, + "st_relatematch": { + "operatorName": "st_relatematch", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": false + }, + "starts_with": { + "operatorName": "starts_with", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": false + }, + "ts_match_tt": { + "operatorName": "ts_match_tt", + "operatorKind": "custom", + "argumentType": "varchar", + "isInfix": false + } + }, + "typeRepresentation": "string" + } + }, + "composite": { + "chara": { + "typeName": "chara", + "schemaName": "public", + "fields": { + "name": { + "fieldName": "name", + "type": { + "scalarType": "text" + }, + "description": null + }, + "popularity": { + "fieldName": "popularity", + "type": { + "scalarType": "int8" + }, + "description": null + } + }, + "description": null + }, + "characters": { + "typeName": "characters", + "schemaName": "public", + "fields": { + "members": { + "fieldName": "members", + "type": { + "arrayType": { + "compositeType": "chara" + } + }, + "description": null + }, + "name": { + "fieldName": "name", + "type": { + "scalarType": "text" + }, + "description": null + } + }, + "description": null + }, + "committee": { + "typeName": "committee", + "schemaName": "public", + "fields": { + "members": { + "fieldName": "members", + "type": { + "arrayType": { + "compositeType": "person_name" + } + }, + "description": null + }, + "name": { + "fieldName": "name", + "type": { + "scalarType": "text" + }, + "description": null + } + }, + "description": null + }, + "discoverable_types": { + "typeName": "discoverable_types", + "schemaName": "public", + "fields": { + "only_occurring_here1": { + "fieldName": "only_occurring_here1", + "type": { + "scalarType": "int8" + }, + "description": null + } + }, + "description": null + }, + "organization": { + "typeName": "organization", + "schemaName": "public", + "fields": { + "committees": { + "fieldName": "committees", + "type": { + "arrayType": { + "compositeType": "committee" + } + }, + "description": null + }, + "name": { + "fieldName": "name", + "type": { + "scalarType": "text" + }, + "description": null + } + }, + "description": null + }, + "person": { + "typeName": "person", + "schemaName": "public", + "fields": { + "address": { + "fieldName": "address", + "type": { + "compositeType": "person_address" + }, + "description": null + }, + "name": { + "fieldName": "name", + "type": { + "compositeType": "person_name" + }, + "description": null + } + }, + "description": null + }, + "person_address": { + "typeName": "person_address", + "schemaName": "public", + "fields": { + "address_line_1": { + "fieldName": "address_line_1", + "type": { + "scalarType": "text" + }, + "description": "Address line No 1" + }, + "address_line_2": { + "fieldName": "address_line_2", + "type": { + "scalarType": "text" + }, + "description": "Address line No 2" + } + }, + "description": "The address of a person, obviously" + }, + "person_name": { + "typeName": "person_name", + "schemaName": "public", + "fields": { + "first_name": { + "fieldName": "first_name", + "type": { + "scalarType": "text" + }, + "description": "The first name of a person" + }, + "last_name": { + "fieldName": "last_name", + "type": { + "scalarType": "text" + }, + "description": "The last name of a person" + } + }, + "description": "The name of a person, obviously" + } + } + }, + "nativeOperations": { + "queries": { + "address_identity_function": { + "sql": { + "inline": "SELECT {{address}} as result" + }, + "columns": { + "result": { + "name": "result", + "type": { + "compositeType": "person_address" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": { + "address": { + "name": "address", + "type": { + "compositeType": "person_address" + }, + "nullable": "nullable", + "description": null + } + }, + "description": "A native query used to test support for composite types" + }, + "album_by_title": { + "sql": { + "inline": "SELECT * FROM public.\"Album\" WHERE \"Title\" LIKE {{title}} AND \"AlbumId\" < {{id}}" + }, + "columns": { + "AlbumId": { + "name": "AlbumId", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "ArtistId": { + "name": "ArtistId", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "Title": { + "name": "Title", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": { + "id": { + "name": "id", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "title": { + "name": "title", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "description": null + }, + "array_reverse": { + "sql": { + "inline": "SELECT array_agg(t.x) as reversed FROM (SELECT x FROM unnest({{array}}) WITH ORDINALITY AS t(x,ix) ORDER BY t.ix DESC) as t(x)" + }, + "columns": { + "reversed": { + "name": "reversed", + "type": { + "arrayType": { + "scalarType": "varchar" + } + }, + "nullable": "nullable", + "description": "The reversed array" + } + }, + "arguments": { + "array": { + "name": "array", + "type": { + "arrayType": { + "scalarType": "varchar" + } + }, + "nullable": "nonNullable", + "description": "The array to reverse. This is necessarily of a monomorphic type." + } + }, + "description": "A native query used to test support for arrays as inputs" + }, + "array_series": { + "sql": { + "inline": "SELECT 3 as three, array_agg(arr.series) AS series FROM (SELECT generate_series({{from}},{{to}}) AS series) AS arr" + }, + "columns": { + "series": { + "name": "series", + "type": { + "arrayType": { + "scalarType": "int4" + } + }, + "nullable": "nullable", + "description": null + }, + "three": { + "name": "three", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": { + "from": { + "name": "from", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "to": { + "name": "to", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + } + }, + "description": "A native query used to test support for arrays" + }, + "artist": { + "sql": { + "inline": "SELECT * FROM public.\"Artist\"" + }, + "columns": { + "ArtistId": { + "name": "ArtistId", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "Name": { + "name": "Name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": {}, + "description": null + }, + "artist_below_id": { + "sql": { + "inline": "SELECT * FROM public.\"Artist\" WHERE \"ArtistId\" < {{id}}" + }, + "columns": { + "id": { + "name": "ArtistId", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "name": { + "name": "Name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": { + "id": { + "name": "id", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + } + }, + "description": null + }, + "count_elements": { + "sql": { + "inline": "SELECT array_length({{array_argument}}, 1) as result" + }, + "columns": { + "result": { + "name": "result", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": { + "array_argument": { + "name": "array_argument", + "type": { + "arrayType": { + "scalarType": "text" + } + }, + "nullable": "nullable", + "description": null + } + }, + "description": "A native query used to test support array-valued variables" + }, + "make_person": { + "sql": { + "inline": "SELECT ROW({{name}}, {{address}})::person as result" + }, + "columns": { + "result": { + "name": "result", + "type": { + "compositeType": "person" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": { + "address": { + "name": "address", + "type": { + "compositeType": "person_address" + }, + "nullable": "nullable", + "description": null + }, + "name": { + "name": "name", + "type": { + "compositeType": "person_name" + }, + "nullable": "nullable", + "description": null + } + }, + "description": "A native query used to test support for composite types" + }, + "organization_identity_function": { + "sql": { + "inline": "SELECT {{organization}} as result_the_column" + }, + "columns": { + "result_the_field": { + "name": "result_the_column", + "type": { + "compositeType": "organization" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": { + "organization": { + "name": "organization", + "type": { + "compositeType": "organization" + }, + "nullable": "nullable", + "description": null + } + }, + "description": "A native query used to test support for composite types" + }, + "summarize_organizations": { + "sql": { + "file": "./native_queries/summarize_organizations.sql" + }, + "columns": { + "result": { + "name": "result", + "type": { + "scalarType": "text" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": { + "organizations": { + "name": "organizations", + "type": { + "arrayType": { + "compositeType": "organization" + } + }, + "nullable": "nullable", + "description": null + } + }, + "description": "A native query used to test support array-valued variables" + }, + "value_types": { + "sql": { + "inline": "SELECT {{bool}} as bool, {{int4}} as int4, {{int2}} as int2, {{int8}} as int8, {{float4}} as float4, {{float8}} as \"float8\", {{numeric}} as numeric, {{char}} as char, {{varchar}} as \"varchar\", {{text}} as text, {{date}} as date, {{time}} as time, {{timetz}} as timetz, {{timestamp}} as timestamp, {{timestamptz}} as timestamptz, {{uuid}} as uuid" + }, + "columns": { + "bool": { + "name": "bool", + "type": { + "scalarType": "bool" + }, + "nullable": "nullable", + "description": null + }, + "char": { + "name": "char", + "type": { + "scalarType": "char" + }, + "nullable": "nullable", + "description": null + }, + "date": { + "name": "date", + "type": { + "scalarType": "date" + }, + "nullable": "nullable", + "description": null + }, + "float4": { + "name": "float4", + "type": { + "scalarType": "float4" + }, + "nullable": "nullable", + "description": null + }, + "float8": { + "name": "float8", + "type": { + "scalarType": "float8" + }, + "nullable": "nullable", + "description": null + }, + "int2": { + "name": "int2", + "type": { + "scalarType": "int2" + }, + "nullable": "nullable", + "description": null + }, + "int4": { + "name": "int4", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "int8": { + "name": "int8", + "type": { + "scalarType": "int8" + }, + "nullable": "nullable", + "description": null + }, + "numeric": { + "name": "numeric", + "type": { + "scalarType": "numeric" + }, + "nullable": "nullable", + "description": null + }, + "text": { + "name": "text", + "type": { + "scalarType": "text" + }, + "nullable": "nullable", + "description": null + }, + "time": { + "name": "time", + "type": { + "scalarType": "time" + }, + "nullable": "nullable", + "description": null + }, + "timestamp": { + "name": "timestamp", + "type": { + "scalarType": "timestamp" + }, + "nullable": "nullable", + "description": null + }, + "timestamptz": { + "name": "timestamptz", + "type": { + "scalarType": "timestamptz" + }, + "nullable": "nullable", + "description": null + }, + "timetz": { + "name": "timetz", + "type": { + "scalarType": "timetz" + }, + "nullable": "nullable", + "description": null + }, + "uuid": { + "name": "uuid", + "type": { + "scalarType": "uuid" + }, + "nullable": "nullable", + "description": null + }, + "varchar": { + "name": "varchar", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": { + "bool": { + "name": "bool", + "type": { + "scalarType": "bool" + }, + "nullable": "nullable", + "description": null + }, + "char": { + "name": "char", + "type": { + "scalarType": "char" + }, + "nullable": "nullable", + "description": null + }, + "date": { + "name": "date", + "type": { + "scalarType": "date" + }, + "nullable": "nullable", + "description": null + }, + "float4": { + "name": "float4", + "type": { + "scalarType": "float4" + }, + "nullable": "nullable", + "description": null + }, + "float8": { + "name": "float8", + "type": { + "scalarType": "float8" + }, + "nullable": "nullable", + "description": null + }, + "int2": { + "name": "int2", + "type": { + "scalarType": "int2" + }, + "nullable": "nullable", + "description": null + }, + "int4": { + "name": "int4", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "int8": { + "name": "int8", + "type": { + "scalarType": "int8" + }, + "nullable": "nullable", + "description": null + }, + "numeric": { + "name": "numeric", + "type": { + "scalarType": "numeric" + }, + "nullable": "nullable", + "description": null + }, + "text": { + "name": "text", + "type": { + "scalarType": "text" + }, + "nullable": "nullable", + "description": null + }, + "time": { + "name": "time", + "type": { + "scalarType": "time" + }, + "nullable": "nullable", + "description": null + }, + "timestamp": { + "name": "timestamp", + "type": { + "scalarType": "timestamp" + }, + "nullable": "nullable", + "description": null + }, + "timestamptz": { + "name": "timestamptz", + "type": { + "scalarType": "timestamptz" + }, + "nullable": "nullable", + "description": null + }, + "timetz": { + "name": "timetz", + "type": { + "scalarType": "timetz" + }, + "nullable": "nullable", + "description": null + }, + "uuid": { + "name": "uuid", + "type": { + "scalarType": "uuid" + }, + "nullable": "nullable", + "description": null + }, + "varchar": { + "name": "varchar", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "description": null + } + }, + "mutations": { + "delete_playlist_track": { + "sql": { + "inline": "DELETE FROM public.\"PlaylistTrack\" WHERE \"TrackId\" = {{track_id}} RETURNING *" + }, + "columns": { + "PlaylistId": { + "name": "PlaylistId", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "TrackId": { + "name": "TrackId", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": { + "track_id": { + "name": "track_id", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + } + }, + "description": null + }, + "insert_album": { + "sql": { + "inline": "INSERT INTO public.\"Album\" VALUES({{id}}, {{title}}, {{artist_id}}) RETURNING *" + }, + "columns": { + "AlbumId": { + "name": "AlbumId", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "ArtistId": { + "name": "ArtistId", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "Title": { + "name": "Title", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": { + "artist_id": { + "name": "artist_id", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "id": { + "name": "id", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "title": { + "name": "title", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "description": null + }, + "insert_artist": { + "sql": { + "inline": "INSERT INTO public.\"Artist\" VALUES ({{id}}, {{name}}) RETURNING *" + }, + "columns": { + "ArtistId": { + "name": "ArtistId", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "Name": { + "name": "Name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "arguments": { + "id": { + "name": "id", + "type": { + "scalarType": "int4" + }, + "nullable": "nullable", + "description": null + }, + "name": { + "name": "name", + "type": { + "scalarType": "varchar" + }, + "nullable": "nullable", + "description": null + } + }, + "description": null + } + } + } + }, + "introspectionOptions": { + "excludedSchemas": [ + "information_schema", + "pg_catalog", + "tiger", + "crdb_internal", + "columnar", + "columnar_internal" + ], + "unqualifiedSchemasForTables": ["public"], + "unqualifiedSchemasForTypesAndProcedures": [ + "public", + "pg_catalog", + "tiger" + ], + "comparisonOperatorMapping": [ + { + "operatorName": "=", + "exposedName": "_eq", + "operatorKind": "equal" + }, + { + "operatorName": "<=", + "exposedName": "_lte", + "operatorKind": "custom" + }, + { + "operatorName": ">", + "exposedName": "_gt", + "operatorKind": "custom" + }, + { + "operatorName": ">=", + "exposedName": "_gte", + "operatorKind": "custom" + }, + { + "operatorName": "<", + "exposedName": "_lt", + "operatorKind": "custom" + }, + { + "operatorName": "<>", + "exposedName": "_neq", + "operatorKind": "custom" + }, + { + "operatorName": "!=", + "exposedName": "_neq", + "operatorKind": "custom" + }, + { + "operatorName": "LIKE", + "exposedName": "_like", + "operatorKind": "custom" + }, + { + "operatorName": "NOT LIKE", + "exposedName": "_nlike", + "operatorKind": "custom" + }, + { + "operatorName": "ILIKE", + "exposedName": "_ilike", + "operatorKind": "custom" + }, + { + "operatorName": "NOT ILIKE", + "exposedName": "_nilike", + "operatorKind": "custom" + }, + { + "operatorName": "SIMILAR TO", + "exposedName": "_similar", + "operatorKind": "custom" + }, + { + "operatorName": "NOT SIMILAR TO", + "exposedName": "_nsimilar", + "operatorKind": "custom" + }, + { + "operatorName": "~~", + "exposedName": "_like", + "operatorKind": "custom" + }, + { + "operatorName": "!~~", + "exposedName": "_nlike", + "operatorKind": "custom" + }, + { + "operatorName": "~~*", + "exposedName": "_ilike", + "operatorKind": "custom" + }, + { + "operatorName": "!~~*", + "exposedName": "_nilike", + "operatorKind": "custom" + }, + { + "operatorName": "~", + "exposedName": "_regex", + "operatorKind": "custom" + }, + { + "operatorName": "!~", + "exposedName": "_nregex", + "operatorKind": "custom" + }, + { + "operatorName": "~*", + "exposedName": "_iregex", + "operatorKind": "custom" + }, + { + "operatorName": "!~*", + "exposedName": "_niregex", + "operatorKind": "custom" + } + ], + "introspectPrefixFunctionComparisonOperators": [ + "box_above", + "box_below", + "box_contain", + "box_contain_pt", + "box_contained", + "box_left", + "box_overabove", + "box_overbelow", + "box_overlap", + "box_overleft", + "box_overright", + "box_right", + "box_same", + "circle_above", + "circle_below", + "circle_contain", + "circle_contain_pt", + "circle_contained", + "circle_left", + "circle_overabove", + "circle_overbelow", + "circle_overlap", + "circle_overleft", + "circle_overright", + "circle_right", + "circle_same", + "contains_2d", + "equals", + "geography_overlaps", + "geometry_above", + "geometry_below", + "geometry_contained_3d", + "geometry_contains", + "geometry_contains_3d", + "geometry_contains_nd", + "geometry_left", + "geometry_overabove", + "geometry_overbelow", + "geometry_overlaps", + "geometry_overlaps_3d", + "geometry_overlaps_nd", + "geometry_overleft", + "geometry_overright", + "geometry_right", + "geometry_same", + "geometry_same_3d", + "geometry_same_nd", + "geometry_within", + "geometry_within_nd", + "inet_same_family", + "inter_lb", + "inter_sb", + "inter_sl", + "is_contained_2d", + "ishorizontal", + "isparallel", + "isperp", + "isvertical", + "jsonb_contained", + "jsonb_contains", + "jsonb_exists", + "jsonb_path_exists_opr", + "jsonb_path_match_opr", + "line_intersect", + "line_parallel", + "line_perp", + "lseg_intersect", + "lseg_parallel", + "lseg_perp", + "network_overlap", + "network_sub", + "network_subeq", + "network_sup", + "network_supeq", + "on_pb", + "on_pl", + "on_ppath", + "on_ps", + "on_sb", + "on_sl", + "overlaps_2d", + "path_contain_pt", + "path_inter", + "point_above", + "point_below", + "point_horiz", + "point_left", + "point_right", + "point_vert", + "poly_above", + "poly_below", + "poly_contain", + "poly_contain_pt", + "poly_contained", + "poly_left", + "poly_overabove", + "poly_overbelow", + "poly_overlap", + "poly_overleft", + "poly_overright", + "poly_right", + "poly_same", + "pt_contained_poly", + "st_3dintersects", + "st_contains", + "st_containsproperly", + "st_coveredby", + "st_covers", + "st_crosses", + "st_disjoint", + "st_equals", + "st_intersects", + "st_isvalid", + "st_orderingequals", + "st_overlaps", + "st_relatematch", + "st_touches", + "st_within", + "starts_with", + "ts_match_qv", + "ts_match_tq", + "ts_match_tt", + "ts_match_vq", + "tsq_mcontained", + "tsq_mcontains", + "xmlexists", + "xmlvalidate", + "xpath_exists" + ], + "typeRepresentations": { + "bit": "string", + "bool": "boolean", + "bpchar": "string", + "char": "string", + "date": "date", + "float4": "float32", + "float8": "float64", + "int2": "int16", + "int4": "int32", + "int8": "int64AsString", + "numeric": "bigDecimalAsString", + "text": "string", + "time": "time", + "timestamp": "timestamp", + "timestamptz": "timestamptz", + "timetz": "timetz", + "uuid": "uUID", + "varchar": "string" + } + }, + "mutationsVersion": "v2" +} diff --git a/static/ndc-metadata-snapshots/c982ed0bbd9938aebd5a2dfc9298bb8221ece869da550dba1a24d733a007b7af/native_queries/summarize_organizations.sql b/static/ndc-metadata-snapshots/c982ed0bbd9938aebd5a2dfc9298bb8221ece869da550dba1a24d733a007b7af/native_queries/summarize_organizations.sql new file mode 100644 index 000000000..58c5e163d --- /dev/null +++ b/static/ndc-metadata-snapshots/c982ed0bbd9938aebd5a2dfc9298bb8221ece869da550dba1a24d733a007b7af/native_queries/summarize_organizations.sql @@ -0,0 +1,22 @@ +SELECT + 'The organization ' || org.name || ' has ' || no_committees :: text || ' committees, ' || 'the largest of which has ' || max_members || ' members.' AS result +FROM + ( + SELECT + orgs.* + FROM + unnest({{organizations}}) AS orgs + ) AS org + JOIN LATERAL ( + SELECT + count(committee.*) AS no_committees, + max(members_agg.no_members) AS max_members + FROM + unnest(org.committees) AS committee + JOIN LATERAL ( + SELECT + count(*) AS no_members + FROM + unnest(committee.members) AS members + ) AS members_agg ON TRUE + ) AS coms ON TRUE diff --git a/static/postgres/v5-configuration/configuration.json b/static/postgres/v5-configuration/configuration.json index 61372d7ba..669535bcd 100644 --- a/static/postgres/v5-configuration/configuration.json +++ b/static/postgres/v5-configuration/configuration.json @@ -969,6 +969,69 @@ "foreignRelations": {}, "description": null }, + "institution_institution": { + "schemaName": "institution", + "tableName": "institution", + "columns": { + "departments": { + "name": "departments", + "type": { + "arrayType": { + "scalarType": "text" + } + }, + "nullable": "nullable", + "description": null + }, + "id": { + "name": "id", + "type": { + "scalarType": "int4" + }, + "nullable": "nonNullable", + "description": null + }, + "location": { + "name": "location", + "type": { + "compositeType": "institution_location" + }, + "nullable": "nullable", + "description": null + }, + "name": { + "name": "name", + "type": { + "scalarType": "text" + }, + "nullable": "nullable", + "description": null + }, + "songs": { + "name": "songs", + "type": { + "compositeType": "institution_institution_songs" + }, + "nullable": "nullable", + "description": null + }, + "staff": { + "name": "staff", + "type": { + "arrayType": { + "compositeType": "institution_staff" + } + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": { + "institution_pkey": ["id"] + }, + "foreignRelations": {}, + "description": null + }, "phone_numbers": { "schemaName": "public", "tableName": "phone_numbers", @@ -3164,6 +3227,115 @@ }, "description": null }, + "institution_country": { + "typeName": "country", + "schemaName": "institution", + "fields": { + "continent": { + "fieldName": "continent", + "type": { + "scalarType": "text" + }, + "description": null + }, + "name": { + "fieldName": "name", + "type": { + "scalarType": "text" + }, + "description": null + } + }, + "description": null + }, + "institution_institution_songs": { + "typeName": "institution_songs", + "schemaName": "institution", + "fields": { + "primary_anthem_track_id": { + "fieldName": "primary_anthem_track_id", + "type": { + "scalarType": "int4" + }, + "description": null + }, + "secondary_anthem_track_id": { + "fieldName": "secondary_anthem_track_id", + "type": { + "scalarType": "int4" + }, + "description": null + } + }, + "description": null + }, + "institution_location": { + "typeName": "location", + "schemaName": "institution", + "fields": { + "campuses": { + "fieldName": "campuses", + "type": { + "arrayType": { + "scalarType": "text" + } + }, + "description": null + }, + "city": { + "fieldName": "city", + "type": { + "scalarType": "text" + }, + "description": null + }, + "country": { + "fieldName": "country", + "type": { + "compositeType": "institution_country" + }, + "description": null + } + }, + "description": null + }, + "institution_staff": { + "typeName": "staff", + "schemaName": "institution", + "fields": { + "favourite_artist_id": { + "fieldName": "favourite_artist_id", + "type": { + "scalarType": "int4" + }, + "description": null + }, + "first_name": { + "fieldName": "first_name", + "type": { + "scalarType": "text" + }, + "description": null + }, + "last_name": { + "fieldName": "last_name", + "type": { + "scalarType": "text" + }, + "description": null + }, + "specialities": { + "fieldName": "specialities", + "type": { + "arrayType": { + "scalarType": "text" + } + }, + "description": null + } + }, + "description": null + }, "organization": { "typeName": "organization", "schemaName": "public", From 2ed1fec61e9cb325beb17f9f078705b42e259554 Mon Sep 17 00:00:00 2001 From: Gil Mizrahi Date: Thu, 8 Aug 2024 17:06:22 +0300 Subject: [PATCH 3/6] explain test and nicer table aliases for nested field bindings --- .../translation/src/translation/helpers.rs | 16 +++-- .../src/translation/query/fields.rs | 58 ++++++++++------- .../src/translation/query/filtering.rs | 3 +- .../translation/src/translation/query/root.rs | 2 +- .../src/translation/query/sorting.rs | 6 +- ...ests__select_composite_column_complex.snap | 18 ++--- ...tests__select_composite_column_simple.snap | 6 +- ...ts__select_composite_variable_complex.snap | 18 ++--- ...sts__select_composite_variable_simple.snap | 6 +- .../tests__select_nested_column_complex.snap | 36 ++++++---- .../tests__select_nested_column_simple.snap | 4 +- .../src/postgres/explain_tests.rs | 6 ++ ...sts__query__nested_field_relationship.snap | 65 +++++++++++++++++++ ...n_tests__query__order_by_nested_field.snap | 20 +++--- 14 files changed, 186 insertions(+), 78 deletions(-) create mode 100644 crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__explain_tests__query__nested_field_relationship.snap diff --git a/crates/query-engine/translation/src/translation/helpers.rs b/crates/query-engine/translation/src/translation/helpers.rs index c06007de6..c0dba6657 100644 --- a/crates/query-engine/translation/src/translation/helpers.rs +++ b/crates/query-engine/translation/src/translation/helpers.rs @@ -76,19 +76,23 @@ pub enum TableSource { Collection(models::CollectionName), /// Using the nested field path. NestedField { - collection_name: models::CollectionName, type_name: models::TypeName, + // These are used to create a nice table alias. + collection_name: models::CollectionName, field_path: FieldPath, }, } impl TableSource { - pub fn name(&self) -> String { + /// Generate a nice name that can be used to give a table alias for this source. + pub fn name_for_alias(&self) -> String { match self { - TableSource::Collection(collection_name) - | TableSource::NestedField { - collection_name, .. - } => collection_name.to_string(), + TableSource::Collection(collection_name) => collection_name.to_string(), + TableSource::NestedField { + collection_name, + field_path, + type_name: _, + } => format!("{collection_name}.{}", field_path.0.join(".")), } } } diff --git a/crates/query-engine/translation/src/translation/query/fields.rs b/crates/query-engine/translation/src/translation/query/fields.rs index b8c5616b8..c6f96c934 100644 --- a/crates/query-engine/translation/src/translation/query/fields.rs +++ b/crates/query-engine/translation/src/translation/query/fields.rs @@ -283,25 +283,26 @@ fn translate_nested_field( } }?; - // The FROM-clause to use for the next layer of fields returned by `translate_fields` below, - // which brings each nested field into scope as separate columns in a sub query. - let nested_field_binding_alias = state.make_table_alias("nested_field_binding".to_string()); - let nested_field_from = sql::ast::From::Select { - select: Box::new(sql::helpers::select_composite(field_binding_expression)), - alias: nested_field_binding_alias.clone(), - }; - // The recursive call to the next layer of fields - let nested_field_table_reference = { + let (nested_field_table_reference, nested_field_binding_alias) = { match ¤t_table.source { - TableSource::Collection(collection_name) => TableNameAndReference { - source: TableSource::NestedField { + TableSource::Collection(collection_name) => { + let source = TableSource::NestedField { collection_name: collection_name.clone(), type_name: nested_field_type_name, - field_path: FieldPath(vec![]), - }, - reference: sql::ast::TableReference::AliasedTable(nested_field_binding_alias), - }, + field_path: FieldPath(vec![current_column_name.clone()]), + }; + let nested_field_binding_alias = state.make_table_alias(source.name_for_alias()); + ( + TableNameAndReference { + source, + reference: sql::ast::TableReference::AliasedTable( + nested_field_binding_alias.clone(), + ), + }, + nested_field_binding_alias, + ) + } TableSource::NestedField { collection_name, type_name: _, @@ -309,17 +310,30 @@ fn translate_nested_field( } => { let mut field_path = field_path.0.clone(); field_path.push(current_column_name.clone()); - TableNameAndReference { - source: TableSource::NestedField { - collection_name: collection_name.clone(), - type_name: nested_field_type_name, - field_path: FieldPath(field_path), + let source = TableSource::NestedField { + collection_name: collection_name.clone(), + type_name: nested_field_type_name, + field_path: FieldPath(field_path), + }; + let nested_field_binding_alias = state.make_table_alias(source.name_for_alias()); + ( + TableNameAndReference { + source, + reference: sql::ast::TableReference::AliasedTable( + nested_field_binding_alias.clone(), + ), }, - reference: sql::ast::TableReference::AliasedTable(nested_field_binding_alias), - } + nested_field_binding_alias, + ) } } }; + // The FROM-clause to use for the next layer of fields returned by `translate_fields` below, + // which brings each nested field into scope as separate columns in a sub query. + let nested_field_from = sql::ast::From::Select { + select: Box::new(sql::helpers::select_composite(field_binding_expression)), + alias: nested_field_binding_alias, + }; // join aliases let mut join_relationship_fields: Vec = vec![]; diff --git a/crates/query-engine/translation/src/translation/query/filtering.rs b/crates/query-engine/translation/src/translation/query/filtering.rs index 1e4b36a18..17418f0f1 100644 --- a/crates/query-engine/translation/src/translation/query/filtering.rs +++ b/crates/query-engine/translation/src/translation/query/filtering.rs @@ -407,7 +407,8 @@ fn translate_comparison_pathelements( outer_select.from = Some(sql::ast::From::Select { select, alias }); outer_select.joins = joins.into(); - let alias = state.make_boolean_expression_table_alias(final_ref.source.name().as_str()); + let alias = state + .make_boolean_expression_table_alias(final_ref.source.name_for_alias().as_str()); let reference = sql::ast::TableReference::AliasedTable(alias.clone()); Ok(( diff --git a/crates/query-engine/translation/src/translation/query/root.rs b/crates/query-engine/translation/src/translation/query/root.rs index d6012a61f..a3ee55fcf 100644 --- a/crates/query-engine/translation/src/translation/query/root.rs +++ b/crates/query-engine/translation/src/translation/query/root.rs @@ -77,7 +77,7 @@ fn translate_aggregates( // So we wrap this query part in another query that performs the aggregation. // Create a from clause selecting from the inner query. - let from_alias = state.make_table_alias(table.source.name()); + let from_alias = state.make_table_alias(table.source.name_for_alias()); let from = sql::ast::From::Select { select: Box::new(inner_query), alias: from_alias.clone(), diff --git a/crates/query-engine/translation/src/translation/query/sorting.rs b/crates/query-engine/translation/src/translation/query/sorting.rs index b014c8f7e..eb6afd034 100644 --- a/crates/query-engine/translation/src/translation/query/sorting.rs +++ b/crates/query-engine/translation/src/translation/query/sorting.rs @@ -275,7 +275,11 @@ fn translate_order_by_target_group( ColumnsOrSelect::Select { columns, select } => { // Give it a nice unique alias. let table_alias = state.make_order_by_table_alias( - root_and_current_tables.current_table.source.name().as_str(), + root_and_current_tables + .current_table + .source + .name_for_alias() + .as_str(), ); // Build a join and push it to the accumulated joins. diff --git a/crates/query-engine/translation/tests/snapshots/tests__select_composite_column_complex.snap b/crates/query-engine/translation/tests/snapshots/tests__select_composite_column_complex.snap index 1a76881f3..73971601d 100644 --- a/crates/query-engine/translation/tests/snapshots/tests__select_composite_column_complex.snap +++ b/crates/query-engine/translation/tests/snapshots/tests__select_composite_column_complex.snap @@ -43,20 +43,20 @@ FROM ( SELECT ("%0_make_person"."result").* - ) AS "%3_nested_field_binding" + ) AS "%3_make_person.result" LEFT OUTER JOIN LATERAL ( SELECT row_to_json("%4_nested_fields") AS "collected" FROM ( SELECT - "%5_nested_field_binding"."address_line_1" AS "address_line_1", - "%5_nested_field_binding"."address_line_2" AS "address_line_2" + "%5_make_person.result.address"."address_line_1" AS "address_line_1", + "%5_make_person.result.address"."address_line_2" AS "address_line_2" FROM ( SELECT - ("%3_nested_field_binding"."address").* - ) AS "%5_nested_field_binding" + ("%3_make_person.result"."address").* + ) AS "%5_make_person.result.address" ) AS "%4_nested_fields" ) AS "%6_nested_fields_collect" ON ('true') LEFT OUTER JOIN LATERAL ( @@ -65,13 +65,13 @@ FROM FROM ( SELECT - "%8_nested_field_binding"."first_name" AS "first_name", - "%8_nested_field_binding"."last_name" AS "last_name" + "%8_make_person.result.name"."first_name" AS "first_name", + "%8_make_person.result.name"."last_name" AS "last_name" FROM ( SELECT - ("%3_nested_field_binding"."name").* - ) AS "%8_nested_field_binding" + ("%3_make_person.result"."name").* + ) AS "%8_make_person.result.name" ) AS "%7_nested_fields" ) AS "%9_nested_fields_collect" ON ('true') ) AS "%2_nested_fields" diff --git a/crates/query-engine/translation/tests/snapshots/tests__select_composite_column_simple.snap b/crates/query-engine/translation/tests/snapshots/tests__select_composite_column_simple.snap index fcb119376..202d57daa 100644 --- a/crates/query-engine/translation/tests/snapshots/tests__select_composite_column_simple.snap +++ b/crates/query-engine/translation/tests/snapshots/tests__select_composite_column_simple.snap @@ -34,13 +34,13 @@ FROM FROM ( SELECT - "%3_nested_field_binding"."address_line_1" AS "address_line_1", - "%3_nested_field_binding"."address_line_2" AS "address_line_2" + "%3_address_identity_function.result"."address_line_1" AS "address_line_1", + "%3_address_identity_function.result"."address_line_2" AS "address_line_2" FROM ( SELECT ("%0_address_identity_function"."result").* - ) AS "%3_nested_field_binding" + ) AS "%3_address_identity_function.result" ) AS "%2_nested_fields" ) AS "%4_nested_fields_collect" ON ('true') ) AS "%6_rows" diff --git a/crates/query-engine/translation/tests/snapshots/tests__select_composite_variable_complex.snap b/crates/query-engine/translation/tests/snapshots/tests__select_composite_variable_complex.snap index caa59ad09..16274c0f8 100644 --- a/crates/query-engine/translation/tests/snapshots/tests__select_composite_variable_complex.snap +++ b/crates/query-engine/translation/tests/snapshots/tests__select_composite_variable_complex.snap @@ -54,20 +54,20 @@ FROM ( SELECT ("%1_make_person"."result").* - ) AS "%4_nested_field_binding" + ) AS "%4_make_person.result" LEFT OUTER JOIN LATERAL ( SELECT row_to_json("%5_nested_fields") AS "collected" FROM ( SELECT - "%6_nested_field_binding"."address_line_1" AS "address_line_1", - "%6_nested_field_binding"."address_line_2" AS "address_line_2" + "%6_make_person.result.address"."address_line_1" AS "address_line_1", + "%6_make_person.result.address"."address_line_2" AS "address_line_2" FROM ( SELECT - ("%4_nested_field_binding"."address").* - ) AS "%6_nested_field_binding" + ("%4_make_person.result"."address").* + ) AS "%6_make_person.result.address" ) AS "%5_nested_fields" ) AS "%7_nested_fields_collect" ON ('true') LEFT OUTER JOIN LATERAL ( @@ -76,13 +76,13 @@ FROM FROM ( SELECT - "%9_nested_field_binding"."first_name" AS "first_name", - "%9_nested_field_binding"."last_name" AS "last_name" + "%9_make_person.result.name"."first_name" AS "first_name", + "%9_make_person.result.name"."last_name" AS "last_name" FROM ( SELECT - ("%4_nested_field_binding"."name").* - ) AS "%9_nested_field_binding" + ("%4_make_person.result"."name").* + ) AS "%9_make_person.result.name" ) AS "%8_nested_fields" ) AS "%10_nested_fields_collect" ON ('true') ) AS "%3_nested_fields" diff --git a/crates/query-engine/translation/tests/snapshots/tests__select_composite_variable_simple.snap b/crates/query-engine/translation/tests/snapshots/tests__select_composite_variable_simple.snap index 822206ac4..19471c483 100644 --- a/crates/query-engine/translation/tests/snapshots/tests__select_composite_variable_simple.snap +++ b/crates/query-engine/translation/tests/snapshots/tests__select_composite_variable_simple.snap @@ -42,13 +42,13 @@ FROM FROM ( SELECT - "%4_nested_field_binding"."address_line_1" AS "address_line_1", - "%4_nested_field_binding"."address_line_2" AS "address_line_2" + "%4_address_identity_function.result"."address_line_1" AS "address_line_1", + "%4_address_identity_function.result"."address_line_2" AS "address_line_2" FROM ( SELECT ("%1_address_identity_function"."result").* - ) AS "%4_nested_field_binding" + ) AS "%4_address_identity_function.result" ) AS "%3_nested_fields" ) AS "%5_nested_fields_collect" ON ('true') ) AS "%7_rows" diff --git a/crates/query-engine/translation/tests/snapshots/tests__select_nested_column_complex.snap b/crates/query-engine/translation/tests/snapshots/tests__select_nested_column_complex.snap index afbfab04d..be3824e68 100644 --- a/crates/query-engine/translation/tests/snapshots/tests__select_nested_column_complex.snap +++ b/crates/query-engine/translation/tests/snapshots/tests__select_nested_column_complex.snap @@ -34,7 +34,7 @@ FROM FROM ( SELECT - "%3_nested_field_binding"."name" AS "name_of_the_org", + "%3_organization_identity_function.result_the_field"."name" AS "name_of_the_org", "%12_nested_fields_collect"."collected" AS "committees_of_the_org" FROM ( @@ -42,34 +42,42 @@ FROM ( "%0_organization_identity_function"."result_the_column" ).* - ) AS "%3_nested_field_binding" + ) AS "%3_organization_identity_function.result_the_field" LEFT OUTER JOIN LATERAL ( SELECT json_agg(row_to_json("%4_nested_fields")) AS "collected" FROM ( SELECT - "%5_nested_field_binding"."name" AS "name_of_the_committee", + "%5_organization_identity_function.result_the_field.committees"."name" AS "name_of_the_committee", "%8_nested_fields_collect"."collected" AS "members_of_the_committee", "%11_nested_fields_collect"."collected" AS "members_of_the_committee_last_names_only" FROM ( SELECT - (unnest("%3_nested_field_binding"."committees")).* - ) AS "%5_nested_field_binding" + ( + unnest( + "%3_organization_identity_function.result_the_field"."committees" + ) + ).* + ) AS "%5_organization_identity_function.result_the_field.committees" LEFT OUTER JOIN LATERAL ( SELECT json_agg(row_to_json("%6_nested_fields")) AS "collected" FROM ( SELECT - "%7_nested_field_binding"."first_name" AS "member_first_name", - "%7_nested_field_binding"."last_name" AS "member_last_name" + "%7_organization_identity_function.result_the_field.committees.members"."first_name" AS "member_first_name", + "%7_organization_identity_function.result_the_field.committees.members"."last_name" AS "member_last_name" FROM ( SELECT - (unnest("%5_nested_field_binding"."members")).* - ) AS "%7_nested_field_binding" + ( + unnest( + "%5_organization_identity_function.result_the_field.committees"."members" + ) + ).* + ) AS "%7_organization_identity_function.result_the_field.committees.members" ) AS "%6_nested_fields" ) AS "%8_nested_fields_collect" ON ('true') LEFT OUTER JOIN LATERAL ( @@ -78,12 +86,16 @@ FROM FROM ( SELECT - "%10_nested_field_binding"."last_name" AS "member_last_name" + "%10_organization_identity_function.result_the_field.committees.members"."last_name" AS "member_last_name" FROM ( SELECT - (unnest("%5_nested_field_binding"."members")).* - ) AS "%10_nested_field_binding" + ( + unnest( + "%5_organization_identity_function.result_the_field.committees"."members" + ) + ).* + ) AS "%10_organization_identity_function.result_the_field.committees.members" ) AS "%9_nested_fields" ) AS "%11_nested_fields_collect" ON ('true') ) AS "%4_nested_fields" diff --git a/crates/query-engine/translation/tests/snapshots/tests__select_nested_column_simple.snap b/crates/query-engine/translation/tests/snapshots/tests__select_nested_column_simple.snap index 791c57a2c..1fae44c4c 100644 --- a/crates/query-engine/translation/tests/snapshots/tests__select_nested_column_simple.snap +++ b/crates/query-engine/translation/tests/snapshots/tests__select_nested_column_simple.snap @@ -34,12 +34,12 @@ FROM FROM ( SELECT - "%3_nested_field_binding"."address_line_1" AS "the_first_line_of_the_address" + "%3_address_identity_function.result"."address_line_1" AS "the_first_line_of_the_address" FROM ( SELECT ("%0_address_identity_function"."result").* - ) AS "%3_nested_field_binding" + ) AS "%3_address_identity_function.result" ) AS "%2_nested_fields" ) AS "%4_nested_fields_collect" ON ('true') ) AS "%6_rows" diff --git a/crates/tests/databases-tests/src/postgres/explain_tests.rs b/crates/tests/databases-tests/src/postgres/explain_tests.rs index 0c1b7df12..2acb32db0 100644 --- a/crates/tests/databases-tests/src/postgres/explain_tests.rs +++ b/crates/tests/databases-tests/src/postgres/explain_tests.rs @@ -93,6 +93,12 @@ mod query { assert!(result.details.plan.is_empty()); insta::assert_snapshot!(result.details.query); } + + #[tokio::test] + async fn nested_field_relationship() { + let result = run_query_explain(create_router().await, "nested_field_relationship").await; + insta::assert_snapshot!(result.details.query); + } } #[cfg(test)] diff --git a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__explain_tests__query__nested_field_relationship.snap b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__explain_tests__query__nested_field_relationship.snap new file mode 100644 index 000000000..8cb880990 --- /dev/null +++ b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__explain_tests__query__nested_field_relationship.snap @@ -0,0 +1,65 @@ +--- +source: crates/tests/databases-tests/src/postgres/explain_tests.rs +expression: result.details.query +--- +EXPLAIN +SELECT + coalesce(json_agg(row_to_json("%8_universe")), '[]') AS "universe" +FROM + ( + SELECT + * + FROM + ( + SELECT + coalesce(json_agg(row_to_json("%9_rows")), '[]') AS "rows" + FROM + ( + SELECT + "%0_institution_institution"."name" AS "name", + "%7_nested_fields_collect"."collected" AS "staff" + FROM + "institution"."institution" AS "%0_institution_institution" + LEFT OUTER JOIN LATERAL ( + SELECT + json_agg(row_to_json("%1_nested_fields")) AS "collected" + FROM + ( + SELECT + "%3_RELATIONSHIP_favourite_artist"."favourite_artist" AS "favourite_artist" + FROM + ( + SELECT + (unnest("%0_institution_institution"."staff")).* + ) AS "%2_institution_institution.staff" + LEFT OUTER JOIN LATERAL ( + SELECT + row_to_json("%3_RELATIONSHIP_favourite_artist") AS "favourite_artist" + FROM + ( + SELECT + * + FROM + ( + SELECT + coalesce(json_agg(row_to_json("%5_rows")), '[]') AS "rows" + FROM + ( + SELECT + "%4_Artist"."ArtistId" AS "artist_id", + "%4_Artist"."Name" AS "name" + FROM + "public"."Artist" AS "%4_Artist" + WHERE + ( + "%2_institution_institution.staff"."favourite_artist_id" = "%4_Artist"."ArtistId" + ) + ) AS "%5_rows" + ) AS "%5_rows" + ) AS "%3_RELATIONSHIP_favourite_artist" + ) AS "%3_RELATIONSHIP_favourite_artist" ON ('true') + ) AS "%1_nested_fields" + ) AS "%7_nested_fields_collect" ON ('true') + ) AS "%9_rows" + ) AS "%9_rows" + ) AS "%8_universe" diff --git a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__explain_tests__query__order_by_nested_field.snap b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__explain_tests__query__order_by_nested_field.snap index 1ccb03aaa..1b360dc4f 100644 --- a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__explain_tests__query__order_by_nested_field.snap +++ b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__explain_tests__query__order_by_nested_field.snap @@ -26,13 +26,13 @@ FROM FROM ( SELECT - "%2_nested_field_binding"."name" AS "name", - cast("%2_nested_field_binding"."popularity" as "text") AS "popularity" + "%2_group_leader.name"."name" AS "name", + cast("%2_group_leader.name"."popularity" as "text") AS "popularity" FROM ( SELECT ("%0_group_leader"."name").* - ) AS "%2_nested_field_binding" + ) AS "%2_group_leader.name" ) AS "%1_nested_fields" ) AS "%3_nested_fields_collect" ON ('true') LEFT OUTER JOIN LATERAL ( @@ -42,25 +42,27 @@ FROM ( SELECT "%8_nested_fields_collect"."collected" AS "members", - "%5_nested_field_binding"."name" AS "name" + "%5_group_leader.characters"."name" AS "name" FROM ( SELECT ("%0_group_leader"."characters").* - ) AS "%5_nested_field_binding" + ) AS "%5_group_leader.characters" LEFT OUTER JOIN LATERAL ( SELECT json_agg(row_to_json("%6_nested_fields")) AS "collected" FROM ( SELECT - "%7_nested_field_binding"."name" AS "name", - cast("%7_nested_field_binding"."popularity" as "text") AS "popularity" + "%7_group_leader.characters.members"."name" AS "name", + cast( + "%7_group_leader.characters.members"."popularity" as "text" + ) AS "popularity" FROM ( SELECT - (unnest("%5_nested_field_binding"."members")).* - ) AS "%7_nested_field_binding" + (unnest("%5_group_leader.characters"."members")).* + ) AS "%7_group_leader.characters.members" ) AS "%6_nested_fields" ) AS "%8_nested_fields_collect" ON ('true') ) AS "%4_nested_fields" From 77c5aeb01a030eb3ebe3952d96bb39e28c78a4f8 Mon Sep 17 00:00:00 2001 From: Gil Mizrahi Date: Thu, 8 Aug 2024 17:11:27 +0300 Subject: [PATCH 4/6] changelog --- changelog.md | 3 + .../filter_by_nested_field_collection.json | 41 +++++++ docs/architecture/query.md | 112 ++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 crates/tests/tests-common/goldenfiles/filter_by_nested_field_collection.json create mode 100644 docs/architecture/query.md diff --git a/changelog.md b/changelog.md index b3a281c0f..6e56cc797 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,9 @@ ### Fixed +- Fix nested field relationships. + [#564](https://github.com/hasura/ndc-postgres/pull/564) + ## [v1.0.1] ### Added diff --git a/crates/tests/tests-common/goldenfiles/filter_by_nested_field_collection.json b/crates/tests/tests-common/goldenfiles/filter_by_nested_field_collection.json new file mode 100644 index 000000000..c6d1b5d8d --- /dev/null +++ b/crates/tests/tests-common/goldenfiles/filter_by_nested_field_collection.json @@ -0,0 +1,41 @@ +{ + "collection": "group_leader", + "query": { + "fields": { + "name": { + "type": "column", + "column": "name", + "arguments": {} + }, + "characters": { + "type": "column", + "column": "characters", + "arguments": {} + } + }, + "predicate": { + "type": "exists", + "in_collection": { + "type": "nested_collection", + "column_name": "characters", + "field_path": ["members"], + "arguments": {} + }, + "predicate": { + "type": "binary_comparison_operator", + "column": { + "type": "column", + "name": "name", + "path": [] + }, + "operator": "_like", + "value": { + "type": "scalar", + "value": "Sam" + } + } + } + }, + "arguments": {}, + "collection_relationships": {} +} diff --git a/docs/architecture/query.md b/docs/architecture/query.md new file mode 100644 index 000000000..65464843a --- /dev/null +++ b/docs/architecture/query.md @@ -0,0 +1,112 @@ +# Query translation + +Query translation is composed of three important components: + +1. Metadata - this includes information about the database, native queries, columns, etc. It comes from the ndc-postgres configuration which in turn is generated using database introspection. +2. SQL AST - this includes the structure of SQL queries that we generate as part of the translation process. +3. Translation step - which we will talk about here. + +The translation step essentially takes the metadata (ndc-postgres configuration) and an ndc-spec query request and generates an SQL execution plan to run against the database. + +The best way to gain some intuition regarding the process is to look at the finished product, which we can do by looking at translation tests. + +Let's look at an example request. In `request.json` we'll find the query request, and in `configuration.json` we'll find the configuration. + +This query requests a fetching the name of tracks for each album for each artist. +Notice how relationship definitions is a field of the request and not part of the metadata. + +Looking at the metadata, we'll find a table, `Album`, as well as the relevant types defined in the metadata. Remember - the ndc-postgres configuration is the source of knowledge of everything, including types and functions. + +Looking at the generated SQL we can see the following information: + +```sql +SELECT + -- An ndc-spec QueryResult returns a row for each variable set. + -- If there are no variables, only a single row in the array will be returned. + -- + -- This row contains up-to 3 fields: `rows`, `aggregates` and `groups`. + -- This part converts each row returned from the query and converts it to json. + coalesce(json_agg(row_to_json("%3_universe")), '[]') AS "universe" +FROM + ( + SELECT + * + FROM + -- Everything here is the "rows" part of the query. + ( + -- We convert each row to json and aggregate them into a json array. + -- We name it `rows` because we return the fields selection. + SELECT + coalesce(json_agg(row_to_json("%4_rows")), '[]') AS "rows" + FROM + -- We select the relevant fields and include the filtering, sorting, etc. + ( + SELECT + "%0_Album"."Title" AS "Title" -- Field selection + FROM + "public"."Album" AS "%0_Album" -- Collection + LIMIT + 5 -- Limit + OFFSET + 3 -- Offset + ) AS "%4_rows" + ) AS "%4_rows" + CROSS JOIN ( + -- Everything here is the "aggregates" part of the query. + SELECT + coalesce(row_to_json("%5_aggregates"), '[]') AS "aggregates" + FROM + ( + SELECT + COUNT(*) AS "how_many_albums" -- Aggregation + FROM + -- The query + ( + SELECT + "%1_Album".* + FROM + "public"."Album" AS "%1_Album" + LIMIT + 5 -- Same limit + OFFSET + 3 -- Same offset + ) AS "%2_Album" + ) AS "%5_aggregates" + ) AS "%5_aggregates" + ) AS "%3_universe"; +``` + +From looking at this generated SQL, we already see the general structure of the SQL: + +1. We have subqueries for each one of the `rows`, `aggregates` and `groups` fields. +2. We join these subqueries and then convert them to json. +3. we write a fairly straightforward query, then convert the rows to json, and aggregate them in a json array. + +(We'll later see that much of the complexity arises from relationship) + +When looking at the entry code in the `mod.rs` function `translate`, we can recognize this structure if we squint: + +1. We set up some environment and state. +2. We `root::translate_query` the `rows` and `aggregates` part. +3. We wrap the result in the universe/rows/aggregates json parts +4. We normalize the SQL AST. +5. We wrap those in an execution plan. + +```sh +crates/query-engine/translation/src/translation +├── error.rs +├── helpers.rs +├── mod.rs +├── mutation +│   └── ... +└── query + ├── aggregates.rs + ├── fields.rs + ├── filtering.rs + ├── mod.rs + ├── native_queries.rs + ├── relationships.rs + ├── root.rs + ├── sorting.rs + └── values.rs +``` From a45c09e6d793e0c2804d9fe0f91bed7848ca41c4 Mon Sep 17 00:00:00 2001 From: Gil Mizrahi Date: Thu, 8 Aug 2024 17:13:26 +0300 Subject: [PATCH 5/6] rename TableNameAndReference to TableSourceAndReference --- .../translation/src/translation/helpers.rs | 8 ++++---- .../src/translation/mutation/v2/delete.rs | 4 ++-- .../src/translation/mutation/v2/insert.rs | 4 ++-- .../src/translation/mutation/v2/update.rs | 4 ++-- .../translation/src/translation/query/fields.rs | 12 ++++++------ .../src/translation/query/filtering.rs | 12 ++++++------ .../src/translation/query/relationships.rs | 6 +++--- .../translation/src/translation/query/root.rs | 16 ++++++++-------- .../translation/src/translation/query/sorting.rs | 14 +++++++------- 9 files changed, 40 insertions(+), 40 deletions(-) diff --git a/crates/query-engine/translation/src/translation/helpers.rs b/crates/query-engine/translation/src/translation/helpers.rs index c0dba6657..157476425 100644 --- a/crates/query-engine/translation/src/translation/helpers.rs +++ b/crates/query-engine/translation/src/translation/helpers.rs @@ -53,16 +53,16 @@ pub struct NativeQueryInfo { #[derive(Debug, Clone, PartialEq, Eq)] pub struct RootAndCurrentTables { /// The root (top-most) table in the query. - pub root_table: TableNameAndReference, + pub root_table: TableSourceAndReference, /// The current table we are processing. - pub current_table: TableNameAndReference, + pub current_table: TableSourceAndReference, } /// For a table in the query, We'd like to track what is its reference in the query -/// (the name we can use to address them, an alias we generate), and what is their name in the +/// (the name we can use to address them, an alias we generate), and what is their source in the /// metadata (so we can get their information such as which columns are available for that table). #[derive(Debug, Clone, PartialEq, Eq)] -pub struct TableNameAndReference { +pub struct TableSourceAndReference { /// Table name for column lookup pub source: TableSource, /// Table alias to query from 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 595774971..bc3eb3cfa 100644 --- a/crates/query-engine/translation/src/translation/mutation/v2/delete.rs +++ b/crates/query-engine/translation/src/translation/mutation/v2/delete.rs @@ -1,7 +1,7 @@ //! Auto-generate delete mutations and translate them into sql ast. use crate::translation::error::Error; -use crate::translation::helpers::{self, TableNameAndReference}; +use crate::translation::helpers::{self, TableSourceAndReference}; use crate::translation::query::filtering; use crate::translation::query::values; use ndc_models as models; @@ -104,7 +104,7 @@ pub fn translate( let table_alias = state.make_table_alias(table_name.0.clone()); - let table_name_and_reference = TableNameAndReference { + let table_name_and_reference = TableSourceAndReference { source: helpers::TableSource::Collection(collection_name.clone()), reference: sql::ast::TableReference::AliasedTable(table_alias.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 3d8b2d109..e1c8684c6 100644 --- a/crates/query-engine/translation/src/translation/mutation/v2/insert.rs +++ b/crates/query-engine/translation/src/translation/mutation/v2/insert.rs @@ -1,7 +1,7 @@ //! Auto-generate insert mutations and translate them into sql ast. use crate::translation::error::Error; -use crate::translation::helpers::{self, TableNameAndReference}; +use crate::translation::helpers::{self, TableSourceAndReference}; use crate::translation::mutation::check_columns; use crate::translation::query::filtering; use crate::translation::query::values; @@ -215,7 +215,7 @@ pub fn translate( let (columns, from) = translate_objects_to_columns_and_values(env, state, mutation, object)?; - let table_name_and_reference = TableNameAndReference { + let table_name_and_reference = TableSourceAndReference { source: helpers::TableSource::Collection(mutation.collection_name.clone()), reference: sql::ast::TableReference::DBTable { schema: mutation.schema_name.clone(), 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 70ba6f84a..8384ddec5 100644 --- a/crates/query-engine/translation/src/translation/mutation/v2/update.rs +++ b/crates/query-engine/translation/src/translation/mutation/v2/update.rs @@ -1,7 +1,7 @@ //! Auto-generate update mutations and translate them into sql ast. use crate::translation::error::Error; -use crate::translation::helpers::{self, TableNameAndReference}; +use crate::translation::helpers::{self, TableSourceAndReference}; use crate::translation::mutation::check_columns; use crate::translation::query::filtering; use crate::translation::query::values; @@ -121,7 +121,7 @@ pub fn translate( let set = parse_update_columns(env, state, mutation, object)?; - let table_name_and_reference = TableNameAndReference { + let table_name_and_reference = TableSourceAndReference { source: helpers::TableSource::Collection(mutation.collection_name.clone()), reference: sql::ast::TableReference::DBTable { schema: mutation.schema_name.clone(), diff --git a/crates/query-engine/translation/src/translation/query/fields.rs b/crates/query-engine/translation/src/translation/query/fields.rs index c6f96c934..e4c272818 100644 --- a/crates/query-engine/translation/src/translation/query/fields.rs +++ b/crates/query-engine/translation/src/translation/query/fields.rs @@ -13,7 +13,7 @@ use crate::translation::error::UnsupportedCapabilities; use crate::translation::helpers::FieldPath; use crate::translation::helpers::FieldsInfo; use crate::translation::helpers::TableSource; -use crate::translation::helpers::{ColumnInfo, Env, State, TableNameAndReference}; +use crate::translation::helpers::{ColumnInfo, Env, State, TableSourceAndReference}; use query_engine_metadata::metadata::{Type, TypeRepresentation}; use query_engine_sql::sql; @@ -24,7 +24,7 @@ pub(crate) fn translate( env: &Env, state: &mut State, fields: IndexMap, - current_table: &TableNameAndReference, + current_table: &TableSourceAndReference, from: sql::ast::From, join_relationship_fields: &mut Vec, ) -> Result { @@ -176,7 +176,7 @@ fn translate_nested_field_joins(joins: Vec) -> Vec, diff --git a/crates/query-engine/translation/src/translation/query/filtering.rs b/crates/query-engine/translation/src/translation/query/filtering.rs index 17418f0f1..d1b4bbb16 100644 --- a/crates/query-engine/translation/src/translation/query/filtering.rs +++ b/crates/query-engine/translation/src/translation/query/filtering.rs @@ -14,7 +14,7 @@ use crate::translation::error::Error; use crate::translation::helpers::wrap_in_field_path; use crate::translation::helpers::TableSource; use crate::translation::helpers::{ - ColumnInfo, CompositeTypeInfo, Env, RootAndCurrentTables, State, TableNameAndReference, + ColumnInfo, CompositeTypeInfo, Env, RootAndCurrentTables, State, TableSourceAndReference, }; use query_engine_metadata::metadata::database; use query_engine_sql::sql; @@ -310,7 +310,7 @@ fn translate_comparison_pathelements( state: &mut State, root_and_current_tables: &RootAndCurrentTables, path: &[models::PathElement], -) -> Result<(TableNameAndReference, Vec), Error> { +) -> Result<(TableSourceAndReference, Vec), Error> { let mut joins = vec![]; let RootAndCurrentTables { current_table, .. } = root_and_current_tables; @@ -354,7 +354,7 @@ fn translate_comparison_pathelements( let new_root_and_current_tables = RootAndCurrentTables { root_table: root_and_current_tables.root_table.clone(), - current_table: TableNameAndReference { + current_table: TableSourceAndReference { reference: table.reference.clone(), source: table.source.clone(), }, @@ -412,7 +412,7 @@ fn translate_comparison_pathelements( let reference = sql::ast::TableReference::AliasedTable(alias.clone()); Ok(( - TableNameAndReference { + TableSourceAndReference { reference, source: final_ref.source, }, @@ -548,7 +548,7 @@ pub fn translate_exists_in_collection( let new_root_and_current_tables = RootAndCurrentTables { root_table: root_and_current_tables.root_table.clone(), - current_table: TableNameAndReference { + current_table: TableSourceAndReference { reference: table.reference, source: table.source, }, @@ -609,7 +609,7 @@ pub fn translate_exists_in_collection( let new_root_and_current_tables = RootAndCurrentTables { root_table: root_and_current_tables.root_table.clone(), - current_table: TableNameAndReference { + current_table: TableSourceAndReference { reference: table.reference.clone(), source: table.source, }, diff --git a/crates/query-engine/translation/src/translation/query/relationships.rs b/crates/query-engine/translation/src/translation/query/relationships.rs index 4e39ad472..56598da3f 100644 --- a/crates/query-engine/translation/src/translation/query/relationships.rs +++ b/crates/query-engine/translation/src/translation/query/relationships.rs @@ -6,7 +6,7 @@ use ndc_models as models; use super::root; use crate::translation::error::Error; -use crate::translation::helpers::{Env, State, TableNameAndReference}; +use crate::translation::helpers::{Env, State, TableSourceAndReference}; use query_engine_sql::sql; #[derive(Debug)] @@ -22,7 +22,7 @@ pub struct JoinFieldInfo { pub fn translate( env: &Env, state: &mut State, - current_table: &TableNameAndReference, + current_table: &TableSourceAndReference, // We got these by processing the fields selection. join_fields: Vec, ) -> Result, Error> { @@ -84,7 +84,7 @@ pub fn translate( /// Given a relationship, turn it into a Where clause for a Join. pub fn translate_column_mapping( env: &Env, - current_table: &TableNameAndReference, + current_table: &TableSourceAndReference, target_collection_alias_reference: &sql::ast::TableReference, expr: sql::ast::Expression, relationship: &models::Relationship, diff --git a/crates/query-engine/translation/src/translation/query/root.rs b/crates/query-engine/translation/src/translation/query/root.rs index a3ee55fcf..925bc201e 100644 --- a/crates/query-engine/translation/src/translation/query/root.rs +++ b/crates/query-engine/translation/src/translation/query/root.rs @@ -14,7 +14,7 @@ use super::sorting; use crate::translation::error::Error; use crate::translation::helpers::TableSource; use crate::translation::helpers::{ - CollectionInfo, Env, RootAndCurrentTables, State, TableNameAndReference, + CollectionInfo, Env, RootAndCurrentTables, State, TableSourceAndReference, }; use query_engine_sql::sql; @@ -82,7 +82,7 @@ fn translate_aggregates( select: Box::new(inner_query), alias: from_alias.clone(), }; - let current_table = TableNameAndReference { + let current_table = TableSourceAndReference { source: table.source, reference: sql::ast::TableReference::AliasedTable(from_alias), }; @@ -168,7 +168,7 @@ fn translate_rows( pub fn translate_query_part( env: &Env, state: &mut State, - current_table: &TableNameAndReference, + current_table: &TableSourceAndReference, join_predicate: &Option>, query: &models::Query, select: &mut sql::ast::Select, @@ -227,7 +227,7 @@ pub fn make_from_clause_and_reference( env: &Env, state: &mut State, collection_alias: Option, -) -> Result<(TableNameAndReference, sql::ast::From), Error> { +) -> Result<(TableSourceAndReference, sql::ast::From), Error> { let collection_alias = match collection_alias { None => state.make_table_alias(collection_name.to_string()), Some(alias) => alias, @@ -237,7 +237,7 @@ pub fn make_from_clause_and_reference( let from_clause = make_from_clause(state, &collection_alias, &collection_info, arguments); let collection_alias_name = sql::ast::TableReference::AliasedTable(collection_alias); - let current_table = TableNameAndReference { + let current_table = TableSourceAndReference { source: TableSource::Collection(collection_name.clone()), reference: collection_alias_name, }; @@ -276,7 +276,7 @@ fn make_from_clause( /// Join predicate. pub struct JoinPredicate<'a, 'b> { /// Join the current table with this table. - pub join_with: &'a TableNameAndReference, + pub join_with: &'a TableSourceAndReference, /// This is the description of the relationship. pub relationship: &'b models::Relationship, } @@ -304,7 +304,7 @@ fn make_reference_and_from_clause( env: &Env, state: &mut State, make_from: &MakeFrom, -) -> Result<(TableNameAndReference, sql::ast::From), Error> { +) -> Result<(TableSourceAndReference, sql::ast::From), Error> { match make_from { MakeFrom::Collection { name, arguments } => { make_from_clause_and_reference(name, arguments, env, state, None) @@ -317,7 +317,7 @@ fn make_reference_and_from_clause( }; let reference = sql::ast::TableReference::AliasedTable(table_alias); Ok(( - TableNameAndReference { + TableSourceAndReference { source: TableSource::Collection(name.clone()), reference, }, diff --git a/crates/query-engine/translation/src/translation/query/sorting.rs b/crates/query-engine/translation/src/translation/query/sorting.rs index eb6afd034..92d41615d 100644 --- a/crates/query-engine/translation/src/translation/query/sorting.rs +++ b/crates/query-engine/translation/src/translation/query/sorting.rs @@ -10,8 +10,8 @@ use super::relationships; use super::root; use crate::translation::error::Error; use crate::translation::helpers::{ - wrap_in_field_path, Env, FieldPath, FieldsInfo, RootAndCurrentTables, State, - TableNameAndReference, TableSource, + wrap_in_field_path, Env, FieldPath, FieldsInfo, RootAndCurrentTables, State, TableSource, + TableSourceAndReference, }; use query_engine_sql::sql; @@ -557,8 +557,8 @@ fn process_path_element_for_order_by_targets( // and join with the previous table. We add a new join to this list of joins. joins: &mut Vec, // the table we are joining with, the current path element and its index. - (last_table, (index, path_element)): (TableNameAndReference, (usize, &models::PathElement)), -) -> Result<(TableNameAndReference, PathElementSelectColumns), Error> { + (last_table, (index, path_element)): (TableSourceAndReference, (usize, &models::PathElement)), +) -> Result<(TableSourceAndReference, PathElementSelectColumns), Error> { // examine the path elements' relationship. let relationship = env.lookup_relationship(&path_element.relationship)?; @@ -655,7 +655,7 @@ fn process_path_element_for_order_by_targets( /// in the order by list. fn translate_targets( target_collection: &FieldsInfo<'_>, - table: &TableNameAndReference, + table: &TableSourceAndReference, element_group: &OrderByElementGroup, ) -> Result, Error> { match element_group { @@ -744,7 +744,7 @@ fn from_clause_for_path_element( relationship: &models::Relationship, target_collection_alias: &sql::ast::TableAlias, arguments: &std::collections::BTreeMap, -) -> Result<(TableNameAndReference, sql::ast::From), Error> { +) -> Result<(TableSourceAndReference, sql::ast::From), Error> { let arguments = relationships::make_relationship_arguments(relationships::MakeRelationshipArguments { caller_arguments: arguments.clone(), @@ -769,7 +769,7 @@ fn select_for_path_element( relationship: &models::Relationship, predicate: &Option>, select_list: sql::ast::SelectList, - (join_table, from_clause): (TableNameAndReference, sql::ast::From), + (join_table, from_clause): (TableSourceAndReference, sql::ast::From), ) -> Result { // build a select query from this table where join condition. let mut select = sql::helpers::simple_select(vec![]); From 514d76368542c4812c660492cbf1a8ef72c02f9e Mon Sep 17 00:00:00 2001 From: Gil Mizrahi Date: Thu, 8 Aug 2024 17:24:41 +0300 Subject: [PATCH 6/6] whoops --- .../filter_by_nested_field_collection.json | 41 ------- docs/architecture/query.md | 112 ------------------ 2 files changed, 153 deletions(-) delete mode 100644 crates/tests/tests-common/goldenfiles/filter_by_nested_field_collection.json delete mode 100644 docs/architecture/query.md diff --git a/crates/tests/tests-common/goldenfiles/filter_by_nested_field_collection.json b/crates/tests/tests-common/goldenfiles/filter_by_nested_field_collection.json deleted file mode 100644 index c6d1b5d8d..000000000 --- a/crates/tests/tests-common/goldenfiles/filter_by_nested_field_collection.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "collection": "group_leader", - "query": { - "fields": { - "name": { - "type": "column", - "column": "name", - "arguments": {} - }, - "characters": { - "type": "column", - "column": "characters", - "arguments": {} - } - }, - "predicate": { - "type": "exists", - "in_collection": { - "type": "nested_collection", - "column_name": "characters", - "field_path": ["members"], - "arguments": {} - }, - "predicate": { - "type": "binary_comparison_operator", - "column": { - "type": "column", - "name": "name", - "path": [] - }, - "operator": "_like", - "value": { - "type": "scalar", - "value": "Sam" - } - } - } - }, - "arguments": {}, - "collection_relationships": {} -} diff --git a/docs/architecture/query.md b/docs/architecture/query.md deleted file mode 100644 index 65464843a..000000000 --- a/docs/architecture/query.md +++ /dev/null @@ -1,112 +0,0 @@ -# Query translation - -Query translation is composed of three important components: - -1. Metadata - this includes information about the database, native queries, columns, etc. It comes from the ndc-postgres configuration which in turn is generated using database introspection. -2. SQL AST - this includes the structure of SQL queries that we generate as part of the translation process. -3. Translation step - which we will talk about here. - -The translation step essentially takes the metadata (ndc-postgres configuration) and an ndc-spec query request and generates an SQL execution plan to run against the database. - -The best way to gain some intuition regarding the process is to look at the finished product, which we can do by looking at translation tests. - -Let's look at an example request. In `request.json` we'll find the query request, and in `configuration.json` we'll find the configuration. - -This query requests a fetching the name of tracks for each album for each artist. -Notice how relationship definitions is a field of the request and not part of the metadata. - -Looking at the metadata, we'll find a table, `Album`, as well as the relevant types defined in the metadata. Remember - the ndc-postgres configuration is the source of knowledge of everything, including types and functions. - -Looking at the generated SQL we can see the following information: - -```sql -SELECT - -- An ndc-spec QueryResult returns a row for each variable set. - -- If there are no variables, only a single row in the array will be returned. - -- - -- This row contains up-to 3 fields: `rows`, `aggregates` and `groups`. - -- This part converts each row returned from the query and converts it to json. - coalesce(json_agg(row_to_json("%3_universe")), '[]') AS "universe" -FROM - ( - SELECT - * - FROM - -- Everything here is the "rows" part of the query. - ( - -- We convert each row to json and aggregate them into a json array. - -- We name it `rows` because we return the fields selection. - SELECT - coalesce(json_agg(row_to_json("%4_rows")), '[]') AS "rows" - FROM - -- We select the relevant fields and include the filtering, sorting, etc. - ( - SELECT - "%0_Album"."Title" AS "Title" -- Field selection - FROM - "public"."Album" AS "%0_Album" -- Collection - LIMIT - 5 -- Limit - OFFSET - 3 -- Offset - ) AS "%4_rows" - ) AS "%4_rows" - CROSS JOIN ( - -- Everything here is the "aggregates" part of the query. - SELECT - coalesce(row_to_json("%5_aggregates"), '[]') AS "aggregates" - FROM - ( - SELECT - COUNT(*) AS "how_many_albums" -- Aggregation - FROM - -- The query - ( - SELECT - "%1_Album".* - FROM - "public"."Album" AS "%1_Album" - LIMIT - 5 -- Same limit - OFFSET - 3 -- Same offset - ) AS "%2_Album" - ) AS "%5_aggregates" - ) AS "%5_aggregates" - ) AS "%3_universe"; -``` - -From looking at this generated SQL, we already see the general structure of the SQL: - -1. We have subqueries for each one of the `rows`, `aggregates` and `groups` fields. -2. We join these subqueries and then convert them to json. -3. we write a fairly straightforward query, then convert the rows to json, and aggregate them in a json array. - -(We'll later see that much of the complexity arises from relationship) - -When looking at the entry code in the `mod.rs` function `translate`, we can recognize this structure if we squint: - -1. We set up some environment and state. -2. We `root::translate_query` the `rows` and `aggregates` part. -3. We wrap the result in the universe/rows/aggregates json parts -4. We normalize the SQL AST. -5. We wrap those in an execution plan. - -```sh -crates/query-engine/translation/src/translation -├── error.rs -├── helpers.rs -├── mod.rs -├── mutation -│   └── ... -└── query - ├── aggregates.rs - ├── fields.rs - ├── filtering.rs - ├── mod.rs - ├── native_queries.rs - ├── relationships.rs - ├── root.rs - ├── sorting.rs - └── values.rs -```