From cfd0d295ecff3b9f67c6bae8ffc7c8bf3a649794 Mon Sep 17 00:00:00 2001 From: Philip Lykke Carlsen Date: Thu, 28 Mar 2024 14:01:26 +0100 Subject: [PATCH] Support introspecting composite types (#391) ### What This PR adds the ability of the introspection query to detect composite types, meaning user-defined record types that do not arise from a table but exist as standalone definitions. This even discovered a typo in the previous, manually crafted composite type metadata. Support for cockroachdb is limited here until https://github.com/cockroachdb/cockroach/issues/109675 is released. ### How **Introspection Query** now captures composite types, filtering out the tables. Making metadata json out of these is quite similar to how we already did tables, so no huge surprises here. **Occurring types logic** now becomes somewhat more complex because we can no longer simply look at which type names are used in the collections we track. A composite type occurring in say, a table column, may refer to scalar types that don't occur anywhere else, and even other composite types. Occurring type discovery thus becomes an iterative procedure rather than a single pass. --------- Co-authored-by: Gil Mizrahi --- crates/configuration/src/version3/mod.rs | 112 ++++++++- .../configuration/src/version3/version3.sql | 222 ++++++++++++++---- ...schema_tests__schema_test__get_schema.snap | 156 +++++++++++- ...schema_tests__schema_test__get_schema.snap | 159 ++++++++++--- ...v3_initial_configuration_is_unchanged.snap | 88 ++++++- ...schema_tests__schema_test__get_schema.snap | 156 +++++++++++- docker-compose.yaml | 30 ++- .../configuration.json | 101 +++++++- .../configuration.json | 71 +++--- static/composite-types-comments.sql | 11 + static/composite-types-simple.sql | 15 ++ .../configuration.json | 101 +++++++- .../configuration.json | 98 +++++++- 13 files changed, 1160 insertions(+), 160 deletions(-) create mode 100644 static/composite-types-comments.sql diff --git a/crates/configuration/src/version3/mod.rs b/crates/configuration/src/version3/mod.rs index d901866a9..18efd8245 100644 --- a/crates/configuration/src/version3/mod.rs +++ b/crates/configuration/src/version3/mod.rs @@ -111,7 +111,7 @@ pub async fn introspect( .instrument(info_span!("Run introspection query")) .await?; - let (tables, aggregate_functions, comparison_operators) = async { + let (tables, aggregate_functions, comparison_operators, composite_types) = async { let tables: metadata::TablesInfo = serde_json::from_value(row.get(0))?; let aggregate_functions: metadata::AggregateFunctions = serde_json::from_value(row.get(1))?; @@ -119,6 +119,8 @@ pub async fn introspect( let metadata::ComparisonOperators(mut comparison_operators): metadata::ComparisonOperators = serde_json::from_value(row.get(2))?; + let composite_types: metadata::CompositeTypes = serde_json::from_value(row.get(3))?; + // We need to include `in` as a comparison operator in the schema, and since it is syntax, it is not introspectable. // Instead, we will check if the scalar type defines an equals operator and if yes, we will insert the `_in` operator // as well. @@ -146,17 +148,24 @@ pub async fn introspect( tables, aggregate_functions, metadata::ComparisonOperators(comparison_operators), + composite_types, )) } .instrument(info_span!("Decode introspection result")) .await?; - let scalar_types = occurring_scalar_types( - &tables, - &args.metadata.native_queries, - &args.metadata.aggregate_functions, + let (scalar_types, composite_types) = transitively_occurring_types( + occurring_scalar_types( + &tables, + &args.metadata.native_queries, + &args.metadata.aggregate_functions, + ), + occurring_composite_types(&tables, &args.metadata.native_queries), + composite_types, ); + // We filter our comparison operators and aggregate functions to only include those relevant to + // types that may actually occur in the schema. let relevant_comparison_operators = filter_comparison_operators(&scalar_types, comparison_operators); let relevant_aggregate_functions = @@ -170,14 +179,103 @@ pub async fn introspect( native_queries: args.metadata.native_queries, aggregate_functions: relevant_aggregate_functions, comparison_operators: relevant_comparison_operators, - composite_types: args.metadata.composite_types, + composite_types, }, introspection_options: args.introspection_options, mutations_version: args.mutations_version, }) } -/// Collect all the types that can occur in the metadata. This is a bit circumstantial. A better +/// Collect all the composite types that can occur in the metadata. +pub fn occurring_composite_types( + tables: &metadata::TablesInfo, + native_queries: &metadata::NativeQueries, +) -> BTreeSet { + let tables_column_types = tables + .0 + .values() + .flat_map(|v| v.columns.values().map(|c| &c.r#type)); + let native_queries_column_types = native_queries + .0 + .values() + .flat_map(|v| v.columns.values().map(|c| &c.r#type)); + let native_queries_arguments_types = native_queries + .0 + .values() + .flat_map(|v| v.arguments.values().map(|c| &c.r#type)); + + tables_column_types + .chain(native_queries_column_types) + .chain(native_queries_arguments_types) + .filter_map(|t| match t { + metadata::Type::CompositeType(ref t) => Some(t.clone()), + metadata::Type::ArrayType(t) => match **t { + metadata::Type::CompositeType(ref t) => Some(t.clone()), + metadata::Type::ArrayType(_) | metadata::Type::ScalarType(_) => None, + }, + metadata::Type::ScalarType(_) => None, + }) + .collect::>() +} + +// Since array types and composite types may refer to other types we have to transitively discover +// the full set of types that are relevant to the schema. +pub fn transitively_occurring_types( + mut occurring_scalar_types: BTreeSet, + occurring_type_names: BTreeSet, + mut composite_types: metadata::CompositeTypes, +) -> (BTreeSet, metadata::CompositeTypes) { + let mut discovered_type_names = occurring_type_names.clone(); + + for t in &occurring_type_names { + match composite_types.0.get(t) { + None => (), + Some(ct) => { + for f in ct.fields.values() { + match &f.r#type { + metadata::Type::CompositeType(ct2) => { + discovered_type_names.insert(ct2.to_string()); + } + metadata::Type::ScalarType(t) => { + occurring_scalar_types.insert(t.clone()); + } + metadata::Type::ArrayType(arr_ty) => match **arr_ty { + metadata::Type::CompositeType(ref ct2) => { + discovered_type_names.insert(ct2.to_string()); + } + metadata::Type::ScalarType(ref t) => { + occurring_scalar_types.insert(t.clone()); + } + metadata::Type::ArrayType(_) => { + // This case is impossible, because we do not support nested arrays + } + }, + } + } + } + } + } + + // Since 'discovered_type_names' only grows monotonically starting from 'occurring_type_names' + // we just have to compare the number of elements to know if new types have been discovered. + if discovered_type_names.len() == occurring_type_names.len() { + // Iterating over occurring types discovered no new types + composite_types + .0 + .retain(|t, _| occurring_type_names.contains(t)); + (occurring_scalar_types, composite_types) + } else { + // Iterating over occurring types did discover new types, + // so we keep on going. + transitively_occurring_types( + occurring_scalar_types, + discovered_type_names, + composite_types, + ) + } +} + +/// Collect all the scalar types that can occur in the metadata. This is a bit circumstantial. A better /// approach is likely to record scalar type names directly in the metadata via version2.sql. pub fn occurring_scalar_types( tables: &metadata::TablesInfo, diff --git a/crates/configuration/src/version3/version3.sql b/crates/configuration/src/version3/version3.sql index a084eba9d..681b9845e 100644 --- a/crates/configuration/src/version3/version3.sql +++ b/crates/configuration/src/version3/version3.sql @@ -176,6 +176,7 @@ WITH AS col USING (relation_id, column_number) ), + table_comments AS ( SELECT @@ -188,6 +189,55 @@ WITH AND objsubid = 0 ), + type_comments AS + ( + SELECT + objoid AS type_id, + description + FROM + pg_description + WHERE + classoid = 'pg_catalog.pg_type'::regclass + AND objsubid = 0 + ), + + -- Composite types, including those defined implicitly through a table and + -- explicitly via `CREATE TYPE`. + composite_types AS + ( + SELECT + t.oid AS type_id, + t.typnamespace AS schema_id, + t.typname AS type_name, + t.typrelid AS relation_id + FROM + pg_type t + INNER JOIN + -- Until the schema is made part of our model of types we only consider + -- those defined in the public schema. + unqualified_schemas_for_types_and_procedures as q + ON (t.typnamespace = q.schema_id) + WHERE typtype = 'c' + ), + + -- Composite types, except those which are also tables. + -- We collect these separately at the top level because we need to be able to + -- talk about them but also know that they are not tables that you can query. + exclusively_composite_types AS + ( + WITH + exclusively_composite_type_ids AS + ( + SELECT relation_id FROM composite_types + EXCEPT + SELECT relation_id FROM relations + ) + SELECT + * + FROM composite_types + NATURAL INNER JOIN exclusively_composite_type_ids + ), + -- Types are recorded in 'pg_types', see -- https://www.postgresql.org/docs/current/catalog-pg-type.html for its -- schema. @@ -336,13 +386,28 @@ WITH SELECT t.oid AS type_id, t.typnamespace AS schema_id, - et.type_name as element_type_name + et.type_name as element_type_name, + et.element_type_kind FROM pg_catalog.pg_type AS t INNER JOIN -- Postgres does not distinguish nested arrays at the type level, so we -- can already tell what the element type is. - scalar_types + ( + SELECT + type_id, + type_name, + schema_id, + 'scalarType' AS element_type_kind + FROM scalar_types + UNION + SELECT + type_id, + type_name, + schema_id, + 'compositeType' AS element_type_kind + FROM composite_types + ) AS et ON (et.type_id = t.typelem) INNER JOIN @@ -352,17 +417,7 @@ WITH USING (schema_id) WHERE -- See 'scalar_types' above - t.typtype NOT IN - ( - -- Interesting t.typtype 'types of types': - -- 'b' for base type - 'c', --for composite type - -- 'd' for domain (a predicate-restricted version of a type) - -- 'e' for enum - 'p' -- for pseudo-type (anyelement etc) - -- 'r' for range - -- 'm' for multi-range - ) + t.typtype = 'b' -- What makes a type an 'array' type in postgres is a surprisingly -- nuanced question. -- @@ -389,6 +444,103 @@ WITH -- the purpose of selecting preferred implicit casts. ), + column_types_json AS + ( + SELECT + type_id, + jsonb_build_object( + 'scalarType', + type_name + ) + AS result + FROM + scalar_types + UNION + SELECT + type_id, + jsonb_build_object( + 'arrayType', + jsonb_build_object( + element_type_kind, + element_type_name + ) + ) + AS result + FROM + array_types + UNION + SELECT + type_id, + jsonb_build_object( + 'compositeType', + type_name + ) + AS result + + FROM + composite_types + ), + + composite_type_fields_json AS + ( + SELECT + c.relation_id, + jsonb_object_agg + ( + c.column_name, + jsonb_build_object + ( + 'name', + c.column_name, + 'type', + t.result, + 'description', + comm.description + ) + ) + AS result + FROM columns + AS c + LEFT OUTER JOIN column_types_json + AS t + USING (type_id) + LEFT OUTER JOIN column_comments + AS comm + USING (relation_id, column_name) + GROUP BY relation_id + HAVING + -- All columns must have a supported type. + bool_and(NOT t.result IS NULL) + ), + + composite_types_json AS + ( + SELECT + ct.type_id, + ct.type_name, + jsonb_build_object + ( + 'name', + ct.type_name, + 'fields', + fields.result, + 'description', + comm.description + ) + AS result + FROM + exclusively_composite_types + AS ct + INNER JOIN + composite_type_fields_json + AS fields + USING (relation_id) + LEFT OUTER JOIN + type_comments + AS comm + USING (type_id) + ), + implicit_casts AS ( SELECT @@ -1022,8 +1174,22 @@ WITH SELECT coalesce(tables.result, '{}'::jsonb) AS "Tables", coalesce(aggregate_functions.result, '{}'::jsonb) AS "AggregateFunctions", - coalesce(comparison_functions.result, '{}'::jsonb) AS "ComparisonFunctions" + coalesce(comparison_functions.result, '{}'::jsonb) AS "ComparisonFunctions", + coalesce(composite_types_json.result, '{}'::jsonb) AS "CompositeTypes" FROM + ( + SELECT + jsonb_object_agg + ( + type_name, + result + ) + AS result + FROM + composite_types_json + ) + AS composite_types_json + CROSS JOIN ( -- Tables and views SELECT @@ -1069,32 +1235,6 @@ FROM -- Columns INNER JOIN ( - WITH - column_types AS - ( - SELECT - type_id, - jsonb_build_object( - 'scalarType', - type_name - ) - AS result - FROM - scalar_types - UNION - SELECT - type_id, - jsonb_build_object( - 'arrayType', - jsonb_build_object( - 'scalarType', - element_type_name - ) - ) - AS result - FROM - array_types - ) SELECT c.relation_id, jsonb_object_agg( @@ -1119,7 +1259,7 @@ FROM AS result FROM columns AS c - LEFT OUTER JOIN column_types + LEFT OUTER JOIN column_types_json AS t USING (type_id) LEFT OUTER JOIN column_comments diff --git a/crates/tests/databases-tests/src/citus/snapshots/databases_tests__citus__schema_tests__schema_test__get_schema.snap b/crates/tests/databases-tests/src/citus/snapshots/databases_tests__citus__schema_tests__schema_test__get_schema.snap index cef5837d4..80d2f4a1f 100644 --- a/crates/tests/databases-tests/src/citus/snapshots/databases_tests__citus__schema_tests__schema_test__get_schema.snap +++ b/crates/tests/databases-tests/src/citus/snapshots/databases_tests__citus__schema_tests__schema_test__get_schema.snap @@ -4,6 +4,71 @@ expression: result --- { "scalar_types": { + "bit": { + "aggregate_functions": { + "bit_and": { + "result_type": { + "type": "named", + "name": "bit" + } + }, + "bit_or": { + "result_type": { + "type": "named", + "name": "bit" + } + }, + "bit_xor": { + "result_type": { + "type": "named", + "name": "bit" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bit" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bit" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bit" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bit" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bit" + } + } + } + }, "bool": { "aggregate_functions": { "bool_and": { @@ -2572,7 +2637,7 @@ expression: result "type": "array", "element_type": { "type": "named", - "name": "text" + "name": "person_name" } } }, @@ -2636,6 +2701,29 @@ expression: result } } }, + "discoverable_types": { + "fields": { + "only_occurring_here1": { + "type": { + "type": "named", + "name": "bit" + } + } + } + }, + "discoverable_types_root_occurrence": { + "fields": { + "col": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "discoverable_types" + } + } + } + } + }, "even_numbers": { "fields": { "the_number": { @@ -2715,7 +2803,7 @@ expression: result }, "organization": { "fields": { - "members": { + "committees": { "type": { "type": "array", "element_type": { @@ -2749,14 +2837,17 @@ expression: result } }, "person_address": { + "description": "The address of a person, obviously", "fields": { "address_line_1": { + "description": "Address line No 1", "type": { "type": "named", "name": "text" } }, "address_line_2": { + "description": "Address line No 2", "type": { "type": "named", "name": "text" @@ -2765,14 +2856,17 @@ expression: result } }, "person_name": { + "description": "The name of a person, obviously", "fields": { "first_name": { + "description": "The first name of a person", "type": { "type": "named", "name": "text" } }, "last_name": { + "description": "The last name of a person", "type": { "type": "named", "name": "text" @@ -3824,6 +3918,41 @@ expression: result } } }, + "v1_insert_discoverable_types_root_occurrence_object": { + "fields": { + "col": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "discoverable_types" + } + } + } + } + }, + "v1_insert_discoverable_types_root_occurrence_response": { + "description": "Responses from the 'v1_insert_discoverable_types_root_occurrence' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "discoverable_types_root_occurrence" + } + } + } + } + }, "v1_insert_even_numbers_object": { "fields": { "the_number": { @@ -4233,6 +4362,13 @@ expression: result "uniqueness_constraints": {}, "foreign_keys": {} }, + { + "name": "discoverable_types_root_occurrence", + "arguments": {}, + "type": "discoverable_types_root_occurrence", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, { "name": "even_numbers", "arguments": {}, @@ -5014,6 +5150,22 @@ expression: result "name": "v1_insert_deck_of_cards_response" } }, + { + "name": "v1_insert_discoverable_types_root_occurrence", + "description": "Insert into the discoverable_types_root_occurrence table", + "arguments": { + "_object": { + "type": { + "type": "named", + "name": "v1_insert_discoverable_types_root_occurrence_object" + } + } + }, + "result_type": { + "type": "named", + "name": "v1_insert_discoverable_types_root_occurrence_response" + } + }, { "name": "v1_insert_even_numbers", "description": "Insert into the even_numbers table", diff --git a/crates/tests/databases-tests/src/cockroach/snapshots/databases_tests__cockroach__schema_tests__schema_test__get_schema.snap b/crates/tests/databases-tests/src/cockroach/snapshots/databases_tests__cockroach__schema_tests__schema_test__get_schema.snap index c1b4b793f..d2fd3bafb 100644 --- a/crates/tests/databases-tests/src/cockroach/snapshots/databases_tests__cockroach__schema_tests__schema_test__get_schema.snap +++ b/crates/tests/databases-tests/src/cockroach/snapshots/databases_tests__cockroach__schema_tests__schema_test__get_schema.snap @@ -2341,6 +2341,25 @@ expression: result } } }, + "discoverable_types_root_occurrence": { + "fields": { + "col": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "discoverable_types" + } + } + }, + "rowid": { + "type": { + "type": "named", + "name": "int8" + } + } + } + }, "insert_album": { "fields": { "AlbumId": { @@ -2394,38 +2413,6 @@ expression: result } } }, - "person_address": { - "fields": { - "address_line_1": { - "type": { - "type": "named", - "name": "text" - } - }, - "address_line_2": { - "type": { - "type": "named", - "name": "text" - } - } - } - }, - "person_name": { - "fields": { - "first_name": { - "type": { - "type": "named", - "name": "text" - } - }, - "last_name": { - "type": { - "type": "named", - "name": "text" - } - } - } - }, "pg_extension_spatial_ref_sys": { "description": "Shows all defined Spatial Reference Identifiers (SRIDs). Matches PostGIS' spatial_ref_sys table.", "fields": { @@ -2718,6 +2705,28 @@ expression: result } } }, + "v1_delete_discoverable_types_root_occurrence_by_rowid_response": { + "description": "Responses from the 'v1_delete_discoverable_types_root_occurrence_by_rowid' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "discoverable_types_root_occurrence" + } + } + } + } + }, "v1_insert_Album_object": { "fields": { "AlbumId": { @@ -3534,6 +3543,47 @@ expression: result } } }, + "v1_insert_discoverable_types_root_occurrence_object": { + "fields": { + "col": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "discoverable_types" + } + } + }, + "rowid": { + "type": { + "type": "named", + "name": "int8" + } + } + } + }, + "v1_insert_discoverable_types_root_occurrence_response": { + "description": "Responses from the 'v1_insert_discoverable_types_root_occurrence' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "discoverable_types_root_occurrence" + } + } + } + } + }, "v1_insert_pg_extension_spatial_ref_sys_object": { "fields": { "auth_name": { @@ -3988,6 +4038,19 @@ expression: result }, "foreign_keys": {} }, + { + "name": "discoverable_types_root_occurrence", + "arguments": {}, + "type": "discoverable_types_root_occurrence", + "uniqueness_constraints": { + "discoverable_types_root_occurrence_pkey": { + "unique_columns": [ + "rowid" + ] + } + }, + "foreign_keys": {} + }, { "name": "pg_extension_spatial_ref_sys", "description": "Shows all defined Spatial Reference Identifiers (SRIDs). Matches PostGIS' spatial_ref_sys table.", @@ -4546,6 +4609,22 @@ expression: result "name": "v1_delete_deck_of_cards_by_rowid_response" } }, + { + "name": "v1_delete_discoverable_types_root_occurrence_by_rowid", + "description": "Delete any value on the discoverable_types_root_occurrence table using the rowid key", + "arguments": { + "rowid": { + "type": { + "type": "named", + "name": "int8" + } + } + }, + "result_type": { + "type": "named", + "name": "v1_delete_discoverable_types_root_occurrence_by_rowid_response" + } + }, { "name": "v1_insert_Album", "description": "Insert into the Album table", @@ -4738,6 +4817,22 @@ expression: result "name": "v1_insert_deck_of_cards_response" } }, + { + "name": "v1_insert_discoverable_types_root_occurrence", + "description": "Insert into the discoverable_types_root_occurrence table", + "arguments": { + "_object": { + "type": { + "type": "named", + "name": "v1_insert_discoverable_types_root_occurrence_object" + } + } + }, + "result_type": { + "type": "named", + "name": "v1_insert_discoverable_types_root_occurrence_response" + } + }, { "name": "v1_insert_pg_extension_spatial_ref_sys", "description": "Insert into the pg_extension_spatial_ref_sys table", diff --git a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__configuration_tests__postgres_current_only_configure_v3_initial_configuration_is_unchanged.snap b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__configuration_tests__postgres_current_only_configure_v3_initial_configuration_is_unchanged.snap index 25108e8aa..5e801db00 100644 --- a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__configuration_tests__postgres_current_only_configure_v3_initial_configuration_is_unchanged.snap +++ b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__configuration_tests__postgres_current_only_configure_v3_initial_configuration_is_unchanged.snap @@ -848,6 +848,23 @@ expression: default_configuration "foreignRelations": {}, "description": null }, + "discoverable_types_root_occurrence": { + "schemaName": "public", + "tableName": "discoverable_types_root_occurrence", + "columns": { + "col": { + "name": "col", + "type": { + "compositeType": "discoverable_types" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": {}, + "foreignRelations": {}, + "description": null + }, "even_numbers": { "schemaName": "public", "tableName": "even_numbers", @@ -1069,9 +1086,34 @@ expression: default_configuration "description": null } }, - "compositeTypes": {}, + "compositeTypes": { + "discoverable_types": { + "name": "discoverable_types", + "fields": { + "only_occurring_here1": { + "name": "only_occurring_here1", + "type": { + "scalarType": "bit" + }, + "description": null + } + }, + "description": null + } + }, "nativeQueries": {}, "aggregateFunctions": { + "bit": { + "bit_and": { + "returnType": "bit" + }, + "bit_or": { + "returnType": "bit" + }, + "bit_xor": { + "returnType": "bit" + } + }, "bool": { "bool_and": { "returnType": "bool" @@ -1353,6 +1395,50 @@ expression: default_configuration } }, "comparisonOperators": { + "bit": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "bit", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "bit", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + } + }, "bool": { "_eq": { "operatorName": "=", diff --git a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__schema_tests__schema_test__get_schema.snap b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__schema_tests__schema_test__get_schema.snap index bee37ca18..6484dfed3 100644 --- a/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__schema_tests__schema_test__get_schema.snap +++ b/crates/tests/databases-tests/src/postgres/snapshots/databases_tests__postgres__schema_tests__schema_test__get_schema.snap @@ -4,6 +4,71 @@ expression: result --- { "scalar_types": { + "bit": { + "aggregate_functions": { + "bit_and": { + "result_type": { + "type": "named", + "name": "bit" + } + }, + "bit_or": { + "result_type": { + "type": "named", + "name": "bit" + } + }, + "bit_xor": { + "result_type": { + "type": "named", + "name": "bit" + } + } + }, + "comparison_operators": { + "_eq": { + "type": "equal" + }, + "_gt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bit" + } + }, + "_gte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bit" + } + }, + "_in": { + "type": "in" + }, + "_lt": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bit" + } + }, + "_lte": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bit" + } + }, + "_neq": { + "type": "custom", + "argument_type": { + "type": "named", + "name": "bit" + } + } + } + }, "bool": { "aggregate_functions": { "bool_and": { @@ -2684,7 +2749,7 @@ expression: result "type": "array", "element_type": { "type": "named", - "name": "text" + "name": "person_name" } } }, @@ -2794,6 +2859,29 @@ expression: result } } }, + "discoverable_types": { + "fields": { + "only_occurring_here1": { + "type": { + "type": "named", + "name": "bit" + } + } + } + }, + "discoverable_types_root_occurrence": { + "fields": { + "col": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "discoverable_types" + } + } + } + } + }, "even_numbers": { "fields": { "the_number": { @@ -2873,7 +2961,7 @@ expression: result }, "organization": { "fields": { - "members": { + "committees": { "type": { "type": "array", "element_type": { @@ -2907,14 +2995,17 @@ expression: result } }, "person_address": { + "description": "The address of a person, obviously", "fields": { "address_line_1": { + "description": "Address line No 1", "type": { "type": "named", "name": "text" } }, "address_line_2": { + "description": "Address line No 2", "type": { "type": "named", "name": "text" @@ -2923,14 +3014,17 @@ expression: result } }, "person_name": { + "description": "The name of a person, obviously", "fields": { "first_name": { + "description": "The first name of a person", "type": { "type": "named", "name": "text" } }, "last_name": { + "description": "The last name of a person", "type": { "type": "named", "name": "text" @@ -4258,6 +4352,41 @@ expression: result } } }, + "v1_insert_discoverable_types_root_occurrence_object": { + "fields": { + "col": { + "type": { + "type": "nullable", + "underlying_type": { + "type": "named", + "name": "discoverable_types" + } + } + } + } + }, + "v1_insert_discoverable_types_root_occurrence_response": { + "description": "Responses from the 'v1_insert_discoverable_types_root_occurrence' procedure", + "fields": { + "affected_rows": { + "description": "The number of rows affected by the mutation", + "type": { + "type": "named", + "name": "int4" + } + }, + "returning": { + "description": "Data from rows affected by the mutation", + "type": { + "type": "array", + "element_type": { + "type": "named", + "name": "discoverable_types_root_occurrence" + } + } + } + } + }, "v1_insert_even_numbers_object": { "fields": { "the_number": { @@ -4881,6 +5010,13 @@ expression: result "uniqueness_constraints": {}, "foreign_keys": {} }, + { + "name": "discoverable_types_root_occurrence", + "arguments": {}, + "type": "discoverable_types_root_occurrence", + "uniqueness_constraints": {}, + "foreign_keys": {} + }, { "name": "even_numbers", "arguments": {}, @@ -5801,6 +5937,22 @@ expression: result "name": "v1_insert_deck_of_cards_response" } }, + { + "name": "v1_insert_discoverable_types_root_occurrence", + "description": "Insert into the discoverable_types_root_occurrence table", + "arguments": { + "_object": { + "type": { + "type": "named", + "name": "v1_insert_discoverable_types_root_occurrence_object" + } + } + }, + "result_type": { + "type": "named", + "name": "v1_insert_discoverable_types_root_occurrence_response" + } + }, { "name": "v1_insert_even_numbers", "description": "Insert into the even_numbers table", diff --git a/docker-compose.yaml b/docker-compose.yaml index 3fa545955..11a10f6d7 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -26,27 +26,31 @@ services: source: ./static/composite-types-simple.sql target: /docker-entrypoint-initdb.d/03-composite-simple.sql read_only: true + - type: bind + source: ./static/composite-types-comments.sql + target: /docker-entrypoint-initdb.d/04-composite-types-comments.sql + read_only: true - type: bind source: ./static/composite-types-complex.sql - target: /docker-entrypoint-initdb.d/04-composite-complex.sql + target: /docker-entrypoint-initdb.d/05-composite-complex.sql read_only: true - type: bind source: ./static/custom-tables.sql - target: /docker-entrypoint-initdb.d/05-custom-tables.sql + target: /docker-entrypoint-initdb.d/06-custom-tables.sql read_only: true - type: bind source: ./static/domain-types.sql - target: /docker-entrypoint-initdb.d/06-domain-types.sql + target: /docker-entrypoint-initdb.d/07-domain-types.sql read_only: true - type: bind source: ./static/enum-types.sql - target: /docker-entrypoint-initdb.d/07-enum-types.sql + target: /docker-entrypoint-initdb.d/08-enum-types.sql read_only: true # The script to copy the database template for mutations tests should # come in last in order to capture all aspects. - type: bind source: ./static/copy-chinook.sql - target: /docker-entrypoint-initdb.d/08-copy-chinook.sql + target: /docker-entrypoint-initdb.d/09-copy-chinook.sql read_only: true healthcheck: test: @@ -114,17 +118,21 @@ services: source: ./static/composite-types-simple.sql target: /docker-entrypoint-initdb.d/01-composite-types-simple.sql read_only: true + - type: bind + source: ./static/composite-types-comments.sql + target: /docker-entrypoint-initdb.d/02-composite-types-comments.sql + read_only: true - type: bind source: ./static/composite-types-complex.sql - target: /docker-entrypoint-initdb.d/02-composite-types-complex.sql + target: /docker-entrypoint-initdb.d/03-composite-types-complex.sql read_only: true - type: bind source: ./static/domain-types.sql - target: /docker-entrypoint-initdb.d/03-domain-types.sql + target: /docker-entrypoint-initdb.d/04-domain-types.sql read_only: true - type: bind source: ./static/enum-types.sql - target: /docker-entrypoint-initdb.d/04-enum-types.sql + target: /docker-entrypoint-initdb.d/05-enum-types.sql read_only: true healthcheck: test: @@ -154,9 +162,13 @@ services: source: ./static/composite-types-simple.sql target: /home/yugabyte/01-composite-types-simple.sql read_only: true + - type: bind + source: ./static/composite-types-comments.sql + target: /home/yugabyte/02-composite-types-comments.sql + read_only: true - type: bind source: ./static/composite-types-complex.sql - target: /home/yugabyte/02-composite-types-complex.sql + target: /home/yugabyte/03-composite-types-complex.sql read_only: true - type: bind source: ./static/domain-types.sql diff --git a/static/citus/v3-chinook-ndc-metadata/configuration.json b/static/citus/v3-chinook-ndc-metadata/configuration.json index f91727dd5..a54f1a2ca 100644 --- a/static/citus/v3-chinook-ndc-metadata/configuration.json +++ b/static/citus/v3-chinook-ndc-metadata/configuration.json @@ -757,6 +757,23 @@ "foreignRelations": {}, "description": null }, + "discoverable_types_root_occurrence": { + "schemaName": "public", + "tableName": "discoverable_types_root_occurrence", + "columns": { + "col": { + "name": "col", + "type": { + "compositeType": "discoverable_types" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": {}, + "foreignRelations": {}, + "description": null + }, "even_numbers": { "schemaName": "public", "tableName": "even_numbers", @@ -783,7 +800,7 @@ "name": "members", "type": { "arrayType": { - "scalarType": "text" + "compositeType": "person_name" } }, "description": null @@ -798,10 +815,23 @@ }, "description": null }, + "discoverable_types": { + "name": "discoverable_types", + "fields": { + "only_occurring_here1": { + "name": "only_occurring_here1", + "type": { + "scalarType": "bit" + }, + "description": null + } + }, + "description": null + }, "organization": { "name": "organization", "fields": { - "members": { + "committees": { "name": "committees", "type": { "arrayType": { @@ -848,17 +878,17 @@ "type": { "scalarType": "text" }, - "description": null + "description": "Address line No 1" }, "address_line_2": { "name": "address_line_2", "type": { "scalarType": "text" }, - "description": null + "description": "Address line No 2" } }, - "description": null + "description": "The address of a person, obviously" }, "person_name": { "name": "person_name", @@ -868,17 +898,17 @@ "type": { "scalarType": "text" }, - "description": null + "description": "The first name of a person" }, "last_name": { "name": "last_name", "type": { "scalarType": "text" }, - "description": null + "description": "The last name of a person" } }, - "description": null + "description": "The name of a person, obviously" } }, "nativeQueries": { @@ -1586,6 +1616,17 @@ } }, "aggregateFunctions": { + "bit": { + "bit_and": { + "returnType": "bit" + }, + "bit_or": { + "returnType": "bit" + }, + "bit_xor": { + "returnType": "bit" + } + }, "bool": { "bool_and": { "returnType": "bool" @@ -1959,6 +2000,50 @@ } }, "comparisonOperators": { + "bit": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "bit", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "bit", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + } + }, "bool": { "_eq": { "operatorName": "=", diff --git a/static/cockroach/v3-chinook-ndc-metadata/configuration.json b/static/cockroach/v3-chinook-ndc-metadata/configuration.json index 16080efd5..edb8e36ba 100644 --- a/static/cockroach/v3-chinook-ndc-metadata/configuration.json +++ b/static/cockroach/v3-chinook-ndc-metadata/configuration.json @@ -768,6 +768,34 @@ "foreignRelations": {}, "description": null }, + "discoverable_types_root_occurrence": { + "schemaName": "public", + "tableName": "discoverable_types_root_occurrence", + "columns": { + "col": { + "name": "col", + "type": { + "compositeType": "discoverable_types" + }, + "nullable": "nullable", + "description": null + }, + "rowid": { + "name": "rowid", + "type": { + "scalarType": "int8" + }, + "nullable": "nonNullable", + "hasDefault": "hasDefault", + "description": null + } + }, + "uniquenessConstraints": { + "discoverable_types_root_occurrence_pkey": ["rowid"] + }, + "foreignRelations": {}, + "description": null + }, "pg_extension_spatial_ref_sys": { "schemaName": "pg_extension", "tableName": "spatial_ref_sys", @@ -818,48 +846,7 @@ "description": "Shows all defined Spatial Reference Identifiers (SRIDs). Matches PostGIS' spatial_ref_sys table." } }, - "compositeTypes": { - "person_address": { - "name": "person_address", - "fields": { - "address_line_1": { - "name": "address_line_1", - "type": { - "scalarType": "text" - }, - "description": null - }, - "address_line_2": { - "name": "address_line_2", - "type": { - "scalarType": "text" - }, - "description": null - } - }, - "description": null - }, - "person_name": { - "name": "person_name", - "fields": { - "first_name": { - "name": "first_name", - "type": { - "scalarType": "text" - }, - "description": null - }, - "last_name": { - "name": "last_name", - "type": { - "scalarType": "text" - }, - "description": null - } - }, - "description": null - } - }, + "compositeTypes": {}, "nativeQueries": { "address_identity_function": { "sql": { diff --git a/static/composite-types-comments.sql b/static/composite-types-comments.sql new file mode 100644 index 000000000..55a69e4c9 --- /dev/null +++ b/static/composite-types-comments.sql @@ -0,0 +1,11 @@ + +-- This file defines types which are used to test the support of composite +-- types. + +COMMENT ON TYPE person_name IS 'The name of a person, obviously'; +COMMENT ON COLUMN person_name.first_name IS 'The first name of a person'; +COMMENT ON COLUMN person_name.last_name IS 'The last name of a person'; + +COMMENT ON TYPE person_address IS 'The address of a person, obviously'; +COMMENT ON COLUMN person_address.address_line_1 IS 'Address line No 1'; +COMMENT ON COLUMN person_address.address_line_2 IS 'Address line No 2'; diff --git a/static/composite-types-simple.sql b/static/composite-types-simple.sql index bc5a71533..0343e8d16 100644 --- a/static/composite-types-simple.sql +++ b/static/composite-types-simple.sql @@ -13,3 +13,18 @@ CREATE TYPE person_address AS address_line_1 text, address_line_2 text ); + +-- The below types and tables serve to show that composite types inform which scalar types are relevant. + +CREATE TYPE discoverable_types AS + ( + only_occurring_here1 bit + ); + +CREATE TABLE discoverable_types_root_occurrence + ( + col discoverable_types + ); + +-- We add this because ndc-test is going to check that this table it not empty. +INSERT INTO discoverable_types_root_occurrence(col) VALUES (ROW(1)::discoverable_types); diff --git a/static/postgres/v3-chinook-ndc-metadata/configuration.json b/static/postgres/v3-chinook-ndc-metadata/configuration.json index b9440f828..a12196ba5 100644 --- a/static/postgres/v3-chinook-ndc-metadata/configuration.json +++ b/static/postgres/v3-chinook-ndc-metadata/configuration.json @@ -820,6 +820,23 @@ "foreignRelations": {}, "description": null }, + "discoverable_types_root_occurrence": { + "schemaName": "public", + "tableName": "discoverable_types_root_occurrence", + "columns": { + "col": { + "name": "col", + "type": { + "compositeType": "discoverable_types" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": {}, + "foreignRelations": {}, + "description": null + }, "even_numbers": { "schemaName": "public", "tableName": "even_numbers", @@ -1040,7 +1057,7 @@ "name": "members", "type": { "arrayType": { - "scalarType": "text" + "compositeType": "person_name" } }, "description": null @@ -1055,10 +1072,23 @@ }, "description": null }, + "discoverable_types": { + "name": "discoverable_types", + "fields": { + "only_occurring_here1": { + "name": "only_occurring_here1", + "type": { + "scalarType": "bit" + }, + "description": null + } + }, + "description": null + }, "organization": { "name": "organization", "fields": { - "members": { + "committees": { "name": "committees", "type": { "arrayType": { @@ -1105,17 +1135,17 @@ "type": { "scalarType": "text" }, - "description": null + "description": "Address line No 1" }, "address_line_2": { "name": "address_line_2", "type": { "scalarType": "text" }, - "description": null + "description": "Address line No 2" } }, - "description": null + "description": "The address of a person, obviously" }, "person_name": { "name": "person_name", @@ -1125,17 +1155,17 @@ "type": { "scalarType": "text" }, - "description": null + "description": "The first name of a person" }, "last_name": { "name": "last_name", "type": { "scalarType": "text" }, - "description": null + "description": "The last name of a person" } }, - "description": null + "description": "The name of a person, obviously" } }, "nativeQueries": { @@ -1843,6 +1873,17 @@ } }, "aggregateFunctions": { + "bit": { + "bit_and": { + "returnType": "bit" + }, + "bit_or": { + "returnType": "bit" + }, + "bit_xor": { + "returnType": "bit" + } + }, "bool": { "bool_and": { "returnType": "bool" @@ -2216,6 +2257,50 @@ } }, "comparisonOperators": { + "bit": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "bit", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "bit", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + } + }, "bool": { "_eq": { "operatorName": "=", diff --git a/static/yugabyte/v3-chinook-ndc-metadata/configuration.json b/static/yugabyte/v3-chinook-ndc-metadata/configuration.json index 66bb972af..107416340 100644 --- a/static/yugabyte/v3-chinook-ndc-metadata/configuration.json +++ b/static/yugabyte/v3-chinook-ndc-metadata/configuration.json @@ -757,6 +757,23 @@ "foreignRelations": {}, "description": null }, + "discoverable_types_root_occurrence": { + "schemaName": "public", + "tableName": "discoverable_types_root_occurrence", + "columns": { + "col": { + "name": "col", + "type": { + "compositeType": "discoverable_types" + }, + "nullable": "nullable", + "description": null + } + }, + "uniquenessConstraints": {}, + "foreignRelations": {}, + "description": null + }, "even_numbers": { "schemaName": "public", "tableName": "even_numbers", @@ -783,7 +800,7 @@ "name": "members", "type": { "arrayType": { - "scalarType": "text" + "compositeType": "person_name" } }, "description": null @@ -798,10 +815,23 @@ }, "description": null }, + "discoverable_types": { + "name": "discoverable_types", + "fields": { + "only_occurring_here1": { + "name": "only_occurring_here1", + "type": { + "scalarType": "bit" + }, + "description": null + } + }, + "description": null + }, "organization": { "name": "organization", "fields": { - "members": { + "committees": { "name": "committees", "type": { "arrayType": { @@ -848,17 +878,17 @@ "type": { "scalarType": "text" }, - "description": null + "description": "Address line No 1" }, "address_line_2": { "name": "address_line_2", "type": { "scalarType": "text" }, - "description": null + "description": "Address line No 2" } }, - "description": null + "description": "The address of a person, obviously" }, "person_name": { "name": "person_name", @@ -868,17 +898,17 @@ "type": { "scalarType": "text" }, - "description": null + "description": "The first name of a person" }, "last_name": { "name": "last_name", "type": { "scalarType": "text" }, - "description": null + "description": "The last name of a person" } }, - "description": null + "description": "The name of a person, obviously" } }, "nativeQueries": { @@ -1560,6 +1590,14 @@ } }, "aggregateFunctions": { + "bit": { + "bit_and": { + "returnType": "bit" + }, + "bit_or": { + "returnType": "bit" + } + }, "bool": { "bool_and": { "returnType": "bool" @@ -1921,6 +1959,50 @@ } }, "comparisonOperators": { + "bit": { + "_eq": { + "operatorName": "=", + "operatorKind": "equal", + "argumentType": "bit", + "isInfix": true + }, + "_gt": { + "operatorName": ">", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_gte": { + "operatorName": ">=", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_in": { + "operatorName": "IN", + "operatorKind": "in", + "argumentType": "bit", + "isInfix": true + }, + "_lt": { + "operatorName": "<", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_lte": { + "operatorName": "<=", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + }, + "_neq": { + "operatorName": "<>", + "operatorKind": "custom", + "argumentType": "bit", + "isInfix": true + } + }, "bool": { "_eq": { "operatorName": "=",