diff --git a/rust/cubesql/cubesql/src/compile/engine/udf/common.rs b/rust/cubesql/cubesql/src/compile/engine/udf/common.rs index 494641bbcac35..078b49d00638b 100644 --- a/rust/cubesql/cubesql/src/compile/engine/udf/common.rs +++ b/rust/cubesql/cubesql/src/compile/engine/udf/common.rs @@ -3087,10 +3087,18 @@ pub fn create_cube_regclass_cast_udf() -> ScalarUDF { Some(as_str) => { match PgType::get_all().iter().find(|e| e.typname == as_str) { None => { - return Err(DataFusionError::Execution(format!( - "Unable to cast expression to Regclass: Unknown type: {}", - as_str - ))) + // If the type name contains a dot, it's a schema-qualified name + // and we should return the approprate RegClass to be converted to OID + // For now, we'll return 0 so metabase can sync without failing + // TODO actually read `pg_type` + if as_str.contains('.') { + builder.append_value(0)?; + } else { + return Err(DataFusionError::Execution(format!( + "Unable to cast expression to Regclass: Unknown type: {}", + as_str + ))); + } } Some(ty) => { builder.append_value(ty.oid as i64)?; @@ -3148,6 +3156,171 @@ pub fn create_pg_get_serial_sequence_udf() -> ScalarUDF { ) } +// Return a NOOP for this so metabase can sync without failing +// See https://www.postgresql.org/docs/17/functions-info.html#FUNCTIONS-INFO-COMMENT here +// TODO: Implement this +pub fn create_col_description_udf() -> ScalarUDF { + let fun = make_scalar_function(move |args: &[ArrayRef]| { + // Ensure the output array has the same length as the input + let input_length = args[0].len(); + let mut builder = StringBuilder::new(input_length); + + for _ in 0..input_length { + builder.append_null()?; + } + + Ok(Arc::new(builder.finish()) as ArrayRef) + }); + + let return_type: ReturnTypeFunction = Arc::new(move |_| Ok(Arc::new(DataType::Utf8))); + + ScalarUDF::new( + "col_description", + // Correct signature for col_description should be `(oid, integer) → text` + // We model oid as UInt32, so [DataType::UInt32, DataType::Int32] is a proper arguments + // However, it seems that coercion rules in DF differs from PostgreSQL at the moment + // And metabase uses col_description(CAST(CAST(... AS regclass) AS oid), cardinal_number) + // And we model regclass as Int64, and cardinal_number as UInt32 + // Which is why second signature is necessary + &Signature::one_of( + vec![ + TypeSignature::Exact(vec![DataType::UInt32, DataType::Int32]), + // TODO remove this signature in favor of proper model/coercion + TypeSignature::Exact(vec![DataType::Int64, DataType::UInt32]), + ], + Volatility::Stable, + ), + &return_type, + &fun, + ) +} + +// See https://www.postgresql.org/docs/17/functions-string.html#FUNCTIONS-STRING-FORMAT +pub fn create_format_udf() -> ScalarUDF { + let fun = make_scalar_function(move |args: &[ArrayRef]| { + // Ensure at least one argument is provided + if args.is_empty() { + return Err(DataFusionError::Execution( + "format() requires at least one argument".to_string(), + )); + } + + // Ensure the first argument is a Utf8 (string) + if args[0].data_type() != &DataType::Utf8 { + return Err(DataFusionError::Execution( + "format() first argument must be a string".to_string(), + )); + } + + let format_strings = downcast_string_arg!(&args[0], "format_str", i32); + let mut builder = StringBuilder::new(format_strings.len()); + + for i in 0..format_strings.len() { + if format_strings.is_null(i) { + builder.append_null()?; + continue; + } + + let format_str = format_strings.value(i); + let mut result = String::new(); + let mut format_chars = format_str.chars().peekable(); + let mut arg_index = 1; // Start from first argument after format string + + while let Some(c) = format_chars.next() { + if c != '%' { + result.push(c); + continue; + } + + match format_chars.next() { + Some('I') => { + // Handle %I - SQL identifier + if arg_index >= args.len() { + return Err(DataFusionError::Execution( + "Not enough arguments for format string".to_string(), + )); + } + + let arg = &args[arg_index]; + let value = match arg.data_type() { + DataType::Utf8 => { + let str_arr = downcast_string_arg!(arg, "arg", i32); + if str_arr.is_null(i) { + return Err(DataFusionError::Execution( + "NULL values cannot be formatted as identifiers" + .to_string(), + )); + } + str_arr.value(i).to_string() + } + _ => { + // For other types, try to convert to string + let str_arr = cast(&arg, &DataType::Utf8)?; + let str_arr = + str_arr.as_any().downcast_ref::().unwrap(); + if str_arr.is_null(i) { + return Err(DataFusionError::Execution( + "NULL values cannot be formatted as identifiers" + .to_string(), + )); + } + str_arr.value(i).to_string() + } + }; + + // Quote any identifier for now + // That's a safety-first approach: it would quote too much, but every edge-case would be covered + // Like `1` or `1a` or `select` + // TODO Quote identifier only if necessary + let needs_quoting = true; + + if needs_quoting { + result.push('"'); + result.push_str(&value.replace('"', "\"\"")); + result.push('"'); + } else { + result.push_str(&value); + } + arg_index += 1; + } + Some('%') => { + // %% is escaped to single % + result.push('%'); + } + Some(c) => { + return Err(DataFusionError::Execution(format!( + "Unsupported format specifier %{}", + c + ))); + } + None => { + return Err(DataFusionError::Execution( + "Invalid format string - ends with %".to_string(), + )); + } + } + } + + builder.append_value(result)?; + } + + Ok(Arc::new(builder.finish()) as ArrayRef) + }); + + let return_type: ReturnTypeFunction = Arc::new(move |_| Ok(Arc::new(DataType::Utf8))); + + ScalarUDF::new( + "format", + // Actually, format should be variadic with types (Utf8, any*) + // But ATM DataFusion does not support those signatures + // And this would work through implicit casting to Utf8 + // TODO migrate to proper custom signature once it's supported by DF + &Signature::variadic(vec![DataType::Utf8], Volatility::Immutable), + &return_type, + &fun, + ) +} + pub fn create_json_build_object_udf() -> ScalarUDF { let fun = make_scalar_function(move |_args: &[ArrayRef]| { // TODO: Implement @@ -3769,13 +3942,6 @@ pub fn register_fun_stubs(mut ctx: SessionContext) -> SessionContext { rettyp = TimestampTz, vol = Volatile ); - register_fun_stub!( - udf, - "col_description", - tsig = [Oid, Int32], - rettyp = Utf8, - vol = Stable - ); register_fun_stub!(udf, "convert", tsig = [Binary, Utf8, Utf8], rettyp = Binary); register_fun_stub!(udf, "convert_from", tsig = [Binary, Utf8], rettyp = Utf8); register_fun_stub!(udf, "convert_to", tsig = [Utf8, Utf8], rettyp = Binary); diff --git a/rust/cubesql/cubesql/src/compile/mod.rs b/rust/cubesql/cubesql/src/compile/mod.rs index 4a9defb889fc8..22c588f299d9c 100644 --- a/rust/cubesql/cubesql/src/compile/mod.rs +++ b/rust/cubesql/cubesql/src/compile/mod.rs @@ -16211,4 +16211,58 @@ LIMIT {{ limit }}{% endif %}"#.to_string(), Ok(()) } + + #[tokio::test] + async fn test_format_function() -> Result<(), CubeError> { + // Test: Basic usage with a single identifier + let result = execute_query( + "SELECT format('%I', 'column_name') AS formatted_identifier".to_string(), + DatabaseProtocol::PostgreSQL, + ) + .await?; + insta::assert_snapshot!("formatted_identifier", result); + + // Test: Using multiple identifiers + let result = execute_query( + "SELECT format('%I, %I', 'table_name', 'column_name') AS formatted_identifiers" + .to_string(), + DatabaseProtocol::PostgreSQL, + ) + .await?; + insta::assert_snapshot!("formatted_identifiers", result); + + // Test: Unsupported format specifier + let result = execute_query( + "SELECT format('%X', 'value') AS unsupported_specifier".to_string(), + DatabaseProtocol::PostgreSQL, + ) + .await; + assert!(result.is_err()); + + // Test: Format string ending with % + let result = execute_query( + "SELECT format('%', 'value') AS invalid_format".to_string(), + DatabaseProtocol::PostgreSQL, + ) + .await; + assert!(result.is_err()); + + // Test: Quoting necessary for special characters + let result = execute_query( + "SELECT format('%I', 'column-name') AS quoted_identifier".to_string(), + DatabaseProtocol::PostgreSQL, + ) + .await?; + insta::assert_snapshot!("quoted_identifier", result); + + // Test: Quoting necessary for reserved keywords + let result = execute_query( + "SELECT format('%I', 'select') AS quoted_keyword".to_string(), + DatabaseProtocol::PostgreSQL, + ) + .await?; + insta::assert_snapshot!("quoted_keyword", result); + + Ok(()) + } } diff --git a/rust/cubesql/cubesql/src/compile/query_engine.rs b/rust/cubesql/cubesql/src/compile/query_engine.rs index c4b76617bdc0a..dac6c9cddc96a 100644 --- a/rust/cubesql/cubesql/src/compile/query_engine.rs +++ b/rust/cubesql/cubesql/src/compile/query_engine.rs @@ -467,7 +467,9 @@ impl QueryEngine for SqlQueryEngine { ctx.register_udf(create_current_timestamp_udf("localtimestamp")); ctx.register_udf(create_current_schema_udf()); ctx.register_udf(create_current_schemas_udf()); + ctx.register_udf(create_format_udf()); ctx.register_udf(create_format_type_udf()); + ctx.register_udf(create_col_description_udf()); ctx.register_udf(create_pg_datetime_precision_udf()); ctx.register_udf(create_pg_numeric_precision_udf()); ctx.register_udf(create_pg_numeric_scale_udf()); diff --git a/rust/cubesql/cubesql/src/compile/snapshots/cubesql__compile__tests__formatted_identifier.snap b/rust/cubesql/cubesql/src/compile/snapshots/cubesql__compile__tests__formatted_identifier.snap new file mode 100644 index 0000000000000..18a0775468b72 --- /dev/null +++ b/rust/cubesql/cubesql/src/compile/snapshots/cubesql__compile__tests__formatted_identifier.snap @@ -0,0 +1,9 @@ +--- +source: cubesql/src/compile/mod.rs +expression: result +--- ++----------------------+ +| formatted_identifier | ++----------------------+ +| "column_name" | ++----------------------+ diff --git a/rust/cubesql/cubesql/src/compile/snapshots/cubesql__compile__tests__formatted_identifiers.snap b/rust/cubesql/cubesql/src/compile/snapshots/cubesql__compile__tests__formatted_identifiers.snap new file mode 100644 index 0000000000000..de5454a9cd550 --- /dev/null +++ b/rust/cubesql/cubesql/src/compile/snapshots/cubesql__compile__tests__formatted_identifiers.snap @@ -0,0 +1,9 @@ +--- +source: cubesql/src/compile/mod.rs +expression: result +--- ++-----------------------------+ +| formatted_identifiers | ++-----------------------------+ +| "table_name", "column_name" | ++-----------------------------+ diff --git a/rust/cubesql/cubesql/src/compile/snapshots/cubesql__compile__tests__quoted_identifier.snap b/rust/cubesql/cubesql/src/compile/snapshots/cubesql__compile__tests__quoted_identifier.snap new file mode 100644 index 0000000000000..fd28d6d842d0e --- /dev/null +++ b/rust/cubesql/cubesql/src/compile/snapshots/cubesql__compile__tests__quoted_identifier.snap @@ -0,0 +1,9 @@ +--- +source: cubesql/src/compile/mod.rs +expression: result +--- ++-------------------+ +| quoted_identifier | ++-------------------+ +| "column-name" | ++-------------------+ diff --git a/rust/cubesql/cubesql/src/compile/snapshots/cubesql__compile__tests__quoted_keyword.snap b/rust/cubesql/cubesql/src/compile/snapshots/cubesql__compile__tests__quoted_keyword.snap new file mode 100644 index 0000000000000..3ea11401a8272 --- /dev/null +++ b/rust/cubesql/cubesql/src/compile/snapshots/cubesql__compile__tests__quoted_keyword.snap @@ -0,0 +1,9 @@ +--- +source: cubesql/src/compile/mod.rs +expression: result +--- ++----------------+ +| quoted_keyword | ++----------------+ +| "select" | ++----------------+ diff --git a/rust/cubesql/cubesql/src/compile/test/snapshots/cubesql__compile__test__test_introspection__test_metabase_v0_51_2_introspection_field_indoption.snap b/rust/cubesql/cubesql/src/compile/test/snapshots/cubesql__compile__test__test_introspection__test_metabase_v0_51_2_introspection_field_indoption.snap new file mode 100644 index 0000000000000..83aaa2ede496b --- /dev/null +++ b/rust/cubesql/cubesql/src/compile/test/snapshots/cubesql__compile__test__test_introspection__test_metabase_v0_51_2_introspection_field_indoption.snap @@ -0,0 +1,308 @@ +--- +source: cubesql/src/compile/test/test_introspection.rs +expression: "execute_query(r#\"\n SELECT\n c.column_name AS name,\n c.udt_name AS database_type,\n c.ordinal_position - 1 AS database_position,\n c.table_schema AS table_schema,\n c.table_name AS table_name,\n pk.column_name IS NOT NULL AS pk,\n COL_DESCRIPTION(\n CAST(\n CAST(\n FORMAT(\n '%I.%I',\n CAST(c.table_schema AS TEXT),\n CAST(c.table_name AS TEXT)\n ) AS REGCLASS\n ) AS OID\n ),\n c.ordinal_position\n ) AS field_comment,\n (\n (column_default IS NULL)\n OR (LOWER(column_default) = 'null')\n )\n AND (is_nullable = 'NO')\n AND NOT (\n (\n (column_default IS NOT NULL)\n AND (column_default LIKE '%nextval(%')\n )\n OR (is_identity <> 'NO')\n ) AS database_required,\n (\n (column_default IS NOT NULL)\n AND (column_default LIKE '%nextval(%')\n )\n OR (is_identity <> 'NO') AS database_is_auto_increment\n FROM\n information_schema.columns AS c\n LEFT JOIN (\n SELECT\n tc.table_schema,\n tc.table_name,\n kc.column_name\n FROM\n information_schema.table_constraints AS tc\n INNER JOIN information_schema.key_column_usage AS kc ON (tc.constraint_name = kc.constraint_name)\n AND (tc.table_schema = kc.table_schema)\n AND (tc.table_name = kc.table_name)\n WHERE\n tc.constraint_type = 'PRIMARY KEY'\n ) AS pk ON (c.table_schema = pk.table_schema)\n AND (c.table_name = pk.table_name)\n AND (c.column_name = pk.column_name)\n WHERE\n c.table_schema !~ '^information_schema|catalog_history|pg_'\n AND (c.table_schema IN ('public'))\n ORDER BY\n table_schema ASC,\n table_name ASC,\n database_position ASC\n \"#.to_string(),\nDatabaseProtocol::PostgreSQL).await?" +--- ++--------------------+---------------+-------------------+--------------+---------------------------+-------+---------------+-------------------+----------------------------+ +| name | database_type | database_position | table_schema | table_name | pk | field_comment | database_required | database_is_auto_increment | ++--------------------+---------------+-------------------+--------------+---------------------------+-------+---------------+-------------------+----------------------------+ +| count | int8 | 0 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| maxPrice | numeric | 1 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| sumPrice | numeric | 2 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| minPrice | numeric | 3 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| avgPrice | numeric | 4 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| countDistinct | int8 | 5 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| order_date | timestamp | 6 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| last_mod | timestamp | 7 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| customer_gender | text | 8 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| notes | text | 9 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| taxful_total_price | numeric | 10 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| has_subscription | bool | 11 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| is_male | bool | 12 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| is_female | bool | 13 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| __user | text | 14 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| __cubeJoinField | text | 15 | public | KibanaSampleDataEcommerce | false | NULL | false | false | +| agentCount | int8 | 0 | public | Logs | false | NULL | false | false | +| agentCountApprox | int8 | 1 | public | Logs | false | NULL | false | false | +| id | numeric | 2 | public | Logs | false | NULL | false | false | +| read | bool | 3 | public | Logs | false | NULL | false | false | +| content | text | 4 | public | Logs | false | NULL | false | false | +| __user | text | 5 | public | Logs | false | NULL | false | false | +| __cubeJoinField | text | 6 | public | Logs | false | NULL | false | false | +| measure_num0 | numeric | 0 | public | MultiTypeCube | false | NULL | false | false | +| measure_str0 | numeric | 1 | public | MultiTypeCube | false | NULL | false | false | +| measure_date0 | numeric | 2 | public | MultiTypeCube | false | NULL | false | false | +| measure_num1 | numeric | 3 | public | MultiTypeCube | false | NULL | false | false | +| measure_str1 | numeric | 4 | public | MultiTypeCube | false | NULL | false | false | +| measure_date1 | numeric | 5 | public | MultiTypeCube | false | NULL | false | false | +| measure_num2 | numeric | 6 | public | MultiTypeCube | false | NULL | false | false | +| measure_str2 | numeric | 7 | public | MultiTypeCube | false | NULL | false | false | +| measure_date2 | numeric | 8 | public | MultiTypeCube | false | NULL | false | false | +| measure_num3 | numeric | 9 | public | MultiTypeCube | false | NULL | false | false | +| measure_str3 | numeric | 10 | public | MultiTypeCube | false | NULL | false | false | +| measure_date3 | numeric | 11 | public | MultiTypeCube | false | NULL | false | false | +| measure_num4 | numeric | 12 | public | MultiTypeCube | false | NULL | false | false | +| measure_str4 | numeric | 13 | public | MultiTypeCube | false | NULL | false | false | +| measure_date4 | numeric | 14 | public | MultiTypeCube | false | NULL | false | false | +| measure_num5 | numeric | 15 | public | MultiTypeCube | false | NULL | false | false | +| measure_str5 | numeric | 16 | public | MultiTypeCube | false | NULL | false | false | +| measure_date5 | numeric | 17 | public | MultiTypeCube | false | NULL | false | false | +| measure_num6 | numeric | 18 | public | MultiTypeCube | false | NULL | false | false | +| measure_str6 | numeric | 19 | public | MultiTypeCube | false | NULL | false | false | +| measure_date6 | numeric | 20 | public | MultiTypeCube | false | NULL | false | false | +| measure_num7 | numeric | 21 | public | MultiTypeCube | false | NULL | false | false | +| measure_str7 | numeric | 22 | public | MultiTypeCube | false | NULL | false | false | +| measure_date7 | numeric | 23 | public | MultiTypeCube | false | NULL | false | false | +| measure_num8 | numeric | 24 | public | MultiTypeCube | false | NULL | false | false | +| measure_str8 | numeric | 25 | public | MultiTypeCube | false | NULL | false | false | +| measure_date8 | numeric | 26 | public | MultiTypeCube | false | NULL | false | false | +| measure_num9 | numeric | 27 | public | MultiTypeCube | false | NULL | false | false | +| measure_str9 | numeric | 28 | public | MultiTypeCube | false | NULL | false | false | +| measure_date9 | numeric | 29 | public | MultiTypeCube | false | NULL | false | false | +| count | int8 | 30 | public | MultiTypeCube | false | NULL | false | false | +| maxPrice | numeric | 31 | public | MultiTypeCube | false | NULL | false | false | +| minPrice | numeric | 32 | public | MultiTypeCube | false | NULL | false | false | +| avgPrice | numeric | 33 | public | MultiTypeCube | false | NULL | false | false | +| countDistinct | int8 | 34 | public | MultiTypeCube | false | NULL | false | false | +| dim_num0 | numeric | 35 | public | MultiTypeCube | false | NULL | false | false | +| dim_str0 | text | 36 | public | MultiTypeCube | false | NULL | false | false | +| dim_date0 | timestamp | 37 | public | MultiTypeCube | false | NULL | false | false | +| dim_num1 | numeric | 38 | public | MultiTypeCube | false | NULL | false | false | +| dim_str1 | text | 39 | public | MultiTypeCube | false | NULL | false | false | +| dim_date1 | timestamp | 40 | public | MultiTypeCube | false | NULL | false | false | +| dim_num2 | numeric | 41 | public | MultiTypeCube | false | NULL | false | false | +| dim_str2 | text | 42 | public | MultiTypeCube | false | NULL | false | false | +| dim_date2 | timestamp | 43 | public | MultiTypeCube | false | NULL | false | false | +| dim_num3 | numeric | 44 | public | MultiTypeCube | false | NULL | false | false | +| dim_str3 | text | 45 | public | MultiTypeCube | false | NULL | false | false | +| dim_date3 | timestamp | 46 | public | MultiTypeCube | false | NULL | false | false | +| dim_num4 | numeric | 47 | public | MultiTypeCube | false | NULL | false | false | +| dim_str4 | text | 48 | public | MultiTypeCube | false | NULL | false | false | +| dim_date4 | timestamp | 49 | public | MultiTypeCube | false | NULL | false | false | +| dim_num5 | numeric | 50 | public | MultiTypeCube | false | NULL | false | false | +| dim_str5 | text | 51 | public | MultiTypeCube | false | NULL | false | false | +| dim_date5 | timestamp | 52 | public | MultiTypeCube | false | NULL | false | false | +| dim_num6 | numeric | 53 | public | MultiTypeCube | false | NULL | false | false | +| dim_str6 | text | 54 | public | MultiTypeCube | false | NULL | false | false | +| dim_date6 | timestamp | 55 | public | MultiTypeCube | false | NULL | false | false | +| dim_num7 | numeric | 56 | public | MultiTypeCube | false | NULL | false | false | +| dim_str7 | text | 57 | public | MultiTypeCube | false | NULL | false | false | +| dim_date7 | timestamp | 58 | public | MultiTypeCube | false | NULL | false | false | +| dim_num8 | numeric | 59 | public | MultiTypeCube | false | NULL | false | false | +| dim_str8 | text | 60 | public | MultiTypeCube | false | NULL | false | false | +| dim_date8 | timestamp | 61 | public | MultiTypeCube | false | NULL | false | false | +| dim_num9 | numeric | 62 | public | MultiTypeCube | false | NULL | false | false | +| dim_str9 | text | 63 | public | MultiTypeCube | false | NULL | false | false | +| dim_date9 | timestamp | 64 | public | MultiTypeCube | false | NULL | false | false | +| __user | text | 65 | public | MultiTypeCube | false | NULL | false | false | +| __cubeJoinField | text | 66 | public | MultiTypeCube | false | NULL | false | false | +| someNumber | numeric | 0 | public | NumberCube | false | NULL | false | false | +| __user | text | 1 | public | NumberCube | false | NULL | false | false | +| __cubeJoinField | text | 2 | public | NumberCube | false | NULL | false | false | +| measure0 | numeric | 0 | public | WideCube | false | NULL | false | false | +| measure1 | numeric | 1 | public | WideCube | false | NULL | false | false | +| measure2 | numeric | 2 | public | WideCube | false | NULL | false | false | +| measure3 | numeric | 3 | public | WideCube | false | NULL | false | false | +| measure4 | numeric | 4 | public | WideCube | false | NULL | false | false | +| measure5 | numeric | 5 | public | WideCube | false | NULL | false | false | +| measure6 | numeric | 6 | public | WideCube | false | NULL | false | false | +| measure7 | numeric | 7 | public | WideCube | false | NULL | false | false | +| measure8 | numeric | 8 | public | WideCube | false | NULL | false | false | +| measure9 | numeric | 9 | public | WideCube | false | NULL | false | false | +| measure10 | numeric | 10 | public | WideCube | false | NULL | false | false | +| measure11 | numeric | 11 | public | WideCube | false | NULL | false | false | +| measure12 | numeric | 12 | public | WideCube | false | NULL | false | false | +| measure13 | numeric | 13 | public | WideCube | false | NULL | false | false | +| measure14 | numeric | 14 | public | WideCube | false | NULL | false | false | +| measure15 | numeric | 15 | public | WideCube | false | NULL | false | false | +| measure16 | numeric | 16 | public | WideCube | false | NULL | false | false | +| measure17 | numeric | 17 | public | WideCube | false | NULL | false | false | +| measure18 | numeric | 18 | public | WideCube | false | NULL | false | false | +| measure19 | numeric | 19 | public | WideCube | false | NULL | false | false | +| measure20 | numeric | 20 | public | WideCube | false | NULL | false | false | +| measure21 | numeric | 21 | public | WideCube | false | NULL | false | false | +| measure22 | numeric | 22 | public | WideCube | false | NULL | false | false | +| measure23 | numeric | 23 | public | WideCube | false | NULL | false | false | +| measure24 | numeric | 24 | public | WideCube | false | NULL | false | false | +| measure25 | numeric | 25 | public | WideCube | false | NULL | false | false | +| measure26 | numeric | 26 | public | WideCube | false | NULL | false | false | +| measure27 | numeric | 27 | public | WideCube | false | NULL | false | false | +| measure28 | numeric | 28 | public | WideCube | false | NULL | false | false | +| measure29 | numeric | 29 | public | WideCube | false | NULL | false | false | +| measure30 | numeric | 30 | public | WideCube | false | NULL | false | false | +| measure31 | numeric | 31 | public | WideCube | false | NULL | false | false | +| measure32 | numeric | 32 | public | WideCube | false | NULL | false | false | +| measure33 | numeric | 33 | public | WideCube | false | NULL | false | false | +| measure34 | numeric | 34 | public | WideCube | false | NULL | false | false | +| measure35 | numeric | 35 | public | WideCube | false | NULL | false | false | +| measure36 | numeric | 36 | public | WideCube | false | NULL | false | false | +| measure37 | numeric | 37 | public | WideCube | false | NULL | false | false | +| measure38 | numeric | 38 | public | WideCube | false | NULL | false | false | +| measure39 | numeric | 39 | public | WideCube | false | NULL | false | false | +| measure40 | numeric | 40 | public | WideCube | false | NULL | false | false | +| measure41 | numeric | 41 | public | WideCube | false | NULL | false | false | +| measure42 | numeric | 42 | public | WideCube | false | NULL | false | false | +| measure43 | numeric | 43 | public | WideCube | false | NULL | false | false | +| measure44 | numeric | 44 | public | WideCube | false | NULL | false | false | +| measure45 | numeric | 45 | public | WideCube | false | NULL | false | false | +| measure46 | numeric | 46 | public | WideCube | false | NULL | false | false | +| measure47 | numeric | 47 | public | WideCube | false | NULL | false | false | +| measure48 | numeric | 48 | public | WideCube | false | NULL | false | false | +| measure49 | numeric | 49 | public | WideCube | false | NULL | false | false | +| measure50 | numeric | 50 | public | WideCube | false | NULL | false | false | +| measure51 | numeric | 51 | public | WideCube | false | NULL | false | false | +| measure52 | numeric | 52 | public | WideCube | false | NULL | false | false | +| measure53 | numeric | 53 | public | WideCube | false | NULL | false | false | +| measure54 | numeric | 54 | public | WideCube | false | NULL | false | false | +| measure55 | numeric | 55 | public | WideCube | false | NULL | false | false | +| measure56 | numeric | 56 | public | WideCube | false | NULL | false | false | +| measure57 | numeric | 57 | public | WideCube | false | NULL | false | false | +| measure58 | numeric | 58 | public | WideCube | false | NULL | false | false | +| measure59 | numeric | 59 | public | WideCube | false | NULL | false | false | +| measure60 | numeric | 60 | public | WideCube | false | NULL | false | false | +| measure61 | numeric | 61 | public | WideCube | false | NULL | false | false | +| measure62 | numeric | 62 | public | WideCube | false | NULL | false | false | +| measure63 | numeric | 63 | public | WideCube | false | NULL | false | false | +| measure64 | numeric | 64 | public | WideCube | false | NULL | false | false | +| measure65 | numeric | 65 | public | WideCube | false | NULL | false | false | +| measure66 | numeric | 66 | public | WideCube | false | NULL | false | false | +| measure67 | numeric | 67 | public | WideCube | false | NULL | false | false | +| measure68 | numeric | 68 | public | WideCube | false | NULL | false | false | +| measure69 | numeric | 69 | public | WideCube | false | NULL | false | false | +| measure70 | numeric | 70 | public | WideCube | false | NULL | false | false | +| measure71 | numeric | 71 | public | WideCube | false | NULL | false | false | +| measure72 | numeric | 72 | public | WideCube | false | NULL | false | false | +| measure73 | numeric | 73 | public | WideCube | false | NULL | false | false | +| measure74 | numeric | 74 | public | WideCube | false | NULL | false | false | +| measure75 | numeric | 75 | public | WideCube | false | NULL | false | false | +| measure76 | numeric | 76 | public | WideCube | false | NULL | false | false | +| measure77 | numeric | 77 | public | WideCube | false | NULL | false | false | +| measure78 | numeric | 78 | public | WideCube | false | NULL | false | false | +| measure79 | numeric | 79 | public | WideCube | false | NULL | false | false | +| measure80 | numeric | 80 | public | WideCube | false | NULL | false | false | +| measure81 | numeric | 81 | public | WideCube | false | NULL | false | false | +| measure82 | numeric | 82 | public | WideCube | false | NULL | false | false | +| measure83 | numeric | 83 | public | WideCube | false | NULL | false | false | +| measure84 | numeric | 84 | public | WideCube | false | NULL | false | false | +| measure85 | numeric | 85 | public | WideCube | false | NULL | false | false | +| measure86 | numeric | 86 | public | WideCube | false | NULL | false | false | +| measure87 | numeric | 87 | public | WideCube | false | NULL | false | false | +| measure88 | numeric | 88 | public | WideCube | false | NULL | false | false | +| measure89 | numeric | 89 | public | WideCube | false | NULL | false | false | +| measure90 | numeric | 90 | public | WideCube | false | NULL | false | false | +| measure91 | numeric | 91 | public | WideCube | false | NULL | false | false | +| measure92 | numeric | 92 | public | WideCube | false | NULL | false | false | +| measure93 | numeric | 93 | public | WideCube | false | NULL | false | false | +| measure94 | numeric | 94 | public | WideCube | false | NULL | false | false | +| measure95 | numeric | 95 | public | WideCube | false | NULL | false | false | +| measure96 | numeric | 96 | public | WideCube | false | NULL | false | false | +| measure97 | numeric | 97 | public | WideCube | false | NULL | false | false | +| measure98 | numeric | 98 | public | WideCube | false | NULL | false | false | +| measure99 | numeric | 99 | public | WideCube | false | NULL | false | false | +| count | int8 | 100 | public | WideCube | false | NULL | false | false | +| maxPrice | numeric | 101 | public | WideCube | false | NULL | false | false | +| minPrice | numeric | 102 | public | WideCube | false | NULL | false | false | +| avgPrice | numeric | 103 | public | WideCube | false | NULL | false | false | +| countDistinct | int8 | 104 | public | WideCube | false | NULL | false | false | +| dim0 | numeric | 105 | public | WideCube | false | NULL | false | false | +| dim1 | numeric | 106 | public | WideCube | false | NULL | false | false | +| dim2 | numeric | 107 | public | WideCube | false | NULL | false | false | +| dim3 | numeric | 108 | public | WideCube | false | NULL | false | false | +| dim4 | numeric | 109 | public | WideCube | false | NULL | false | false | +| dim5 | numeric | 110 | public | WideCube | false | NULL | false | false | +| dim6 | numeric | 111 | public | WideCube | false | NULL | false | false | +| dim7 | numeric | 112 | public | WideCube | false | NULL | false | false | +| dim8 | numeric | 113 | public | WideCube | false | NULL | false | false | +| dim9 | numeric | 114 | public | WideCube | false | NULL | false | false | +| dim10 | numeric | 115 | public | WideCube | false | NULL | false | false | +| dim11 | numeric | 116 | public | WideCube | false | NULL | false | false | +| dim12 | numeric | 117 | public | WideCube | false | NULL | false | false | +| dim13 | numeric | 118 | public | WideCube | false | NULL | false | false | +| dim14 | numeric | 119 | public | WideCube | false | NULL | false | false | +| dim15 | numeric | 120 | public | WideCube | false | NULL | false | false | +| dim16 | numeric | 121 | public | WideCube | false | NULL | false | false | +| dim17 | numeric | 122 | public | WideCube | false | NULL | false | false | +| dim18 | numeric | 123 | public | WideCube | false | NULL | false | false | +| dim19 | numeric | 124 | public | WideCube | false | NULL | false | false | +| dim20 | numeric | 125 | public | WideCube | false | NULL | false | false | +| dim21 | numeric | 126 | public | WideCube | false | NULL | false | false | +| dim22 | numeric | 127 | public | WideCube | false | NULL | false | false | +| dim23 | numeric | 128 | public | WideCube | false | NULL | false | false | +| dim24 | numeric | 129 | public | WideCube | false | NULL | false | false | +| dim25 | numeric | 130 | public | WideCube | false | NULL | false | false | +| dim26 | numeric | 131 | public | WideCube | false | NULL | false | false | +| dim27 | numeric | 132 | public | WideCube | false | NULL | false | false | +| dim28 | numeric | 133 | public | WideCube | false | NULL | false | false | +| dim29 | numeric | 134 | public | WideCube | false | NULL | false | false | +| dim30 | numeric | 135 | public | WideCube | false | NULL | false | false | +| dim31 | numeric | 136 | public | WideCube | false | NULL | false | false | +| dim32 | numeric | 137 | public | WideCube | false | NULL | false | false | +| dim33 | numeric | 138 | public | WideCube | false | NULL | false | false | +| dim34 | numeric | 139 | public | WideCube | false | NULL | false | false | +| dim35 | numeric | 140 | public | WideCube | false | NULL | false | false | +| dim36 | numeric | 141 | public | WideCube | false | NULL | false | false | +| dim37 | numeric | 142 | public | WideCube | false | NULL | false | false | +| dim38 | numeric | 143 | public | WideCube | false | NULL | false | false | +| dim39 | numeric | 144 | public | WideCube | false | NULL | false | false | +| dim40 | numeric | 145 | public | WideCube | false | NULL | false | false | +| dim41 | numeric | 146 | public | WideCube | false | NULL | false | false | +| dim42 | numeric | 147 | public | WideCube | false | NULL | false | false | +| dim43 | numeric | 148 | public | WideCube | false | NULL | false | false | +| dim44 | numeric | 149 | public | WideCube | false | NULL | false | false | +| dim45 | numeric | 150 | public | WideCube | false | NULL | false | false | +| dim46 | numeric | 151 | public | WideCube | false | NULL | false | false | +| dim47 | numeric | 152 | public | WideCube | false | NULL | false | false | +| dim48 | numeric | 153 | public | WideCube | false | NULL | false | false | +| dim49 | numeric | 154 | public | WideCube | false | NULL | false | false | +| dim50 | numeric | 155 | public | WideCube | false | NULL | false | false | +| dim51 | numeric | 156 | public | WideCube | false | NULL | false | false | +| dim52 | numeric | 157 | public | WideCube | false | NULL | false | false | +| dim53 | numeric | 158 | public | WideCube | false | NULL | false | false | +| dim54 | numeric | 159 | public | WideCube | false | NULL | false | false | +| dim55 | numeric | 160 | public | WideCube | false | NULL | false | false | +| dim56 | numeric | 161 | public | WideCube | false | NULL | false | false | +| dim57 | numeric | 162 | public | WideCube | false | NULL | false | false | +| dim58 | numeric | 163 | public | WideCube | false | NULL | false | false | +| dim59 | numeric | 164 | public | WideCube | false | NULL | false | false | +| dim60 | numeric | 165 | public | WideCube | false | NULL | false | false | +| dim61 | numeric | 166 | public | WideCube | false | NULL | false | false | +| dim62 | numeric | 167 | public | WideCube | false | NULL | false | false | +| dim63 | numeric | 168 | public | WideCube | false | NULL | false | false | +| dim64 | numeric | 169 | public | WideCube | false | NULL | false | false | +| dim65 | numeric | 170 | public | WideCube | false | NULL | false | false | +| dim66 | numeric | 171 | public | WideCube | false | NULL | false | false | +| dim67 | numeric | 172 | public | WideCube | false | NULL | false | false | +| dim68 | numeric | 173 | public | WideCube | false | NULL | false | false | +| dim69 | numeric | 174 | public | WideCube | false | NULL | false | false | +| dim70 | numeric | 175 | public | WideCube | false | NULL | false | false | +| dim71 | numeric | 176 | public | WideCube | false | NULL | false | false | +| dim72 | numeric | 177 | public | WideCube | false | NULL | false | false | +| dim73 | numeric | 178 | public | WideCube | false | NULL | false | false | +| dim74 | numeric | 179 | public | WideCube | false | NULL | false | false | +| dim75 | numeric | 180 | public | WideCube | false | NULL | false | false | +| dim76 | numeric | 181 | public | WideCube | false | NULL | false | false | +| dim77 | numeric | 182 | public | WideCube | false | NULL | false | false | +| dim78 | numeric | 183 | public | WideCube | false | NULL | false | false | +| dim79 | numeric | 184 | public | WideCube | false | NULL | false | false | +| dim80 | numeric | 185 | public | WideCube | false | NULL | false | false | +| dim81 | numeric | 186 | public | WideCube | false | NULL | false | false | +| dim82 | numeric | 187 | public | WideCube | false | NULL | false | false | +| dim83 | numeric | 188 | public | WideCube | false | NULL | false | false | +| dim84 | numeric | 189 | public | WideCube | false | NULL | false | false | +| dim85 | numeric | 190 | public | WideCube | false | NULL | false | false | +| dim86 | numeric | 191 | public | WideCube | false | NULL | false | false | +| dim87 | numeric | 192 | public | WideCube | false | NULL | false | false | +| dim88 | numeric | 193 | public | WideCube | false | NULL | false | false | +| dim89 | numeric | 194 | public | WideCube | false | NULL | false | false | +| dim90 | numeric | 195 | public | WideCube | false | NULL | false | false | +| dim91 | numeric | 196 | public | WideCube | false | NULL | false | false | +| dim92 | numeric | 197 | public | WideCube | false | NULL | false | false | +| dim93 | numeric | 198 | public | WideCube | false | NULL | false | false | +| dim94 | numeric | 199 | public | WideCube | false | NULL | false | false | +| dim95 | numeric | 200 | public | WideCube | false | NULL | false | false | +| dim96 | numeric | 201 | public | WideCube | false | NULL | false | false | +| dim97 | numeric | 202 | public | WideCube | false | NULL | false | false | +| dim98 | numeric | 203 | public | WideCube | false | NULL | false | false | +| dim99 | numeric | 204 | public | WideCube | false | NULL | false | false | +| __user | text | 205 | public | WideCube | false | NULL | false | false | +| __cubeJoinField | text | 206 | public | WideCube | false | NULL | false | false | ++--------------------+---------------+-------------------+--------------+---------------------------+-------+---------------+-------------------+----------------------------+ diff --git a/rust/cubesql/cubesql/src/compile/test/test_introspection.rs b/rust/cubesql/cubesql/src/compile/test/test_introspection.rs index 30f1511cca66c..e0e40723d2f4d 100644 --- a/rust/cubesql/cubesql/src/compile/test/test_introspection.rs +++ b/rust/cubesql/cubesql/src/compile/test/test_introspection.rs @@ -3140,3 +3140,81 @@ async fn test_metabase_introspection_indoption() -> Result<(), CubeError> { ); Ok(()) } + +#[tokio::test] +async fn test_metabase_v0_51_2_introspection_field_indoption() -> Result<(), CubeError> { + init_testing_logger(); + + insta::assert_snapshot!( + "test_metabase_v0_51_2_introspection_field_indoption", + execute_query( + // language=PostgreSQL + r#" + SELECT + c.column_name AS name, + c.udt_name AS database_type, + c.ordinal_position - 1 AS database_position, + c.table_schema AS table_schema, + c.table_name AS table_name, + pk.column_name IS NOT NULL AS pk, + COL_DESCRIPTION( + CAST( + CAST( + FORMAT( + '%I.%I', + CAST(c.table_schema AS TEXT), + CAST(c.table_name AS TEXT) + ) AS REGCLASS + ) AS OID + ), + c.ordinal_position + ) AS field_comment, + ( + (column_default IS NULL) + OR (LOWER(column_default) = 'null') + ) + AND (is_nullable = 'NO') + AND NOT ( + ( + (column_default IS NOT NULL) + AND (column_default LIKE '%nextval(%') + ) + OR (is_identity <> 'NO') + ) AS database_required, + ( + (column_default IS NOT NULL) + AND (column_default LIKE '%nextval(%') + ) + OR (is_identity <> 'NO') AS database_is_auto_increment + FROM + information_schema.columns AS c + LEFT JOIN ( + SELECT + tc.table_schema, + tc.table_name, + kc.column_name + FROM + information_schema.table_constraints AS tc + INNER JOIN information_schema.key_column_usage AS kc ON (tc.constraint_name = kc.constraint_name) + AND (tc.table_schema = kc.table_schema) + AND (tc.table_name = kc.table_name) + WHERE + tc.constraint_type = 'PRIMARY KEY' + ) AS pk ON (c.table_schema = pk.table_schema) + AND (c.table_name = pk.table_name) + AND (c.column_name = pk.column_name) + WHERE + c.table_schema !~ '^information_schema|catalog_history|pg_' + AND (c.table_schema IN ('public')) + ORDER BY + table_schema ASC, + table_name ASC, + database_position ASC + "# + .to_string(), + DatabaseProtocol::PostgreSQL + ) + .await? + ); + Ok(()) +}