From 79c7ad1e3410976d0439581f236b724bdbe1c4c1 Mon Sep 17 00:00:00 2001 From: Benoit Ranque Date: Tue, 27 Aug 2024 15:06:11 -0400 Subject: [PATCH 01/10] allow type shorthands, make types case insensitive --- crates/common/src/clickhouse_parser.rs | 149 +++++++++++++----- .../common/src/clickhouse_parser/datatype.rs | 24 ++- 2 files changed, 128 insertions(+), 45 deletions(-) diff --git a/crates/common/src/clickhouse_parser.rs b/crates/common/src/clickhouse_parser.rs index b939c6d..16040a2 100644 --- a/crates/common/src/clickhouse_parser.rs +++ b/crates/common/src/clickhouse_parser.rs @@ -58,45 +58,48 @@ peg::parser! { / aggregate_function() / simple_aggregate_function() / nothing() - rule nullable() -> DT = "Nullable(" t:data_type() ")" { DT::Nullable(Box::new(t)) } - rule uint8() -> DT = "UInt8" { DT::UInt8 } - rule uint16() -> DT = "UInt16" { DT::UInt16 } - rule uint32() -> DT = "UInt32" { DT::UInt32 } - rule uint64() -> DT = "UInt64" { DT::UInt64 } - rule uint128() -> DT = "UInt128" { DT::UInt128 } - rule uint256() -> DT = "UInt256" { DT::UInt256 } - rule int8() -> DT = "Int8" { DT::Int8 } - rule int16() -> DT = "Int16" { DT::Int16 } - rule int32() -> DT = "Int32" { DT::Int32 } - rule int64() -> DT = "Int64" { DT::Int64 } - rule int128() -> DT = "Int128" { DT::Int128 } - rule int256() -> DT = "Int256" { DT::Int256 } - rule float32() -> DT = "Float32" { DT::Float32 } - rule float64() -> DT = "Float64" { DT::Float64 } - rule decimal() -> DT = "Decimal(" precision:integer_value() comma_separator() scale:integer_value() ")" { DT::Decimal { precision, scale } } - rule decimal32() -> DT = "Decimal32(" scale:integer_value() ")" { DT::Decimal32 { scale } } - rule decimal64() -> DT = "Decimal64(" scale:integer_value() ")" { DT::Decimal64 { scale } } - rule decimal128() -> DT = "Decimal128(" scale:integer_value() ")" { DT::Decimal128 { scale } } - rule decimal256() -> DT = "Decimal256(" scale:integer_value() ")" { DT::Decimal256 { scale } } - rule bool() -> DT = "Bool" { DT::Bool } - rule string() -> DT = "String" { DT::String } - rule fixed_string() -> DT = "FixedString(" n:integer_value() ")" { DT::FixedString(n) } - rule date() -> DT = "Date" { DT::Date } - rule date32() -> DT = "Date32" { DT::Date32 } - rule date_time() -> DT = "DateTime" tz:("(" tz:single_quoted_string_value()? ")" { tz })? { DT::DateTime { timezone: tz.flatten().map(|s| s.to_owned()) } } - rule date_time64() -> DT = "DateTime64(" precision:integer_value() tz:(comma_separator() tz:single_quoted_string_value()? { tz })? ")" { DT::DateTime64{ precision, timezone: tz.flatten().map(|s| s.to_owned())} } - rule uuid() -> DT = "UUID" { DT::Uuid } - rule ipv4() -> DT = "IPv4" { DT::IPv4 } - rule ipv6() -> DT = "IPv6" { DT::IPv6 } - rule low_cardinality() -> DT = "LowCardinality(" t:data_type() ")" { DT::LowCardinality(Box::new(t)) } - rule nested() -> DT = "Nested(" e:((n:identifier() __ t:data_type() { (n, t)}) ** comma_separator()) ")" { DT::Nested(e) } - rule array() -> DT = "Array(" t:data_type() ")" { DT::Array(Box::new(t)) } - rule map() -> DT = "Map(" k:data_type() comma_separator() v:data_type() ")" { DT::Map { key: Box::new(k), value: Box::new(v) } } - rule tuple() -> DT = "Tuple(" e:((n:(n:identifier() __ { n })? t:data_type() { (n, t) }) ** comma_separator()) ")" { DT::Tuple(e) } - rule r#enum() -> DT = "Enum" ("8" / "16")? "(" e:((n:single_quoted_string_value() i:(_ "=" _ i:integer_value() { i })? { (n, i) }) ** comma_separator()) ")" { DT::Enum(e)} - rule aggregate_function() -> DT = "AggregateFunction(" f:aggregate_function_definition() comma_separator() a:(data_type() ** comma_separator()) ")" { DT::AggregateFunction { function: f, arguments: a }} - rule simple_aggregate_function() -> DT = "SimpleAggregateFunction(" f:aggregate_function_definition() comma_separator() a:(data_type() ** comma_separator()) ")" { DT::SimpleAggregateFunction { function: f, arguments: a }} - rule nothing() -> DT = "Nothing" { DT::Nothing } + rule nullable() -> DT = i("Nullable(") t:data_type() ")" { DT::Nullable(Box::new(t)) } + rule uint8() -> DT = i("UInt8") { DT::UInt8 } + rule uint16() -> DT = i("UInt16") { DT::UInt16 } + rule uint32() -> DT = i("UInt32") { DT::UInt32 } + rule uint64() -> DT = i("UInt64") { DT::UInt64 } + rule uint128() -> DT = i("UInt128") { DT::UInt128 } + rule uint256() -> DT = i("UInt256") { DT::UInt256 } + rule int8() -> DT = i("Int8") { DT::Int8 } + rule int16() -> DT = i("Int16") { DT::Int16 } + rule int32() -> DT = i("Int32") { DT::Int32 } + rule int64() -> DT = i("Int64") { DT::Int64 } + rule int128() -> DT = i("Int128") { DT::Int128 } + rule int256() -> DT = i("Int256") { DT::Int256 } + rule float32() -> DT = i("Float32") { DT::Float32 } + rule float64() -> DT = i("Float64") { DT::Float64 } + rule decimal() -> DT = i("Decimal(") precision:integer_value() comma_separator() scale:integer_value() ")" { DT::Decimal { precision, scale } } + / i("Decimal(") precision:integer_value() ")" { DT::Decimal { precision, scale: 0 } } + / i("Decimal") { DT::Decimal { precision: 10, scale: 0 }} + rule decimal32() -> DT = i("Decimal32(") scale:integer_value() ")" { DT::Decimal32 { scale } } + rule decimal64() -> DT = i("Decimal64(") scale:integer_value() ")" { DT::Decimal64 { scale } } + rule decimal128() -> DT = i("Decimal128(") scale:integer_value() ")" { DT::Decimal128 { scale } } + rule decimal256() -> DT = i("Decimal256(") scale:integer_value() ")" { DT::Decimal256 { scale } } + rule bool() -> DT = i("Bool") { DT::Bool } + rule string() -> DT = i("String") { DT::String } + rule fixed_string() -> DT = i("FixedString(") n:integer_value() ")" { DT::FixedString(n) } + rule date() -> DT = i("Date") { DT::Date } + rule date32() -> DT = i("Date32") { DT::Date32 } + rule date_time() -> DT = i("DateTime") tz:("(" tz:single_quoted_string_value()? ")" { tz })? { DT::DateTime { timezone: tz.flatten().map(|s| s.to_owned()) } } + rule date_time64() -> DT = i("DateTime64(") precision:integer_value() tz:(comma_separator() tz:single_quoted_string_value()? { tz })? ")" { DT::DateTime64{ precision, timezone: tz.flatten().map(|s| s.to_owned())} } + / i("DateTime64") { DT::DateTime64 { precision: 3, timezone: None }} + rule uuid() -> DT = i("UUID") { DT::Uuid } + rule ipv4() -> DT = i("IPv4") { DT::IPv4 } + rule ipv6() -> DT = i("IPv6") { DT::IPv6 } + rule low_cardinality() -> DT = i("LowCardinality(") t:data_type() ")" { DT::LowCardinality(Box::new(t)) } + rule nested() -> DT = i("Nested(") e:((n:identifier() __ t:data_type() { (n, t)}) ** comma_separator()) ")" { DT::Nested(e) } + rule array() -> DT = i("Array(") t:data_type() ")" { DT::Array(Box::new(t)) } + rule map() -> DT = i("Map(") k:data_type() comma_separator() v:data_type() ")" { DT::Map { key: Box::new(k), value: Box::new(v) } } + rule tuple() -> DT = i("Tuple(") e:((n:(n:identifier() __ { n })? t:data_type() { (n, t) }) ** comma_separator()) ")" { DT::Tuple(e) } + rule r#enum() -> DT = i("Enum") ("8" / "16")? "(" e:((n:single_quoted_string_value() i:(_ "=" _ i:integer_value() { i })? { (n, i) }) ** comma_separator()) ")" { DT::Enum(e)} + rule aggregate_function() -> DT = i("AggregateFunction(") f:aggregate_function_definition() comma_separator() a:(data_type() ** comma_separator()) ")" { DT::AggregateFunction { function: f, arguments: a }} + rule simple_aggregate_function() -> DT = i("SimpleAggregateFunction(") f:aggregate_function_definition() comma_separator() a:(data_type() ** comma_separator()) ")" { DT::SimpleAggregateFunction { function: f, arguments: a }} + rule nothing() -> DT = i("Nothing") { DT::Nothing } rule aggregate_function_definition() -> AggregateFunctionDefinition = n:identifier() p:("(" p:(aggregate_function_parameter() ** comma_separator()) ")" { p })? { AggregateFunctionDefinition { name: n, parameters: p }} rule aggregate_function_parameter() -> AggregateFunctionParameter = s:single_quoted_string_value() { AggregateFunctionParameter::SingleQuotedString(s)} @@ -120,6 +123,8 @@ peg::parser! { rule _ = [' ' | '\t' | '\r' | '\n']* /// A comma surrounded by optional whitespace rule comma_separator() = _ "," _ + /// A case insensitive string + rule i(literal: &'static str) = input:$([_]*<{literal.len()}>) {? if input.eq_ignore_ascii_case(literal) { Ok(()) } else { Err(literal) } } } } @@ -195,7 +200,71 @@ fn can_parse_clickhouse_data_type() { for (s, t) in data_types { let parsed = clickhouse_parser::data_type(s); - assert_eq!(parsed, Ok(t), "Able to parse correctly"); + assert_eq!(parsed, Ok(t), "Able to parse {s} correctly"); + } +} + +#[test] +fn support_shorthands() { + let test_cases = vec![ + ( + "Decimal", + DT::Decimal { + precision: 10, + scale: 0, + }, + ), + ( + "Decimal(3)", + DT::Decimal { + precision: 3, + scale: 0, + }, + ), + ( + "DateTime64", + DT::DateTime64 { + precision: 3, + timezone: None, + }, + ), + ]; + + for (s, t) in test_cases { + let parsed = clickhouse_parser::data_type(s); + assert_eq!(parsed, Ok(t), "Able to parse {s} correctly"); + } +} + +#[test] +fn print_as_shorthand_when_possible() { + let test_cases = vec!["DateTime64", "Decimal", "Decimal(2)"]; + for s in test_cases { + let parsed = clickhouse_parser::data_type(s).expect("Able to parse type"); + assert_eq!( + s, + &parsed.to_string(), + "Serialize {s} as shorthand when possible" + ) + } +} + +#[test] +fn is_case_insensitive() { + let test_cases = vec![ + ("dateTime", "DateTime"), + ("daTetIme64", "DateTime64"), + ("bool", "Bool"), + ("STRING", "String"), + ]; + + for (input, cased) in test_cases { + let parsed = clickhouse_parser::data_type(input).expect("Able to parse type"); + assert_eq!( + &parsed.to_string(), + cased, + "Should parse incorrectly cased type {input} into {cased}" + ); } } diff --git a/crates/common/src/clickhouse_parser/datatype.rs b/crates/common/src/clickhouse_parser/datatype.rs index d5a2815..f08860b 100644 --- a/crates/common/src/clickhouse_parser/datatype.rs +++ b/crates/common/src/clickhouse_parser/datatype.rs @@ -178,7 +178,17 @@ impl Display for ClickHouseDataType { DT::Int256 => write!(f, "Int256"), DT::Float32 => write!(f, "Float32"), DT::Float64 => write!(f, "Float64"), - DT::Decimal { precision, scale } => write!(f, "Decimal({precision}, {scale})"), + DT::Decimal { precision, scale } => { + if scale == &0 { + if precision == &10 { + write!(f, "Decimal") + } else { + write!(f, "Decimal({precision})") + } + } else { + write!(f, "Decimal({precision}, {scale})") + } + } DT::Decimal32 { scale } => write!(f, "Decimal32({scale})"), DT::Decimal64 { scale } => write!(f, "Decimal64({scale})"), DT::Decimal128 { scale } => write!(f, "Decimal128({scale})"), @@ -196,11 +206,15 @@ impl Display for ClickHouseDataType { precision, timezone, } => { - write!(f, "DateTime64({precision}")?; - if let Some(tz) = timezone { - write!(f, ", {tz}")?; + if precision == &3 && timezone.is_none() { + write!(f, "DateTime64") + } else { + write!(f, "DateTime64({precision}")?; + if let Some(tz) = timezone { + write!(f, ", {tz}")?; + } + write!(f, ")") } - write!(f, ")") } DT::Uuid => write!(f, "UUID"), DT::IPv4 => write!(f, "IPv4"), From fff124cdfc091a85587f00ca54d883726e851947 Mon Sep 17 00:00:00 2001 From: Benoit Ranque Date: Tue, 27 Aug 2024 15:09:15 -0400 Subject: [PATCH 02/10] add changelog, bump version --- CHANGELOG.md | 6 ++++++ Cargo.lock | 6 +++--- Cargo.toml | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77adc3e..516d412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.2] + +- Allow `DateTime64` shorthand for `DateTime64(3)` +- Allow `Decimal` shorthand for `Decimal(10, 0)` +- Make datatypes case insensitive + ## [1.0.1] - Bug fix: remove erroneous group by and order by clauses in `foreach` queries. Remote relationships should now function as expected. The previous fix was incorrect. diff --git a/Cargo.lock b/Cargo.lock index f1a2d10..a478737 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,7 +334,7 @@ checksum = "97af0562545a7d7f3d9222fcf909963bec36dcb502afaacab98c6ffac8da47ce" [[package]] name = "common" -version = "1.0.1" +version = "1.0.2" dependencies = [ "bytes", "peg", @@ -1053,7 +1053,7 @@ dependencies = [ [[package]] name = "ndc-clickhouse" -version = "1.0.1" +version = "1.0.2" dependencies = [ "async-trait", "bytes", @@ -1073,7 +1073,7 @@ dependencies = [ [[package]] name = "ndc-clickhouse-cli" -version = "1.0.1" +version = "1.0.2" dependencies = [ "clap", "common", diff --git a/Cargo.toml b/Cargo.toml index e91b15b..baaf773 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,5 @@ members = [ ] resolver = "2" -package.version = "1.0.1" +package.version = "1.0.2" package.edition = "2021" From 627fdf7bef46089b1e47438ca2d0f5af73243d56 Mon Sep 17 00:00:00 2001 From: Benoit Ranque Date: Fri, 30 Aug 2024 15:26:38 -0400 Subject: [PATCH 03/10] revert: don't prefer printing as shorthand, to be consistent with clickhouse server --- .../common/src/clickhouse_parser/datatype.rs | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/crates/common/src/clickhouse_parser/datatype.rs b/crates/common/src/clickhouse_parser/datatype.rs index f08860b..d5a2815 100644 --- a/crates/common/src/clickhouse_parser/datatype.rs +++ b/crates/common/src/clickhouse_parser/datatype.rs @@ -178,17 +178,7 @@ impl Display for ClickHouseDataType { DT::Int256 => write!(f, "Int256"), DT::Float32 => write!(f, "Float32"), DT::Float64 => write!(f, "Float64"), - DT::Decimal { precision, scale } => { - if scale == &0 { - if precision == &10 { - write!(f, "Decimal") - } else { - write!(f, "Decimal({precision})") - } - } else { - write!(f, "Decimal({precision}, {scale})") - } - } + DT::Decimal { precision, scale } => write!(f, "Decimal({precision}, {scale})"), DT::Decimal32 { scale } => write!(f, "Decimal32({scale})"), DT::Decimal64 { scale } => write!(f, "Decimal64({scale})"), DT::Decimal128 { scale } => write!(f, "Decimal128({scale})"), @@ -206,15 +196,11 @@ impl Display for ClickHouseDataType { precision, timezone, } => { - if precision == &3 && timezone.is_none() { - write!(f, "DateTime64") - } else { - write!(f, "DateTime64({precision}")?; - if let Some(tz) = timezone { - write!(f, ", {tz}")?; - } - write!(f, ")") + write!(f, "DateTime64({precision}")?; + if let Some(tz) = timezone { + write!(f, ", {tz}")?; } + write!(f, ")") } DT::Uuid => write!(f, "UUID"), DT::IPv4 => write!(f, "IPv4"), From 2c7e9d89d39c8dd585e51b344f785589b471dd31 Mon Sep 17 00:00:00 2001 From: Benoit Ranque Date: Fri, 30 Aug 2024 15:28:21 -0400 Subject: [PATCH 04/10] if introspection returns no columns, preserve any manually written columns --- CHANGELOG.md | 1 + crates/ndc-clickhouse-cli/src/main.rs | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 516d412..8e14d7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow `DateTime64` shorthand for `DateTime64(3)` - Allow `Decimal` shorthand for `Decimal(10, 0)` - Make datatypes case insensitive +- When introspection returns no columns ([parameterized view issue](https://github.com/ClickHouse/ClickHouse/issues/65402)), preserve any manually written columns ## [1.0.1] diff --git a/crates/ndc-clickhouse-cli/src/main.rs b/crates/ndc-clickhouse-cli/src/main.rs index e01dfec..015723f 100644 --- a/crates/ndc-clickhouse-cli/src/main.rs +++ b/crates/ndc-clickhouse-cli/src/main.rs @@ -446,7 +446,18 @@ fn get_table_return_type( let old_return_type = old_table.and_then( |(_table_alias, table_config)| match &table_config.return_type { - ReturnType::Definition { .. } => None, + ReturnType::Definition { columns } => { + // introspection of parameterized views may return no columns + // ref: https://github.com/ClickHouse/ClickHouse/issues/65402 + // if introspection returned no columns, and existing config does have (user written) columns, preserve those + if new_columns.is_empty() && !columns.is_empty() { + Some(ReturnType::Definition { + columns: columns.clone(), + }) + } else { + None + } + } ReturnType::TableReference { table_name } => { // get the old table config for the referenced table let referenced_table_config = old_config From 7da954c429580edba968b8fbb0e6c01dac02dbf3 Mon Sep 17 00:00:00 2001 From: Benoit Ranque Date: Fri, 30 Aug 2024 17:02:49 -0400 Subject: [PATCH 05/10] update tests --- crates/common/src/clickhouse_parser.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/crates/common/src/clickhouse_parser.rs b/crates/common/src/clickhouse_parser.rs index 16040a2..e724ce2 100644 --- a/crates/common/src/clickhouse_parser.rs +++ b/crates/common/src/clickhouse_parser.rs @@ -236,24 +236,11 @@ fn support_shorthands() { } } -#[test] -fn print_as_shorthand_when_possible() { - let test_cases = vec!["DateTime64", "Decimal", "Decimal(2)"]; - for s in test_cases { - let parsed = clickhouse_parser::data_type(s).expect("Able to parse type"); - assert_eq!( - s, - &parsed.to_string(), - "Serialize {s} as shorthand when possible" - ) - } -} - #[test] fn is_case_insensitive() { let test_cases = vec![ ("dateTime", "DateTime"), - ("daTetIme64", "DateTime64"), + ("daTetIme64", "DateTime64(3)"), ("bool", "Bool"), ("STRING", "String"), ]; From 1a200c8654844ec2f2e144fd7e23194024357165 Mon Sep 17 00:00:00 2001 From: Benoit Ranque Date: Fri, 30 Aug 2024 18:00:54 -0400 Subject: [PATCH 06/10] add schema and capabilities tests --- .../src/connector/handler/schema.rs | 8 +- crates/ndc-clickhouse/tests/capabilities.rs | 42 + .../tests/capabilities/capabilities.json | 16 + crates/ndc-clickhouse/tests/query_builder.rs | 45 +- .../query_builder/chinook/_schema/schema.json | 1160 ++++++++++++ .../complex_columns/_schema/schema.json | 491 +++++ .../star_schema/_schema/schema.json | 1654 +++++++++++++++++ 7 files changed, 3410 insertions(+), 6 deletions(-) create mode 100644 crates/ndc-clickhouse/tests/capabilities.rs create mode 100644 crates/ndc-clickhouse/tests/capabilities/capabilities.json create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/_schema/schema.json create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/_schema/schema.json create mode 100644 crates/ndc-clickhouse/tests/query_builder/star_schema/_schema/schema.json diff --git a/crates/ndc-clickhouse/src/connector/handler/schema.rs b/crates/ndc-clickhouse/src/connector/handler/schema.rs index 525665f..c0312f2 100644 --- a/crates/ndc-clickhouse/src/connector/handler/schema.rs +++ b/crates/ndc-clickhouse/src/connector/handler/schema.rs @@ -13,6 +13,10 @@ use std::collections::BTreeMap; pub async fn schema( configuration: &ServerConfig, ) -> Result, SchemaError> { + Ok(JsonResponse::Value(schema_response(configuration))) +} + +pub fn schema_response(configuration: &ServerConfig) -> models::SchemaResponse { let mut scalar_type_definitions = BTreeMap::new(); let mut object_type_definitions = vec![]; @@ -198,7 +202,7 @@ pub async fn schema( let collections = table_collections.chain(query_collections).collect(); - Ok(JsonResponse::Value(models::SchemaResponse { + models::SchemaResponse { scalar_types: scalar_type_definitions, // converting vector to map drops any duplicate definitions // this could be an issue if there are name collisions @@ -206,5 +210,5 @@ pub async fn schema( collections, functions: vec![], procedures: vec![], - })) + } } diff --git a/crates/ndc-clickhouse/tests/capabilities.rs b/crates/ndc-clickhouse/tests/capabilities.rs new file mode 100644 index 0000000..d700a93 --- /dev/null +++ b/crates/ndc-clickhouse/tests/capabilities.rs @@ -0,0 +1,42 @@ +use ndc_clickhouse::connector::handler::capabilities; +use ndc_sdk::models::CapabilitiesResponse; +use std::path::PathBuf; +use tokio::fs; + +#[tokio::test] +async fn prints_expected_capabilities() { + let expected_capabilities_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("capabilities") + .join("capabilities.json"); + let expected_capabilities_file = fs::read_to_string(expected_capabilities_path) + .await + .expect("Capabilities file should be present"); + let expected_capabilities: CapabilitiesResponse = + serde_json::from_str(&expected_capabilities_file) + .expect("Expected capabilities shoudl be valid capabilities response"); + + let capabilities = capabilities(); + + assert_eq!( + expected_capabilities, capabilities, + "Capabilities response should match snapshot" + ); +} + +#[tokio::test] +// update the expected capabilities. We only want thise called explicitly when capabilities have changed +#[ignore] +async fn update_expected_capabilities() { + let capabilities = serde_json::to_string_pretty(&capabilities()) + .expect("Capabilities should serialize to json"); + + let capabilities_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("capabilities") + .join("capabilities.json"); + + fs::write(capabilities_path, capabilities) + .await + .expect("Should write out capabilities"); +} diff --git a/crates/ndc-clickhouse/tests/capabilities/capabilities.json b/crates/ndc-clickhouse/tests/capabilities/capabilities.json new file mode 100644 index 0000000..9cce6ea --- /dev/null +++ b/crates/ndc-clickhouse/tests/capabilities/capabilities.json @@ -0,0 +1,16 @@ +{ + "version": "0.1.4", + "capabilities": { + "query": { + "aggregates": {}, + "variables": {}, + "explain": {}, + "nested_fields": {} + }, + "mutation": {}, + "relationships": { + "relation_comparisons": {}, + "order_by_aggregate": {} + } + } +} \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder.rs b/crates/ndc-clickhouse/tests/query_builder.rs index 9b1ce87..ac95c44 100644 --- a/crates/ndc-clickhouse/tests/query_builder.rs +++ b/crates/ndc-clickhouse/tests/query_builder.rs @@ -8,17 +8,17 @@ use tokio::fs; mod test_utils { use common::config::ServerConfig; use ndc_clickhouse::{ - connector::setup::ClickhouseConnectorSetup, + connector::{handler::schema_response, setup::ClickhouseConnectorSetup}, sql::{QueryBuilder, QueryBuilderError}, }; - use ndc_sdk::models; + use ndc_sdk::models::{self, SchemaResponse}; use std::{collections::HashMap, env, error::Error, path::PathBuf}; use tokio::fs; /// when running tests locally, this can be set to true to update reference files /// this allows us to view diffs between commited samples and fresh samples /// we don't want that behavior when running CI, so this value should be false in commited code - const UPDATE_GENERATED_SQL: bool = false; + const UPDATE_SNAPSHOTS: bool = false; fn base_path(schema_dir: &str) -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) @@ -32,6 +32,9 @@ mod test_utils { fn config_dir_path(schema_dir: &str) -> PathBuf { base_path(schema_dir).join("_config") } + fn expected_schema_path(schema_dir: &str) -> PathBuf { + base_path(schema_dir).join("_schema").join("schema.json") + } async fn read_mock_configuration(schema_dir: &str) -> Result> { // set mock values for required env vars, we won't be reading these anyways let env = HashMap::from_iter(vec![ @@ -111,7 +114,7 @@ mod test_utils { let generated_sql = generate_sql(&configuration, &request)?; - if UPDATE_GENERATED_SQL { + if UPDATE_SNAPSHOTS { write_expected_sql(schema_dir, group_dir, test_name, &generated_sql).await?; } else { let expected_sql = read_expected_sql(schema_dir, group_dir, test_name).await?; @@ -134,6 +137,22 @@ mod test_utils { assert_eq!(result, Err(err)); + Ok(()) + } + pub async fn test_schema(schema_dir: &str) -> Result<(), Box> { + let configuration = read_mock_configuration(schema_dir).await?; + let schema = schema_response(&configuration); + let expected_schema_path = expected_schema_path(schema_dir); + + if UPDATE_SNAPSHOTS { + fs::write(expected_schema_path, serde_json::to_string_pretty(&schema)?).await?; + } else { + let expected_schema_file = fs::read_to_string(expected_schema_path).await?; + let expected_schema: SchemaResponse = serde_json::from_str(&expected_schema_file)?; + + assert_eq!(schema, expected_schema, "Schema should match snapshot") + } + Ok(()) } } @@ -155,6 +174,24 @@ async fn update_json_schema() -> Result<(), Box> { Ok(()) } +#[cfg(test)] +mod schemas { + use super::*; + + #[tokio::test] + async fn chinook_schema() -> Result<(), Box> { + test_utils::test_schema("chinook").await + } + #[tokio::test] + async fn complex_columns_schema() -> Result<(), Box> { + test_utils::test_schema("complex_columns").await + } + #[tokio::test] + async fn star_schema_schema() -> Result<(), Box> { + test_utils::test_schema("star_schema").await + } +} + #[cfg(test)] mod simple_queries { use super::*; diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/_schema/schema.json b/crates/ndc-clickhouse/tests/query_builder/chinook/_schema/schema.json new file mode 100644 index 0000000..0390cb7 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/_schema/schema.json @@ -0,0 +1,1160 @@ +{ + "scalar_types": { + "Date32": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "named", + "name": "Date32" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "Date32" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Date32" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Date32" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Date32" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Date32" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Date32" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Date32" + } + } + } + } + }, + "DateTime64(9)": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "named", + "name": "DateTime64(9)" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "DateTime64(9)" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "DateTime64(9)" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "DateTime64(9)" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "DateTime64(9)" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "DateTime64(9)" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "DateTime64(9)" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "DateTime64(9)" + } + } + } + } + }, + "Float64": { + "representation": { + "type": "float64" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Float64" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Float64" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Float64" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Float64" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Float64" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Float64" + } + } + } + } + }, + "Int32": { + "representation": { + "type": "int32" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "Int32" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "Int32" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "Int64" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Int32" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Int32" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Int32" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Int32" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Int32" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Int32" + } + } + } + } + }, + "String": { + "representation": { + "type": "string" + }, + "aggregate_functions": {}, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_ilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_in": { + "type": "in" + }, + "_like": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_match": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_nilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "String" + } + } + }, + "_nlike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + } + } + } + }, + "object_types": { + "Chinook_Album": { + "description": "", + "fields": { + "AlbumId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "ArtistId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "Title": { + "type": { + "type": "named", + "name": "String" + } + } + } + }, + "Chinook_Artist": { + "description": "", + "fields": { + "ArtistId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + } + } + }, + "Chinook_Customer": { + "description": "", + "fields": { + "Address": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "City": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "Company": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "Country": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "CustomerId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "Email": { + "type": { + "type": "named", + "name": "String" + } + }, + "Fax": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "FirstName": { + "type": { + "type": "named", + "name": "String" + } + }, + "LastName": { + "type": { + "type": "named", + "name": "String" + } + }, + "Phone": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "PostalCode": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "State": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "SupportRepId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Int32" + } + } + } + } + }, + "Chinook_Employee": { + "description": "", + "fields": { + "Address": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "BirthDate": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Date32" + } + } + }, + "City": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "Country": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "Email": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "EmployeeId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "Fax": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "FirstName": { + "type": { + "type": "named", + "name": "String" + } + }, + "HireDate": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Date32" + } + } + }, + "LastName": { + "type": { + "type": "named", + "name": "String" + } + }, + "Phone": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "PostalCode": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "ReportsTo": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Int32" + } + } + }, + "State": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "Title": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + } + } + }, + "Chinook_Genre": { + "description": "", + "fields": { + "GenreId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + } + } + }, + "Chinook_Invoice": { + "description": "", + "fields": { + "BillingAddress": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "BillingCity": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "BillingCountry": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "BillingPostalCode": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "BillingState": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "CustomerId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "InvoiceDate": { + "type": { + "type": "named", + "name": "DateTime64(9)" + } + }, + "InvoiceId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "Total": { + "type": { + "type": "named", + "name": "Float64" + } + } + } + }, + "Chinook_InvoiceLine": { + "description": "", + "fields": { + "InvoiceId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "InvoiceLineId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "Quantity": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "TrackId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "UnitPrice": { + "type": { + "type": "named", + "name": "Float64" + } + } + } + }, + "Chinook_MediaType": { + "description": "", + "fields": { + "MediaTypeId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + } + } + }, + "Chinook_Playlist": { + "description": "", + "fields": { + "Name": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "PlaylistId": { + "type": { + "type": "named", + "name": "Int32" + } + } + } + }, + "Chinook_PlaylistTrack": { + "description": "", + "fields": { + "PlaylistId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "TrackId": { + "type": { + "type": "named", + "name": "Int32" + } + } + } + }, + "Chinook_Track": { + "description": "", + "fields": { + "AlbumId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Int32" + } + } + }, + "Bytes": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Int32" + } + } + }, + "Composer": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "GenreId": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "Int32" + } + } + }, + "MediaTypeId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "Milliseconds": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "Name": { + "type": { + "type": "named", + "name": "String" + } + }, + "TrackId": { + "type": { + "type": "named", + "name": "Int32" + } + }, + "UnitPrice": { + "type": { + "type": "named", + "name": "Float64" + } + } + } + } + }, + "collections": [ + { + "name": "Chinook_Album", + "description": "", + "arguments": {}, + "type": "Chinook_Album", + "uniqueness_constraints": { + "AlbumId": { + "unique_columns": [ + "AlbumId" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "Chinook_Artist", + "description": "", + "arguments": {}, + "type": "Chinook_Artist", + "uniqueness_constraints": { + "ArtistId": { + "unique_columns": [ + "ArtistId" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "Chinook_Customer", + "description": "", + "arguments": {}, + "type": "Chinook_Customer", + "uniqueness_constraints": { + "CustomerId": { + "unique_columns": [ + "CustomerId" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "Chinook_Employee", + "description": "", + "arguments": {}, + "type": "Chinook_Employee", + "uniqueness_constraints": { + "EmployeeId": { + "unique_columns": [ + "EmployeeId" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "Chinook_Genre", + "description": "", + "arguments": {}, + "type": "Chinook_Genre", + "uniqueness_constraints": { + "GenreId": { + "unique_columns": [ + "GenreId" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "Chinook_Invoice", + "description": "", + "arguments": {}, + "type": "Chinook_Invoice", + "uniqueness_constraints": { + "InvoiceId": { + "unique_columns": [ + "InvoiceId" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "Chinook_InvoiceLine", + "description": "", + "arguments": {}, + "type": "Chinook_InvoiceLine", + "uniqueness_constraints": { + "InvoiceLineId": { + "unique_columns": [ + "InvoiceLineId" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "Chinook_MediaType", + "description": "", + "arguments": {}, + "type": "Chinook_MediaType", + "uniqueness_constraints": { + "MediaTypeId": { + "unique_columns": [ + "MediaTypeId" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "Chinook_Playlist", + "description": "", + "arguments": {}, + "type": "Chinook_Playlist", + "uniqueness_constraints": { + "PlaylistId": { + "unique_columns": [ + "PlaylistId" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "Chinook_PlaylistTrack", + "description": "", + "arguments": {}, + "type": "Chinook_PlaylistTrack", + "uniqueness_constraints": { + "PlaylistId, TrackId": { + "unique_columns": [ + "PlaylistId", + "TrackId" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "Chinook_Track", + "description": "", + "arguments": {}, + "type": "Chinook_Track", + "uniqueness_constraints": { + "TrackId": { + "unique_columns": [ + "TrackId" + ] + } + }, + "foreign_keys": {} + } + ], + "functions": [], + "procedures": [] +} \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/_schema/schema.json b/crates/ndc-clickhouse/tests/query_builder/complex_columns/_schema/schema.json new file mode 100644 index 0000000..76e392f --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/_schema/schema.json @@ -0,0 +1,491 @@ +{ + "scalar_types": { + "Map(String, String)": { + "aggregate_functions": {}, + "comparison_operators": {} + }, + "Nested(field1 String, field1 String)": { + "aggregate_functions": {}, + "comparison_operators": {} + }, + "String": { + "representation": { + "type": "string" + }, + "aggregate_functions": {}, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_ilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_in": { + "type": "in" + }, + "_like": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_match": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_nilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "String" + } + } + }, + "_nlike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + } + } + }, + "Tuple(String, String)": { + "aggregate_functions": {}, + "comparison_operators": {} + }, + "UInt32": { + "representation": { + "type": "int64" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "UInt32" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "UInt32" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "UInt64" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt32" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt32" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt32" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt32" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt32" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "UInt32" + } + } + } + } + } + }, + "object_types": { + "TableOne": { + "fields": { + "ColumnA": { + "type": { + "type": "named", + "name": "String" + } + }, + "ColumnB": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "TableOne.ColumnB" + } + } + }, + "ColumnC": { + "type": { + "type": "named", + "name": "Nested(field1 String, field1 String)" + } + }, + "ColumnD": { + "type": { + "type": "named", + "name": "TableOne.ColumnD" + } + }, + "ColumnE": { + "type": { + "type": "named", + "name": "TableOne.ColumnE" + } + }, + "ColumnF": { + "type": { + "type": "named", + "name": "TableOne.ColumnF" + } + }, + "ColumnG": { + "type": { + "type": "named", + "name": "TableOne.ColumnG" + } + } + } + }, + "TableOne.ColumnB": { + "fields": { + "field1": { + "type": { + "type": "named", + "name": "String" + } + }, + "field2": { + "type": { + "type": "named", + "name": "String" + } + } + } + }, + "TableOne.ColumnD": { + "fields": { + "child": { + "type": { + "type": "named", + "name": "TableOne.ColumnD.child" + } + } + } + }, + "TableOne.ColumnD.child": { + "fields": { + "id": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "name": { + "type": { + "type": "named", + "name": "String" + } + } + } + }, + "TableOne.ColumnE": { + "fields": { + "child": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "TableOne.ColumnE.child" + } + } + } + } + }, + "TableOne.ColumnE.child": { + "fields": { + "id": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "name": { + "type": { + "type": "named", + "name": "String" + } + } + } + }, + "TableOne.ColumnF": { + "fields": { + "child": { + "type": { + "type": "named", + "name": "TableOne.ColumnF.child" + } + } + } + }, + "TableOne.ColumnF.child": { + "fields": { + "id": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "name": { + "type": { + "type": "named", + "name": "String" + } + }, + "toys": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "TableOne.ColumnF.child.toys" + } + } + } + } + }, + "TableOne.ColumnF.child.toys": { + "fields": { + "id": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "name": { + "type": { + "type": "named", + "name": "String" + } + } + } + }, + "TableOne.ColumnG": { + "fields": { + "a": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "String" + } + } + }, + "b": { + "type": { + "type": "named", + "name": "Map(String, String)" + } + }, + "c": { + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "TableOne.ColumnG.c" + } + } + }, + "d": { + "type": { + "type": "named", + "name": "TableOne.ColumnG.d" + } + } + } + }, + "TableOne.ColumnG.c": { + "fields": { + "a": { + "type": { + "type": "named", + "name": "String" + } + }, + "b": { + "type": { + "type": "named", + "name": "Tuple(String, String)" + } + } + } + }, + "TableOne.ColumnG.d": { + "fields": { + "a": { + "type": { + "type": "named", + "name": "String" + } + }, + "b": { + "type": { + "type": "named", + "name": "String" + } + } + } + }, + "TableTwo": { + "fields": { + "Id": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "Name": { + "type": { + "type": "named", + "name": "String" + } + } + } + } + }, + "collections": [ + { + "name": "TableOne", + "arguments": {}, + "type": "TableOne", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "TableTwo", + "arguments": {}, + "type": "TableTwo", + "uniqueness_constraints": {}, + "foreign_keys": {} + } + ], + "functions": [], + "procedures": [] +} \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/star_schema/_schema/schema.json b/crates/ndc-clickhouse/tests/query_builder/star_schema/_schema/schema.json new file mode 100644 index 0000000..059073a --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/star_schema/_schema/schema.json @@ -0,0 +1,1654 @@ +{ + "scalar_types": { + "Date": { + "representation": { + "type": "string" + }, + "aggregate_functions": { + "max": { + "result_type": { + "type": "named", + "name": "Date" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "Date" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Date" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Date" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Date" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Date" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Date" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Date" + } + } + } + } + }, + "Int64": { + "representation": { + "type": "int64" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "Int64" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "Int64" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "Int64" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Int64" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Int64" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Int64" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Int64" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "Int64" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "Int64" + } + } + } + } + }, + "String": { + "representation": { + "type": "string" + }, + "aggregate_functions": {}, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_ilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_in": { + "type": "in" + }, + "_like": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_match": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_nilike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "String" + } + } + }, + "_nlike": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "String" + } + } + } + }, + "UInt16": { + "representation": { + "type": "int32" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "UInt16" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "UInt16" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "UInt64" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt16" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt16" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt16" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt16" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt16" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "UInt16" + } + } + } + } + }, + "UInt32": { + "representation": { + "type": "int64" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "UInt32" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "UInt32" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "UInt64" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt32" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt32" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt32" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt32" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt32" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "UInt32" + } + } + } + } + }, + "UInt64": { + "representation": { + "type": "biginteger" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "UInt64" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "UInt64" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "UInt64" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt64" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt64" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt64" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt64" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt64" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "UInt64" + } + } + } + } + }, + "UInt8": { + "representation": { + "type": "int16" + }, + "aggregate_functions": { + "avg": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "max": { + "result_type": { + "type": "named", + "name": "UInt8" + } + }, + "min": { + "result_type": { + "type": "named", + "name": "UInt8" + } + }, + "stddev_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "stddev_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "sum": { + "result_type": { + "type": "named", + "name": "UInt64" + } + }, + "var_pop": { + "result_type": { + "type": "named", + "name": "Float64" + } + }, + "var_samp": { + "result_type": { + "type": "named", + "name": "Float64" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt8" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt8" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt8" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt8" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "UInt8" + } + }, + "_nin": { + "type": "custom", + "argument_type": { + "type": "array", + "element_type": { + "type": "named", + "name": "UInt8" + } + } + } + } + } + }, + "object_types": { + "q11": { + "fields": { + "revenue": { + "type": { + "type": "named", + "name": "UInt64" + } + } + } + }, + "q13": { + "fields": { + "revenue": { + "type": { + "type": "named", + "name": "UInt64" + } + } + } + }, + "q21": { + "fields": { + "P_BRAND": { + "type": { + "type": "named", + "name": "String" + } + }, + "sum(LO_REVENUE)": { + "type": { + "type": "named", + "name": "UInt64" + } + }, + "year": { + "type": { + "type": "named", + "name": "UInt16" + } + } + } + }, + "q22": { + "fields": { + "P_BRAND": { + "type": { + "type": "named", + "name": "String" + } + }, + "sum(LO_REVENUE)": { + "type": { + "type": "named", + "name": "UInt64" + } + }, + "year": { + "type": { + "type": "named", + "name": "UInt16" + } + } + } + }, + "q23": { + "fields": { + "P_BRAND": { + "type": { + "type": "named", + "name": "String" + } + }, + "sum(LO_REVENUE)": { + "type": { + "type": "named", + "name": "UInt64" + } + }, + "year": { + "type": { + "type": "named", + "name": "UInt16" + } + } + } + }, + "q31": { + "fields": { + "C_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "revenue": { + "type": { + "type": "named", + "name": "UInt64" + } + }, + "year": { + "type": { + "type": "named", + "name": "UInt16" + } + } + } + }, + "q32": { + "fields": { + "C_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "revenue": { + "type": { + "type": "named", + "name": "UInt64" + } + }, + "year": { + "type": { + "type": "named", + "name": "UInt16" + } + } + } + }, + "q33": { + "fields": { + "C_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "revenue": { + "type": { + "type": "named", + "name": "UInt64" + } + }, + "year": { + "type": { + "type": "named", + "name": "UInt16" + } + } + } + }, + "q34": { + "fields": { + "C_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "revenue": { + "type": { + "type": "named", + "name": "UInt64" + } + }, + "year": { + "type": { + "type": "named", + "name": "UInt16" + } + } + } + }, + "q41": { + "fields": { + "C_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "profit": { + "type": { + "type": "named", + "name": "Int64" + } + }, + "year": { + "type": { + "type": "named", + "name": "UInt16" + } + } + } + }, + "q42": { + "fields": { + "P_CATEGORY": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "profit": { + "type": { + "type": "named", + "name": "Int64" + } + }, + "year": { + "type": { + "type": "named", + "name": "UInt16" + } + } + } + }, + "q43": { + "fields": { + "P_BRAND": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_CITY": { + "type": { + "type": "named", + "name": "String" + } + }, + "profit": { + "type": { + "type": "named", + "name": "Int64" + } + }, + "year": { + "type": { + "type": "named", + "name": "UInt16" + } + } + } + }, + "star_customer": { + "description": "", + "fields": { + "C_ADDRESS": { + "type": { + "type": "named", + "name": "String" + } + }, + "C_CITY": { + "type": { + "type": "named", + "name": "String" + } + }, + "C_CUSTKEY": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "C_MKTSEGMENT": { + "type": { + "type": "named", + "name": "String" + } + }, + "C_NAME": { + "type": { + "type": "named", + "name": "String" + } + }, + "C_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "C_PHONE": { + "type": { + "type": "named", + "name": "String" + } + }, + "C_REGION": { + "type": { + "type": "named", + "name": "String" + } + } + } + }, + "star_lineorder": { + "description": "", + "fields": { + "LO_COMMITDATE": { + "type": { + "type": "named", + "name": "Date" + } + }, + "LO_CUSTKEY": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_DISCOUNT": { + "type": { + "type": "named", + "name": "UInt8" + } + }, + "LO_EXTENDEDPRICE": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_LINENUMBER": { + "type": { + "type": "named", + "name": "UInt8" + } + }, + "LO_ORDERDATE": { + "type": { + "type": "named", + "name": "Date" + } + }, + "LO_ORDERKEY": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_ORDERPRIORITY": { + "type": { + "type": "named", + "name": "String" + } + }, + "LO_ORDTOTALPRICE": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_PARTKEY": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_QUANTITY": { + "type": { + "type": "named", + "name": "UInt8" + } + }, + "LO_REVENUE": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_SHIPMODE": { + "type": { + "type": "named", + "name": "String" + } + }, + "LO_SHIPPRIORITY": { + "type": { + "type": "named", + "name": "UInt8" + } + }, + "LO_SUPPKEY": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_SUPPLYCOST": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_TAX": { + "type": { + "type": "named", + "name": "UInt8" + } + } + } + }, + "star_lineorder_flat": { + "description": "", + "fields": { + "C_ADDRESS": { + "type": { + "type": "named", + "name": "String" + } + }, + "C_CITY": { + "type": { + "type": "named", + "name": "String" + } + }, + "C_MKTSEGMENT": { + "type": { + "type": "named", + "name": "String" + } + }, + "C_NAME": { + "type": { + "type": "named", + "name": "String" + } + }, + "C_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "C_PHONE": { + "type": { + "type": "named", + "name": "String" + } + }, + "C_REGION": { + "type": { + "type": "named", + "name": "String" + } + }, + "LO_COMMITDATE": { + "type": { + "type": "named", + "name": "Date" + } + }, + "LO_CUSTKEY": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_DISCOUNT": { + "type": { + "type": "named", + "name": "UInt8" + } + }, + "LO_EXTENDEDPRICE": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_LINENUMBER": { + "type": { + "type": "named", + "name": "UInt8" + } + }, + "LO_ORDERDATE": { + "type": { + "type": "named", + "name": "Date" + } + }, + "LO_ORDERKEY": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_ORDERPRIORITY": { + "type": { + "type": "named", + "name": "String" + } + }, + "LO_ORDTOTALPRICE": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_PARTKEY": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_QUANTITY": { + "type": { + "type": "named", + "name": "UInt8" + } + }, + "LO_REVENUE": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_SHIPMODE": { + "type": { + "type": "named", + "name": "String" + } + }, + "LO_SHIPPRIORITY": { + "type": { + "type": "named", + "name": "UInt8" + } + }, + "LO_SUPPKEY": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_SUPPLYCOST": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "LO_TAX": { + "type": { + "type": "named", + "name": "UInt8" + } + }, + "P_BRAND": { + "type": { + "type": "named", + "name": "String" + } + }, + "P_CATEGORY": { + "type": { + "type": "named", + "name": "String" + } + }, + "P_COLOR": { + "type": { + "type": "named", + "name": "String" + } + }, + "P_CONTAINER": { + "type": { + "type": "named", + "name": "String" + } + }, + "P_MFGR": { + "type": { + "type": "named", + "name": "String" + } + }, + "P_NAME": { + "type": { + "type": "named", + "name": "String" + } + }, + "P_SIZE": { + "type": { + "type": "named", + "name": "UInt8" + } + }, + "P_TYPE": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_ADDRESS": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_CITY": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_NAME": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_PHONE": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_REGION": { + "type": { + "type": "named", + "name": "String" + } + } + } + }, + "star_part": { + "description": "", + "fields": { + "P_BRAND": { + "type": { + "type": "named", + "name": "String" + } + }, + "P_CATEGORY": { + "type": { + "type": "named", + "name": "String" + } + }, + "P_COLOR": { + "type": { + "type": "named", + "name": "String" + } + }, + "P_CONTAINER": { + "type": { + "type": "named", + "name": "String" + } + }, + "P_MFGR": { + "type": { + "type": "named", + "name": "String" + } + }, + "P_NAME": { + "type": { + "type": "named", + "name": "String" + } + }, + "P_PARTKEY": { + "type": { + "type": "named", + "name": "UInt32" + } + }, + "P_SIZE": { + "type": { + "type": "named", + "name": "UInt8" + } + }, + "P_TYPE": { + "type": { + "type": "named", + "name": "String" + } + } + } + }, + "star_supplier": { + "description": "", + "fields": { + "S_ADDRESS": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_CITY": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_NAME": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_NATION": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_PHONE": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_REGION": { + "type": { + "type": "named", + "name": "String" + } + }, + "S_SUPPKEY": { + "type": { + "type": "named", + "name": "UInt32" + } + } + } + } + }, + "collections": [ + { + "name": "star_customer", + "description": "", + "arguments": {}, + "type": "star_customer", + "uniqueness_constraints": { + "C_CUSTKEY": { + "unique_columns": [ + "C_CUSTKEY" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "star_lineorder", + "description": "", + "arguments": {}, + "type": "star_lineorder", + "uniqueness_constraints": { + "LO_ORDERDATE, LO_ORDERKEY": { + "unique_columns": [ + "LO_ORDERKEY", + "LO_ORDERDATE" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "star_lineorder_flat", + "description": "", + "arguments": {}, + "type": "star_lineorder_flat", + "uniqueness_constraints": { + "LO_ORDERDATE, LO_ORDERKEY": { + "unique_columns": [ + "LO_ORDERKEY", + "LO_ORDERDATE" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "star_part", + "description": "", + "arguments": {}, + "type": "star_part", + "uniqueness_constraints": { + "P_PARTKEY": { + "unique_columns": [ + "P_PARTKEY" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "star_supplier", + "description": "", + "arguments": {}, + "type": "star_supplier", + "uniqueness_constraints": { + "S_SUPPKEY": { + "unique_columns": [ + "S_SUPPKEY" + ] + } + }, + "foreign_keys": {} + }, + { + "name": "q11", + "arguments": {}, + "type": "q11", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "q12", + "arguments": {}, + "type": "q11", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "q13", + "arguments": {}, + "type": "q13", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "q21", + "arguments": {}, + "type": "q21", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "q22", + "arguments": {}, + "type": "q22", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "q23", + "arguments": {}, + "type": "q23", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "q31", + "arguments": {}, + "type": "q31", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "q32", + "arguments": {}, + "type": "q32", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "q33", + "arguments": {}, + "type": "q33", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "q34", + "arguments": {}, + "type": "q34", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "q41", + "arguments": {}, + "type": "q41", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "q42", + "arguments": {}, + "type": "q42", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, + { + "name": "q43", + "arguments": {}, + "type": "q43", + "uniqueness_constraints": {}, + "foreign_keys": {} + } + ], + "functions": [], + "procedures": [] +} \ No newline at end of file From cd94c71d033ec87ffea82cef1fb5a223760a052c Mon Sep 17 00:00:00 2001 From: Benoit Ranque Date: Sun, 1 Sep 2024 13:35:59 -0400 Subject: [PATCH 07/10] fix tuple printing, add type-aware casting from json values to clickhouse values --- CHANGELOG.md | 2 + crates/ndc-clickhouse/src/sql/ast.rs | 126 +++++++++++++++--- .../ndc-clickhouse/src/sql/query_builder.rs | 14 +- .../src/sql/query_builder/error.rs | 20 ++- .../src/sql/query_builder/parameter.rs | 122 ++++++++++++----- 5 files changed, 223 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e14d7d..a10e93b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow `Decimal` shorthand for `Decimal(10, 0)` - Make datatypes case insensitive - When introspection returns no columns ([parameterized view issue](https://github.com/ClickHouse/ClickHouse/issues/65402)), preserve any manually written columns +- Correct support for casting from JSON Objects paramters to named Tuples, and JSON Arrays to anonymous tuples +- Fix printing tuples in bound parameters ## [1.0.1] diff --git a/crates/ndc-clickhouse/src/sql/ast.rs b/crates/ndc-clickhouse/src/sql/ast.rs index 6779206..0d82e9f 100644 --- a/crates/ndc-clickhouse/src/sql/ast.rs +++ b/crates/ndc-clickhouse/src/sql/ast.rs @@ -1,6 +1,7 @@ use std::fmt; pub mod format; -use common::clickhouse_parser::parameterized_query::ParameterType; +use super::QueryBuilderError; +use common::clickhouse_parser::{datatype::ClickHouseDataType, parameterized_query::ParameterType}; use format::{display_comma_separated, display_period_separated, display_separated, escape_string}; use indexmap::IndexMap; @@ -793,32 +794,122 @@ pub enum Value { Null, Map(IndexMap), Array(Vec), + Tuple(Vec), +} + +pub enum ValueCastingError { + UnsupportedCast { + json_value: serde_json::Value, + data_type: ClickHouseDataType, + }, } impl Value { pub fn into_expr(self) -> Expr { Expr::Value(self) } -} + pub fn try_from_json( + value: &serde_json::Value, + data_type: &ClickHouseDataType, + ) -> Result { + fn underlying_type(data_type: &ClickHouseDataType) -> &ClickHouseDataType { + match data_type { + ClickHouseDataType::Nullable(t) | ClickHouseDataType::LowCardinality(t) => { + underlying_type(t) + } + _ => data_type, + } + } -impl From<&serde_json::Value> for Value { - fn from(value: &serde_json::Value) -> Self { - fn map_json_value(value: &serde_json::Value) -> Value { + fn map_json_value( + value: &serde_json::Value, + data_type: &ClickHouseDataType, + ) -> Result { match value { - serde_json::Value::Null => Value::Null, - serde_json::Value::Bool(b) => Value::Boolean(b.to_owned()), - serde_json::Value::Number(n) => Value::Number(n.to_string()), - serde_json::Value::String(s) => Value::SingleQuotedString(s.to_owned()), - serde_json::Value::Array(arr) => { - Value::Array(arr.iter().map(map_json_value).collect()) - } - serde_json::Value::Object(obj) => Value::Map(IndexMap::from_iter( - obj.into_iter() - .map(|(key, value)| (key.to_owned(), map_json_value(value))), - )), + serde_json::Value::Null => Ok(Value::Null), + serde_json::Value::Bool(b) => Ok(Value::Boolean(b.to_owned())), + serde_json::Value::Number(n) => Ok(Value::Number(n.to_string())), + serde_json::Value::String(s) => Ok(Value::SingleQuotedString(s.to_owned())), + serde_json::Value::Array(arr) => match underlying_type(data_type) { + ClickHouseDataType::Array(element_type) => Ok(Value::Array( + arr.iter() + .map(|value| map_json_value(value, element_type)) + .collect::>()?, + )), + ClickHouseDataType::Tuple(elements) => { + // tuple should be anonymous, and tuple length should match array length + if elements.len() != arr.len() { + Err(QueryBuilderError::UnsupportedParameterCast { + json_value: value.to_owned(), + data_type: data_type.to_owned().into(), + }) + } else { + let values = arr + .iter() + .zip(elements.iter()) + .map(|(value, (identifier, data_type))| { + if identifier.is_some() { + todo!("unexpected identifer error") + } else { + map_json_value(value, data_type) + } + }) + .collect::>()?; + + Ok(Value::Tuple(values)) + } + } + _ => Err(QueryBuilderError::UnsupportedParameterCast { + json_value: value.to_owned(), + data_type: data_type.to_owned().into(), + }), + }, + serde_json::Value::Object(obj) => match underlying_type(data_type) { + ClickHouseDataType::Map { + key: _key_type, + value: value_type, + } => Ok(Value::Map( + obj.into_iter() + .map(|(key, value)| { + Ok((key.to_owned(), map_json_value(value, value_type)?)) + }) + .collect::, QueryBuilderError>>()?, + )), + ClickHouseDataType::Tuple(elements) => { + // tuple should be named, and all tuple keys should be in the input object + let mut values = vec![]; + + for (identifier, data_type) in elements { + if let Some(identifier) = identifier { + if let Some(value) = obj.get(identifier.value()) { + values.push(map_json_value(value, data_type)?) + } else { + // all tuple keys should be in the value object + return Err(QueryBuilderError::UnsupportedParameterCast { + json_value: value.to_owned(), + data_type: data_type.to_owned().into(), + }); + } + } else { + // tuple should not be anonymous + return Err(QueryBuilderError::UnsupportedParameterCast { + json_value: value.to_owned(), + data_type: data_type.to_owned().into(), + }); + } + } + + Ok(Value::Tuple(values)) + } + _ => Err(QueryBuilderError::UnsupportedParameterCast { + json_value: value.to_owned(), + data_type: data_type.to_owned().into(), + }), + }, } } - map_json_value(value) + + map_json_value(value, data_type) } } @@ -850,6 +941,7 @@ impl fmt::Display for Value { ) } Value::Array(arr) => write!(f, "[{}]", display_comma_separated(arr)), + Value::Tuple(values) => write!(f, "({})", display_comma_separated(values)), } } } diff --git a/crates/ndc-clickhouse/src/sql/query_builder.rs b/crates/ndc-clickhouse/src/sql/query_builder.rs index 6b890dc..8f026fd 100644 --- a/crates/ndc-clickhouse/src/sql/query_builder.rs +++ b/crates/ndc-clickhouse/src/sql/query_builder.rs @@ -1075,7 +1075,7 @@ impl<'r, 'c> QueryBuilder<'r, 'c> { parameters, )?, models::ComparisonValue::Scalar { value } => ComparisonColumn::new_simple( - parameters.bind_json(value, right_col_type.clone().into()), + parameters.bind_json(value, right_col_type.clone().into())?, right_col_type, ), @@ -1756,7 +1756,7 @@ impl<'r, 'c> QueryBuilder<'r, 'c> { }; let mut literal_argument = |arg_name: &String, value: &serde_json::Value| { Ok(parameters - .bind_json(value, table_argument_type(arg_name)?.to_owned().into()) + .bind_json(value, table_argument_type(arg_name)?.to_owned().into())? .into_arg() .name(Ident::new_quoted(arg_name))) }; @@ -1885,14 +1885,14 @@ impl<'r, 'c> QueryBuilder<'r, 'c> { } ParameterizedQueryElement::Parameter(p) => get_argument(p.name.value()) .transpose()? - .map(|value| { - NativeQueryElement::Expr( - parameters.bind_json(value, p.r#type.to_owned()), - ) - }) .ok_or_else(|| QueryBuilderError::MissingNativeQueryArgument { query: collection.alias().to_owned(), argument: p.name.value().to_owned(), + }) + .and_then(|value| { + Ok(NativeQueryElement::Expr( + parameters.bind_json(value, p.r#type.to_owned())?, + )) }), }) .collect::>()?; diff --git a/crates/ndc-clickhouse/src/sql/query_builder/error.rs b/crates/ndc-clickhouse/src/sql/query_builder/error.rs index b04bc92..0302a92 100644 --- a/crates/ndc-clickhouse/src/sql/query_builder/error.rs +++ b/crates/ndc-clickhouse/src/sql/query_builder/error.rs @@ -1,5 +1,6 @@ use std::fmt; +use common::clickhouse_parser::parameterized_query::ParameterType; use ndc_sdk::connector::{ExplainError, QueryError}; use super::typecasting::TypeStringError; @@ -39,6 +40,11 @@ pub enum QueryBuilderError { Typecasting(TypeStringError), /// Column type did not match type asserted by request ColumnTypeMismatch { expected: String, got: String }, + /// Attempted to cast a JSON value to a mismatching data type + UnsupportedParameterCast { + json_value: serde_json::Value, + data_type: ParameterType, + }, } impl fmt::Display for QueryBuilderError { @@ -84,6 +90,14 @@ impl fmt::Display for QueryBuilderError { QueryBuilderError::ColumnTypeMismatch { expected, got } => { write!(f, "Column Type Mismatch: expected {expected}, got {got}") } + QueryBuilderError::UnsupportedParameterCast { + json_value, + data_type, + } => write!( + f, + "Cannot cast value `{}` to type `{}`", + json_value, data_type + ), } } } @@ -105,7 +119,8 @@ impl From for QueryError { | QueryBuilderError::UnknownSingleColumnAggregateFunction(_) | QueryBuilderError::UnknownBinaryComparisonOperator(_) | QueryBuilderError::Typecasting(_) - | QueryBuilderError::ColumnTypeMismatch { .. } => { + | QueryBuilderError::ColumnTypeMismatch { .. } + | QueryBuilderError::UnsupportedParameterCast { .. } => { QueryError::new_invalid_request(&value) } QueryBuilderError::NotSupported(_) => QueryError::new_unsupported_operation(&value), @@ -129,7 +144,8 @@ impl From for ExplainError { | QueryBuilderError::UnknownSingleColumnAggregateFunction(_) | QueryBuilderError::UnknownBinaryComparisonOperator(_) | QueryBuilderError::Typecasting(_) - | QueryBuilderError::ColumnTypeMismatch { .. } => { + | QueryBuilderError::ColumnTypeMismatch { .. } + | QueryBuilderError::UnsupportedParameterCast { .. } => { ExplainError::new_invalid_request(&value) } QueryBuilderError::NotSupported(_) => ExplainError::new_unsupported_operation(&value), diff --git a/crates/ndc-clickhouse/src/sql/query_builder/parameter.rs b/crates/ndc-clickhouse/src/sql/query_builder/parameter.rs index 27618e7..2432fe6 100644 --- a/crates/ndc-clickhouse/src/sql/query_builder/parameter.rs +++ b/crates/ndc-clickhouse/src/sql/query_builder/parameter.rs @@ -1,4 +1,4 @@ -use super::{format::escape_string, Expr, Parameter, Value}; +use super::{format::escape_string, Expr, Parameter, QueryBuilderError, Value}; use crate::sql::ast::format::display_separated; use common::clickhouse_parser::{datatype::ClickHouseDataType, parameterized_query::ParameterType}; use std::fmt::Display; @@ -6,7 +6,7 @@ use std::fmt::Display; pub struct ParameterBuilder { inline_parameters: bool, index: u32, - parameters: Vec<(String, String)>, + parameters: Vec<(String, ParameterValue)>, } impl ParameterBuilder { @@ -17,7 +17,7 @@ impl ParameterBuilder { parameters: vec![], } } - fn bind_parameter(&mut self, value: String, data_type: ParameterType) -> Expr { + fn bind_parameter(&mut self, value: ParameterValue, data_type: ParameterType) -> Expr { let parameter_name = format!("param_p{}", self.index); let placeholder_name = format!("p{}", self.index); self.index += 1; @@ -27,12 +27,28 @@ impl ParameterBuilder { let placeholder = Parameter::new(placeholder_name, data_type); placeholder.into_expr() } - pub fn bind_json(&mut self, value: &serde_json::Value, data_type: ParameterType) -> Expr { + pub fn bind_json( + &mut self, + value: &serde_json::Value, + data_type: ParameterType, + ) -> Result { + let value = match &data_type { + ParameterType::DataType(data_type) => Value::try_from_json(value, data_type)?, + ParameterType::Identifier => match value { + serde_json::Value::String(s) => Value::SingleQuotedString(s.to_owned()), + _ => { + return Err(QueryBuilderError::UnsupportedParameterCast { + json_value: value.to_owned(), + data_type: data_type.to_owned(), + }) + } + }, + }; + if self.inline_parameters { - let value: Value = value.into(); - value.into_expr() + Ok(value.into_expr()) } else { - self.bind_parameter(ParameterValue(value).to_string(), data_type) + Ok(self.bind_parameter(ParameterValue(value), data_type)) } } pub fn bind_string(&mut self, value: &str) -> Expr { @@ -41,64 +57,71 @@ impl ParameterBuilder { value.into_expr() } else { self.bind_parameter( - escape_string(value).to_string(), + ParameterValue(Value::SingleQuotedString(value.to_owned())), ClickHouseDataType::String.into(), ) } } pub fn into_parameters(self) -> Vec<(String, String)> { self.parameters + .into_iter() + .map(|(name, value)| (name, value.to_string())) + .collect() } } -struct ParameterValue<'a>(&'a serde_json::Value); +struct ParameterValue(Value); -impl<'a> Display for ParameterValue<'a> { +impl Display for ParameterValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.0 { + match &self.0 { // top level string should not be quoted - serde_json::Value::String(s) => write!(f, "{}", escape_string(s)), - _ => print_parameter_value(f, self.0), + Value::SingleQuotedString(s) => write!(f, "{}", escape_string(s)), + _ => print_parameter_value(f, &self.0), } } } -fn print_parameter_value( - f: &mut std::fmt::Formatter<'_>, - value: &serde_json::Value, -) -> std::fmt::Result { +fn print_parameter_value(f: &mut std::fmt::Formatter<'_>, value: &Value) -> std::fmt::Result { match value { // note: serializing null as \N seems to be a default configuration // we may need to add a configuration option for this in the future, // but let's wait until a user actually asks for it // ref: https://clickhouse.com/docs/en/operations/settings/formats#format_tsv_null_representation - serde_json::Value::Null => write!(f, "\\N"), - serde_json::Value::Bool(b) => { + Value::Null => write!(f, "\\N"), + Value::Boolean(b) => { if *b { write!(f, "true") } else { write!(f, "false") } } - serde_json::Value::Number(n) => write!(f, "{n}"), - serde_json::Value::String(s) => write!(f, "'{}'", escape_string(s)), - serde_json::Value::Array(arr) => { + Value::Number(n) => write!(f, "{n}"), + Value::SingleQuotedString(s) => write!(f, "'{}'", escape_string(s)), + Value::Array(arr) => { write!( f, "[{}]", display_separated(arr, ",", |f, i| print_parameter_value(f, i)) ) } - serde_json::Value::Object(obj) => { + Value::Map(elements) => { write!( f, "{{{}}}", - display_separated(obj, ",", |f, (key, value)| { + display_separated(elements, ",", |f, (key, value)| { write!(f, "'{}':", escape_string(key))?; print_parameter_value(f, value) }) ) } + Value::Tuple(elements) => { + write!( + f, + "({})", + display_separated(elements, ",", |f, i| print_parameter_value(f, i)) + ) + } } } @@ -111,16 +134,26 @@ mod tests { #[test] fn can_print_parameter() { let test_cases = vec![ - (json!("foo"), "foo"), - (json!(true), "true"), - (json!(false), "false"), - (json!({ "foo": "bar"}), "{'foo':'bar'}"), - (json!(["foo", "bar"]), "['foo','bar']"), - (json!(null), "\\N"), + (json!("foo"), "String", "foo"), + (json!(true), "Bool", "true"), + (json!(false), "Bool", "false"), + ( + json!({ "foo": "bar"}), + "Map(String, String)", + "{'foo':'bar'}", + ), + (json!(["foo", "bar"]), "Array(String)", "['foo','bar']"), + (json!(null), "Nullable(String)", "\\N"), ]; - for (value, printed) in test_cases { - assert_eq!(ParameterValue(&value).to_string().as_str(), printed) + for (value, data_type, printed) in test_cases { + let data_type = + ClickHouseDataType::from_str(&data_type).expect("Should parse data type"); + + let value = Value::try_from_json(&value, &data_type) + .expect("Should convert type based on data type"); + + assert_eq!(ParameterValue(value).to_string().as_str(), printed) } } #[test] @@ -172,14 +205,17 @@ mod tests { ( json!({ "foo": ["bar"], "baz": null }), "Tuple(foo Array(String), baz Nullable(String))", - "{'foo': ['bar'],'baz': NULL}", + "(['bar'], NULL)", ), + (json!(["foo", 1]), "Tuple(String, Int32)", "('foo', 1)"), ]; for (value, data_type_string, expected) in &test_cases { let data_type = ClickHouseDataType::from_str(data_type_string) .expect("Data type string should be valid ClickHouseDataType"); - let inlined = parameters.bind_json(value, data_type.into()); + let inlined = parameters + .bind_json(value, data_type.into()) + .expect("Should succesessfully bind parameter"); assert_eq!( &inlined.to_string().as_str(), @@ -297,13 +333,29 @@ mod tests { "param_p10", "['foo','bar']", ), + ( + json!(["foo", "bar"]), + "Tuple(String, String)", + "{p11:Tuple(String, String)}", + "param_p11", + "('foo','bar')", + ), + ( + json!({"foo": "bar", "baz": "qux"}), + "Tuple(foo String, baz String)", + "{p12:Tuple(foo String, baz String)}", + "param_p12", + "('bar','qux')", + ), ]; let mut placeholders = vec![]; for (value, data_type_string, _, _, _) in &test_cases { let data_type = ClickHouseDataType::from_str(data_type_string) .expect("Data type string should be valid ClickHouseDataType"); - let placeholder = parameters.bind_json(value, data_type.into()); + let placeholder = parameters + .bind_json(value, data_type.into()) + .expect("Should successfully bind parameter"); placeholders.push(placeholder); } From 13f96ab245248a127fd09e657146442275512007 Mon Sep 17 00:00:00 2001 From: Benoit Ranque Date: Mon, 2 Sep 2024 01:10:36 -0400 Subject: [PATCH 08/10] improve errors --- crates/ndc-clickhouse/src/sql/ast.rs | 89 ++++++++++++------- .../src/sql/query_builder/error.rs | 63 ++++++++++--- .../src/sql/query_builder/parameter.rs | 2 +- 3 files changed, 112 insertions(+), 42 deletions(-) diff --git a/crates/ndc-clickhouse/src/sql/ast.rs b/crates/ndc-clickhouse/src/sql/ast.rs index 0d82e9f..a287f12 100644 --- a/crates/ndc-clickhouse/src/sql/ast.rs +++ b/crates/ndc-clickhouse/src/sql/ast.rs @@ -839,19 +839,22 @@ impl Value { ClickHouseDataType::Tuple(elements) => { // tuple should be anonymous, and tuple length should match array length if elements.len() != arr.len() { - Err(QueryBuilderError::UnsupportedParameterCast { - json_value: value.to_owned(), + Err(QueryBuilderError::TupleLengthMismatch { + value: value.to_owned(), data_type: data_type.to_owned().into(), }) } else { let values = arr .iter() .zip(elements.iter()) - .map(|(value, (identifier, data_type))| { + .map(|(value, (identifier, element_type))| { if identifier.is_some() { - todo!("unexpected identifer error") + Err(QueryBuilderError::ExpectedAnonymousTuple { + value: value.to_owned(), + data_type: data_type.to_owned().into(), + }) } else { - map_json_value(value, data_type) + map_json_value(value, element_type) } }) .collect::>()?; @@ -859,8 +862,34 @@ impl Value { Ok(Value::Tuple(values)) } } + ClickHouseDataType::Nested(elements) => Ok(Value::Array( + arr.iter() + .map(|value| match value { + serde_json::Value::Object(obj) => Ok(Value::Tuple( + elements + .iter() + .map(|(identifier, field_type)| { + if let Some(value) = obj.get(identifier.value()) { + map_json_value(value, field_type) + } else { + Err(QueryBuilderError::MissingNamedField { + value: value.to_owned(), + data_type: data_type.to_owned().into(), + field: identifier.value().to_string(), + }) + } + }) + .collect::>()?, + )), + _ => Err(QueryBuilderError::UnsupportedParameterCast { + value: value.to_owned(), + data_type: data_type.to_owned().into(), + }), + }) + .collect::>()?, + )), _ => Err(QueryBuilderError::UnsupportedParameterCast { - json_value: value.to_owned(), + value: value.to_owned(), data_type: data_type.to_owned().into(), }), }, @@ -877,32 +906,32 @@ impl Value { )), ClickHouseDataType::Tuple(elements) => { // tuple should be named, and all tuple keys should be in the input object - let mut values = vec![]; - - for (identifier, data_type) in elements { - if let Some(identifier) = identifier { - if let Some(value) = obj.get(identifier.value()) { - values.push(map_json_value(value, data_type)?) - } else { - // all tuple keys should be in the value object - return Err(QueryBuilderError::UnsupportedParameterCast { - json_value: value.to_owned(), - data_type: data_type.to_owned().into(), - }); - } - } else { - // tuple should not be anonymous - return Err(QueryBuilderError::UnsupportedParameterCast { - json_value: value.to_owned(), - data_type: data_type.to_owned().into(), - }); - } - } - - Ok(Value::Tuple(values)) + Ok(Value::Tuple( + elements + .iter() + .map(|(identifier, element_type)| { + if let Some(identifier) = identifier { + if let Some(value) = obj.get(identifier.value()) { + map_json_value(value, element_type) + } else { + Err(QueryBuilderError::MissingNamedField { + value: value.to_owned(), + data_type: data_type.to_owned().into(), + field: identifier.value().to_string(), + }) + } + } else { + Err(QueryBuilderError::ExpectedNamedTuple { + value: value.to_owned(), + data_type: data_type.to_owned().into(), + }) + } + }) + .collect::>()?, + )) } _ => Err(QueryBuilderError::UnsupportedParameterCast { - json_value: value.to_owned(), + value: value.to_owned(), data_type: data_type.to_owned().into(), }), }, diff --git a/crates/ndc-clickhouse/src/sql/query_builder/error.rs b/crates/ndc-clickhouse/src/sql/query_builder/error.rs index 0302a92..3a281f6 100644 --- a/crates/ndc-clickhouse/src/sql/query_builder/error.rs +++ b/crates/ndc-clickhouse/src/sql/query_builder/error.rs @@ -1,7 +1,6 @@ -use std::fmt; - use common::clickhouse_parser::parameterized_query::ParameterType; use ndc_sdk::connector::{ExplainError, QueryError}; +use std::fmt; use super::typecasting::TypeStringError; @@ -42,8 +41,29 @@ pub enum QueryBuilderError { ColumnTypeMismatch { expected: String, got: String }, /// Attempted to cast a JSON value to a mismatching data type UnsupportedParameterCast { - json_value: serde_json::Value, + value: serde_json::Value, + data_type: ParameterType, + }, + /// Attempted to cast a value to a tuple with a mismatching length + TupleLengthMismatch { + value: serde_json::Value, + data_type: ParameterType, + }, + /// Attempted to cast an Array to a named tuple, which should be represented as an object + ExpectedAnonymousTuple { + value: serde_json::Value, + data_type: ParameterType, + }, + /// Attempted to cast an Object to an anonymous tuple, which should be represented as an array + ExpectedNamedTuple { + value: serde_json::Value, + data_type: ParameterType, + }, + /// could not find field required by named tuple or nested in the source json object + MissingNamedField { + value: serde_json::Value, data_type: ParameterType, + field: String, }, } @@ -90,14 +110,27 @@ impl fmt::Display for QueryBuilderError { QueryBuilderError::ColumnTypeMismatch { expected, got } => { write!(f, "Column Type Mismatch: expected {expected}, got {got}") } - QueryBuilderError::UnsupportedParameterCast { - json_value, - data_type, - } => write!( + QueryBuilderError::UnsupportedParameterCast { value, data_type } => { + write!(f, "Cannot cast value `{}` to type `{}`", value, data_type) + } + QueryBuilderError::TupleLengthMismatch { value, data_type } => { + write!(f, "Tuple `{data_type}` length does not match value {value}") + } + QueryBuilderError::ExpectedAnonymousTuple { value, data_type } => { + write!( + f, + "Expected anonymous tuple for value `{value}`, got `{data_type}`" + ) + } + QueryBuilderError::ExpectedNamedTuple { value, data_type } => write!( f, - "Cannot cast value `{}` to type `{}`", - json_value, data_type + "Expected named tuple for value `{value}`, got `{data_type}`" ), + QueryBuilderError::MissingNamedField { + value, + data_type, + field, + } => write!(f, "Missing field `{field}` for `{data_type}` in `{value}`"), } } } @@ -120,7 +153,11 @@ impl From for QueryError { | QueryBuilderError::UnknownBinaryComparisonOperator(_) | QueryBuilderError::Typecasting(_) | QueryBuilderError::ColumnTypeMismatch { .. } - | QueryBuilderError::UnsupportedParameterCast { .. } => { + | QueryBuilderError::UnsupportedParameterCast { .. } + | QueryBuilderError::ExpectedAnonymousTuple { .. } + | QueryBuilderError::ExpectedNamedTuple { .. } + | QueryBuilderError::MissingNamedField { .. } + | QueryBuilderError::TupleLengthMismatch { .. } => { QueryError::new_invalid_request(&value) } QueryBuilderError::NotSupported(_) => QueryError::new_unsupported_operation(&value), @@ -145,7 +182,11 @@ impl From for ExplainError { | QueryBuilderError::UnknownBinaryComparisonOperator(_) | QueryBuilderError::Typecasting(_) | QueryBuilderError::ColumnTypeMismatch { .. } - | QueryBuilderError::UnsupportedParameterCast { .. } => { + | QueryBuilderError::UnsupportedParameterCast { .. } + | QueryBuilderError::ExpectedAnonymousTuple { .. } + | QueryBuilderError::ExpectedNamedTuple { .. } + | QueryBuilderError::MissingNamedField { .. } + | QueryBuilderError::TupleLengthMismatch { .. } => { ExplainError::new_invalid_request(&value) } QueryBuilderError::NotSupported(_) => ExplainError::new_unsupported_operation(&value), diff --git a/crates/ndc-clickhouse/src/sql/query_builder/parameter.rs b/crates/ndc-clickhouse/src/sql/query_builder/parameter.rs index 2432fe6..de42d5f 100644 --- a/crates/ndc-clickhouse/src/sql/query_builder/parameter.rs +++ b/crates/ndc-clickhouse/src/sql/query_builder/parameter.rs @@ -38,7 +38,7 @@ impl ParameterBuilder { serde_json::Value::String(s) => Value::SingleQuotedString(s.to_owned()), _ => { return Err(QueryBuilderError::UnsupportedParameterCast { - json_value: value.to_owned(), + value: value.to_owned(), data_type: data_type.to_owned(), }) } From 66bd2dc37295ca2cc7a50d16c849e72cef5887a5 Mon Sep 17 00:00:00 2001 From: Benoit Ranque Date: Mon, 2 Sep 2024 01:12:50 -0400 Subject: [PATCH 09/10] Add tests for bound parameters --- crates/ndc-clickhouse/tests/query_builder.rs | 110 ++++++++++++- ...01_select_rows.parameterized_statement.sql | 31 ++++ .../01_select_rows.parameters.txt | 0 ...with_predicate.parameterized_statement.sql | 33 ++++ .../02_with_predicate.parameters.txt | 1 + ...rger_predicate.parameterized_statement.sql | 36 +++++ .../03_larger_predicate.parameters.txt | 2 + .../03_larger_predicate.request.json | 2 +- .../04_limit.parameterized_statement.sql | 33 ++++ .../01_simple_queries/04_limit.parameters.txt | 0 .../01_simple_queries/04_limit.request.json | 2 +- .../05_offset.parameterized_statement.sql | 31 ++++ .../05_offset.parameters.txt | 0 .../01_simple_queries/05_offset.request.json | 2 +- ...6_limit_offset.parameterized_statement.sql | 33 ++++ .../06_limit_offset.parameters.txt | 0 .../06_limit_offset.request.json | 2 +- .../07_order_by.parameterized_statement.sql | 33 ++++ .../07_order_by.parameters.txt | 0 .../07_order_by.request.json | 2 +- ...ffset_order_by.parameterized_statement.sql | 37 +++++ ...icate_limit_offset_order_by.parameters.txt | 1 + ...edicate_limit_offset_order_by.request.json | 2 +- ...t_relationship.parameterized_statement.sql | 53 +++++++ .../01_object_relationship.parameters.txt | 0 ...y_relationship.parameterized_statement.sql | 58 +++++++ .../02_array_relationship.parameters.txt | 0 ...rent_predicate.parameterized_statement.sql | 60 +++++++ .../03_parent_predicate.parameters.txt | 1 + ...hild_predicate.parameterized_statement.sql | 62 ++++++++ .../04_child_predicate.parameters.txt | 2 + ...p_in_predicate.parameterized_statement.sql | 71 +++++++++ ...e_relationship_in_predicate.parameters.txt | 1 + ...ip_in_order_by.parameterized_statement.sql | 80 ++++++++++ ...se_relationship_in_order_by.parameters.txt | 0 ..._relationships.parameterized_statement.sql | 76 +++++++++ ...regate_across_relationships.parameters.txt | 0 ...mple_predicate.parameterized_statement.sql | 45 ++++++ .../01_simple_predicate.parameters.txt | 1 + ..._variable_sets.parameterized_statement.sql | 45 ++++++ .../02_empty_variable_sets.parameters.txt | 1 + .../03_single_set.parameterized_statement.sql | 45 ++++++ .../03_variables/03_single_set.parameters.txt | 1 + .../_config/configuration.json | 2 +- .../complex_columns/_schema/schema.json | 27 +++- ...lex_parameters.parameterized_statement.sql | 41 +++++ .../01_bind_complex_parameters.parameters.txt | 6 + .../01_bind_complex_parameters.request.json | 148 ++++++++++++++++++ .../01_bind_complex_parameters.statement.sql | 35 +++++ ...olumn_accessor.parameterized_statement.sql | 24 +++ ...01_generate_column_accessor.parameters.txt | 0 .../01_generate_column_accessor.request.json | 38 ++--- ...f_not_required.parameterized_statement.sql | 21 +++ .../02_skip_if_not_required.parameters.txt | 0 .../02_skip_if_not_required.request.json | 46 +++--- ...n_nested_field.parameterized_statement.sql | 47 ++++++ ...lationships_on_nested_field.parameters.txt | 0 ...relationships_on_nested_field.request.json | 74 ++++----- ...onships_on_array_nested_field.request.json | 77 +++++---- ...omplex_example.parameterized_statement.sql | 51 ++++++ .../05_complex_example.parameters.txt | 0 .../05_complex_example.request.json | 101 ++++++------ ...sted_accessors.parameterized_statement.sql | 30 ++++ ...no_useless_nested_accessors.parameters.txt | 0 ...6_no_useless_nested_accessors.request.json | 96 ++++++------ ...1_native_query.parameterized_statement.sql | 30 ++++ .../01_native_query.parameters.txt | 0 67 files changed, 1656 insertions(+), 233 deletions(-) create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/01_select_rows.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/01_select_rows.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/02_with_predicate.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/02_with_predicate.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/03_larger_predicate.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/03_larger_predicate.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/04_limit.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/04_limit.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/05_offset.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/05_offset.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/06_limit_offset.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/06_limit_offset.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/07_order_by.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/07_order_by.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/08_predicate_limit_offset_order_by.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/08_predicate_limit_offset_order_by.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/01_object_relationship.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/01_object_relationship.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/02_array_relationship.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/02_array_relationship.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/03_parent_predicate.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/03_parent_predicate.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/04_child_predicate.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/04_child_predicate.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/05_traverse_relationship_in_predicate.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/05_traverse_relationship_in_predicate.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/06_traverse_relationship_in_order_by.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/06_traverse_relationship_in_order_by.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/07_order_by_aggregate_across_relationships.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/07_order_by_aggregate_across_relationships.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/01_simple_predicate.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/01_simple_predicate.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/02_empty_variable_sets.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/02_empty_variable_sets.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/03_single_set.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/03_single_set.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.request.json create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/01_generate_column_accessor.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/01_generate_column_accessor.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/02_skip_if_not_required.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/02_skip_if_not_required.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/03_support_relationships_on_nested_field.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/03_support_relationships_on_nested_field.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/05_complex_example.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/05_complex_example.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/06_no_useless_nested_accessors.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/06_no_useless_nested_accessors.parameters.txt create mode 100644 crates/ndc-clickhouse/tests/query_builder/star_schema/01_native_queries/01_native_query.parameterized_statement.sql create mode 100644 crates/ndc-clickhouse/tests/query_builder/star_schema/01_native_queries/01_native_query.parameters.txt diff --git a/crates/ndc-clickhouse/tests/query_builder.rs b/crates/ndc-clickhouse/tests/query_builder.rs index ac95c44..b4a192a 100644 --- a/crates/ndc-clickhouse/tests/query_builder.rs +++ b/crates/ndc-clickhouse/tests/query_builder.rs @@ -70,6 +70,26 @@ mod test_utils { let expected_statement = fs::read_to_string(&statement_path).await?; Ok(expected_statement) } + async fn read_expected_parameterized_sql( + schema_dir: &str, + group_dir: &str, + test_name: &str, + ) -> Result> { + let statement_path = tests_dir_path(schema_dir, group_dir) + .join(format!("{test_name}.parameterized_statement.sql")); + let expected_statement = fs::read_to_string(&statement_path).await?; + Ok(expected_statement) + } + async fn read_expected_parameters( + schema_dir: &str, + group_dir: &str, + test_name: &str, + ) -> Result> { + let parameters_path = + tests_dir_path(schema_dir, group_dir).join(format!("{test_name}.parameters.txt")); + let expected_parameters = fs::read_to_string(¶meters_path).await?; + Ok(expected_parameters) + } async fn write_expected_sql( schema_dir: &str, group_dir: &str, @@ -78,8 +98,23 @@ mod test_utils { ) -> Result<(), Box> { let statement_path = tests_dir_path(schema_dir, group_dir).join(format!("{test_name}.statement.sql")); - let pretty_statement = pretty_print_sql(generated_statement); - fs::write(&statement_path, &pretty_statement).await?; + fs::write(&statement_path, &generated_statement).await?; + Ok(()) + } + async fn write_expected_parameterized_sql( + schema_dir: &str, + group_dir: &str, + test_name: &str, + generated_statement: &str, + parameters: &str, + ) -> Result<(), Box> { + let statement_path = tests_dir_path(schema_dir, group_dir) + .join(format!("{test_name}.parameterized_statement.sql")); + let parameters_path = + tests_dir_path(schema_dir, group_dir).join(format!("{test_name}.parameters.txt")); + fs::write(&statement_path, &generated_statement).await?; + fs::write(¶meters_path, ¶meters).await?; + Ok(()) } fn pretty_print_sql(query: &str) -> String { @@ -104,6 +139,26 @@ mod test_utils { ); Ok(generated_statement) } + fn generate_parameterized_sql( + configuration: &ServerConfig, + request: &models::QueryRequest, + ) -> Result<(String, String), QueryBuilderError> { + let (statement, parameters) = + QueryBuilder::new(request, configuration).build_parameterized()?; + let pretty_statement = pretty_print_sql(&statement.to_string()); + let printed_parameters = + parameters + .into_iter() + .fold(String::new(), |mut acc, (name, value)| { + acc.reserve(name.len() + value.len() + 2); + acc.push_str(&name); + acc.push('='); + acc.push_str(&value); + acc.push('\n'); + acc + }); + Ok((pretty_statement, printed_parameters)) + } pub async fn test_generated_sql( schema_dir: &str, group_dir: &str, @@ -113,13 +168,48 @@ mod test_utils { let request = read_request(schema_dir, group_dir, test_name).await?; let generated_sql = generate_sql(&configuration, &request)?; + let (parameterized_sql, parameters) = generate_parameterized_sql(&configuration, &request)?; if UPDATE_SNAPSHOTS { write_expected_sql(schema_dir, group_dir, test_name, &generated_sql).await?; + write_expected_parameterized_sql( + schema_dir, + group_dir, + test_name, + ¶meterized_sql, + ¶meters, + ) + .await?; } else { let expected_sql = read_expected_sql(schema_dir, group_dir, test_name).await?; - assert_eq!(generated_sql, expected_sql); + assert_eq!( + generated_sql, expected_sql, + "Inlined SQL should match snapshot" + ); + + let expected_parameterized_sql = + read_expected_parameterized_sql(schema_dir, group_dir, test_name).await?; + + assert_eq!( + parameterized_sql, expected_parameterized_sql, + "Parameterized SQL should match snapshot" + ); + + let expected_parameters = + read_expected_parameters(schema_dir, group_dir, test_name).await?; + + assert_eq!( + parameters, expected_parameters, + "Parameters should match snapshot" + ); + + if expected_parameters.is_empty() { + assert_eq!( + generated_sql, parameterized_sql, + "If no parameters are present, parameterized sql should match inlined sql" + ); + } } Ok(()) @@ -349,3 +439,17 @@ mod field_selector { test_generated_sql("06_no_useless_nested_accessors").await } } + +#[cfg(test)] +mod bound_parameters { + use super::*; + + async fn test_generated_sql(name: &str) -> Result<(), Box> { + super::test_utils::test_generated_sql("complex_columns", "bound_parameters", name).await + } + + #[tokio::test] + async fn bind_complex_parameters() -> Result<(), Box> { + test_generated_sql("01_bind_complex_parameters").await + } +} diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/01_select_rows.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/01_select_rows.parameterized_statement.sql new file mode 100644 index 0000000..6d3e6f9 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/01_select_rows.parameterized_statement.sql @@ -0,0 +1,31 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String)))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title" + FROM + "Chinook"."Album" AS "_origin" + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/01_select_rows.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/01_select_rows.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/02_with_predicate.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/02_with_predicate.parameterized_statement.sql new file mode 100644 index 0000000..f93ae9e --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/02_with_predicate.parameterized_statement.sql @@ -0,0 +1,33 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String)))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title" + FROM + "Chinook"."Album" AS "_origin" + WHERE + "_origin"."ArtistId" = { p0 :Int32 } + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/02_with_predicate.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/02_with_predicate.parameters.txt new file mode 100644 index 0000000..d4dcdf8 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/02_with_predicate.parameters.txt @@ -0,0 +1 @@ +param_p0=1 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/03_larger_predicate.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/03_larger_predicate.parameterized_statement.sql new file mode 100644 index 0000000..5189ff6 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/03_larger_predicate.parameterized_statement.sql @@ -0,0 +1,36 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String)))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title" + FROM + "Chinook"."Album" AS "_origin" + WHERE + ( + "_origin"."ArtistId" = { p0 :Int32 } + AND NOT ("_origin"."ArtistId" = { p1 :Int32 }) + ) + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/03_larger_predicate.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/03_larger_predicate.parameters.txt new file mode 100644 index 0000000..e30b664 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/03_larger_predicate.parameters.txt @@ -0,0 +1,2 @@ +param_p0=1 +param_p1=2 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/03_larger_predicate.request.json b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/03_larger_predicate.request.json index a41e8fe..6a89bee 100644 --- a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/03_larger_predicate.request.json +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/03_larger_predicate.request.json @@ -1,5 +1,5 @@ { - "$schema": "../request.schema.json", + "$schema": "../../request.schema.json", "collection": "Chinook_Album", "query": { "fields": { diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/04_limit.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/04_limit.parameterized_statement.sql new file mode 100644 index 0000000..3063baa --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/04_limit.parameterized_statement.sql @@ -0,0 +1,33 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String)))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title" + FROM + "Chinook"."Album" AS "_origin" + LIMIT + 10 + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/04_limit.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/04_limit.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/04_limit.request.json b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/04_limit.request.json index acb33fa..f5b55eb 100644 --- a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/04_limit.request.json +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/04_limit.request.json @@ -1,5 +1,5 @@ { - "$schema": "../request.schema.json", + "$schema": "../../request.schema.json", "collection": "Chinook_Album", "query": { "fields": { diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/05_offset.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/05_offset.parameterized_statement.sql new file mode 100644 index 0000000..c54bca3 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/05_offset.parameterized_statement.sql @@ -0,0 +1,31 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String)))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title" + FROM + "Chinook"."Album" AS "_origin" OFFSET 10 + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/05_offset.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/05_offset.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/05_offset.request.json b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/05_offset.request.json index 63d6984..426c9c2 100644 --- a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/05_offset.request.json +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/05_offset.request.json @@ -1,5 +1,5 @@ { - "$schema": "../request.schema.json", + "$schema": "../../request.schema.json", "collection": "Chinook_Album", "query": { "fields": { diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/06_limit_offset.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/06_limit_offset.parameterized_statement.sql new file mode 100644 index 0000000..20d8f04 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/06_limit_offset.parameterized_statement.sql @@ -0,0 +1,33 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String)))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title" + FROM + "Chinook"."Album" AS "_origin" + LIMIT + 10 OFFSET 10 + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/06_limit_offset.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/06_limit_offset.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/06_limit_offset.request.json b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/06_limit_offset.request.json index b69b6c4..4b9bce7 100644 --- a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/06_limit_offset.request.json +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/06_limit_offset.request.json @@ -1,5 +1,5 @@ { - "$schema": "../request.schema.json", + "$schema": "../../request.schema.json", "collection": "Chinook_Album", "query": { "fields": { diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/07_order_by.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/07_order_by.parameterized_statement.sql new file mode 100644 index 0000000..d9df4f4 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/07_order_by.parameterized_statement.sql @@ -0,0 +1,33 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String)))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title" + FROM + "Chinook"."Album" AS "_origin" + ORDER BY + "_origin"."ArtistId" ASC + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/07_order_by.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/07_order_by.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/07_order_by.request.json b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/07_order_by.request.json index 27b8008..413cb08 100644 --- a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/07_order_by.request.json +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/07_order_by.request.json @@ -1,5 +1,5 @@ { - "$schema": "../request.schema.json", + "$schema": "../../request.schema.json", "collection": "Chinook_Album", "query": { "fields": { diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/08_predicate_limit_offset_order_by.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/08_predicate_limit_offset_order_by.parameterized_statement.sql new file mode 100644 index 0000000..840f9a3 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/08_predicate_limit_offset_order_by.parameterized_statement.sql @@ -0,0 +1,37 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String)))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title" + FROM + "Chinook"."Album" AS "_origin" + WHERE + "_origin"."ArtistId" > { p0 :Int32 } + ORDER BY + "_origin"."ArtistId" ASC + LIMIT + 10 OFFSET 10 + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/08_predicate_limit_offset_order_by.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/08_predicate_limit_offset_order_by.parameters.txt new file mode 100644 index 0000000..4214a1e --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/08_predicate_limit_offset_order_by.parameters.txt @@ -0,0 +1 @@ +param_p0=10 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/08_predicate_limit_offset_order_by.request.json b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/08_predicate_limit_offset_order_by.request.json index c1275db..ba6fdd1 100644 --- a/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/08_predicate_limit_offset_order_by.request.json +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/01_simple_queries/08_predicate_limit_offset_order_by.request.json @@ -1,5 +1,5 @@ { - "$schema": "../request.schema.json", + "$schema": "../../request.schema.json", "collection": "Chinook_Album", "query": { "fields": { diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/01_object_relationship.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/01_object_relationship.parameterized_statement.sql new file mode 100644 index 0000000..80d4b9a --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/01_object_relationship.parameterized_statement.sql @@ -0,0 +1,53 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String, "Artist" Tuple(rows Array(Tuple("artistId" Int32, "name" Nullable(String)))))))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title", + "_row"."_field_Artist" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title", + "_rel_0_Artist"."_rowset" AS "_field_Artist" + FROM + "Chinook"."Album" AS "_origin" + LEFT JOIN ( + SELECT + tuple( + groupArray( + tuple("_row"."_field_artistId", "_row"."_field_name") + ) + ) AS "_rowset", + "_row"."_relkey_ArtistId" AS "_relkey_ArtistId" + FROM + ( + SELECT + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Name" AS "_field_name", + "_origin"."ArtistId" AS "_relkey_ArtistId" + FROM + "Chinook"."Artist" AS "_origin" + ) AS "_row" + GROUP BY + "_row"."_relkey_ArtistId" + ) AS "_rel_0_Artist" ON "_origin"."ArtistId" = "_rel_0_Artist"."_relkey_ArtistId" + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/01_object_relationship.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/01_object_relationship.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/02_array_relationship.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/02_array_relationship.parameterized_statement.sql new file mode 100644 index 0000000..ebf6a7d --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/02_array_relationship.parameterized_statement.sql @@ -0,0 +1,58 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String, "Tracks" Tuple(rows Array(Tuple("trackId" Int32, "name" String, "unitPrice" Float64))))))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title", + "_row"."_field_Tracks" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title", + "_rel_0_Tracks"."_rowset" AS "_field_Tracks" + FROM + "Chinook"."Album" AS "_origin" + LEFT JOIN ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_trackId", + "_row"."_field_name", + "_row"."_field_unitPrice" + ) + ) + ) AS "_rowset", + "_row"."_relkey_AlbumId" AS "_relkey_AlbumId" + FROM + ( + SELECT + "_origin"."TrackId" AS "_field_trackId", + "_origin"."Name" AS "_field_name", + "_origin"."UnitPrice" AS "_field_unitPrice", + "_origin"."AlbumId" AS "_relkey_AlbumId" + FROM + "Chinook"."Track" AS "_origin" + ) AS "_row" + GROUP BY + "_row"."_relkey_AlbumId" + ) AS "_rel_0_Tracks" ON "_origin"."AlbumId" = "_rel_0_Tracks"."_relkey_AlbumId" + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/02_array_relationship.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/02_array_relationship.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/03_parent_predicate.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/03_parent_predicate.parameterized_statement.sql new file mode 100644 index 0000000..eb4a050 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/03_parent_predicate.parameterized_statement.sql @@ -0,0 +1,60 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String, "Tracks" Tuple(rows Array(Tuple("trackId" Int32, "name" String, "unitPrice" Float64))))))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title", + "_row"."_field_Tracks" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title", + "_rel_0_Tracks"."_rowset" AS "_field_Tracks" + FROM + "Chinook"."Album" AS "_origin" + LEFT JOIN ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_trackId", + "_row"."_field_name", + "_row"."_field_unitPrice" + ) + ) + ) AS "_rowset", + "_row"."_relkey_AlbumId" AS "_relkey_AlbumId" + FROM + ( + SELECT + "_origin"."TrackId" AS "_field_trackId", + "_origin"."Name" AS "_field_name", + "_origin"."UnitPrice" AS "_field_unitPrice", + "_origin"."AlbumId" AS "_relkey_AlbumId" + FROM + "Chinook"."Track" AS "_origin" + ) AS "_row" + GROUP BY + "_row"."_relkey_AlbumId" + ) AS "_rel_0_Tracks" ON "_origin"."AlbumId" = "_rel_0_Tracks"."_relkey_AlbumId" + WHERE + "_origin"."ArtistId" > { p0 :Int32 } + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/03_parent_predicate.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/03_parent_predicate.parameters.txt new file mode 100644 index 0000000..4214a1e --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/03_parent_predicate.parameters.txt @@ -0,0 +1 @@ +param_p0=10 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/04_child_predicate.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/04_child_predicate.parameterized_statement.sql new file mode 100644 index 0000000..f522d94 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/04_child_predicate.parameterized_statement.sql @@ -0,0 +1,62 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String, "Tracks" Tuple(rows Array(Tuple("trackId" Int32, "name" String, "unitPrice" Float64))))))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title", + "_row"."_field_Tracks" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title", + "_rel_0_Tracks"."_rowset" AS "_field_Tracks" + FROM + "Chinook"."Album" AS "_origin" + LEFT JOIN ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_trackId", + "_row"."_field_name", + "_row"."_field_unitPrice" + ) + ) + ) AS "_rowset", + "_row"."_relkey_AlbumId" AS "_relkey_AlbumId" + FROM + ( + SELECT + "_origin"."TrackId" AS "_field_trackId", + "_origin"."Name" AS "_field_name", + "_origin"."UnitPrice" AS "_field_unitPrice", + "_origin"."AlbumId" AS "_relkey_AlbumId" + FROM + "Chinook"."Track" AS "_origin" + WHERE + "_origin"."TrackId" > { p0 :Int32 } + ) AS "_row" + GROUP BY + "_row"."_relkey_AlbumId" + ) AS "_rel_0_Tracks" ON "_origin"."AlbumId" = "_rel_0_Tracks"."_relkey_AlbumId" + WHERE + "_origin"."ArtistId" > { p1 :Int32 } + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/04_child_predicate.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/04_child_predicate.parameters.txt new file mode 100644 index 0000000..44205cd --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/04_child_predicate.parameters.txt @@ -0,0 +1,2 @@ +param_p0=10 +param_p1=10 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/05_traverse_relationship_in_predicate.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/05_traverse_relationship_in_predicate.parameterized_statement.sql new file mode 100644 index 0000000..c4c02e8 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/05_traverse_relationship_in_predicate.parameterized_statement.sql @@ -0,0 +1,71 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String, "Tracks" Tuple(rows Array(Tuple("trackId" Int32, "name" String, "unitPrice" Float64))))))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title", + "_row"."_field_Tracks" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title", + "_rel_0_Tracks"."_rowset" AS "_field_Tracks" + FROM + "Chinook"."Album" AS "_origin" + LEFT JOIN ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_trackId", + "_row"."_field_name", + "_row"."_field_unitPrice" + ) + ) + ) AS "_rowset", + "_row"."_relkey_AlbumId" AS "_relkey_AlbumId" + FROM + ( + SELECT + "_origin"."TrackId" AS "_field_trackId", + "_origin"."Name" AS "_field_name", + "_origin"."UnitPrice" AS "_field_unitPrice", + "_origin"."AlbumId" AS "_relkey_AlbumId" + FROM + "Chinook"."Track" AS "_origin" + ) AS "_row" + GROUP BY + "_row"."_relkey_AlbumId" + ) AS "_rel_0_Tracks" ON "_origin"."AlbumId" = "_rel_0_Tracks"."_relkey_AlbumId" + LEFT JOIN ( + SELECT + TRUE AS "_exists_0", + "_exists_1"."ArtistId" AS "_relkey_ArtistId" + FROM + "Chinook"."Artist" AS "_exists_1" + WHERE + "_exists_1"."Name" = { p0 :Nullable(String) } + LIMIT + 1 BY "_exists_1"."ArtistId" + ) AS "_exists_0" ON "_origin"."ArtistId" = "_exists_0"."_relkey_ArtistId" + WHERE + "_exists_0"."_exists_0" = TRUE + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/05_traverse_relationship_in_predicate.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/05_traverse_relationship_in_predicate.parameters.txt new file mode 100644 index 0000000..6f1520b --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/05_traverse_relationship_in_predicate.parameters.txt @@ -0,0 +1 @@ +param_p0=AC/DC diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/06_traverse_relationship_in_order_by.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/06_traverse_relationship_in_order_by.parameterized_statement.sql new file mode 100644 index 0000000..0839ad6 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/06_traverse_relationship_in_order_by.parameterized_statement.sql @@ -0,0 +1,80 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("trackId" Int32, "name" String, "Album" Tuple(rows Array(Tuple("Artist" Tuple(rows Array(Tuple("name" Nullable(String))))))))))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_trackId", + "_row"."_field_name", + "_row"."_field_Album" + ) + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."TrackId" AS "_field_trackId", + "_origin"."Name" AS "_field_name", + "_rel_0_Album"."_rowset" AS "_field_Album" + FROM + "Chinook"."Track" AS "_origin" + LEFT JOIN ( + SELECT + tuple(groupArray(tuple("_row"."_field_Artist"))) AS "_rowset", + "_row"."_relkey_AlbumId" AS "_relkey_AlbumId" + FROM + ( + SELECT + "_rel_0_Artist"."_rowset" AS "_field_Artist", + "_origin"."AlbumId" AS "_relkey_AlbumId" + FROM + "Chinook"."Album" AS "_origin" + LEFT JOIN ( + SELECT + tuple(groupArray(tuple("_row"."_field_name"))) AS "_rowset", + "_row"."_relkey_ArtistId" AS "_relkey_ArtistId" + FROM + ( + SELECT + "_origin"."Name" AS "_field_name", + "_origin"."ArtistId" AS "_relkey_ArtistId" + FROM + "Chinook"."Artist" AS "_origin" + ) AS "_row" + GROUP BY + "_row"."_relkey_ArtistId" + ) AS "_rel_0_Artist" ON "_origin"."ArtistId" = "_rel_0_Artist"."_relkey_ArtistId" + ) AS "_row" + GROUP BY + "_row"."_relkey_AlbumId" + ) AS "_rel_0_Album" ON "_origin"."AlbumId" = "_rel_0_Album"."_relkey_AlbumId" + LEFT JOIN ( + SELECT + "_order_by_0"."AlbumId" AS "_relkey_AlbumId", + "_order_by_1"."Name" AS "_order_by_value" + FROM + "Chinook"."Album" AS "_order_by_0" + JOIN "Chinook"."Artist" AS "_order_by_1" ON "_order_by_0"."ArtistId" = "_order_by_1"."ArtistId" + WHERE + TRUE + AND TRUE + GROUP BY + "_order_by_0"."AlbumId", + "_order_by_1"."Name" + LIMIT + 1 BY "_order_by_0"."AlbumId" + ) AS "_order_by_0" ON "_origin"."AlbumId" = "_order_by_0"."_relkey_AlbumId" + ORDER BY + "_order_by_0"."_order_by_value" ASC, + "_origin"."Name" ASC + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/06_traverse_relationship_in_order_by.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/06_traverse_relationship_in_order_by.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/07_order_by_aggregate_across_relationships.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/07_order_by_aggregate_across_relationships.parameterized_statement.sql new file mode 100644 index 0000000..db6585a --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/07_order_by_aggregate_across_relationships.parameterized_statement.sql @@ -0,0 +1,76 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("trackId" Int32, "name" String)))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple("_row"."_field_trackId", "_row"."_field_name") + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."TrackId" AS "_field_trackId", + "_origin"."Name" AS "_field_name" + FROM + "Chinook"."Track" AS "_origin" + LEFT JOIN ( + SELECT + "_order_by_0"."AlbumId" AS "_relkey_AlbumId", + "_order_by_1"."Name" AS "_order_by_value" + FROM + "Chinook"."Album" AS "_order_by_0" + JOIN "Chinook"."Artist" AS "_order_by_1" ON "_order_by_0"."ArtistId" = "_order_by_1"."ArtistId" + WHERE + TRUE + AND TRUE + GROUP BY + "_order_by_0"."AlbumId", + "_order_by_1"."Name" + LIMIT + 1 BY "_order_by_0"."AlbumId" + ) AS "_order_by_0" ON "_origin"."AlbumId" = "_order_by_0"."_relkey_AlbumId" + LEFT JOIN ( + SELECT + "_order_by_0"."AlbumId" AS "_relkey_AlbumId", + COUNT(*) AS "_order_by_value" + FROM + "Chinook"."Album" AS "_order_by_0" + JOIN "Chinook"."Artist" AS "_order_by_1" ON "_order_by_0"."ArtistId" = "_order_by_1"."ArtistId" + WHERE + TRUE + AND TRUE + GROUP BY + "_order_by_0"."AlbumId" + LIMIT + 1 BY "_order_by_0"."AlbumId" + ) AS "_order_by_1" ON "_origin"."AlbumId" = "_order_by_1"."_relkey_AlbumId" + LEFT JOIN ( + SELECT + "_order_by_0"."AlbumId" AS "_relkey_AlbumId", + max("_order_by_1"."Name") AS "_order_by_value" + FROM + "Chinook"."Album" AS "_order_by_0" + JOIN "Chinook"."Artist" AS "_order_by_1" ON "_order_by_0"."ArtistId" = "_order_by_1"."ArtistId" + WHERE + TRUE + AND TRUE + GROUP BY + "_order_by_0"."AlbumId" + LIMIT + 1 BY "_order_by_0"."AlbumId" + ) AS "_order_by_2" ON "_origin"."AlbumId" = "_order_by_2"."_relkey_AlbumId" + ORDER BY + "_order_by_0"."_order_by_value" ASC, + "_order_by_1"."_order_by_value" ASC, + "_order_by_2"."_order_by_value" ASC + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/07_order_by_aggregate_across_relationships.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/02_relationships/07_order_by_aggregate_across_relationships.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/01_simple_predicate.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/01_simple_predicate.parameterized_statement.sql new file mode 100644 index 0000000..e3706da --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/01_simple_predicate.parameterized_statement.sql @@ -0,0 +1,45 @@ +WITH "_vars" AS ( + SELECT + * + FROM + format(JSONColumns, { p0 :String }) +) +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String)))' + ) + ) + ) AS "rowsets" +FROM + "_vars" AS "_vars" + LEFT JOIN ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title" + ) + ) + ) AS "_rowset", + "_row"."_varset_id" AS "_varset_id" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title", + "_vars"."_varset_id" AS "_varset_id" + FROM + "_vars" AS "_vars" + CROSS JOIN "Chinook"."Album" AS "_origin" + WHERE + "_origin"."ArtistId" = "_vars"."_var_ArtistId" + ) AS "_row" + GROUP BY + "_row"."_varset_id" + ) AS "_rowset" ON "_vars"."_varset_id" = "_rowset"."_varset_id" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/01_simple_predicate.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/01_simple_predicate.parameters.txt new file mode 100644 index 0000000..693abaf --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/01_simple_predicate.parameters.txt @@ -0,0 +1 @@ +param_p0={"_varset_id":[1,2],"_var_ArtistId":[1,2]} diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/02_empty_variable_sets.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/02_empty_variable_sets.parameterized_statement.sql new file mode 100644 index 0000000..e3706da --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/02_empty_variable_sets.parameterized_statement.sql @@ -0,0 +1,45 @@ +WITH "_vars" AS ( + SELECT + * + FROM + format(JSONColumns, { p0 :String }) +) +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String)))' + ) + ) + ) AS "rowsets" +FROM + "_vars" AS "_vars" + LEFT JOIN ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title" + ) + ) + ) AS "_rowset", + "_row"."_varset_id" AS "_varset_id" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title", + "_vars"."_varset_id" AS "_varset_id" + FROM + "_vars" AS "_vars" + CROSS JOIN "Chinook"."Album" AS "_origin" + WHERE + "_origin"."ArtistId" = "_vars"."_var_ArtistId" + ) AS "_row" + GROUP BY + "_row"."_varset_id" + ) AS "_rowset" ON "_vars"."_varset_id" = "_rowset"."_varset_id" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/02_empty_variable_sets.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/02_empty_variable_sets.parameters.txt new file mode 100644 index 0000000..a71a904 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/02_empty_variable_sets.parameters.txt @@ -0,0 +1 @@ +param_p0={"_varset_id":[]} diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/03_single_set.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/03_single_set.parameterized_statement.sql new file mode 100644 index 0000000..e3706da --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/03_single_set.parameterized_statement.sql @@ -0,0 +1,45 @@ +WITH "_vars" AS ( + SELECT + * + FROM + format(JSONColumns, { p0 :String }) +) +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String)))' + ) + ) + ) AS "rowsets" +FROM + "_vars" AS "_vars" + LEFT JOIN ( + SELECT + tuple( + groupArray( + tuple( + "_row"."_field_albumId", + "_row"."_field_artistId", + "_row"."_field_title" + ) + ) + ) AS "_rowset", + "_row"."_varset_id" AS "_varset_id" + FROM + ( + SELECT + "_origin"."AlbumId" AS "_field_albumId", + "_origin"."ArtistId" AS "_field_artistId", + "_origin"."Title" AS "_field_title", + "_vars"."_varset_id" AS "_varset_id" + FROM + "_vars" AS "_vars" + CROSS JOIN "Chinook"."Album" AS "_origin" + WHERE + "_origin"."ArtistId" = "_vars"."_var_ArtistId" + ) AS "_row" + GROUP BY + "_row"."_varset_id" + ) AS "_rowset" ON "_vars"."_varset_id" = "_rowset"."_varset_id" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/03_single_set.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/03_single_set.parameters.txt new file mode 100644 index 0000000..8df3aeb --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/chinook/03_variables/03_single_set.parameters.txt @@ -0,0 +1 @@ +param_p0={"_varset_id":[1],"_var_ArtistId":[1]} diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/_config/configuration.json b/crates/ndc-clickhouse/tests/query_builder/complex_columns/_config/configuration.json index 4751783..3c8fcf6 100644 --- a/crates/ndc-clickhouse/tests/query_builder/complex_columns/_config/configuration.json +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/_config/configuration.json @@ -9,7 +9,7 @@ "columns": { "ColumnA": "String", "ColumnB": "Array(Tuple(field1 String, field2 String))", - "ColumnC": "Nested(field1 String, field1 String)", + "ColumnC": "Nested(field1 String, field2 String)", "ColumnD": "Tuple(child Tuple(id UInt32, name String))", "ColumnE": "Tuple(child Array(Tuple(id UInt32, name String)))", "ColumnF": "Tuple(child Tuple(id UInt32, name String, toys Nested(id UInt32, name String)))", diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/_schema/schema.json b/crates/ndc-clickhouse/tests/query_builder/complex_columns/_schema/schema.json index 76e392f..c277af9 100644 --- a/crates/ndc-clickhouse/tests/query_builder/complex_columns/_schema/schema.json +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/_schema/schema.json @@ -4,10 +4,6 @@ "aggregate_functions": {}, "comparison_operators": {} }, - "Nested(field1 String, field1 String)": { - "aggregate_functions": {}, - "comparison_operators": {} - }, "String": { "representation": { "type": "string" @@ -235,8 +231,11 @@ }, "ColumnC": { "type": { - "type": "named", - "name": "Nested(field1 String, field1 String)" + "type": "array", + "element_type": { + "type": "named", + "name": "TableOne.ColumnC" + } } }, "ColumnD": { @@ -281,6 +280,22 @@ } } }, + "TableOne.ColumnC": { + "fields": { + "field1": { + "type": { + "type": "named", + "name": "String" + } + }, + "field2": { + "type": { + "type": "named", + "name": "String" + } + } + } + }, "TableOne.ColumnD": { "fields": { "child": { diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.parameterized_statement.sql new file mode 100644 index 0000000..b799206 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.parameterized_statement.sql @@ -0,0 +1,41 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("field1" String)))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple(groupArray(tuple("_row"."_field_field1"))) AS "_rowset" + FROM + ( + SELECT + "_origin"."ColumnA" AS "_field_field1" + FROM + "Schema1"."Table1" AS "_origin" + WHERE + ( + "_origin"."ColumnB" = { p0 :Array(Tuple(field1 String, field2 String)) } + AND "_origin"."ColumnC" = { p1 :Nested(field1 String, field2 String) } + AND "_origin"."ColumnD" = { p2 :Tuple(child Tuple(id UInt32, name String)) } + AND "_origin"."ColumnE" = { p3 :Tuple(child Array(Tuple(id UInt32, name String))) } + AND "_origin"."ColumnF" = { p4 :Tuple( + child Tuple( + id UInt32, + name String, + toys Nested(id UInt32, name String) + ) + ) } + AND "_origin"."ColumnG" = { p5 :Tuple( + a Nullable(String), + b Map(String, String), + c Array(Tuple(a String, b Tuple(String, String))), + d Tuple(a String, b String) + ) } + ) + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.parameters.txt new file mode 100644 index 0000000..9f76ce0 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.parameters.txt @@ -0,0 +1,6 @@ +param_p0=[('foo','bar')] +param_p1=[('foo','bar')] +param_p2=((1,'foo')) +param_p3=([(1,'foo')]) +param_p4=((1,'foo',[(2,'bar')])) +param_p5=(\N,{'foo':'bar'},[('foo',('foo','bar'))],('foo','bar')) diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.request.json b/crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.request.json new file mode 100644 index 0000000..e3d2f52 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.request.json @@ -0,0 +1,148 @@ +{ + "$schema": "../../request.schema.json", + "collection": "TableOne", + "collection_relationships": {}, + "arguments": {}, + "query": { + "fields": { + "field1": { + "type": "column", + "column": "ColumnA" + } + }, + "predicate": { + "type": "and", + "expressions": [ + { + "type": "binary_comparison_operator", + "operator": "_eq", + "column": { + "type": "column", + "name": "ColumnB", + "path": [] + }, + "value": { + "type": "scalar", + "value": [ + { + "field1": "foo", + "field2": "bar" + } + ] + } + }, + { + "type": "binary_comparison_operator", + "operator": "_eq", + "column": { + "type": "column", + "name": "ColumnC", + "path": [] + }, + "value": { + "type": "scalar", + "value": [ + { + "field1": "foo", + "field2": "bar" + } + ] + } + }, + { + "type": "binary_comparison_operator", + "operator": "_eq", + "column": { + "type": "column", + "name": "ColumnD", + "path": [] + }, + "value": { + "type": "scalar", + "value": { + "child": { + "id": 1, + "name": "foo" + } + } + } + }, + { + "type": "binary_comparison_operator", + "operator": "_eq", + "column": { + "type": "column", + "name": "ColumnE", + "path": [] + }, + "value": { + "type": "scalar", + "value": { + "child": [ + { + "id": 1, + "name": "foo" + } + ] + } + } + }, + { + "type": "binary_comparison_operator", + "operator": "_eq", + "column": { + "type": "column", + "name": "ColumnF", + "path": [] + }, + "value": { + "type": "scalar", + "value": { + "child": { + "id": 1, + "name": "foo", + "toys": [ + { + "id": 2, + "name": "bar" + } + ] + } + } + } + }, + { + "type": "binary_comparison_operator", + "operator": "_eq", + "column": { + "type": "column", + "name": "ColumnG", + "path": [] + }, + "value": { + "type": "scalar", + "value": { + "a": null, + "b": { + "foo": "bar" + }, + "c": [ + { + "a": "foo", + "b": [ + "foo", + "bar" + ] + } + ], + "d": { + "a": "foo", + "b": "bar" + } + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.statement.sql b/crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.statement.sql new file mode 100644 index 0000000..7e2d7dc --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/bound_parameters/01_bind_complex_parameters.statement.sql @@ -0,0 +1,35 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("field1" String)))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple(groupArray(tuple("_row"."_field_field1"))) AS "_rowset" + FROM + ( + SELECT + "_origin"."ColumnA" AS "_field_field1" + FROM + "Schema1"."Table1" AS "_origin" + WHERE + ( + "_origin"."ColumnB" = [('foo', 'bar')] + AND "_origin"."ColumnC" = [('foo', 'bar')] + AND "_origin"."ColumnD" = ((1, 'foo')) + AND "_origin"."ColumnE" = ([(1, 'foo')]) + AND "_origin"."ColumnF" = ((1, 'foo', [(2, 'bar')])) + AND "_origin"."ColumnG" = ( + NULL, + { 'foo': 'bar' }, + [('foo', ('foo', 'bar'))], + ('foo', 'bar') + ) + ) + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/01_generate_column_accessor.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/01_generate_column_accessor.parameterized_statement.sql new file mode 100644 index 0000000..dae2c71 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/01_generate_column_accessor.parameterized_statement.sql @@ -0,0 +1,24 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("field1" Array(Tuple("subfield1" String)))))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple(groupArray(tuple("_row"."_field_field1"))) AS "_rowset" + FROM + ( + SELECT + arrayMap( + (_value) -> tuple(_value."field1"), + "_origin"."ColumnB" + ) AS "_field_field1" + FROM + "Schema1"."Table1" AS "_origin" + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/01_generate_column_accessor.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/01_generate_column_accessor.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/01_generate_column_accessor.request.json b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/01_generate_column_accessor.request.json index 2131ee4..7ed762b 100644 --- a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/01_generate_column_accessor.request.json +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/01_generate_column_accessor.request.json @@ -1,26 +1,26 @@ { - "$schema": "../request.schema.json", - "collection": "TableOne", - "collection_relationships": {}, - "arguments": {}, - "query": { - "fields": { - "field1": { - "type": "column", - "column": "ColumnB", + "$schema": "../../request.schema.json", + "collection": "TableOne", + "collection_relationships": {}, + "arguments": {}, + "query": { + "fields": { + "field1": { + "type": "column", + "column": "ColumnB", + "fields": { + "type": "array", + "fields": { + "type": "object", "fields": { - "type": "array", - "fields": { - "type": "object", - "fields": { - "subfield1": { - "type": "column", - "column": "field1" - } - } + "subfield1": { + "type": "column", + "column": "field1" } } } - } + } + } } + } } \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/02_skip_if_not_required.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/02_skip_if_not_required.parameterized_statement.sql new file mode 100644 index 0000000..6e6bb2f --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/02_skip_if_not_required.parameterized_statement.sql @@ -0,0 +1,21 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("field1" Array(Tuple("subfield1" String, "subfield2" String)))))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple(groupArray(tuple("_row"."_field_field1"))) AS "_rowset" + FROM + ( + SELECT + "_origin"."ColumnB" AS "_field_field1" + FROM + "Schema1"."Table1" AS "_origin" + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/02_skip_if_not_required.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/02_skip_if_not_required.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/02_skip_if_not_required.request.json b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/02_skip_if_not_required.request.json index 9c62245..47f1350 100644 --- a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/02_skip_if_not_required.request.json +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/02_skip_if_not_required.request.json @@ -1,30 +1,30 @@ { - "$schema": "../request.schema.json", - "collection": "TableOne", - "collection_relationships": {}, - "arguments": {}, - "query": { - "fields": { - "field1": { - "type": "column", - "column": "ColumnB", + "$schema": "../../request.schema.json", + "collection": "TableOne", + "collection_relationships": {}, + "arguments": {}, + "query": { + "fields": { + "field1": { + "type": "column", + "column": "ColumnB", + "fields": { + "type": "array", + "fields": { + "type": "object", "fields": { - "type": "array", - "fields": { - "type": "object", - "fields": { - "subfield1": { - "type": "column", - "column": "field1" - }, - "subfield2": { - "type": "column", - "column": "field2" - } - } + "subfield1": { + "type": "column", + "column": "field1" + }, + "subfield2": { + "type": "column", + "column": "field2" } } } - } + } + } } + } } \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/03_support_relationships_on_nested_field.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/03_support_relationships_on_nested_field.parameterized_statement.sql new file mode 100644 index 0000000..c925ede --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/03_support_relationships_on_nested_field.parameterized_statement.sql @@ -0,0 +1,47 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("field1" String, "field2" Tuple("child" Tuple("id" UInt32, "name" String, "child" Tuple(rows Array(Tuple("name" String))))))))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple("_row"."_field_field1", "_row"."_field_field2") + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."ColumnA" AS "_field_field1", + tuple( + tuple( + "_origin"."ColumnD"."child"."id", + "_origin"."ColumnD"."child"."name", + "_rel_0_child"."_rowset" + ) + ) AS "_field_field2" + FROM + "Schema1"."Table1" AS "_origin" + LEFT JOIN ( + SELECT + tuple(groupArray(tuple("_row"."_field_name"))) AS "_rowset", + "_row"."_relkey_Id" AS "_relkey_Id" + FROM + ( + SELECT + "_origin"."Name" AS "_field_name", + "_origin"."Id" AS "_relkey_Id" + FROM + "Schema1"."Table2" AS "_origin" + ) AS "_row" + GROUP BY + "_row"."_relkey_Id" + ) AS "_rel_0_child" ON "_origin"."ColumnD"."child"."id" = "_rel_0_child"."_relkey_Id" + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/03_support_relationships_on_nested_field.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/03_support_relationships_on_nested_field.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/03_support_relationships_on_nested_field.request.json b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/03_support_relationships_on_nested_field.request.json index 41b63b4..dccd395 100644 --- a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/03_support_relationships_on_nested_field.request.json +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/03_support_relationships_on_nested_field.request.json @@ -1,5 +1,5 @@ { - "$schema": "../request.schema.json", + "$schema": "../../request.schema.json", "collection": "TableOne", "collection_relationships": { "rel1": { @@ -13,40 +13,42 @@ }, "arguments": {}, "query": { - "fields": { - "field1": { - "type": "column", - "column": "ColumnA" - }, - "field2": { - "type": "column", - "column": "ColumnD", - "fields": { - "type": "object", - "fields": { - "child": { - "type": "column", - "column": "child", + "fields": { + "field1": { + "type": "column", + "column": "ColumnA" + }, + "field2": { + "type": "column", + "column": "ColumnD", + "fields": { + "type": "object", "fields": { - "type": "object", - "fields": { - "id": { - "type": "column", - "column": "id" - }, - "name": { - "type": "column", - "column": "name" - }, - "child": { - "type": "relationship", - "arguments": {}, - "relationship": "rel1", - "query": { - "fields": { - "name": { - "type": "column", - "column": "Name" + "child": { + "type": "column", + "column": "child", + "fields": { + "type": "object", + "fields": { + "id": { + "type": "column", + "column": "id" + }, + "name": { + "type": "column", + "column": "name" + }, + "child": { + "type": "relationship", + "arguments": {}, + "relationship": "rel1", + "query": { + "fields": { + "name": { + "type": "column", + "column": "Name" + } + } } } } @@ -54,9 +56,7 @@ } } } - } } - } - } + } } } \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/04_error_on_relationships_on_array_nested_field.request.json b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/04_error_on_relationships_on_array_nested_field.request.json index 969d459..4d8034b 100644 --- a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/04_error_on_relationships_on_array_nested_field.request.json +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/04_error_on_relationships_on_array_nested_field.request.json @@ -1,5 +1,5 @@ { - "$schema": "../request.schema.json", + "$schema": "../../request.schema.json", "collection": "TableOne", "collection_relationships": { "rel1": { @@ -13,54 +13,53 @@ }, "arguments": {}, "query": { - "fields": { - "field1": { - "type": "column", - "column": "ColumnA" - }, - "field2": { - "type": "column", - "column": "ColumnE", - "fields": { - "type": "object", - "fields": { - "child": { - "type": "column", - "column": "child", + "fields": { + "field1": { + "type": "column", + "column": "ColumnA" + }, + "field2": { + "type": "column", + "column": "ColumnE", + "fields": { + "type": "object", "fields": { - "type": "array", - "fields": { - "type": "object", + "child": { + "type": "column", + "column": "child", "fields": { - "id": { - "type": "column", - "column": "id" - }, - "name": { - "type": "column", - "column": "name" - }, - "child": { - "type": "relationship", - "arguments": {}, - "relationship": "rel1", - "query": { - "fields": { - "name": { - "type": "column", - "column": "Name" + "type": "array", + "fields": { + "type": "object", + "fields": { + "id": { + "type": "column", + "column": "id" + }, + "name": { + "type": "column", + "column": "name" + }, + "child": { + "type": "relationship", + "arguments": {}, + "relationship": "rel1", + "query": { + "fields": { + "name": { + "type": "column", + "column": "Name" + } + } } } } } } } - } } - } } - } - } + } } } \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/05_complex_example.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/05_complex_example.parameterized_statement.sql new file mode 100644 index 0000000..5ce92c5 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/05_complex_example.parameterized_statement.sql @@ -0,0 +1,51 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("field1" String, "field2" Tuple("child" Tuple("id" UInt32, "name" String, "child" Tuple(rows Array(Tuple("name" String))), "toys" Array(Tuple("name" String)))))))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple("_row"."_field_field1", "_row"."_field_field2") + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."ColumnA" AS "_field_field1", + tuple( + tuple( + "_origin"."ColumnF"."child"."id", + "_origin"."ColumnF"."child"."name", + "_rel_0_child"."_rowset", + arrayMap( + (_value) -> tuple(_value."name"), + "_origin"."ColumnF"."child"."toys" + ) + ) + ) AS "_field_field2" + FROM + "Schema1"."Table1" AS "_origin" + LEFT JOIN ( + SELECT + tuple(groupArray(tuple("_row"."_field_name"))) AS "_rowset", + "_row"."_relkey_Id" AS "_relkey_Id" + FROM + ( + SELECT + "_origin"."Name" AS "_field_name", + "_origin"."Id" AS "_relkey_Id" + FROM + "Schema1"."Table2" AS "_origin" + ) AS "_row" + GROUP BY + "_row"."_relkey_Id" + ) AS "_rel_0_child" ON "_origin"."ColumnF"."child"."id" = "_rel_0_child"."_relkey_Id" + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/05_complex_example.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/05_complex_example.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/05_complex_example.request.json b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/05_complex_example.request.json index 8cb0d79..c3d86c0 100644 --- a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/05_complex_example.request.json +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/05_complex_example.request.json @@ -1,5 +1,5 @@ { - "$schema": "../request.schema.json", + "$schema": "../../request.schema.json", "collection": "TableOne", "collection_relationships": { "rel1": { @@ -13,67 +13,66 @@ }, "arguments": {}, "query": { - "fields": { - "field1": { - "type": "column", - "column": "ColumnA" - }, - "field2": { - "type": "column", - "column": "ColumnF", - "fields": { - "type": "object", - "fields": { - "child": { - "type": "column", - "column": "child", + "fields": { + "field1": { + "type": "column", + "column": "ColumnA" + }, + "field2": { + "type": "column", + "column": "ColumnF", + "fields": { + "type": "object", "fields": { - "type": "object", - "fields": { - "id": { - "type": "column", - "column": "id" - }, - "name": { - "type": "column", - "column": "name" - }, - "child": { - "type": "relationship", - "arguments": {}, - "relationship": "rel1", - "query": { - "fields": { - "name": { - "type": "column", - "column": "Name" - } - } - } - }, - "toys": { - "type": "column", - "column": "toys", + "child": { + "type": "column", + "column": "child", + "fields": { + "type": "object", "fields": { - "type": "array", - "fields": { - "type": "object", + "id": { + "type": "column", + "column": "id" + }, + "name": { + "type": "column", + "column": "name" + }, + "child": { + "type": "relationship", + "arguments": {}, + "relationship": "rel1", + "query": { + "fields": { + "name": { + "type": "column", + "column": "Name" + } + } + } + }, + "toys": { + "type": "column", + "column": "toys", "fields": { - "name": { - "type": "column", - "column": "name" + "type": "array", + "fields": { + "type": "object", + "fields": { + "name": { + "type": "column", + "column": "name" + } + } } } } } } } - } } - } } - } - } + } } } \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/06_no_useless_nested_accessors.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/06_no_useless_nested_accessors.parameterized_statement.sql new file mode 100644 index 0000000..4e3d084 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/06_no_useless_nested_accessors.parameterized_statement.sql @@ -0,0 +1,30 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("field1" String, "field2" Tuple("b" Map(String, String), "c" Array(Tuple("a" String, "b" Tuple(String, String))), "d" Tuple("a" String, "b" String)))))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple( + groupArray( + tuple("_row"."_field_field1", "_row"."_field_field2") + ) + ) AS "_rowset" + FROM + ( + SELECT + "_origin"."ColumnA" AS "_field_field1", + tuple( + "_origin"."ColumnG"."b", + "_origin"."ColumnG"."c", + "_origin"."ColumnG"."d" + ) AS "_field_field2" + FROM + "Schema1"."Table1" AS "_origin" + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/06_no_useless_nested_accessors.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/06_no_useless_nested_accessors.parameters.txt new file mode 100644 index 0000000..e69de29 diff --git a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/06_no_useless_nested_accessors.request.json b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/06_no_useless_nested_accessors.request.json index dcd295e..d1c0dd0 100644 --- a/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/06_no_useless_nested_accessors.request.json +++ b/crates/ndc-clickhouse/tests/query_builder/complex_columns/field_selector/06_no_useless_nested_accessors.request.json @@ -1,5 +1,5 @@ { - "$schema": "../request.schema.json", + "$schema": "../../request.schema.json", "collection": "TableOne", "collection_relationships": { "rel1": { @@ -13,61 +13,61 @@ }, "arguments": {}, "query": { - "fields": { - "field1": { - "type": "column", - "column": "ColumnA" - }, - "field2": { - "type": "column", - "column": "ColumnG", - "fields": { - "type": "object", - "fields": { - "b": { - "type": "column", - "column": "b" - }, - "c": { - "type": "column", - "column": "c", + "fields": { + "field1": { + "type": "column", + "column": "ColumnA" + }, + "field2": { + "type": "column", + "column": "ColumnG", + "fields": { + "type": "object", "fields": { - "type": "array", - "fields": { - "type": "object", + "b": { + "type": "column", + "column": "b" + }, + "c": { + "type": "column", + "column": "c", "fields": { - "a": { - "type": "column", - "column": "a" - }, - "b": { - "type": "column", - "column": "b" + "type": "array", + "fields": { + "type": "object", + "fields": { + "a": { + "type": "column", + "column": "a" + }, + "b": { + "type": "column", + "column": "b" + } + } } } - } - } - }, - "d": { - "type": "column", - "column": "d", - "fields": { - "type": "object", - "fields": { - "a": { - "type": "column", - "column": "a" - }, - "b": { - "type": "column", - "column": "b" + }, + "d": { + "type": "column", + "column": "d", + "fields": { + "type": "object", + "fields": { + "a": { + "type": "column", + "column": "a" + }, + "b": { + "type": "column", + "column": "b" + } + } } } } } - } } - } - } + } } } \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/star_schema/01_native_queries/01_native_query.parameterized_statement.sql b/crates/ndc-clickhouse/tests/query_builder/star_schema/01_native_queries/01_native_query.parameterized_statement.sql new file mode 100644 index 0000000..2568396 --- /dev/null +++ b/crates/ndc-clickhouse/tests/query_builder/star_schema/01_native_queries/01_native_query.parameterized_statement.sql @@ -0,0 +1,30 @@ +SELECT + toJSONString( + groupArray( + cast( + "_rowset"."_rowset", + 'Tuple(rows Array(Tuple("revenue" UInt64)))' + ) + ) + ) AS "rowsets" +FROM + ( + SELECT + tuple(groupArray(tuple("_row"."_field_revenue"))) AS "_rowset" + FROM + ( + SELECT + "_origin"."revenue" AS "_field_revenue" + FROM + ( + SELECT + sum(LO_EXTENDEDPRICE * LO_DISCOUNT) AS revenue + FROM + star.lineorder_flat + WHERE + toYear(LO_ORDERDATE) = 1993 + AND LO_DISCOUNT BETWEEN 1 AND 3 + AND LO_QUANTITY < 25 + ) AS "_origin" + ) AS "_row" + ) AS "_rowset" FORMAT TabSeparatedRaw; \ No newline at end of file diff --git a/crates/ndc-clickhouse/tests/query_builder/star_schema/01_native_queries/01_native_query.parameters.txt b/crates/ndc-clickhouse/tests/query_builder/star_schema/01_native_queries/01_native_query.parameters.txt new file mode 100644 index 0000000..e69de29 From ec41a871d70b4e9fab6e69b174f3f3a2411a2498 Mon Sep 17 00:00:00 2001 From: Benoit Ranque Date: Mon, 2 Sep 2024 13:01:36 -0400 Subject: [PATCH 10/10] set `defaultValue` for all env vars to empty string --- ci/templates/connector-metadata.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ci/templates/connector-metadata.yaml b/ci/templates/connector-metadata.yaml index be47aab..6569096 100644 --- a/ci/templates/connector-metadata.yaml +++ b/ci/templates/connector-metadata.yaml @@ -4,10 +4,13 @@ packagingDefinition: supportedEnvironmentVariables: - name: CLICKHOUSE_URL description: The ClickHouse connection URL + defaultValue: "" - name: CLICKHOUSE_USERNAME description: The ClickHouse connection username + defaultValue: "" - name: CLICKHOUSE_PASSWORD description: The ClickHouse connection password + defaultValue: "" commands: update: hasura-clickhouse update cliPlugin: