Skip to content

fix nested fields relationships #564

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

- Allow Native Operations that end with a semicolon when it's easy to remove them.
[#566](https://github.com/hasura/ndc-postgres/pull/566)
- Fix nested field relationships.
[#564](https://github.com/hasura/ndc-postgres/pull/564)

## [v1.0.1]

Expand Down
9 changes: 7 additions & 2 deletions crates/query-engine/sql/src/sql/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ pub enum From {
Unnest {
expression: Expression,
alias: TableAlias,
column: ColumnAlias,
columns: Vec<ColumnAlias>,
},
GenerateSeries {
from: usize,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -409,6 +409,11 @@ pub enum TableReference {
},
/// refers to an alias we created
AliasedTable(TableAlias),
/// refers to a nested field
NestedField {
source: Box<TableReference>,
field: NestedField,
},
}

/// A database table's column name
Expand Down
24 changes: 20 additions & 4 deletions crates/query-engine/sql/src/sql/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,17 +315,26 @@ impl From {
From::Unnest {
expression,
alias,
column,
columns,
} => {
sql.append_syntax("UNNEST");
sql.append_syntax("(");
expression.to_sql(sql);
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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change confused me a lot until I realized it is in a From clause.

Mulling it over a bit I think it's an alright change. But I wonder if it's ever necessary to give a column signature to FROM UNNEST(..). Maybe we could do without it entirely?

Copy link
Contributor Author

@soupi soupi Aug 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will have a look at it in the future. I don't remember the exact case right now, but I think we use it for variables or something where we need to specify the names and types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And sorry about this, I'm working on a similar ticket and kind of got some changes mixed together.

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");
Expand Down Expand Up @@ -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);
}
};
}
}
Expand Down
149 changes: 97 additions & 52 deletions crates/query-engine/translation/src/translation/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,50 @@ 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 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 {
type_name: models::TypeName,
// These are used to create a nice table alias.
collection_name: models::CollectionName,
field_path: FieldPath,
},
}

impl TableSource {
/// 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) => collection_name.to_string(),
TableSource::NestedField {
collection_name,
field_path,
type_name: _,
} => format!("{collection_name}.{}", field_path.0.join(".")),
}
}
}

#[derive(Debug)]
/// Information about columns
pub struct ColumnInfo {
Expand Down Expand Up @@ -108,11 +136,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 {
Expand All @@ -124,7 +152,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,
Expand All @@ -136,8 +167,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,
},
}
}
}
Expand Down Expand Up @@ -182,55 +219,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<FieldsInfo<'request>, 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<FieldsInfo<'request>, 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
Expand Down Expand Up @@ -265,7 +310,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.
Expand Down Expand Up @@ -591,7 +636,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<models::FieldName>);

impl From<&Option<Vec<models::FieldName>>> for FieldPath {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -104,8 +104,8 @@ pub fn translate(

let table_alias = state.make_table_alias(table_name.0.clone());

let table_name_and_reference = TableNameAndReference {
name: collection_name.clone(),
let table_name_and_reference = TableSourceAndReference {
source: helpers::TableSource::Collection(collection_name.clone()),
reference: sql::ast::TableReference::AliasedTable(table_alias.clone()),
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -215,8 +215,8 @@ 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(),
let table_name_and_reference = TableSourceAndReference {
source: helpers::TableSource::Collection(mutation.collection_name.clone()),
reference: sql::ast::TableReference::DBTable {
schema: mutation.schema_name.clone(),
table: mutation.table_name.clone(),
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -121,8 +121,8 @@ pub fn translate(

let set = parse_update_columns(env, state, mutation, object)?;

let table_name_and_reference = TableNameAndReference {
name: mutation.collection_name.clone(),
let table_name_and_reference = TableSourceAndReference {
source: helpers::TableSource::Collection(mutation.collection_name.clone()),
reference: sql::ast::TableReference::DBTable {
schema: mutation.schema_name.clone(),
table: mutation.table_name.clone(),
Expand Down
Loading