From f3784ac5d8844eea62dbeba74ee4dd1145838989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Thu, 23 Nov 2023 01:32:46 +0100 Subject: [PATCH 01/47] feat: geospatial types support --- .github/workflows/test-query-engine.yml | 16 + .github/workflows/test-schema-engine.yml | 2 + .test_database_urls/postgis_15 | 1 + Cargo.lock | 112 ++- Makefile | 6 + docker-compose.yml | 13 + libs/prisma-value/src/lib.rs | 4 + libs/test-setup/src/postgres.rs | 9 +- libs/test-setup/src/sqlite.rs | 18 + libs/test-setup/src/tags.rs | 2 + libs/test-setup/src/test_api_args.rs | 4 +- prisma-fmt/tests/native_types.rs | 2 +- .../src/cockroach_datamodel_connector.rs | 41 +- .../native_types.rs | 4 + psl/builtin-connectors/src/geometry.rs | 355 +++++++++ psl/builtin-connectors/src/lib.rs | 3 + psl/builtin-connectors/src/mongodb.rs | 1 + .../src/mongodb/mongodb_types.rs | 3 +- .../src/mssql_datamodel_connector.rs | 10 + .../mssql_datamodel_connector/native_types.rs | 2 + .../src/mysql_datamodel_connector.rs | 37 + .../mysql_datamodel_connector/native_types.rs | 8 + .../mysql_datamodel_connector/validations.rs | 8 + .../src/postgres_datamodel_connector.rs | 39 +- .../native_types.rs | 4 + .../src/sqlite_datamodel_connector.rs | 98 ++- .../native_types.rs | 7 + psl/parser-database/src/attributes/default.rs | 2 + psl/parser-database/src/types.rs | 19 +- psl/psl-core/src/datamodel_connector.rs | 12 + .../src/datamodel_connector/capabilities.rs | 6 + .../datamodel_connector/empty_connector.rs | 1 + .../validations/default_value.rs | 2 + .../validation_pipeline/validations/fields.rs | 32 + psl/psl/tests/base/base_types.rs | 5 + .../tests/types/cockroachdb_native_types.rs | 512 +++++++++++++ psl/psl/tests/types/mod.rs | 1 + psl/psl/tests/types/mssql_native_types.rs | 176 +++++ psl/psl/tests/types/mysql_native_types.rs | 531 ++++++++++++++ psl/psl/tests/types/postgres_native_types.rs | 499 +++++++++++++ psl/psl/tests/types/sqlite_native_types.rs | 487 ++++++++++++ .../mongodb/invalid_json_usage_in_type.prisma | 12 +- quaint/Cargo.toml | 40 +- quaint/src/ast.rs | 3 +- quaint/src/ast/column.rs | 6 +- quaint/src/ast/compare.rs | 329 ++++++++- quaint/src/ast/expression.rs | 90 +++ quaint/src/ast/function.rs | 16 + quaint/src/ast/function/geom_as_text.rs | 31 + quaint/src/ast/function/geom_from_text.rs | 41 ++ quaint/src/ast/row.rs | 75 ++ quaint/src/ast/values.rs | 88 ++- quaint/src/connector/mssql/conversion.rs | 0 .../src/connector/mssql/native/conversion.rs | 2 + .../src/connector/mysql/native/conversion.rs | 23 +- .../connector/postgres/native/conversion.rs | 25 + .../src/connector/sqlite/native/conversion.rs | 27 + quaint/src/connector/sqlite/native/mod.rs | 16 +- quaint/src/connector/type_identifier.rs | 1 + quaint/src/serde.rs | 0 quaint/src/visitor.rs | 67 ++ quaint/src/visitor/mssql.rs | 132 +++- quaint/src/visitor/mysql.rs | 75 ++ quaint/src/visitor/postgres.rs | 61 ++ quaint/src/visitor/sqlite.rs | 69 +- .../connector-test-kit-rs/qe-setup/src/lib.rs | 10 +- .../qe-setup/src/postgres.rs | 3 + .../qe-setup/src/sqlite.rs | 11 + .../src/schemas/geometry.rs | 25 + .../query-engine-tests/src/schemas/mod.rs | 2 + .../query-engine-tests/tests/new/metrics.rs | 4 +- .../tests/queries/filters/geometry_filter.rs | 123 ++++ .../tests/queries/filters/mod.rs | 1 + .../writes/data_types/native_types/mod.rs | 1 + .../writes/data_types/native_types/mongodb.rs | 5 +- .../writes/data_types/native_types/mysql.rs | 204 ++++++ .../data_types/native_types/postgres.rs | 392 ++++++++++ .../data_types/native_types/sql_server.rs | 12 +- .../writes/data_types/native_types/sqlite.rs | 160 ++++ .../writes/top_level_mutations/create.rs | 33 + .../src/connector_tag/mod.rs | 4 + .../src/connector_tag/postgres.rs | 3 + .../test-configs/postgis15 | 3 + .../mongodb-query-connector/src/filter.rs | 28 + .../mongodb-query-connector/src/value.rs | 17 +- .../connectors/sql-query-connector/Cargo.toml | 2 + .../sql-query-connector/src/filter/visitor.rs | 16 + .../src/model_extensions/scalar_field.rs | 42 +- .../src/query_builder/read.rs | 18 +- .../src/query_builder/write.rs | 1 + .../connectors/sql-query-connector/src/row.rs | 27 +- .../sql-query-connector/src/value.rs | 4 + .../sql-query-connector/src/value_ext.rs | 2 + query-engine/core/Cargo.toml | 1 + query-engine/core/src/constants.rs | 2 + .../core/src/query_document/parser.rs | 24 + .../extractors/filters/scalar.rs | 18 + query-engine/core/src/response_ir/internal.rs | 8 + .../src/ast_builders/datamodel_ast_builder.rs | 2 + .../schema_ast_builder/type_renderer.rs | 2 + query-engine/query-structure/src/field/mod.rs | 11 + .../query-structure/src/field/scalar.rs | 5 + .../query-structure/src/filter/compare.rs | 16 + .../src/filter/scalar/compare.rs | 132 ++++ .../src/filter/scalar/condition/mod.rs | 12 + .../query-structure/src/prisma_value_ext.rs | 4 +- .../graphql/schema_renderer/type_renderer.rs | 4 + .../fields/data_input_mapper/update.rs | 2 + .../input_types/fields/field_filter_types.rs | 16 + .../schema/src/build/input_types/mod.rs | 4 +- .../schema/src/build/output_types/field.rs | 4 +- query-engine/schema/src/constants.rs | 4 + query-engine/schema/src/input_types.rs | 8 + query-engine/schema/src/output_types.rs | 8 + query-engine/schema/src/query_schema.rs | 4 + .../src/flavour/postgres.rs | 5 + .../src/flavour/postgres/connection.rs | 4 + .../src/flavour/sqlite.rs | 8 + .../src/flavour/sqlite/connection.rs | 22 +- .../introspection_pair/scalar_field.rs | 2 + .../sql-schema-connector/src/lib.rs | 2 +- .../sql-schema-connector/src/sql_renderer.rs | 5 + .../src/sql_renderer/mssql_renderer.rs | 2 + .../src/sql_renderer/mysql_renderer.rs | 15 + .../src/sql_renderer/postgres_renderer.rs | 14 +- .../src/sql_renderer/sqlite_renderer.rs | 72 +- .../src/sql_schema_calculator.rs | 2 + .../sql_schema_differ_flavour/mssql.rs | 10 + .../sql_schema_differ_flavour/mysql.rs | 361 +++++++++ .../sql_schema_differ_flavour/postgres.rs | 8 + .../sql_schema_differ_flavour/sqlite.rs | 83 +++ .../tests/cockroachdb/gin.rs | 4 +- .../tests/commenting_out/cockroachdb.rs | 11 +- .../tests/native_types/mod.rs | 1 + .../tests/native_types/mssql.rs | 4 + .../tests/native_types/mysql.rs | 70 ++ .../tests/native_types/postgres.rs | 373 +++++++++- .../tests/native_types/sqlite.rs | 91 +++ .../mssql/geometry_should_be_unsupported.sql | 24 - .../migrations/indexes/cockroachdb/gin.rs | 4 +- .../tests/native_types/mssql.rs | 2 + .../tests/native_types/mysql.rs | 85 +-- schema-engine/sql-schema-describer/src/lib.rs | 6 + .../sql-schema-describer/src/mssql.rs | 3 + .../sql-schema-describer/src/mysql.rs | 82 ++- .../sql-schema-describer/src/postgres.rs | 101 ++- .../src/postgres/default.rs | 3 +- .../postgres/default/c_style_scalar_lists.rs | 1 + .../sql-schema-describer/src/sqlite.rs | 147 +++- .../tests/describers/mssql_describer_tests.rs | 38 + .../tests/describers/mysql_describer_tests.rs | 208 +++--- .../describers/postgres_describer_tests.rs | 199 ++++- .../cockroach_describer_tests.rs | 333 +++++++++ .../describers/sqlite_describer_tests.rs | 693 +++++++++++++++++- .../tests/test_api/mod.rs | 20 +- 155 files changed, 8503 insertions(+), 382 deletions(-) create mode 100644 .test_database_urls/postgis_15 create mode 100644 psl/builtin-connectors/src/geometry.rs create mode 100644 psl/builtin-connectors/src/sqlite_datamodel_connector/native_types.rs create mode 100644 psl/psl/tests/types/sqlite_native_types.rs create mode 100644 quaint/src/ast/function/geom_as_text.rs create mode 100644 quaint/src/ast/function/geom_from_text.rs create mode 100644 quaint/src/connector/mssql/conversion.rs create mode 100644 quaint/src/serde.rs create mode 100644 query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs create mode 100644 query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/geometry.rs create mode 100644 query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs create mode 100644 query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sqlite.rs create mode 100644 query-engine/connector-test-kit-rs/test-configs/postgis15 create mode 100644 schema-engine/sql-introspection-tests/tests/native_types/sqlite.rs delete mode 100644 schema-engine/sql-introspection-tests/tests/simple/mssql/geometry_should_be_unsupported.sql diff --git a/.github/workflows/test-query-engine.yml b/.github/workflows/test-query-engine.yml index 6d5e0ada4eb3..24f1032e6929 100644 --- a/.github/workflows/test-query-engine.yml +++ b/.github/workflows/test-query-engine.yml @@ -57,6 +57,22 @@ jobs: single_threaded: false connector: 'cockroachdb' version: '22.1' + - name: 'mysql_5_6' + single_threaded: true + connector: 'mysql' + version: '5.6' + - name: 'mysql_5_7' + single_threaded: true + connector: 'mysql' + version: '5.7' + - name: 'mysql_8' + single_threaded: true + connector: 'mysql' + version: '8' + - name: 'mysql_mariadb' + single_threaded: true + connector: 'mysql' + version: 'mariadb' engine_protocol: [graphql, json] env: diff --git a/.github/workflows/test-schema-engine.yml b/.github/workflows/test-schema-engine.yml index 425085d3af48..6b392f373ba6 100644 --- a/.github/workflows/test-schema-engine.yml +++ b/.github/workflows/test-schema-engine.yml @@ -86,6 +86,8 @@ jobs: url: 'postgresql://postgres:prisma@localhost:5437' - name: postgres15 url: 'postgresql://postgres:prisma@localhost:5438' + - name: postgis15 + url: 'postgresql://postgres:prisma@localhost:5439' - name: cockroach_23_1 url: 'postgresql://prisma@localhost:26260' - name: cockroach_22_2 diff --git a/.test_database_urls/postgis_15 b/.test_database_urls/postgis_15 new file mode 100644 index 000000000000..3bc64e1b6f69 --- /dev/null +++ b/.test_database_urls/postgis_15 @@ -0,0 +1 @@ +export TEST_DATABASE_URL="postgresql://postgres:prisma@localhost:5439" diff --git a/Cargo.lock b/Cargo.lock index 74f0b840d4f4..84356d6de794 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,15 @@ version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arrayvec" version = "0.5.2" @@ -1537,6 +1546,57 @@ dependencies = [ "version_check", ] +[[package]] +name = "geo-types" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9705398c5c7b26132e74513f4ee7c1d7dafd786004991b375c172be2be0eecaa" +dependencies = [ + "approx", + "num-traits", + "serde", +] + +[[package]] +name = "geojson" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d728c1df1fbf328d74151efe6cb0586f79ee813346ea981add69bd22c9241b" +dependencies = [ + "log", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "geozero" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "937818b9c084b253f929b5f5dbe050e744331d94ceb0a908b08873bcb2da3066" +dependencies = [ + "geo-types", + "geojson", + "log", + "serde_json", + "thiserror", + "wkt", +] + +[[package]] +name = "geozero" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b1b9a1eeae9ad09e12ec50243956105184b26440f81f978cd3aae009b214d4d" +dependencies = [ + "geojson", + "log", + "scroll", + "serde_json", + "thiserror", + "wkt", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -2125,6 +2185,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "libsqlite3-sys" version = "0.26.0" @@ -2262,9 +2328,9 @@ checksum = "7e6bcd6433cff03a4bfc3d9834d504467db1f1cf6d0ea765d37d330249ed629d" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" @@ -2822,6 +2888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3555,6 +3622,7 @@ dependencies = [ "connection-string", "either", "futures", + "geozero 0.11.0", "getrandom 0.2.10", "hex", "indoc 0.3.6", @@ -3572,6 +3640,7 @@ dependencies = [ "postgres-types", "quaint-test-macros", "quaint-test-setup", + "regex", "rusqlite", "serde", "serde_json", @@ -3658,6 +3727,7 @@ dependencies = [ "cuid", "enumflags2", "futures", + "geojson", "indexmap 1.9.3", "itertools", "lru 0.7.8", @@ -4092,14 +4162,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick 1.0.3", "memchr", - "regex-automata 0.3.6", - "regex-syntax 0.7.4", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", ] [[package]] @@ -4113,13 +4183,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick 1.0.3", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.8.2", ] [[package]] @@ -4130,9 +4200,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rend" @@ -4515,6 +4585,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" + [[package]] name = "sct" version = "0.6.1" @@ -4939,6 +5015,7 @@ dependencies = [ "chrono", "cuid", "futures", + "geozero 0.10.0", "itertools", "once_cell", "opentelemetry", @@ -4948,6 +5025,7 @@ dependencies = [ "query-connector", "query-structure", "rand 0.7.3", + "regex", "serde", "serde_json", "thiserror", @@ -6353,6 +6431,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wkt" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c2252781f8927974e8ba6a67c965a759a2b88ea2b1825f6862426bbb1c8f41" +dependencies = [ + "geo-types", + "log", + "num-traits", + "thiserror", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/Makefile b/Makefile index 3aec261dc2f0..151a58ed4c2b 100644 --- a/Makefile +++ b/Makefile @@ -173,6 +173,12 @@ start-postgres15: dev-postgres15: start-postgres15 cp $(CONFIG_PATH)/postgres15 $(CONFIG_FILE) +start-postgis15: + docker compose -f docker-compose.yml up -d --remove-orphans postgis15 + +dev-postgis15: start-postgis15 + cp $(CONFIG_PATH)/postgis15 $(CONFIG_FILE) + start-cockroach_23_1: docker compose -f docker-compose.yml up --wait -d --remove-orphans cockroach_23_1 diff --git a/docker-compose.yml b/docker-compose.yml index b9694a2c7998..3ca0a85f6d43 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -213,6 +213,19 @@ services: networks: - databases + postgis15: + image: postgis/postgis:15-3.3 + restart: always + command: postgres -c 'max_connections=1000' + environment: + POSTGRES_PASSWORD: "prisma" + POSTGRES_HOST_AUTH_METHOD: "md5" + POSTGRES_INITDB_ARGS: "--auth-host=md5" + ports: + - "5439:5432" + networks: + - databases + mysql-5-6: image: mysql:5.6.50 command: mysqld diff --git a/libs/prisma-value/src/lib.rs b/libs/prisma-value/src/lib.rs index d797605ecccc..2d61a73bea91 100644 --- a/libs/prisma-value/src/lib.rs +++ b/libs/prisma-value/src/lib.rs @@ -24,6 +24,8 @@ pub enum PrismaValue { Uuid(Uuid), List(PrismaListValue), Json(String), + GeoJson(String), + Geometry(String), /// A collections of key-value pairs constituting an object. #[serde(serialize_with = "serialize_object")] @@ -318,6 +320,8 @@ impl fmt::Display for PrismaValue { PrismaValue::Null => "null".fmt(f), PrismaValue::Uuid(x) => x.fmt(f), PrismaValue::Json(x) => x.fmt(f), + PrismaValue::GeoJson(x) => x.fmt(f), + PrismaValue::Geometry(x) => x.fmt(f), PrismaValue::BigInt(x) => x.fmt(f), PrismaValue::List(x) => { let as_string = format!("{x:?}"); diff --git a/libs/test-setup/src/postgres.rs b/libs/test-setup/src/postgres.rs index 11b62700504b..e547c847ca49 100644 --- a/libs/test-setup/src/postgres.rs +++ b/libs/test-setup/src/postgres.rs @@ -7,9 +7,14 @@ pub(crate) fn get_postgres_tags(database_url: &str) -> Result, St let fut = async { let quaint = Quaint::new(database_url).await.map_err(|err| err.to_string())?; let mut tags = Tags::Postgres.into(); - let version = quaint.version().await.map_err(|err| err.to_string())?; - match version { + if let Ok(_postgis_version) = quaint.query_raw("SELECT PostGIS_version()", &[]).await { + tags |= Tags::PostGIS; + } + + let postgres_version = quaint.version().await.map_err(|err| err.to_string())?; + + match postgres_version { None => Ok(tags), Some(version) => { eprintln!("version: {version:?}"); diff --git a/libs/test-setup/src/sqlite.rs b/libs/test-setup/src/sqlite.rs index 869fdf990480..33c006e2dfe2 100644 --- a/libs/test-setup/src/sqlite.rs +++ b/libs/test-setup/src/sqlite.rs @@ -1,4 +1,8 @@ +use enumflags2::BitFlags; use once_cell::sync::Lazy; +use quaint::{prelude::Queryable, single::Quaint}; + +use crate::{runtime::run_with_thread_local_runtime as tok, Tags}; pub fn sqlite_test_url(db_name: &str) -> String { std::env::var("SQLITE_TEST_URL").unwrap_or_else(|_| format!("file:{}", sqlite_test_file(db_name))) @@ -25,3 +29,17 @@ fn sqlite_test_file(db_name: &str) -> String { file_path.to_string_lossy().into_owned() } + +pub(crate) fn get_sqlite_tags() -> Result, String> { + let fut = async { + let mut tags: BitFlags = Tags::Sqlite.into(); + // The SpatiaLite extension is loaded by quaint, assuming the SPATIALITE_PATH env variable is set + // If the extension can be loaded in a dummy database, it means it will also be available for the tests + let quaint = Quaint::new_in_memory().map_err(|err| err.to_string())?; + if let Ok(_has_spatialite) = quaint.query_raw("SELECT spatialite_version();", &[]).await { + tags |= Tags::Spatialite; + } + Ok(tags) + }; + tok(fut) +} diff --git a/libs/test-setup/src/tags.rs b/libs/test-setup/src/tags.rs index ed5720234986..26f9e5767ced 100644 --- a/libs/test-setup/src/tags.rs +++ b/libs/test-setup/src/tags.rs @@ -41,6 +41,8 @@ tags![ CockroachDb221 = 1 << 19, CockroachDb222 = 1 << 20, CockroachDb231 = 1 << 21, + PostGIS = 1 << 22, + Spatialite = 1 << 23, ]; pub fn tags_from_comma_separated_list(input: &str) -> BitFlags { diff --git a/libs/test-setup/src/test_api_args.rs b/libs/test-setup/src/test_api_args.rs index 28f645729409..f3e5fb8bfa2a 100644 --- a/libs/test-setup/src/test_api_args.rs +++ b/libs/test-setup/src/test_api_args.rs @@ -1,4 +1,4 @@ -use crate::{logging, mssql, mysql, postgres, Capabilities, Tags}; +use crate::{logging, mssql, mysql, postgres, sqlite, Capabilities, Tags}; use enumflags2::BitFlags; use once_cell::sync::Lazy; use quaint::single::Quaint; @@ -41,7 +41,7 @@ static DB_UNDER_TEST: Lazy> = Lazy::new(|| { match prefix { "file" | "sqlite" => Ok(DbUnderTest { database_url, - tags: Tags::Sqlite.into(), + tags: sqlite::get_sqlite_tags()?, capabilities: Capabilities::CreateDatabase.into(), provider: "sqlite", shadow_database_url, diff --git a/prisma-fmt/tests/native_types.rs b/prisma-fmt/tests/native_types.rs index f33e4b52a07e..25e4f4ad5181 100644 --- a/prisma-fmt/tests/native_types.rs +++ b/prisma-fmt/tests/native_types.rs @@ -11,7 +11,7 @@ fn test_native_types_list_on_crdb() { let result = prisma_fmt::native_types(schema.to_owned()); let expected = expect![[ - r#"[{"name":"Bit","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Bool","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Boolean"]},{"name":"Bytes","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Bytes"]},{"name":"Char","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Date","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["DateTime"]},{"name":"Decimal","_number_of_args":0,"_number_of_optional_args":2,"prisma_types":["Decimal"]},{"name":"Float4","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Float"]},{"name":"Float8","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Float"]},{"name":"Inet","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"Int2","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Int"]},{"name":"Int4","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Int"]},{"name":"Int8","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["BigInt"]},{"name":"JsonB","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Json"]},{"name":"Oid","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Int"]},{"name":"CatalogSingleChar","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"String","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Time","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Timestamp","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Timestamptz","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Timetz","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Uuid","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"VarBit","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]}]"# + r#"[{"name":"Bit","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Bool","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Boolean"]},{"name":"Bytes","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Bytes"]},{"name":"Char","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Date","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["DateTime"]},{"name":"Decimal","_number_of_args":0,"_number_of_optional_args":2,"prisma_types":["Decimal"]},{"name":"Float4","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Float"]},{"name":"Float8","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Float"]},{"name":"Inet","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"Int2","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Int"]},{"name":"Int4","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Int"]},{"name":"Int8","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["BigInt"]},{"name":"JsonB","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Json"]},{"name":"Oid","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Int"]},{"name":"CatalogSingleChar","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"String","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Time","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Timestamp","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Timestamptz","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Timetz","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Uuid","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"VarBit","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Geometry","_number_of_args":0,"_number_of_optional_args":2,"prisma_types":["Geometry","GeoJson"]},{"name":"Geography","_number_of_args":0,"_number_of_optional_args":2,"prisma_types":["Geometry","GeoJson"]}]"# ]]; expected.assert_eq(&result); } diff --git a/psl/builtin-connectors/src/cockroach_datamodel_connector.rs b/psl/builtin-connectors/src/cockroach_datamodel_connector.rs index 5456deb59df6..54084a5512b8 100644 --- a/psl/builtin-connectors/src/cockroach_datamodel_connector.rs +++ b/psl/builtin-connectors/src/cockroach_datamodel_connector.rs @@ -1,6 +1,7 @@ mod native_types; mod validations; +pub use crate::geometry::GeometryParams; pub use native_types::CockroachType; use enumflags2::BitFlags; @@ -22,7 +23,7 @@ use psl_core::{ }; use std::borrow::Cow; -use crate::completions; +use crate::{completions, geometry::GeometryType}; const CONSTRAINT_SCOPES: &[ConstraintScope] = &[ConstraintScope::ModelPrimaryKeyKeyIndexForeignKey]; @@ -42,6 +43,12 @@ const CAPABILITIES: ConnectorCapabilities = enumflags2::make_bitflags!(Connector Json | JsonFiltering | JsonFilteringArrayPath | + EwktGeometry | + GeoJsonGeometry | + GeometryRawRead | + GeometryFiltering | + GeometryExtraDims | + GeometryExtraTypes | NamedPrimaryKeys | NamedForeignKeys | SqlQueryRaw | @@ -71,6 +78,20 @@ const SCALAR_TYPE_DEFAULTS: &[(ScalarType, CockroachType)] = &[ (ScalarType::DateTime, CockroachType::Timestamp(Some(3))), (ScalarType::Bytes, CockroachType::Bytes), (ScalarType::Json, CockroachType::JsonB), + ( + ScalarType::Geometry, + CockroachType::Geometry(Some(GeometryParams { + ty: GeometryType::Geometry, + srid: 0, + })), + ), + ( + ScalarType::GeoJson, + CockroachType::Geometry(Some(GeometryParams { + ty: GeometryType::Geometry, + srid: 4326, + })), + ), ]; pub(crate) struct CockroachDatamodelConnector; @@ -136,6 +157,9 @@ impl Connector for CockroachDatamodelConnector { CockroachType::JsonB => ScalarType::Json, // Bytes CockroachType::Bytes => ScalarType::Bytes, + // Geometry + CockroachType::Geometry(_) => ScalarType::Geometry, + CockroachType::Geography(_) => ScalarType::Geometry, } } @@ -169,7 +193,7 @@ impl Connector for CockroachDatamodelConnector { fn validate_native_type_arguments( &self, native_type_instance: &NativeTypeInstance, - _scalar_type: &ScalarType, + scalar_type: &ScalarType, span: ast::Span, errors: &mut Diagnostics, ) { @@ -189,6 +213,19 @@ impl Connector for CockroachDatamodelConnector { CockroachType::Bit(Some(0)) | CockroachType::VarBit(Some(0)) => { errors.push_error(error.new_argument_m_out_of_range_error("M must be a positive integer.", span)) } + CockroachType::Geometry(Some(g)) | CockroachType::Geography(Some(g)) + if *scalar_type == ScalarType::GeoJson && g.srid != 4326 => + { + errors.push_error(error.new_argument_m_out_of_range_error("GeoJson SRID must be 4326.", span)) + } + CockroachType::Geometry(Some(g)) | CockroachType::Geography(Some(g)) if g.srid < 0 || g.srid > 999000 => { + errors.push_error(error.new_argument_m_out_of_range_error("SRID must be between 0 and 999000.", span)) + } + CockroachType::Geometry(Some(g)) | CockroachType::Geography(Some(g)) if g.ty.is_extra() => errors + .push_error(error.new_argument_m_out_of_range_error( + &format!("{} isn't supported for the current connector.", g.ty), + span, + )), CockroachType::Timestamp(Some(p)) | CockroachType::Timestamptz(Some(p)) | CockroachType::Time(Some(p)) diff --git a/psl/builtin-connectors/src/cockroach_datamodel_connector/native_types.rs b/psl/builtin-connectors/src/cockroach_datamodel_connector/native_types.rs index 0fb8f523b5b0..5c03e226cb6b 100644 --- a/psl/builtin-connectors/src/cockroach_datamodel_connector/native_types.rs +++ b/psl/builtin-connectors/src/cockroach_datamodel_connector/native_types.rs @@ -1,3 +1,5 @@ +use crate::geometry::GeometryParams; + crate::native_type_definition! { CockroachType; Bit(Option) -> String, @@ -22,4 +24,6 @@ crate::native_type_definition! { Timetz(Option) -> DateTime, Uuid -> String, VarBit(Option) -> String, + Geometry(Option) -> Geometry | GeoJson, + Geography(Option) -> Geometry | GeoJson, } diff --git a/psl/builtin-connectors/src/geometry.rs b/psl/builtin-connectors/src/geometry.rs new file mode 100644 index 000000000000..522eaa202e14 --- /dev/null +++ b/psl/builtin-connectors/src/geometry.rs @@ -0,0 +1,355 @@ +use std::{fmt, str::FromStr}; + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct GeometryParams { + pub ty: GeometryType, + pub srid: i32, +} + +#[repr(u32)] +#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] +pub enum GeometryType { + #[default] + Geometry = 0, + Point = 1, + LineString = 2, + Polygon = 3, + MultiPoint = 4, + MultiLineString = 5, + MultiPolygon = 6, + GeometryCollection = 7, + CircularString = 8, + CompoundCurve = 9, + CurvePolygon = 10, + MultiCurve = 11, + MultiSurface = 12, + // Curve = 13, + // Surface = 14,, + PolyhedralSurface = 15, + Tin = 16, + Triangle = 17, + GeometryZ = 1000, + PointZ = 1001, + LineStringZ = 1002, + PolygonZ = 1003, + MultiPointZ = 1004, + MultiLineStringZ = 1005, + MultiPolygonZ = 1006, + GeometryCollectionZ = 1007, + CircularStringZ = 1008, + CompoundCurveZ = 1009, + CurvePolygonZ = 1010, + MultiCurveZ = 1011, + MultiSurfaceZ = 1012, + // CurveZ = 1013, + // SurfaceZ = 1014, + PolyhedralSurfaceZ = 1015, + TinZ = 1016, + TriangleZ = 1017, + GeometryM = 2000, + PointM = 2001, + LineStringM = 2002, + PolygonM = 2003, + MultiPointM = 2004, + MultiLineStringM = 2005, + MultiPolygonM = 2006, + GeometryCollectionM = 2007, + CircularStringM = 2008, + CompoundCurveM = 2009, + CurvePolygonM = 2010, + MultiCurveM = 2011, + MultiSurfaceM = 2012, + // CurveM = 2013, + // SurfaceM = 2014, + PolyhedralSurfaceM = 2015, + TinM = 2016, + TriangleM = 2017, + GeometryZM = 3000, + PointZM = 3001, + LineStringZM = 3002, + PolygonZM = 3003, + MultiPointZM = 3004, + MultiLineStringZM = 3005, + MultiPolygonZM = 3006, + GeometryCollectionZM = 3007, + CircularStringZM = 3008, + CompoundCurveZM = 3009, + CurvePolygonZM = 3010, + MultiCurveZM = 3011, + MultiSurfaceZM = 3012, + // CurveZM = 3013, + // SurfaceZM = 3014, + PolyhedralSurfaceZM = 3015, + TinZM = 3016, + TriangleZM = 3017, +} + +impl GeometryType { + pub fn is_extra(&self) -> bool { + self.as_2d() > Self::GeometryCollection + } + + pub fn as_2d(&self) -> Self { + Self::try_from(*self as u32 % 1000).unwrap() + } + + pub fn dimensions(&self) -> &'static str { + match *self as u32 / 1000 { + 0 => "XY", + 1 => "XYZ", + 2 => "XYM", + 3 => "XYZM", + _ => unreachable!(), + } + } +} + +impl TryFrom for GeometryType { + type Error = String; + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(Self::Geometry), + 1 => Ok(Self::Point), + 2 => Ok(Self::LineString), + 3 => Ok(Self::Polygon), + 4 => Ok(Self::MultiPoint), + 5 => Ok(Self::MultiLineString), + 6 => Ok(Self::MultiPolygon), + 7 => Ok(Self::GeometryCollection), + 8 => Ok(Self::CircularString), + 9 => Ok(Self::CompoundCurve), + 10 => Ok(Self::CurvePolygon), + 11 => Ok(Self::MultiCurve), + 12 => Ok(Self::MultiSurface), + // 13 => Ok(Self::Curve), + // 14 => Ok(Self::Surface), + 15 => Ok(Self::PolyhedralSurface), + 16 => Ok(Self::Tin), + 17 => Ok(Self::Triangle), + 1000 => Ok(Self::GeometryZ), + 1001 => Ok(Self::PointZ), + 1002 => Ok(Self::LineStringZ), + 1003 => Ok(Self::PolygonZ), + 1004 => Ok(Self::MultiPointZ), + 1005 => Ok(Self::MultiLineStringZ), + 1006 => Ok(Self::MultiPolygonZ), + 1007 => Ok(Self::GeometryCollectionZ), + 1008 => Ok(Self::CircularStringZ), + 1009 => Ok(Self::CompoundCurveZ), + 1010 => Ok(Self::CurvePolygonZ), + 1011 => Ok(Self::MultiCurveZ), + 1012 => Ok(Self::MultiSurfaceZ), + // 1013 => Ok(Self::CurveZ), + // 1014 => Ok(Self::SurfaceZ), + 1015 => Ok(Self::PolyhedralSurfaceZ), + 1016 => Ok(Self::TinZ), + 1017 => Ok(Self::TriangleZ), + 2000 => Ok(Self::GeometryM), + 2001 => Ok(Self::PointM), + 2002 => Ok(Self::LineStringM), + 2003 => Ok(Self::PolygonM), + 2004 => Ok(Self::MultiPointM), + 2005 => Ok(Self::MultiLineStringM), + 2006 => Ok(Self::MultiPolygonM), + 2007 => Ok(Self::GeometryCollectionM), + 2008 => Ok(Self::CircularStringM), + 2009 => Ok(Self::CompoundCurveM), + 2010 => Ok(Self::CurvePolygonM), + 2011 => Ok(Self::MultiCurveM), + 2012 => Ok(Self::MultiSurfaceM), + // 2013 => Ok(Self::CurveM), + // 2014 => Ok(Self::SurfaceM), + 2015 => Ok(Self::PolyhedralSurfaceM), + 2016 => Ok(Self::TinM), + 2017 => Ok(Self::TriangleM), + 3000 => Ok(Self::GeometryZM), + 3001 => Ok(Self::PointZM), + 3002 => Ok(Self::LineStringZM), + 3003 => Ok(Self::PolygonZM), + 3004 => Ok(Self::MultiPointZM), + 3005 => Ok(Self::MultiLineStringZM), + 3006 => Ok(Self::MultiPolygonZM), + 3007 => Ok(Self::GeometryCollectionZM), + 3008 => Ok(Self::CircularStringZM), + 3009 => Ok(Self::CompoundCurveZM), + 3010 => Ok(Self::CurvePolygonZM), + 3011 => Ok(Self::MultiCurveZM), + 3012 => Ok(Self::MultiSurfaceZM), + // 3013 => Ok(Self::CurveZM), + // 3014 => Ok(Self::SurfaceZM), + 3015 => Ok(Self::PolyhedralSurfaceZM), + 3016 => Ok(Self::TinZM), + 3017 => Ok(Self::TriangleZM), + i => Err(format!("Invalid geometry type code: {i}")), + } + } +} + +impl FromStr for GeometryType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "geometry" => Ok(GeometryType::Geometry), + "geometrym" => Ok(GeometryType::GeometryM), + "geometryz" => Ok(GeometryType::GeometryZ), + "geometryzm" => Ok(GeometryType::GeometryZM), + "point" => Ok(GeometryType::Point), + "pointm" => Ok(GeometryType::PointM), + "pointz" => Ok(GeometryType::PointZ), + "pointzm" => Ok(GeometryType::PointZM), + "linestring" => Ok(GeometryType::LineString), + "linestringm" => Ok(GeometryType::LineStringM), + "linestringz" => Ok(GeometryType::LineStringZ), + "linestringzm" => Ok(GeometryType::LineStringZM), + "polygon" => Ok(GeometryType::Polygon), + "polygonm" => Ok(GeometryType::PolygonM), + "polygonz" => Ok(GeometryType::PolygonZ), + "polygonzm" => Ok(GeometryType::PolygonZM), + "multipoint" => Ok(GeometryType::MultiPoint), + "multipointm" => Ok(GeometryType::MultiPointM), + "multipointz" => Ok(GeometryType::MultiPointZ), + "multipointzm" => Ok(GeometryType::MultiPointZM), + "multilinestring" => Ok(GeometryType::MultiLineString), + "multilinestringm" => Ok(GeometryType::MultiLineStringM), + "multilinestringz" => Ok(GeometryType::MultiLineStringZ), + "multilinestringzm" => Ok(GeometryType::MultiLineStringZM), + "multipolygon" => Ok(GeometryType::MultiPolygon), + "multipolygonm" => Ok(GeometryType::MultiPolygonM), + "multipolygonz" => Ok(GeometryType::MultiPolygonZ), + "multipolygonzm" => Ok(GeometryType::MultiPolygonZM), + "geometrycollection" => Ok(GeometryType::GeometryCollection), + "geometrycollectionm" => Ok(GeometryType::GeometryCollectionM), + "geometrycollectionz" => Ok(GeometryType::GeometryCollectionZ), + "geometrycollectionzm" => Ok(GeometryType::GeometryCollectionZM), + "circularstring" => Ok(GeometryType::CircularString), + "circularstringm" => Ok(GeometryType::CircularStringM), + "circularstringz" => Ok(GeometryType::CircularStringZ), + "circularstringzm" => Ok(GeometryType::CircularStringZM), + "compoundcurve" => Ok(GeometryType::CompoundCurve), + "compoundcurvem" => Ok(GeometryType::CompoundCurveM), + "compoundcurvez" => Ok(GeometryType::CompoundCurveZ), + "compoundcurvezm" => Ok(GeometryType::CompoundCurveZM), + "curvepolygon" => Ok(GeometryType::CurvePolygon), + "curvepolygonm" => Ok(GeometryType::CurvePolygonM), + "curvepolygonz" => Ok(GeometryType::CurvePolygonZ), + "curvepolygonzm" => Ok(GeometryType::CurvePolygonZM), + "multicurve" => Ok(GeometryType::MultiCurve), + "multicurvem" => Ok(GeometryType::MultiCurveM), + "multicurvez" => Ok(GeometryType::MultiCurveZ), + "multicurvezm" => Ok(GeometryType::MultiCurveZM), + "multisurface" => Ok(GeometryType::MultiSurface), + "multisurfacem" => Ok(GeometryType::MultiSurfaceM), + "multisurfacez" => Ok(GeometryType::MultiSurfaceZ), + "multisurfacezm" => Ok(GeometryType::MultiSurfaceZM), + "polyhedralsurface" => Ok(GeometryType::PolyhedralSurface), + "polyhedralsurfacem" => Ok(GeometryType::PolyhedralSurfaceM), + "polyhedralsurfacez" => Ok(GeometryType::PolyhedralSurfaceZ), + "polyhedralsurfacezm" => Ok(GeometryType::PolyhedralSurfaceZM), + "triangle" => Ok(GeometryType::Triangle), + "trianglem" => Ok(GeometryType::TriangleM), + "trianglez" => Ok(GeometryType::TriangleZ), + "trianglezm" => Ok(GeometryType::TriangleZM), + "tin" => Ok(GeometryType::Tin), + "tinm" => Ok(GeometryType::TinM), + "tinz" => Ok(GeometryType::TinZ), + "tinzm" => Ok(GeometryType::TinZM), + _ => Err(format!("{} is not a valid geometry type.", s)), + } + } +} + +impl fmt::Display for GeometryType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + GeometryType::Geometry => write!(f, "Geometry"), + GeometryType::GeometryM => write!(f, "GeometryM"), + GeometryType::GeometryZ => write!(f, "GeometryZ"), + GeometryType::GeometryZM => write!(f, "GeometryZM"), + GeometryType::Point => write!(f, "Point"), + GeometryType::PointM => write!(f, "PointM"), + GeometryType::PointZ => write!(f, "PointZ"), + GeometryType::PointZM => write!(f, "PointZM"), + GeometryType::LineString => write!(f, "LineString"), + GeometryType::LineStringM => write!(f, "LineStringM"), + GeometryType::LineStringZ => write!(f, "LineStringZ"), + GeometryType::LineStringZM => write!(f, "LineStringZM"), + GeometryType::Polygon => write!(f, "Polygon"), + GeometryType::PolygonM => write!(f, "PolygonM"), + GeometryType::PolygonZ => write!(f, "PolygonZ"), + GeometryType::PolygonZM => write!(f, "PolygonZM"), + GeometryType::MultiPoint => write!(f, "MultiPoint"), + GeometryType::MultiPointM => write!(f, "MultiPointM"), + GeometryType::MultiPointZ => write!(f, "MultiPointZ"), + GeometryType::MultiPointZM => write!(f, "MultiPointZM"), + GeometryType::MultiLineString => write!(f, "MultiLineString"), + GeometryType::MultiLineStringM => write!(f, "MultiLineStringM"), + GeometryType::MultiLineStringZ => write!(f, "MultiLineStringZ"), + GeometryType::MultiLineStringZM => write!(f, "MultiLineStringZM"), + GeometryType::MultiPolygon => write!(f, "MultiPolygon"), + GeometryType::MultiPolygonM => write!(f, "MultiPolygonM"), + GeometryType::MultiPolygonZ => write!(f, "MultiPolygonZ"), + GeometryType::MultiPolygonZM => write!(f, "MultiPolygonZM"), + GeometryType::GeometryCollection => write!(f, "GeometryCollection"), + GeometryType::GeometryCollectionM => write!(f, "GeometryCollectionM"), + GeometryType::GeometryCollectionZ => write!(f, "GeometryCollectionZ"), + GeometryType::GeometryCollectionZM => write!(f, "GeometryCollectionZM"), + GeometryType::CircularString => write!(f, "CircularString"), + GeometryType::CircularStringM => write!(f, "CircularStringM"), + GeometryType::CircularStringZ => write!(f, "CircularStringZ"), + GeometryType::CircularStringZM => write!(f, "CircularStringZM"), + GeometryType::CompoundCurve => write!(f, "CompoundCurve"), + GeometryType::CompoundCurveM => write!(f, "CompoundCurveM"), + GeometryType::CompoundCurveZ => write!(f, "CompoundCurveZ"), + GeometryType::CompoundCurveZM => write!(f, "CompoundCurveZM"), + GeometryType::CurvePolygon => write!(f, "CurvePolygon"), + GeometryType::CurvePolygonM => write!(f, "CurvePolygonM"), + GeometryType::CurvePolygonZ => write!(f, "CurvePolygonZ"), + GeometryType::CurvePolygonZM => write!(f, "CurvePolygonZM"), + GeometryType::MultiCurve => write!(f, "MultiCurve"), + GeometryType::MultiCurveM => write!(f, "MultiCurveM"), + GeometryType::MultiCurveZ => write!(f, "MultiCurveZ"), + GeometryType::MultiCurveZM => write!(f, "MultiCurveZM"), + GeometryType::MultiSurface => write!(f, "MultiSurface"), + GeometryType::MultiSurfaceM => write!(f, "MultiSurfaceM"), + GeometryType::MultiSurfaceZ => write!(f, "MultiSurfaceZ"), + GeometryType::MultiSurfaceZM => write!(f, "MultiSurfaceZM"), + GeometryType::PolyhedralSurface => write!(f, "PolyhedralSurface"), + GeometryType::PolyhedralSurfaceM => write!(f, "PolyhedralSurfaceM"), + GeometryType::PolyhedralSurfaceZ => write!(f, "PolyhedralSurfaceZ"), + GeometryType::PolyhedralSurfaceZM => write!(f, "PolyhedralSurfaceZM"), + GeometryType::Triangle => write!(f, "Triangle"), + GeometryType::TriangleM => write!(f, "TriangleM"), + GeometryType::TriangleZ => write!(f, "TriangleZ"), + GeometryType::TriangleZM => write!(f, "TriangleZM"), + GeometryType::Tin => write!(f, "Tin"), + GeometryType::TinM => write!(f, "TinM"), + GeometryType::TinZ => write!(f, "TinZ"), + GeometryType::TinZM => write!(f, "TinZM"), + } + } +} + +impl psl_core::datamodel_connector::NativeTypeArguments for GeometryParams { + const DESCRIPTION: &'static str = "a geometry type and an optional srid"; + const OPTIONAL_ARGUMENTS_COUNT: usize = 0; + const REQUIRED_ARGUMENTS_COUNT: usize = 2; + + fn from_parts(parts: &[String]) -> Option { + match parts { + [geom] => GeometryType::from_str(geom).ok().map(|ty| Self { ty, srid: 0 }), + [geom, srid] => GeometryType::from_str(geom) + .ok() + .and_then(|ty| srid.parse().ok().map(|srid| Self { ty, srid })), + _ => None, + } + } + + fn to_parts(&self) -> Vec { + match self.srid { + 0 => vec![self.ty.to_string()], + srid => vec![self.ty.to_string(), srid.to_string()], + } + } +} diff --git a/psl/builtin-connectors/src/lib.rs b/psl/builtin-connectors/src/lib.rs index c477386a23ed..a890ef5d2acd 100644 --- a/psl/builtin-connectors/src/lib.rs +++ b/psl/builtin-connectors/src/lib.rs @@ -5,11 +5,14 @@ pub mod cockroach_datamodel_connector; pub mod completions; pub use cockroach_datamodel_connector::CockroachType; +pub use geometry::{GeometryParams, GeometryType}; pub use mongodb::MongoDbType; pub use mssql_datamodel_connector::{MsSqlType, MsSqlTypeParameter}; pub use mysql_datamodel_connector::MySqlType; pub use postgres_datamodel_connector::{PostgresDatasourceProperties, PostgresType}; +pub use sqlite_datamodel_connector::SQLiteType; +mod geometry; mod mongodb; mod mssql_datamodel_connector; mod mysql_datamodel_connector; diff --git a/psl/builtin-connectors/src/mongodb.rs b/psl/builtin-connectors/src/mongodb.rs index e2e820f6b166..068a92e28571 100644 --- a/psl/builtin-connectors/src/mongodb.rs +++ b/psl/builtin-connectors/src/mongodb.rs @@ -17,6 +17,7 @@ use std::result::Result as StdResult; const CAPABILITIES: ConnectorCapabilities = enumflags2::make_bitflags!(ConnectorCapability::{ Json | + GeoJsonGeometry | Enums | EnumArrayPush | RelationFieldsInArbitraryOrder | diff --git a/psl/builtin-connectors/src/mongodb/mongodb_types.rs b/psl/builtin-connectors/src/mongodb/mongodb_types.rs index c75a7f0b3bd4..dc3dc82f5b81 100644 --- a/psl/builtin-connectors/src/mongodb/mongodb_types.rs +++ b/psl/builtin-connectors/src/mongodb/mongodb_types.rs @@ -16,7 +16,7 @@ crate::native_type_definition! { Int -> Int, Timestamp -> DateTime, Long -> Int | BigInt, - Json -> Json, + Json -> Json | GeoJson, // Deprecated: // DbPointer // Undefined @@ -42,6 +42,7 @@ static DEFAULT_MAPPING: Lazy> = Lazy::new(|| { (ScalarType::DateTime, MongoDbType::Timestamp), (ScalarType::Bytes, MongoDbType::BinData), (ScalarType::Json, MongoDbType::Json), + (ScalarType::GeoJson, MongoDbType::Json), ] .into_iter() .collect() diff --git a/psl/builtin-connectors/src/mssql_datamodel_connector.rs b/psl/builtin-connectors/src/mssql_datamodel_connector.rs index 46647fabe8af..a9879c18778d 100644 --- a/psl/builtin-connectors/src/mssql_datamodel_connector.rs +++ b/psl/builtin-connectors/src/mssql_datamodel_connector.rs @@ -28,6 +28,9 @@ const CONSTRAINT_SCOPES: &[ConstraintScope] = &[ ]; const CAPABILITIES: ConnectorCapabilities = enumflags2::make_bitflags!(ConnectorCapability::{ + EwktGeometry | + GeoJsonGeometry | + GeometryFiltering | AnyId | AutoIncrement | AutoIncrementAllowedOnNonId | @@ -73,6 +76,8 @@ const SCALAR_TYPE_DEFAULTS: &[(ScalarType, MsSqlType)] = &[ ScalarType::Json, MsSqlType::NVarChar(Some(MsSqlTypeParameter::Number(1000))), ), + (ScalarType::Geometry, MsSqlType::Geometry), + (ScalarType::GeoJson, MsSqlType::Geometry), ]; impl Connector for MsSqlDatamodelConnector { @@ -138,6 +143,9 @@ impl Connector for MsSqlDatamodelConnector { VarBinary(_) => ScalarType::Bytes, Image => ScalarType::Bytes, Bit => ScalarType::Bytes, + //Geometry + Geometry => ScalarType::Geometry, + Geography => ScalarType::Geometry, } } @@ -327,5 +335,7 @@ pub(crate) fn heap_allocated_types() -> &'static [MsSqlType] { VarBinary(Some(Max)), VarChar(Some(Max)), NVarChar(Some(Max)), + Geometry, + Geography, ] } diff --git a/psl/builtin-connectors/src/mssql_datamodel_connector/native_types.rs b/psl/builtin-connectors/src/mssql_datamodel_connector/native_types.rs index 06898c2ee08a..04f6d880646f 100644 --- a/psl/builtin-connectors/src/mssql_datamodel_connector/native_types.rs +++ b/psl/builtin-connectors/src/mssql_datamodel_connector/native_types.rs @@ -104,4 +104,6 @@ crate::native_type_definition! { /// GUID, which is UUID but Microsoft invented them so they have their own /// term for it. UniqueIdentifier -> String, + Geometry -> Geometry | GeoJson, + Geography -> Geometry | GeoJson, } diff --git a/psl/builtin-connectors/src/mysql_datamodel_connector.rs b/psl/builtin-connectors/src/mysql_datamodel_connector.rs index 39995fb5d48d..1ad5fe753133 100644 --- a/psl/builtin-connectors/src/mysql_datamodel_connector.rs +++ b/psl/builtin-connectors/src/mysql_datamodel_connector.rs @@ -25,11 +25,23 @@ const TINY_TEXT_TYPE_NAME: &str = "TinyText"; const TEXT_TYPE_NAME: &str = "Text"; const MEDIUM_TEXT_TYPE_NAME: &str = "MediumText"; const LONG_TEXT_TYPE_NAME: &str = "LongText"; +const GEOMETRY_TYPE_NAME: &str = "Geometry"; +const POINT_TYPE_NAME: &str = "Point"; +const LINESTRING_TYPE_NAME: &str = "LineString"; +const POLYGON_TYPE_NAME: &str = "Polygon"; +const MULTIPOINT_TYPE_NAME: &str = "MultiPoint"; +const MULTILINESTRING_TYPE_NAME: &str = "MultiLineString"; +const MULTIPOLYGON_TYPE_NAME: &str = "MultiPolygon"; +const GEOMETRYCOLLECTION_TYPE_NAME: &str = "GeometryCollection"; const CAPABILITIES: ConnectorCapabilities = enumflags2::make_bitflags!(ConnectorCapability::{ Enums | EnumArrayPush | Json | + EwktGeometry | + GeoJsonGeometry | + GeometryRawRead | + GeometryFiltering | AutoIncrementAllowedOnNonId | RelationFieldsInArbitraryOrder | CreateMany | @@ -76,6 +88,9 @@ const SCALAR_TYPE_DEFAULTS: &[(ScalarType, MySqlType)] = &[ (ScalarType::DateTime, MySqlType::DateTime(Some(3))), (ScalarType::Bytes, MySqlType::LongBlob), (ScalarType::Json, MySqlType::Json), + (ScalarType::Geometry, MySqlType::Geometry(None)), + // TODO@geometry In MYSQL8+, ideally we'd set the default SRID to 4326 + (ScalarType::GeoJson, MySqlType::Geometry(None)), ]; impl Connector for MySqlDatamodelConnector { @@ -146,6 +161,15 @@ impl Connector for MySqlDatamodelConnector { Blob => ScalarType::Bytes, MediumBlob => ScalarType::Bytes, Bit(_) => ScalarType::Bytes, + //Geometry + Geometry(_) => ScalarType::Geometry, + Point(_) => ScalarType::Geometry, + LineString(_) => ScalarType::Geometry, + Polygon(_) => ScalarType::Geometry, + MultiPoint(_) => ScalarType::Geometry, + MultiLineString(_) => ScalarType::Geometry, + MultiPolygon(_) => ScalarType::Geometry, + GeometryCollection(_) => ScalarType::Geometry, //Missing from docs UnsignedInt => ScalarType::Int, UnsignedSmallInt => ScalarType::Int, @@ -207,6 +231,19 @@ impl Connector for MySqlDatamodelConnector { VarChar(length) if *length > 65535 => { errors.push_error(error.new_argument_m_out_of_range_error("M can range from 0 to 65,535.", span)) } + Geometry(Some(srid)) + | Point(Some(srid)) + | LineString(Some(srid)) + | Polygon(Some(srid)) + | MultiPoint(Some(srid)) + | MultiLineString(Some(srid)) + | MultiPolygon(Some(srid)) + | GeometryCollection(Some(srid)) + if *scalar_type == ScalarType::GeoJson && *srid != 4326 => + { + // TODO@geometry MySQL <8 doesn't support SRID parameter, is there a way to catch this here ? + errors.push_error(error.new_argument_m_out_of_range_error("GeoJson SRID must be 4326.", span)) + } Bit(n) if *n > 1 && matches!(scalar_type, ScalarType::Boolean) => { errors.push_error(error.new_argument_m_out_of_range_error("only Bit(1) can be used as Boolean.", span)) } diff --git a/psl/builtin-connectors/src/mysql_datamodel_connector/native_types.rs b/psl/builtin-connectors/src/mysql_datamodel_connector/native_types.rs index 4f17b68dfead..0226658e5568 100644 --- a/psl/builtin-connectors/src/mysql_datamodel_connector/native_types.rs +++ b/psl/builtin-connectors/src/mysql_datamodel_connector/native_types.rs @@ -33,6 +33,14 @@ crate::native_type_definition! { Timestamp(Option) -> DateTime, Year -> Int, Json -> Json, + Geometry(Option) -> Geometry | GeoJson, + Point(Option) -> Geometry | GeoJson, + LineString(Option) -> Geometry | GeoJson, + Polygon(Option) -> Geometry | GeoJson, + MultiPoint(Option) -> Geometry | GeoJson, + MultiLineString(Option) -> Geometry | GeoJson, + MultiPolygon(Option) -> Geometry | GeoJson, + GeometryCollection(Option) -> Geometry | GeoJson, } impl MySqlType { diff --git a/psl/builtin-connectors/src/mysql_datamodel_connector/validations.rs b/psl/builtin-connectors/src/mysql_datamodel_connector/validations.rs index e96ab4ab4078..b4107c6c8a52 100644 --- a/psl/builtin-connectors/src/mysql_datamodel_connector/validations.rs +++ b/psl/builtin-connectors/src/mysql_datamodel_connector/validations.rs @@ -19,6 +19,14 @@ const NATIVE_TYPES_THAT_CAN_NOT_BE_USED_IN_KEY_SPECIFICATION: &[&str] = &[ super::TINY_BLOB_TYPE_NAME, super::MEDIUM_BLOB_TYPE_NAME, super::LONG_BLOB_TYPE_NAME, + super::GEOMETRY_TYPE_NAME, + super::POINT_TYPE_NAME, + super::LINESTRING_TYPE_NAME, + super::POLYGON_TYPE_NAME, + super::MULTIPOINT_TYPE_NAME, + super::MULTILINESTRING_TYPE_NAME, + super::MULTIPOLYGON_TYPE_NAME, + super::GEOMETRYCOLLECTION_TYPE_NAME, ]; pub(crate) fn field_types_can_be_used_in_an_index( diff --git a/psl/builtin-connectors/src/postgres_datamodel_connector.rs b/psl/builtin-connectors/src/postgres_datamodel_connector.rs index 8fac79165c58..6342d633c2d0 100644 --- a/psl/builtin-connectors/src/postgres_datamodel_connector.rs +++ b/psl/builtin-connectors/src/postgres_datamodel_connector.rs @@ -2,6 +2,7 @@ mod datasource; mod native_types; mod validations; +pub use crate::geometry::GeometryParams; pub use native_types::PostgresType; use enumflags2::BitFlags; @@ -18,7 +19,7 @@ use psl_core::{ use std::{borrow::Cow, collections::HashMap}; use PostgresType::*; -use crate::completions; +use crate::{completions, geometry::GeometryType}; const CONSTRAINT_SCOPES: &[ConstraintScope] = &[ ConstraintScope::GlobalPrimaryKeyKeyIndex, @@ -45,6 +46,12 @@ const CAPABILITIES: ConnectorCapabilities = enumflags2::make_bitflags!(Connector JsonFilteringArrayPath | JsonFilteringAlphanumeric | JsonFilteringAlphanumericFieldRef | + EwktGeometry | + GeoJsonGeometry | + GeometryRawRead | + GeometryFiltering | + GeometryExtraDims | + GeometryExtraTypes | MultiSchema | NamedForeignKeys | NamedPrimaryKeys | @@ -80,6 +87,20 @@ const SCALAR_TYPE_DEFAULTS: &[(ScalarType, PostgresType)] = &[ (ScalarType::DateTime, PostgresType::Timestamp(Some(3))), (ScalarType::Bytes, PostgresType::ByteA), (ScalarType::Json, PostgresType::JsonB), + ( + ScalarType::Geometry, + PostgresType::Geometry(Some(GeometryParams { + ty: GeometryType::Geometry, + srid: 0, + })), + ), + ( + ScalarType::GeoJson, + PostgresType::Geometry(Some(GeometryParams { + ty: GeometryType::Geometry, + srid: 4326, + })), + ), ]; /// Postgres-specific properties in the datasource block. @@ -323,6 +344,9 @@ impl Connector for PostgresDatamodelConnector { JsonB => ScalarType::Json, // Bytes ByteA => ScalarType::Bytes, + // Geometry + Geometry(_) => ScalarType::Geometry, + Geography(_) => ScalarType::Geometry, } } @@ -352,7 +376,7 @@ impl Connector for PostgresDatamodelConnector { fn validate_native_type_arguments( &self, native_type_instance: &NativeTypeInstance, - _scalar_type: &ScalarType, + scalar_type: &ScalarType, span: ast::Span, errors: &mut Diagnostics, ) { @@ -375,6 +399,16 @@ impl Connector for PostgresDatamodelConnector { Timestamp(Some(p)) | Timestamptz(Some(p)) | Time(Some(p)) | Timetz(Some(p)) if *p > 6 => { errors.push_error(error.new_argument_m_out_of_range_error("M can range from 0 to 6.", span)) } + Geometry(Some(g)) | Geography(Some(g)) if *scalar_type == ScalarType::GeoJson && g.ty.is_extra() => errors + .push_error( + error.new_argument_m_out_of_range_error(&format!("{} isn't compatible with GeoJson.", g.ty), span), + ), + Geometry(Some(g)) | Geography(Some(g)) if *scalar_type == ScalarType::GeoJson && g.srid != 4326 => { + errors.push_error(error.new_argument_m_out_of_range_error("GeoJson SRID must be 4326.", span)) + } + Geometry(Some(g)) | Geography(Some(g)) if g.srid < 0 || g.srid > 999000 => { + errors.push_error(error.new_argument_m_out_of_range_error("SRID must be between 0 and 999000.", span)) + } _ => (), } } @@ -567,6 +601,7 @@ impl Connector for PostgresDatamodelConnector { } } +// TODO@geometry: Add index operator classes fn allowed_index_operator_classes(algo: IndexAlgorithm, field: walkers::ScalarFieldWalker<'_>) -> Vec { let scalar_type = field.scalar_type(); let native_type = field.raw_native_type().map(|t| t.1); diff --git a/psl/builtin-connectors/src/postgres_datamodel_connector/native_types.rs b/psl/builtin-connectors/src/postgres_datamodel_connector/native_types.rs index d33f03a22d4f..21bcb5342e48 100644 --- a/psl/builtin-connectors/src/postgres_datamodel_connector/native_types.rs +++ b/psl/builtin-connectors/src/postgres_datamodel_connector/native_types.rs @@ -1,3 +1,5 @@ +use crate::geometry::GeometryParams; + crate::native_type_definition! { PostgresType; SmallInt -> Int, @@ -26,4 +28,6 @@ crate::native_type_definition! { Xml -> String, Json -> Json, JsonB -> Json, + Geometry(Option) -> Geometry | GeoJson, + Geography(Option) -> Geometry | GeoJson, } diff --git a/psl/builtin-connectors/src/sqlite_datamodel_connector.rs b/psl/builtin-connectors/src/sqlite_datamodel_connector.rs index d5e6041f9b43..7c599d5aca12 100644 --- a/psl/builtin-connectors/src/sqlite_datamodel_connector.rs +++ b/psl/builtin-connectors/src/sqlite_datamodel_connector.rs @@ -1,20 +1,29 @@ +mod native_types; +pub use native_types::SQLiteType; + use enumflags2::BitFlags; use psl_core::{ datamodel_connector::{ Connector, ConnectorCapabilities, ConnectorCapability, ConstraintScope, Flavour, NativeTypeConstructor, NativeTypeInstance, }, - diagnostics::{DatamodelError, Diagnostics, Span}, + diagnostics::{Diagnostics, Span}, parser_database::{ReferentialAction, ScalarType}, }; use std::borrow::Cow; -const NATIVE_TYPE_CONSTRUCTORS: &[NativeTypeConstructor] = &[]; +use crate::geometry::{GeometryParams, GeometryType}; + const CONSTRAINT_SCOPES: &[ConstraintScope] = &[ConstraintScope::GlobalKeyIndex]; const CAPABILITIES: ConnectorCapabilities = enumflags2::make_bitflags!(ConnectorCapability::{ AnyId | AutoIncrement | CompoundIds | + EwktGeometry | + GeoJsonGeometry | + GeometryRawRead | + GeometryFiltering | + GeometryExtraDims | SqlQueryRaw | RelationFieldsInArbitraryOrder | UpdateableId | @@ -31,6 +40,23 @@ const CAPABILITIES: ConnectorCapabilities = enumflags2::make_bitflags!(Connector // Since we care to stay consistent with reads, it is not enabled. }); +const SCALAR_TYPE_DEFAULTS: &[(ScalarType, SQLiteType)] = &[ + ( + ScalarType::Geometry, + SQLiteType::Geometry(Some(GeometryParams { + ty: GeometryType::Geometry, + srid: 0, + })), + ), + ( + ScalarType::GeoJson, + SQLiteType::Geometry(Some(GeometryParams { + ty: GeometryType::Geometry, + srid: 4326, + })), + ), +]; + pub struct SqliteDatamodelConnector; impl Connector for SqliteDatamodelConnector { @@ -62,24 +88,62 @@ impl Connector for SqliteDatamodelConnector { Restrict | SetNull | Cascade } - fn scalar_type_for_native_type(&self, _native_type: &NativeTypeInstance) -> ScalarType { - unreachable!("No native types on Sqlite"); + fn scalar_type_for_native_type(&self, native_type: &NativeTypeInstance) -> ScalarType { + let native_type: &SQLiteType = native_type.downcast_ref(); + match native_type { + SQLiteType::Geometry(_) => ScalarType::Geometry, + } } - fn default_native_type_for_scalar_type(&self, _scalar_type: &ScalarType) -> NativeTypeInstance { - NativeTypeInstance::new(()) + fn default_native_type_for_scalar_type(&self, scalar_type: &ScalarType) -> NativeTypeInstance { + SCALAR_TYPE_DEFAULTS + .iter() + .find(|(st, _)| st == scalar_type) + .map(|(_, native_type)| native_type) + .map(|nt| NativeTypeInstance::new::(*nt)) + .unwrap_or(NativeTypeInstance::new(())) } fn native_type_is_default_for_scalar_type( &self, - _native_type: &NativeTypeInstance, - _scalar_type: &ScalarType, + native_type: &NativeTypeInstance, + scalar_type: &ScalarType, ) -> bool { - false + let native_type: &SQLiteType = native_type.downcast_ref(); + + SCALAR_TYPE_DEFAULTS + .iter() + .any(|(st, nt)| scalar_type == st && native_type == nt) + } + + fn validate_native_type_arguments( + &self, + native_type_instance: &NativeTypeInstance, + scalar_type: &ScalarType, + span: Span, + errors: &mut Diagnostics, + ) { + let native_type: &SQLiteType = native_type_instance.downcast_ref(); + let error = self.native_instance_error(native_type_instance); + + match native_type { + SQLiteType::Geometry(Some(g)) if *scalar_type == ScalarType::GeoJson && g.srid != 4326 => { + errors.push_error(error.new_argument_m_out_of_range_error("GeoJson SRID must be 4326.", span)) + } + SQLiteType::Geometry(Some(g)) if g.srid < -1 => errors + .push_error(error.new_argument_m_out_of_range_error("SRID must be superior or equal to -1.", span)), + SQLiteType::Geometry(Some(g)) if g.ty.is_extra() => { + errors.push_error(error.new_argument_m_out_of_range_error( + &format!("{} isn't supported for the current connector.", g.ty), + span, + )) + } + _ => (), + } } - fn native_type_to_parts(&self, _native_type: &NativeTypeInstance) -> (&'static str, Vec) { - unreachable!() + fn native_type_to_parts(&self, native_type: &NativeTypeInstance) -> (&'static str, Vec) { + native_type.downcast_ref::().to_parts() } fn constraint_violation_scopes(&self) -> &'static [ConstraintScope] { @@ -87,21 +151,17 @@ impl Connector for SqliteDatamodelConnector { } fn available_native_type_constructors(&self) -> &'static [NativeTypeConstructor] { - NATIVE_TYPE_CONSTRUCTORS + native_types::CONSTRUCTORS } fn parse_native_type( &self, - _name: &str, - _args: &[String], + name: &str, + args: &[String], span: Span, diagnostics: &mut Diagnostics, ) -> Option { - diagnostics.push_error(DatamodelError::new_native_types_not_supported( - self.name().to_owned(), - span, - )); - None + SQLiteType::from_parts(name, args, span, diagnostics).map(NativeTypeInstance::new::) } fn set_config_dir<'a>(&self, config_dir: &std::path::Path, url: &'a str) -> Cow<'a, str> { diff --git a/psl/builtin-connectors/src/sqlite_datamodel_connector/native_types.rs b/psl/builtin-connectors/src/sqlite_datamodel_connector/native_types.rs new file mode 100644 index 000000000000..034c50a40136 --- /dev/null +++ b/psl/builtin-connectors/src/sqlite_datamodel_connector/native_types.rs @@ -0,0 +1,7 @@ +use crate::geometry::GeometryParams; + +crate::native_type_definition! { + /// The SQLite native type enum. + SQLiteType; + Geometry(Option) -> Geometry | GeoJson, +} diff --git a/psl/parser-database/src/attributes/default.rs b/psl/parser-database/src/attributes/default.rs index 74593353bd84..6638d085bdd8 100644 --- a/psl/parser-database/src/attributes/default.rs +++ b/psl/parser-database/src/attributes/default.rs @@ -157,6 +157,8 @@ fn validate_scalar_default_literal( match (scalar_type, value) { (ScalarType::String, ast::Expression::StringValue(_, _)) | (ScalarType::Json, ast::Expression::StringValue(_, _)) + | (ScalarType::Geometry, ast::Expression::StringValue(_, _)) + | (ScalarType::GeoJson, ast::Expression::StringValue(_, _)) | (ScalarType::Bytes, ast::Expression::StringValue(_, _)) | (ScalarType::Int, ast::Expression::NumericValue(_, _)) | (ScalarType::BigInt, ast::Expression::NumericValue(_, _)) diff --git a/psl/parser-database/src/types.rs b/psl/parser-database/src/types.rs index 1668243247bb..c2d35c2e823e 100644 --- a/psl/parser-database/src/types.rs +++ b/psl/parser-database/src/types.rs @@ -255,6 +255,11 @@ impl ScalarFieldType { pub fn is_decimal(self) -> bool { matches!(self, Self::BuiltInScalar(ScalarType::Decimal)) } + + /// True if the field's type is Geometry. + pub fn is_geometry(self) -> bool { + matches!(self, Self::BuiltInScalar(ScalarType::Geometry | ScalarType::GeoJson)) + } } #[derive(Debug, Clone)] @@ -416,9 +421,9 @@ impl IndexAlgorithm { match self { IndexAlgorithm::BTree => true, IndexAlgorithm::Hash => true, - IndexAlgorithm::Gist => r#type.is_string(), - IndexAlgorithm::Gin => r#type.is_json() || field.ast_field().arity.is_list(), - IndexAlgorithm::SpGist => r#type.is_string(), + IndexAlgorithm::Gist => r#type.is_string() || r#type.is_geometry(), + IndexAlgorithm::Gin => r#type.is_json() || r#type.is_geometry() || field.ast_field().arity.is_list(), + IndexAlgorithm::SpGist => r#type.is_string() || r#type.is_geometry(), IndexAlgorithm::Brin => { r#type.is_string() || r#type.is_bytes() @@ -427,6 +432,7 @@ impl IndexAlgorithm { || r#type.is_int() || r#type.is_bigint() || r#type.is_decimal() + || r#type.is_geometry() } } } @@ -1222,6 +1228,7 @@ pub enum OperatorClass { /// - `<= (uuid,uuid)` /// - `>= (uuid,uuid)` UuidMinMaxMultiOps, + // TODO@geometry: Define operator classes } impl OperatorClass { @@ -1400,6 +1407,8 @@ pub enum ScalarType { Json, Bytes, Decimal, + Geometry, + GeoJson, } impl ScalarType { @@ -1415,6 +1424,8 @@ impl ScalarType { ScalarType::Json => "Json", ScalarType::Bytes => "Bytes", ScalarType::Decimal => "Decimal", + ScalarType::Geometry => "Geometry", + ScalarType::GeoJson => "GeoJson", } } @@ -1434,6 +1445,8 @@ impl ScalarType { "Json" => Some(ScalarType::Json), "Bytes" => Some(ScalarType::Bytes), "Decimal" => Some(ScalarType::Decimal), + "GeoJson" => Some(ScalarType::GeoJson), + "Geometry" => Some(ScalarType::Geometry), _ => None, } } diff --git a/psl/psl-core/src/datamodel_connector.rs b/psl/psl-core/src/datamodel_connector.rs index 72671e06688f..9ce11726dfdc 100644 --- a/psl/psl-core/src/datamodel_connector.rs +++ b/psl/psl-core/src/datamodel_connector.rs @@ -308,6 +308,18 @@ pub trait Connector: Send + Sync { self.has_capability(ConnectorCapability::DecimalType) } + fn supports_ewkt_geometry_format(&self) -> bool { + self.has_capability(ConnectorCapability::EwktGeometry) + } + + fn supports_geojson_geometry_format(&self) -> bool { + self.has_capability(ConnectorCapability::GeoJsonGeometry) + } + + fn supports_raw_geometry_read(&self) -> bool { + self.has_capability(ConnectorCapability::GeometryRawRead) + } + fn supported_index_types(&self) -> BitFlags { IndexAlgorithm::BTree.into() } diff --git a/psl/psl-core/src/datamodel_connector/capabilities.rs b/psl/psl-core/src/datamodel_connector/capabilities.rs index 1b3f557e6285..e67a7d700c77 100644 --- a/psl/psl-core/src/datamodel_connector/capabilities.rs +++ b/psl/psl-core/src/datamodel_connector/capabilities.rs @@ -55,6 +55,12 @@ capabilities!( TwoWayEmbeddedManyToManyRelation, ImplicitManyToManyRelation, MultiSchema, + EwktGeometry, + GeoJsonGeometry, + GeometryRawRead, + GeometryFiltering, + GeometryExtraDims, + GeometryExtraTypes, //Start of ME/IE only capabilities AutoIncrementAllowedOnNonId, AutoIncrementMultipleAllowed, diff --git a/psl/psl-core/src/datamodel_connector/empty_connector.rs b/psl/psl-core/src/datamodel_connector/empty_connector.rs index 7ac7879c08f4..93692c64012e 100644 --- a/psl/psl-core/src/datamodel_connector/empty_connector.rs +++ b/psl/psl-core/src/datamodel_connector/empty_connector.rs @@ -25,6 +25,7 @@ impl Connector for EmptyDatamodelConnector { CompoundIds | Enums | Json | + GeoJsonGeometry | ImplicitManyToManyRelation }) } diff --git a/psl/psl-core/src/validate/validation_pipeline/validations/default_value.rs b/psl/psl-core/src/validate/validation_pipeline/validations/default_value.rs index 0ac90e8c65d0..990e28bc8afd 100644 --- a/psl/psl-core/src/validate/validation_pipeline/validations/default_value.rs +++ b/psl/psl-core/src/validate/validation_pipeline/validations/default_value.rs @@ -116,6 +116,8 @@ pub(super) fn validate_default_value( &message, "default", *span, )); } + // TODO@geometry: Add geometry default value validation + (ScalarType::Geometry, ast::Expression::StringValue(_value, _span)) => (), _ => (), } } diff --git a/psl/psl-core/src/validate/validation_pipeline/validations/fields.rs b/psl/psl-core/src/validate/validation_pipeline/validations/fields.rs index bdfb340f5191..8cb760e7a602 100644 --- a/psl/psl-core/src/validate/validation_pipeline/validations/fields.rs +++ b/psl/psl-core/src/validate/validation_pipeline/validations/fields.rs @@ -301,6 +301,38 @@ pub(super) fn validate_scalar_field_connector_specific(field: ScalarFieldWalker< } } + ScalarFieldType::BuiltInScalar(ScalarType::Geometry) => { + if !ctx.connector.supports_ewkt_geometry_format() { + ctx.push_error(DatamodelError::new_field_validation_error( + &format!( + "Field `{}` in {container} `{}` can't be of type Geometry. The current connector does not support the Geometry type.", + field.name(), + field.model().name(), + ), + container, + field.model().name(), + field.name(), + field.ast_field().span(), + )); + } + } + + ScalarFieldType::BuiltInScalar(ScalarType::GeoJson) => { + if !ctx.connector.supports_geojson_geometry_format() { + ctx.push_error(DatamodelError::new_field_validation_error( + &format!( + "Field `{}` in {container} `{}` can't be of type GeoJson. The current connector does not support the GeoJson type.", + field.name(), + field.model().name(), + ), + container, + field.model().name(), + field.name(), + field.ast_field().span(), + )); + } + } + _ => (), } diff --git a/psl/psl/tests/base/base_types.rs b/psl/psl/tests/base/base_types.rs index 59348e9fecd6..aacd4dae4198 100644 --- a/psl/psl/tests/base/base_types.rs +++ b/psl/psl/tests/base/base_types.rs @@ -10,6 +10,7 @@ fn parse_scalar_types() { age Int isPro Boolean averageGrade Float + location GeoJson } "#; @@ -31,6 +32,10 @@ fn parse_scalar_types() { user_model .assert_has_scalar_field("averageGrade") .assert_scalar_type(ScalarType::Float); + + user_model + .assert_has_scalar_field("location") + .assert_scalar_type(ScalarType::GeoJson); } #[test] diff --git a/psl/psl/tests/types/cockroachdb_native_types.rs b/psl/psl/tests/types/cockroachdb_native_types.rs index 4f2b4118dbbd..98e6b4c352d9 100644 --- a/psl/psl/tests/types/cockroachdb_native_types.rs +++ b/psl/psl/tests/types/cockroachdb_native_types.rs @@ -184,8 +184,520 @@ fn cockroach_specific_native_types_are_valid() { timesttzcol DateTime @db.Timestamptz uuidcol String @db.Uuid varbitcol String @db.VarBit(200) + geomcol1 GeoJson @db.Geometry(Geometry, 4326) + geomcol2 GeoJson @db.Geometry(GeometryZ, 4326) + geomcol3 GeoJson @db.Geometry(GeometryM, 4326) + geomcol4 GeoJson @db.Geometry(GeometryZM, 4326) + geomcol5 GeoJson @db.Geometry(Point, 4326) + geomcol6 GeoJson @db.Geometry(PointZ, 4326) + geomcol7 GeoJson @db.Geometry(PointM, 4326) + geomcol8 GeoJson @db.Geometry(PointZM, 4326) + geomcol9 GeoJson @db.Geometry(Point, 4326) + geomcol10 GeoJson @db.Geometry(PointZ, 4326) + geomcol11 GeoJson @db.Geometry(PointM, 4326) + geomcol12 GeoJson @db.Geometry(PointZM, 4326) + geomcol13 GeoJson @db.Geometry(LineString, 4326) + geomcol14 GeoJson @db.Geometry(LineStringZ, 4326) + geomcol15 GeoJson @db.Geometry(LineStringM, 4326) + geomcol16 GeoJson @db.Geometry(LineStringZM, 4326) + geomcol17 GeoJson @db.Geometry(Polygon, 4326) + geomcol18 GeoJson @db.Geometry(PolygonZ, 4326) + geomcol19 GeoJson @db.Geometry(PolygonM, 4326) + geomcol20 GeoJson @db.Geometry(PolygonZM, 4326) + geomcol21 GeoJson @db.Geometry(MultiPoint, 4326) + geomcol22 GeoJson @db.Geometry(MultiPointZ, 4326) + geomcol23 GeoJson @db.Geometry(MultiPointM, 4326) + geomcol24 GeoJson @db.Geometry(MultiPointZM, 4326) + geomcol25 GeoJson @db.Geometry(MultiLineString, 4326) + geomcol26 GeoJson @db.Geometry(MultiLineStringZ, 4326) + geomcol27 GeoJson @db.Geometry(MultiLineStringM, 4326) + geomcol28 GeoJson @db.Geometry(MultiLineStringZM, 4326) + geomcol29 GeoJson @db.Geometry(MultiPolygon, 4326) + geomcol30 GeoJson @db.Geometry(MultiPolygonZ, 4326) + geomcol31 GeoJson @db.Geometry(MultiPolygonM, 4326) + geomcol32 GeoJson @db.Geometry(MultiPolygonZM, 4326) + geomcol33 GeoJson @db.Geometry(GeometryCollection, 4326) + geomcol34 GeoJson @db.Geometry(GeometryCollectionZ, 4326) + geomcol35 GeoJson @db.Geometry(GeometryCollectionM, 4326) + geomcol36 GeoJson @db.Geometry(GeometryCollectionZM, 4326) + geogcol1 GeoJson @db.Geography(Geometry, 4326) + geogcol2 GeoJson @db.Geography(GeometryZ, 4326) + geogcol3 GeoJson @db.Geography(GeometryM, 4326) + geogcol4 GeoJson @db.Geography(GeometryZM, 4326) + geogcol5 GeoJson @db.Geography(Point, 4326) + geogcol6 GeoJson @db.Geography(PointZ, 4326) + geogcol7 GeoJson @db.Geography(PointM, 4326) + geogcol8 GeoJson @db.Geography(PointZM, 4326) + geogcol9 GeoJson @db.Geography(Point, 4326) + geogcol10 GeoJson @db.Geography(PointZ, 4326) + geogcol11 GeoJson @db.Geography(PointM, 4326) + geogcol12 GeoJson @db.Geography(PointZM, 4326) + geogcol13 GeoJson @db.Geography(LineString, 4326) + geogcol14 GeoJson @db.Geography(LineStringZ, 4326) + geogcol15 GeoJson @db.Geography(LineStringM, 4326) + geogcol16 GeoJson @db.Geography(LineStringZM, 4326) + geogcol17 GeoJson @db.Geography(Polygon, 4326) + geogcol18 GeoJson @db.Geography(PolygonZ, 4326) + geogcol19 GeoJson @db.Geography(PolygonM, 4326) + geogcol20 GeoJson @db.Geography(PolygonZM, 4326) + geogcol21 GeoJson @db.Geography(MultiPoint, 4326) + geogcol22 GeoJson @db.Geography(MultiPointZ, 4326) + geogcol23 GeoJson @db.Geography(MultiPointM, 4326) + geogcol24 GeoJson @db.Geography(MultiPointZM, 4326) + geogcol25 GeoJson @db.Geography(MultiLineString, 4326) + geogcol26 GeoJson @db.Geography(MultiLineStringZ, 4326) + geogcol27 GeoJson @db.Geography(MultiLineStringM, 4326) + geogcol28 GeoJson @db.Geography(MultiLineStringZM, 4326) + geogcol29 GeoJson @db.Geography(MultiPolygon, 4326) + geogcol30 GeoJson @db.Geography(MultiPolygonZ, 4326) + geogcol31 GeoJson @db.Geography(MultiPolygonM, 4326) + geogcol32 GeoJson @db.Geography(MultiPolygonZM, 4326) + geogcol33 GeoJson @db.Geography(GeometryCollection, 4326) + geogcol34 GeoJson @db.Geography(GeometryCollectionZ, 4326) + geogcol35 GeoJson @db.Geography(GeometryCollectionM, 4326) + geogcol36 GeoJson @db.Geography(GeometryCollectionZM, 4326) } "#}; psl::parse_schema(schema).unwrap(); } + +#[test] +fn should_fail_on_geojson_when_invalid_geometry_type() { + let dml = indoc! {r#" + datasource db { + provider = "cockroachdb" + url = env("DATABASE_URL") + } + + model Blog { + id Int @id + geom GeoJson @db.Geometry(Invalid) + } + "#}; + + let expectation = expect![[r#" + error: Expected a geometry type and an optional srid, but found (Invalid). + --> schema.prisma:8 +  |  +  7 |  id Int @id +  8 |  geom GeoJson @db.Geometry(Invalid) +  |  + "#]]; + + expect_error(dml, &expectation); +} + +#[test] +fn should_fail_on_geojson_when_non_wgs84_srid() { + let schema = indoc! {r#" + datasource db { + provider = "cockroachdb" + url = env("DATABASE_URL") + } + + model User { + id Int @id + geom GeoJson @db.Geometry(Point, 3857) + } + "#}; + + let expectation = expect![[r#" + error: Argument M is out of range for native type `Geometry(Point,3857)` of CockroachDB: GeoJson SRID must be 4326. + --> schema.prisma:8 +  |  +  7 |  id Int @id +  8 |  geom GeoJson @db.Geometry(Point, 3857) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn should_fail_on_geometry_when_out_of_bound_srid() { + let schema = indoc! {r#" + datasource db { + provider = "cockroachdb" + url = env("DATABASE_URL") + } + + model User { + id Int @id + geom1 Geometry @db.Geometry(Point, -1) + geom2 Geometry @db.Geometry(Point, 1000000) + } + "#}; + + let expectation = expect![[r#" + error: Argument M is out of range for native type `Geometry(Point,-1)` of CockroachDB: SRID must be between 0 and 999000. + --> schema.prisma:8 +  |  +  7 |  id Int @id +  8 |  geom1 Geometry @db.Geometry(Point, -1) +  |  + error: Argument M is out of range for native type `Geometry(Point,1000000)` of CockroachDB: SRID must be between 0 and 999000. + --> schema.prisma:9 +  |  +  8 |  geom1 Geometry @db.Geometry(Point, -1) +  9 |  geom2 Geometry @db.Geometry(Point, 1000000) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn should_fail_on_geometry_when_extra_geometry_type() { + let schema = indoc! {r#" + datasource db { + provider = "cockroachdb" + url = env("DATABASE_URL") + } + + model User { + id Int @id + geom_00 Geometry @db.Geometry(CircularString, 4326) + geom_01 Geometry @db.Geometry(CircularStringZ, 4326) + geom_02 Geometry @db.Geometry(CircularStringM, 4326) + geom_03 Geometry @db.Geometry(CircularStringZM, 4326) + geom_04 Geometry @db.Geometry(CompoundCurve, 4326) + geom_05 Geometry @db.Geometry(CompoundCurveZ, 4326) + geom_06 Geometry @db.Geometry(CompoundCurveM, 4326) + geom_07 Geometry @db.Geometry(CompoundCurveZM, 4326) + geom_08 Geometry @db.Geometry(CurvePolygon, 4326) + geom_09 Geometry @db.Geometry(CurvePolygonZ, 4326) + geom_10 Geometry @db.Geometry(CurvePolygonM, 4326) + geom_11 Geometry @db.Geometry(CurvePolygonZM, 4326) + geom_12 Geometry @db.Geometry(MultiCurve, 4326) + geom_13 Geometry @db.Geometry(MultiCurveZ, 4326) + geom_14 Geometry @db.Geometry(MultiCurveM, 4326) + geom_15 Geometry @db.Geometry(MultiCurveZM, 4326) + geom_16 Geometry @db.Geometry(MultiSurface, 4326) + geom_17 Geometry @db.Geometry(MultiSurfaceZ, 4326) + geom_18 Geometry @db.Geometry(MultiSurfaceM, 4326) + geom_19 Geometry @db.Geometry(MultiSurfaceZM, 4326) + geom_20 Geometry @db.Geometry(PolyhedralSurface, 4326) + geom_21 Geometry @db.Geometry(PolyhedralSurfaceZ, 4326) + geom_22 Geometry @db.Geometry(PolyhedralSurfaceM, 4326) + geom_23 Geometry @db.Geometry(PolyhedralSurfaceZM, 4326) + geog_00 Geometry @db.Geography(CircularString, 4326) + geog_01 Geometry @db.Geography(CircularStringZ, 4326) + geog_02 Geometry @db.Geography(CircularStringM, 4326) + geog_03 Geometry @db.Geography(CircularStringZM, 4326) + geog_04 Geometry @db.Geography(CompoundCurve, 4326) + geog_05 Geometry @db.Geography(CompoundCurveZ, 4326) + geog_06 Geometry @db.Geography(CompoundCurveM, 4326) + geog_07 Geometry @db.Geography(CompoundCurveZM, 4326) + geog_08 Geometry @db.Geography(CurvePolygon, 4326) + geog_09 Geometry @db.Geography(CurvePolygonZ, 4326) + geog_10 Geometry @db.Geography(CurvePolygonM, 4326) + geog_11 Geometry @db.Geography(CurvePolygonZM, 4326) + geog_12 Geometry @db.Geography(MultiCurve, 4326) + geog_13 Geometry @db.Geography(MultiCurveZ, 4326) + geog_14 Geometry @db.Geography(MultiCurveM, 4326) + geog_15 Geometry @db.Geography(MultiCurveZM, 4326) + geog_16 Geometry @db.Geography(MultiSurface, 4326) + geog_17 Geometry @db.Geography(MultiSurfaceZ, 4326) + geog_18 Geometry @db.Geography(MultiSurfaceM, 4326) + geog_19 Geometry @db.Geography(MultiSurfaceZM, 4326) + geog_20 Geometry @db.Geography(PolyhedralSurface, 4326) + geog_21 Geometry @db.Geography(PolyhedralSurfaceZ, 4326) + geog_22 Geometry @db.Geography(PolyhedralSurfaceM, 4326) + geog_23 Geometry @db.Geography(PolyhedralSurfaceZM, 4326) + } + "#}; + + let expectation = expect![[r#" + error: Argument M is out of range for native type `Geometry(CircularString,4326)` of CockroachDB: CircularString isn't supported for the current connector. + --> schema.prisma:8 +  |  +  7 |  id Int @id +  8 |  geom_00 Geometry @db.Geometry(CircularString, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CircularStringZ,4326)` of CockroachDB: CircularStringZ isn't supported for the current connector. + --> schema.prisma:9 +  |  +  8 |  geom_00 Geometry @db.Geometry(CircularString, 4326) +  9 |  geom_01 Geometry @db.Geometry(CircularStringZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CircularStringM,4326)` of CockroachDB: CircularStringM isn't supported for the current connector. + --> schema.prisma:10 +  |  +  9 |  geom_01 Geometry @db.Geometry(CircularStringZ, 4326) + 10 |  geom_02 Geometry @db.Geometry(CircularStringM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CircularStringZM,4326)` of CockroachDB: CircularStringZM isn't supported for the current connector. + --> schema.prisma:11 +  |  + 10 |  geom_02 Geometry @db.Geometry(CircularStringM, 4326) + 11 |  geom_03 Geometry @db.Geometry(CircularStringZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CompoundCurve,4326)` of CockroachDB: CompoundCurve isn't supported for the current connector. + --> schema.prisma:12 +  |  + 11 |  geom_03 Geometry @db.Geometry(CircularStringZM, 4326) + 12 |  geom_04 Geometry @db.Geometry(CompoundCurve, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CompoundCurveZ,4326)` of CockroachDB: CompoundCurveZ isn't supported for the current connector. + --> schema.prisma:13 +  |  + 12 |  geom_04 Geometry @db.Geometry(CompoundCurve, 4326) + 13 |  geom_05 Geometry @db.Geometry(CompoundCurveZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CompoundCurveM,4326)` of CockroachDB: CompoundCurveM isn't supported for the current connector. + --> schema.prisma:14 +  |  + 13 |  geom_05 Geometry @db.Geometry(CompoundCurveZ, 4326) + 14 |  geom_06 Geometry @db.Geometry(CompoundCurveM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CompoundCurveZM,4326)` of CockroachDB: CompoundCurveZM isn't supported for the current connector. + --> schema.prisma:15 +  |  + 14 |  geom_06 Geometry @db.Geometry(CompoundCurveM, 4326) + 15 |  geom_07 Geometry @db.Geometry(CompoundCurveZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CurvePolygon,4326)` of CockroachDB: CurvePolygon isn't supported for the current connector. + --> schema.prisma:16 +  |  + 15 |  geom_07 Geometry @db.Geometry(CompoundCurveZM, 4326) + 16 |  geom_08 Geometry @db.Geometry(CurvePolygon, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CurvePolygonZ,4326)` of CockroachDB: CurvePolygonZ isn't supported for the current connector. + --> schema.prisma:17 +  |  + 16 |  geom_08 Geometry @db.Geometry(CurvePolygon, 4326) + 17 |  geom_09 Geometry @db.Geometry(CurvePolygonZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CurvePolygonM,4326)` of CockroachDB: CurvePolygonM isn't supported for the current connector. + --> schema.prisma:18 +  |  + 17 |  geom_09 Geometry @db.Geometry(CurvePolygonZ, 4326) + 18 |  geom_10 Geometry @db.Geometry(CurvePolygonM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CurvePolygonZM,4326)` of CockroachDB: CurvePolygonZM isn't supported for the current connector. + --> schema.prisma:19 +  |  + 18 |  geom_10 Geometry @db.Geometry(CurvePolygonM, 4326) + 19 |  geom_11 Geometry @db.Geometry(CurvePolygonZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiCurve,4326)` of CockroachDB: MultiCurve isn't supported for the current connector. + --> schema.prisma:20 +  |  + 19 |  geom_11 Geometry @db.Geometry(CurvePolygonZM, 4326) + 20 |  geom_12 Geometry @db.Geometry(MultiCurve, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiCurveZ,4326)` of CockroachDB: MultiCurveZ isn't supported for the current connector. + --> schema.prisma:21 +  |  + 20 |  geom_12 Geometry @db.Geometry(MultiCurve, 4326) + 21 |  geom_13 Geometry @db.Geometry(MultiCurveZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiCurveM,4326)` of CockroachDB: MultiCurveM isn't supported for the current connector. + --> schema.prisma:22 +  |  + 21 |  geom_13 Geometry @db.Geometry(MultiCurveZ, 4326) + 22 |  geom_14 Geometry @db.Geometry(MultiCurveM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiCurveZM,4326)` of CockroachDB: MultiCurveZM isn't supported for the current connector. + --> schema.prisma:23 +  |  + 22 |  geom_14 Geometry @db.Geometry(MultiCurveM, 4326) + 23 |  geom_15 Geometry @db.Geometry(MultiCurveZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiSurface,4326)` of CockroachDB: MultiSurface isn't supported for the current connector. + --> schema.prisma:24 +  |  + 23 |  geom_15 Geometry @db.Geometry(MultiCurveZM, 4326) + 24 |  geom_16 Geometry @db.Geometry(MultiSurface, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiSurfaceZ,4326)` of CockroachDB: MultiSurfaceZ isn't supported for the current connector. + --> schema.prisma:25 +  |  + 24 |  geom_16 Geometry @db.Geometry(MultiSurface, 4326) + 25 |  geom_17 Geometry @db.Geometry(MultiSurfaceZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiSurfaceM,4326)` of CockroachDB: MultiSurfaceM isn't supported for the current connector. + --> schema.prisma:26 +  |  + 25 |  geom_17 Geometry @db.Geometry(MultiSurfaceZ, 4326) + 26 |  geom_18 Geometry @db.Geometry(MultiSurfaceM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiSurfaceZM,4326)` of CockroachDB: MultiSurfaceZM isn't supported for the current connector. + --> schema.prisma:27 +  |  + 26 |  geom_18 Geometry @db.Geometry(MultiSurfaceM, 4326) + 27 |  geom_19 Geometry @db.Geometry(MultiSurfaceZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(PolyhedralSurface,4326)` of CockroachDB: PolyhedralSurface isn't supported for the current connector. + --> schema.prisma:28 +  |  + 27 |  geom_19 Geometry @db.Geometry(MultiSurfaceZM, 4326) + 28 |  geom_20 Geometry @db.Geometry(PolyhedralSurface, 4326) +  |  + error: Argument M is out of range for native type `Geometry(PolyhedralSurfaceZ,4326)` of CockroachDB: PolyhedralSurfaceZ isn't supported for the current connector. + --> schema.prisma:29 +  |  + 28 |  geom_20 Geometry @db.Geometry(PolyhedralSurface, 4326) + 29 |  geom_21 Geometry @db.Geometry(PolyhedralSurfaceZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(PolyhedralSurfaceM,4326)` of CockroachDB: PolyhedralSurfaceM isn't supported for the current connector. + --> schema.prisma:30 +  |  + 29 |  geom_21 Geometry @db.Geometry(PolyhedralSurfaceZ, 4326) + 30 |  geom_22 Geometry @db.Geometry(PolyhedralSurfaceM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(PolyhedralSurfaceZM,4326)` of CockroachDB: PolyhedralSurfaceZM isn't supported for the current connector. + --> schema.prisma:31 +  |  + 30 |  geom_22 Geometry @db.Geometry(PolyhedralSurfaceM, 4326) + 31 |  geom_23 Geometry @db.Geometry(PolyhedralSurfaceZM, 4326) +  |  + error: Argument M is out of range for native type `Geography(CircularString,4326)` of CockroachDB: CircularString isn't supported for the current connector. + --> schema.prisma:32 +  |  + 31 |  geom_23 Geometry @db.Geometry(PolyhedralSurfaceZM, 4326) + 32 |  geog_00 Geometry @db.Geography(CircularString, 4326) +  |  + error: Argument M is out of range for native type `Geography(CircularStringZ,4326)` of CockroachDB: CircularStringZ isn't supported for the current connector. + --> schema.prisma:33 +  |  + 32 |  geog_00 Geometry @db.Geography(CircularString, 4326) + 33 |  geog_01 Geometry @db.Geography(CircularStringZ, 4326) +  |  + error: Argument M is out of range for native type `Geography(CircularStringM,4326)` of CockroachDB: CircularStringM isn't supported for the current connector. + --> schema.prisma:34 +  |  + 33 |  geog_01 Geometry @db.Geography(CircularStringZ, 4326) + 34 |  geog_02 Geometry @db.Geography(CircularStringM, 4326) +  |  + error: Argument M is out of range for native type `Geography(CircularStringZM,4326)` of CockroachDB: CircularStringZM isn't supported for the current connector. + --> schema.prisma:35 +  |  + 34 |  geog_02 Geometry @db.Geography(CircularStringM, 4326) + 35 |  geog_03 Geometry @db.Geography(CircularStringZM, 4326) +  |  + error: Argument M is out of range for native type `Geography(CompoundCurve,4326)` of CockroachDB: CompoundCurve isn't supported for the current connector. + --> schema.prisma:36 +  |  + 35 |  geog_03 Geometry @db.Geography(CircularStringZM, 4326) + 36 |  geog_04 Geometry @db.Geography(CompoundCurve, 4326) +  |  + error: Argument M is out of range for native type `Geography(CompoundCurveZ,4326)` of CockroachDB: CompoundCurveZ isn't supported for the current connector. + --> schema.prisma:37 +  |  + 36 |  geog_04 Geometry @db.Geography(CompoundCurve, 4326) + 37 |  geog_05 Geometry @db.Geography(CompoundCurveZ, 4326) +  |  + error: Argument M is out of range for native type `Geography(CompoundCurveM,4326)` of CockroachDB: CompoundCurveM isn't supported for the current connector. + --> schema.prisma:38 +  |  + 37 |  geog_05 Geometry @db.Geography(CompoundCurveZ, 4326) + 38 |  geog_06 Geometry @db.Geography(CompoundCurveM, 4326) +  |  + error: Argument M is out of range for native type `Geography(CompoundCurveZM,4326)` of CockroachDB: CompoundCurveZM isn't supported for the current connector. + --> schema.prisma:39 +  |  + 38 |  geog_06 Geometry @db.Geography(CompoundCurveM, 4326) + 39 |  geog_07 Geometry @db.Geography(CompoundCurveZM, 4326) +  |  + error: Argument M is out of range for native type `Geography(CurvePolygon,4326)` of CockroachDB: CurvePolygon isn't supported for the current connector. + --> schema.prisma:40 +  |  + 39 |  geog_07 Geometry @db.Geography(CompoundCurveZM, 4326) + 40 |  geog_08 Geometry @db.Geography(CurvePolygon, 4326) +  |  + error: Argument M is out of range for native type `Geography(CurvePolygonZ,4326)` of CockroachDB: CurvePolygonZ isn't supported for the current connector. + --> schema.prisma:41 +  |  + 40 |  geog_08 Geometry @db.Geography(CurvePolygon, 4326) + 41 |  geog_09 Geometry @db.Geography(CurvePolygonZ, 4326) +  |  + error: Argument M is out of range for native type `Geography(CurvePolygonM,4326)` of CockroachDB: CurvePolygonM isn't supported for the current connector. + --> schema.prisma:42 +  |  + 41 |  geog_09 Geometry @db.Geography(CurvePolygonZ, 4326) + 42 |  geog_10 Geometry @db.Geography(CurvePolygonM, 4326) +  |  + error: Argument M is out of range for native type `Geography(CurvePolygonZM,4326)` of CockroachDB: CurvePolygonZM isn't supported for the current connector. + --> schema.prisma:43 +  |  + 42 |  geog_10 Geometry @db.Geography(CurvePolygonM, 4326) + 43 |  geog_11 Geometry @db.Geography(CurvePolygonZM, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiCurve,4326)` of CockroachDB: MultiCurve isn't supported for the current connector. + --> schema.prisma:44 +  |  + 43 |  geog_11 Geometry @db.Geography(CurvePolygonZM, 4326) + 44 |  geog_12 Geometry @db.Geography(MultiCurve, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiCurveZ,4326)` of CockroachDB: MultiCurveZ isn't supported for the current connector. + --> schema.prisma:45 +  |  + 44 |  geog_12 Geometry @db.Geography(MultiCurve, 4326) + 45 |  geog_13 Geometry @db.Geography(MultiCurveZ, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiCurveM,4326)` of CockroachDB: MultiCurveM isn't supported for the current connector. + --> schema.prisma:46 +  |  + 45 |  geog_13 Geometry @db.Geography(MultiCurveZ, 4326) + 46 |  geog_14 Geometry @db.Geography(MultiCurveM, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiCurveZM,4326)` of CockroachDB: MultiCurveZM isn't supported for the current connector. + --> schema.prisma:47 +  |  + 46 |  geog_14 Geometry @db.Geography(MultiCurveM, 4326) + 47 |  geog_15 Geometry @db.Geography(MultiCurveZM, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiSurface,4326)` of CockroachDB: MultiSurface isn't supported for the current connector. + --> schema.prisma:48 +  |  + 47 |  geog_15 Geometry @db.Geography(MultiCurveZM, 4326) + 48 |  geog_16 Geometry @db.Geography(MultiSurface, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiSurfaceZ,4326)` of CockroachDB: MultiSurfaceZ isn't supported for the current connector. + --> schema.prisma:49 +  |  + 48 |  geog_16 Geometry @db.Geography(MultiSurface, 4326) + 49 |  geog_17 Geometry @db.Geography(MultiSurfaceZ, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiSurfaceM,4326)` of CockroachDB: MultiSurfaceM isn't supported for the current connector. + --> schema.prisma:50 +  |  + 49 |  geog_17 Geometry @db.Geography(MultiSurfaceZ, 4326) + 50 |  geog_18 Geometry @db.Geography(MultiSurfaceM, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiSurfaceZM,4326)` of CockroachDB: MultiSurfaceZM isn't supported for the current connector. + --> schema.prisma:51 +  |  + 50 |  geog_18 Geometry @db.Geography(MultiSurfaceM, 4326) + 51 |  geog_19 Geometry @db.Geography(MultiSurfaceZM, 4326) +  |  + error: Argument M is out of range for native type `Geography(PolyhedralSurface,4326)` of CockroachDB: PolyhedralSurface isn't supported for the current connector. + --> schema.prisma:52 +  |  + 51 |  geog_19 Geometry @db.Geography(MultiSurfaceZM, 4326) + 52 |  geog_20 Geometry @db.Geography(PolyhedralSurface, 4326) +  |  + error: Argument M is out of range for native type `Geography(PolyhedralSurfaceZ,4326)` of CockroachDB: PolyhedralSurfaceZ isn't supported for the current connector. + --> schema.prisma:53 +  |  + 52 |  geog_20 Geometry @db.Geography(PolyhedralSurface, 4326) + 53 |  geog_21 Geometry @db.Geography(PolyhedralSurfaceZ, 4326) +  |  + error: Argument M is out of range for native type `Geography(PolyhedralSurfaceM,4326)` of CockroachDB: PolyhedralSurfaceM isn't supported for the current connector. + --> schema.prisma:54 +  |  + 53 |  geog_21 Geometry @db.Geography(PolyhedralSurfaceZ, 4326) + 54 |  geog_22 Geometry @db.Geography(PolyhedralSurfaceM, 4326) +  |  + error: Argument M is out of range for native type `Geography(PolyhedralSurfaceZM,4326)` of CockroachDB: PolyhedralSurfaceZM isn't supported for the current connector. + --> schema.prisma:55 +  |  + 54 |  geog_22 Geometry @db.Geography(PolyhedralSurfaceM, 4326) + 55 |  geog_23 Geometry @db.Geography(PolyhedralSurfaceZM, 4326) +  |  + "#]]; + + expect_error(schema, &expectation); +} diff --git a/psl/psl/tests/types/mod.rs b/psl/psl/tests/types/mod.rs index 91d64a22db89..8759552a0aa5 100644 --- a/psl/psl/tests/types/mod.rs +++ b/psl/psl/tests/types/mod.rs @@ -6,3 +6,4 @@ mod mysql_native_types; mod negative; mod positive; mod postgres_native_types; +mod sqlite_native_types; diff --git a/psl/psl/tests/types/mssql_native_types.rs b/psl/psl/tests/types/mssql_native_types.rs index 44b2f36f5e31..e85ede859506 100644 --- a/psl/psl/tests/types/mssql_native_types.rs +++ b/psl/psl/tests/types/mssql_native_types.rs @@ -204,6 +204,64 @@ fn image_type_should_fail_on_unique() { expect_error(schema, &expectation); } +#[test] +fn geometry_type_should_fail_on_unique() { + let schema = indoc! {r#" + datasource db { + provider = "sqlserver" + url = env("DATABASE_URL") + } + + model User { + id Int @id + geom GeoJson @db.Geometry + geog GeoJson @db.Geometry + + @@unique([geom, geog]) + } + "#}; + + let expectation = expect![[r#" + error: Native type `Geometry` cannot be unique in SQL Server. + --> schema.prisma:11 +  |  + 10 |  + 11 |  @@unique([geom, geog]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn geography_type_should_fail_on_unique() { + let schema = indoc! {r#" + datasource db { + provider = "sqlserver" + url = env("DATABASE_URL") + } + + model User { + id Int @id + geom GeoJson @db.Geography + geog GeoJson @db.Geography + + @@unique([geom, geog]) + } + "#}; + + let expectation = expect![[r#" + error: Native type `Geography` cannot be unique in SQL Server. + --> schema.prisma:11 +  |  + 10 |  + 11 |  @@unique([geom, geog]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + #[test] fn text_type_should_fail_on_index() { let schema = indoc! {r#" @@ -407,6 +465,64 @@ fn image_type_should_fail_on_index() { expect_error(schema, &expectation); } +#[test] +fn geometry_type_should_fail_on_index() { + let schema = indoc! {r#" + datasource db { + provider = "sqlserver" + url = env("DATABASE_URL") + } + + model User { + id Int @id + firstName GeoJson @db.Geometry + lastName GeoJson @db.Geometry + + @@index([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: You cannot define an index on fields with native type `Geometry` of SQL Server. + --> schema.prisma:11 +  |  + 10 |  + 11 |  @@index([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn geography_type_should_fail_on_index() { + let schema = indoc! {r#" + datasource db { + provider = "sqlserver" + url = env("DATABASE_URL") + } + + model User { + id Int @id + firstName GeoJson @db.Geography + lastName GeoJson @db.Geography + + @@index([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: You cannot define an index on fields with native type `Geography` of SQL Server. + --> schema.prisma:11 +  |  + 10 |  + 11 |  @@index([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + #[test] fn text_type_should_fail_on_id() { let schema = indoc! {r#" @@ -603,6 +719,62 @@ fn image_type_should_fail_on_id() { expect_error(schema, &expectation); } +#[test] +fn geometry_type_should_fail_on_id() { + let schema = indoc! {r#" + datasource db { + provider = "sqlserver" + url = env("DATABASE_URL") + } + + model User { + firstName GeoJson @db.Geometry + lastName GeoJson @db.Geometry + + @@id([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: Native type `Geometry` of SQL Server cannot be used on a field that is `@id` or `@@id`. + --> schema.prisma:10 +  |  +  9 |  + 10 |  @@id([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn geography_type_should_fail_on_id() { + let schema = indoc! {r#" + datasource db { + provider = "sqlserver" + url = env("DATABASE_URL") + } + + model User { + firstName GeoJson @db.Geography + lastName GeoJson @db.Geography + + @@id([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: Native type `Geography` of SQL Server cannot be used on a field that is `@id` or `@@id`. + --> schema.prisma:10 +  |  +  9 |  + 10 |  @@id([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + #[test] fn should_fail_on_native_type_decimal_when_scale_is_bigger_than_precision() { let schema = indoc! {r#" @@ -910,6 +1082,10 @@ mod test_type_mapping { test_type!(ntext(("String @db.NText", MsSqlType::NText))); test_type!(image(("Bytes @db.Image", MsSqlType::Image))); test_type!(xml(("String @db.Xml", MsSqlType::Xml))); + test_type!(geojsongeometry(("GeoJson @db.Geometry", MsSqlType::Geometry))); + test_type!(geojsongeography(("GeoJson @db.Geography", MsSqlType::Geography))); + test_type!(ewktgeometry(("Geometry @db.Geometry", MsSqlType::Geometry))); + test_type!(ewktgeography(("Geometry @db.Geography", MsSqlType::Geography))); test_type!(datetimeoffset(( "DateTime @db.DateTimeOffset", diff --git a/psl/psl/tests/types/mysql_native_types.rs b/psl/psl/tests/types/mysql_native_types.rs index 2aeea1808718..2c15b4c7f499 100644 --- a/psl/psl/tests/types/mysql_native_types.rs +++ b/psl/psl/tests/types/mysql_native_types.rs @@ -464,6 +464,238 @@ fn tinyblob_type_should_fail_on_index() { expect_error(schema, &expectation); } +#[test] +fn geometry_type_should_fail_on_index() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + firstName GeoJson @db.Geometry + lastName GeoJson @db.Geometry + + @@index([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: You cannot define an index on fields with native type `Geometry` of MySQL. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:11 +  |  + 10 |  + 11 |  @@index([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn point_type_should_fail_on_index() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + firstName GeoJson @db.Point + lastName GeoJson @db.Point + + @@index([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: You cannot define an index on fields with native type `Point` of MySQL. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:11 +  |  + 10 |  + 11 |  @@index([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn linestring_type_should_fail_on_index() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + firstName GeoJson @db.LineString + lastName GeoJson @db.LineString + + @@index([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: You cannot define an index on fields with native type `LineString` of MySQL. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:11 +  |  + 10 |  + 11 |  @@index([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn polygon_type_should_fail_on_index() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + firstName GeoJson @db.Polygon + lastName GeoJson @db.Polygon + + @@index([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: You cannot define an index on fields with native type `Polygon` of MySQL. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:11 +  |  + 10 |  + 11 |  @@index([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn multipoint_type_should_fail_on_index() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + firstName GeoJson @db.MultiPoint + lastName GeoJson @db.MultiPoint + + @@index([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: You cannot define an index on fields with native type `MultiPoint` of MySQL. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:11 +  |  + 10 |  + 11 |  @@index([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn multilinestring_type_should_fail_on_index() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + firstName GeoJson @db.MultiLineString + lastName GeoJson @db.MultiLineString + + @@index([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: You cannot define an index on fields with native type `MultiLineString` of MySQL. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:11 +  |  + 10 |  + 11 |  @@index([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn multipolygon_type_should_fail_on_index() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + firstName GeoJson @db.MultiPolygon + lastName GeoJson @db.MultiPolygon + + @@index([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: You cannot define an index on fields with native type `MultiPolygon` of MySQL. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:11 +  |  + 10 |  + 11 |  @@index([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn geometrycollection_type_should_fail_on_index() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + firstName GeoJson @db.GeometryCollection + lastName GeoJson @db.GeometryCollection + + @@index([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: You cannot define an index on fields with native type `GeometryCollection` of MySQL. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:11 +  |  + 10 |  + 11 |  @@index([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + #[test] fn text_type_should_fail_on_id() { let schema = indoc! {r#" @@ -688,6 +920,305 @@ fn tinyblob_type_should_fail_on_id() { expect_error(schema, &expectation); } +#[test] +fn geometry_type_should_fail_on_id() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + firstName GeoJson @db.Geometry + lastName GeoJson @db.Geometry + + @@id([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: Native type `Geometry` of MySQL cannot be used on a field that is `@id` or `@@id`. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:10 +  |  +  9 |  + 10 |  @@id([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn point_type_should_fail_on_id() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + firstName GeoJson @db.Point + lastName GeoJson @db.Point + + @@id([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: Native type `Point` of MySQL cannot be used on a field that is `@id` or `@@id`. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:10 +  |  +  9 |  + 10 |  @@id([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn linestring_type_should_fail_on_id() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + firstName GeoJson @db.LineString + lastName GeoJson @db.LineString + + @@id([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: Native type `LineString` of MySQL cannot be used on a field that is `@id` or `@@id`. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:10 +  |  +  9 |  + 10 |  @@id([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn polygon_type_should_fail_on_id() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + firstName GeoJson @db.Polygon + lastName GeoJson @db.Polygon + + @@id([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: Native type `Polygon` of MySQL cannot be used on a field that is `@id` or `@@id`. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:10 +  |  +  9 |  + 10 |  @@id([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn multipoint_type_should_fail_on_id() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + firstName GeoJson @db.MultiPoint + lastName GeoJson @db.MultiPoint + + @@id([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: Native type `MultiPoint` of MySQL cannot be used on a field that is `@id` or `@@id`. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:10 +  |  +  9 |  + 10 |  @@id([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn multilinestring_type_should_fail_on_id() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + firstName GeoJson @db.MultiLineString + lastName GeoJson @db.MultiLineString + + @@id([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: Native type `MultiLineString` of MySQL cannot be used on a field that is `@id` or `@@id`. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:10 +  |  +  9 |  + 10 |  @@id([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn multipolygon_type_should_fail_on_id() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + firstName GeoJson @db.MultiPolygon + lastName GeoJson @db.MultiPolygon + + @@id([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: Native type `MultiPolygon` of MySQL cannot be used on a field that is `@id` or `@@id`. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:10 +  |  +  9 |  + 10 |  @@id([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn geometrycollection_type_should_fail_on_id() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + firstName GeoJson @db.GeometryCollection + lastName GeoJson @db.GeometryCollection + + @@id([firstName, lastName]) + } + "#}; + + let expectation = expect![[r#" + error: Native type `GeometryCollection` of MySQL cannot be used on a field that is `@id` or `@@id`. Please use the `length` argument to the field in the index definition to allow this. + --> schema.prisma:10 +  |  +  9 |  + 10 |  @@id([firstName, lastName]) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn geojson_type_should_fail_on_invalid_srid() { + let schema = indoc! {r#" + datasource db { + provider = "mysql" + url = env("DATABASE_URL") + } + + model User { + id Int @id + geom1 GeoJson @db.Geometry(3857) + geom2 GeoJson @db.Point(3857) + geom3 GeoJson @db.LineString(3857) + geom4 GeoJson @db.Polygon(3857) + geom5 GeoJson @db.MultiPoint(3857) + geom6 GeoJson @db.MultiLineString(3857) + geom7 GeoJson @db.MultiPolygon(3857) + geom8 GeoJson @db.GeometryCollection(3857) + } + "#}; + + let expectation = expect![[r#" + error: Argument M is out of range for native type `Geometry(3857)` of MySQL: GeoJson SRID must be 4326. + --> schema.prisma:8 +  |  +  7 |  id Int @id +  8 |  geom1 GeoJson @db.Geometry(3857) +  |  + error: Argument M is out of range for native type `Point(3857)` of MySQL: GeoJson SRID must be 4326. + --> schema.prisma:9 +  |  +  8 |  geom1 GeoJson @db.Geometry(3857) +  9 |  geom2 GeoJson @db.Point(3857) +  |  + error: Argument M is out of range for native type `LineString(3857)` of MySQL: GeoJson SRID must be 4326. + --> schema.prisma:10 +  |  +  9 |  geom2 GeoJson @db.Point(3857) + 10 |  geom3 GeoJson @db.LineString(3857) +  |  + error: Argument M is out of range for native type `Polygon(3857)` of MySQL: GeoJson SRID must be 4326. + --> schema.prisma:11 +  |  + 10 |  geom3 GeoJson @db.LineString(3857) + 11 |  geom4 GeoJson @db.Polygon(3857) +  |  + error: Argument M is out of range for native type `MultiPoint(3857)` of MySQL: GeoJson SRID must be 4326. + --> schema.prisma:12 +  |  + 11 |  geom4 GeoJson @db.Polygon(3857) + 12 |  geom5 GeoJson @db.MultiPoint(3857) +  |  + error: Argument M is out of range for native type `MultiLineString(3857)` of MySQL: GeoJson SRID must be 4326. + --> schema.prisma:13 +  |  + 12 |  geom5 GeoJson @db.MultiPoint(3857) + 13 |  geom6 GeoJson @db.MultiLineString(3857) +  |  + error: Argument M is out of range for native type `MultiPolygon(3857)` of MySQL: GeoJson SRID must be 4326. + --> schema.prisma:14 +  |  + 13 |  geom6 GeoJson @db.MultiLineString(3857) + 14 |  geom7 GeoJson @db.MultiPolygon(3857) +  |  + error: Argument M is out of range for native type `GeometryCollection(3857)` of MySQL: GeoJson SRID must be 4326. + --> schema.prisma:15 +  |  + 14 |  geom7 GeoJson @db.MultiPolygon(3857) + 15 |  geom8 GeoJson @db.GeometryCollection(3857) +  |  + "#]]; + + expect_error(schema, &expectation); +} + #[test] fn text_should_not_fail_on_length_prefixed_index() { let dml = indoc! {r#" diff --git a/psl/psl/tests/types/postgres_native_types.rs b/psl/psl/tests/types/postgres_native_types.rs index a2e71ad3b973..fca309beb801 100644 --- a/psl/psl/tests/types/postgres_native_types.rs +++ b/psl/psl/tests/types/postgres_native_types.rs @@ -232,3 +232,502 @@ fn xml_should_work_with_string_scalar_type() { .assert_has_scalar_field("dec") .assert_native_type(datamodel.connector, &PostgresType::Xml); } + +#[test] +fn postgis_specific_native_types_are_valid() { + let schema = indoc! {r#" + datasource db { + provider = "postgres" + url = env("TEST_DATABASE_URL") + } + + model NativeTypesTest { + id Int @id + geom_01 Geometry @db.Geometry(Geometry, 4326) + geom_02 Geometry @db.Geometry(GeometryZ, 4326) + geom_03 Geometry @db.Geometry(GeometryM, 4326) + geom_04 Geometry @db.Geometry(GeometryZM, 4326) + geom_05 Geometry @db.Geometry(Point, 4326) + geom_06 Geometry @db.Geometry(PointZ, 4326) + geom_07 Geometry @db.Geometry(PointM, 4326) + geom_08 Geometry @db.Geometry(PointZM, 4326) + geom_09 Geometry @db.Geometry(LineString, 4326) + geom_10 Geometry @db.Geometry(LineStringZ, 4326) + geom_11 Geometry @db.Geometry(LineStringM, 4326) + geom_12 Geometry @db.Geometry(LineStringZM, 4326) + geom_13 Geometry @db.Geometry(Polygon, 4326) + geom_14 Geometry @db.Geometry(PolygonZ, 4326) + geom_15 Geometry @db.Geometry(PolygonM, 4326) + geom_16 Geometry @db.Geometry(PolygonZM, 4326) + geom_17 Geometry @db.Geometry(MultiPoint, 4326) + geom_18 Geometry @db.Geometry(MultiPointZ, 4326) + geom_19 Geometry @db.Geometry(MultiPointM, 4326) + geom_20 Geometry @db.Geometry(MultiPointZM, 4326) + geom_21 Geometry @db.Geometry(MultiLineString, 4326) + geom_22 Geometry @db.Geometry(MultiLineStringZ, 4326) + geom_23 Geometry @db.Geometry(MultiLineStringM, 4326) + geom_24 Geometry @db.Geometry(MultiLineStringZM, 4326) + geom_25 Geometry @db.Geometry(MultiPolygon, 4326) + geom_26 Geometry @db.Geometry(MultiPolygonZ, 4326) + geom_27 Geometry @db.Geometry(MultiPolygonM, 4326) + geom_28 Geometry @db.Geometry(MultiPolygonZM, 4326) + geom_29 Geometry @db.Geometry(GeometryCollection, 4326) + geom_30 Geometry @db.Geometry(GeometryCollectionZ, 4326) + geom_31 Geometry @db.Geometry(GeometryCollectionM, 4326) + geom_32 Geometry @db.Geometry(GeometryCollectionZM, 4326) + geom_33 Geometry @db.Geometry(CircularString, 4326) + geom_34 Geometry @db.Geometry(CircularStringZ, 4326) + geom_35 Geometry @db.Geometry(CircularStringM, 4326) + geom_36 Geometry @db.Geometry(CircularStringZM, 4326) + geom_37 Geometry @db.Geometry(CompoundCurve, 4326) + geom_38 Geometry @db.Geometry(CompoundCurveZ, 4326) + geom_39 Geometry @db.Geometry(CompoundCurveM, 4326) + geom_40 Geometry @db.Geometry(CompoundCurveZM, 4326) + geom_41 Geometry @db.Geometry(CurvePolygon, 4326) + geom_42 Geometry @db.Geometry(CurvePolygonZ, 4326) + geom_43 Geometry @db.Geometry(CurvePolygonM, 4326) + geom_44 Geometry @db.Geometry(CurvePolygonZM, 4326) + geom_45 Geometry @db.Geometry(MultiCurve, 4326) + geom_46 Geometry @db.Geometry(MultiCurveZ, 4326) + geom_47 Geometry @db.Geometry(MultiCurveM, 4326) + geom_48 Geometry @db.Geometry(MultiCurveZM, 4326) + geom_49 Geometry @db.Geometry(MultiSurface, 4326) + geom_50 Geometry @db.Geometry(MultiSurfaceZ, 4326) + geom_51 Geometry @db.Geometry(MultiSurfaceM, 4326) + geom_52 Geometry @db.Geometry(MultiSurfaceZM, 4326) + geom_53 Geometry @db.Geometry(PolyhedralSurface, 4326) + geom_54 Geometry @db.Geometry(PolyhedralSurfaceZ, 4326) + geom_55 Geometry @db.Geometry(PolyhedralSurfaceM, 4326) + geom_56 Geometry @db.Geometry(PolyhedralSurfaceZM, 4326) + geom_57 Geometry @db.Geometry(Tin, 4326) + geom_58 Geometry @db.Geometry(TinZ, 4326) + geom_59 Geometry @db.Geometry(TinM, 4326) + geom_60 Geometry @db.Geometry(TinZM, 4326) + geom_61 Geometry @db.Geometry(Triangle, 4326) + geom_62 Geometry @db.Geometry(TriangleZ, 4326) + geom_63 Geometry @db.Geometry(TriangleM, 4326) + geom_64 Geometry @db.Geometry(TriangleZM, 4326) + geog_01 Geometry @db.Geography(Geometry, 4326) + geog_02 Geometry @db.Geography(GeometryZ, 4326) + geog_03 Geometry @db.Geography(GeometryM, 4326) + geog_04 Geometry @db.Geography(GeometryZM, 4326) + geog_05 Geometry @db.Geography(Point, 4326) + geog_06 Geometry @db.Geography(PointZ, 4326) + geog_07 Geometry @db.Geography(PointM, 4326) + geog_08 Geometry @db.Geography(PointZM, 4326) + geog_09 Geometry @db.Geography(LineString, 4326) + geog_10 Geometry @db.Geography(LineStringZ, 4326) + geog_11 Geometry @db.Geography(LineStringM, 4326) + geog_12 Geometry @db.Geography(LineStringZM, 4326) + geog_13 Geometry @db.Geography(Polygon, 4326) + geog_14 Geometry @db.Geography(PolygonZ, 4326) + geog_15 Geometry @db.Geography(PolygonM, 4326) + geog_16 Geometry @db.Geography(PolygonZM, 4326) + geog_17 Geometry @db.Geography(MultiPoint, 4326) + geog_18 Geometry @db.Geography(MultiPointZ, 4326) + geog_19 Geometry @db.Geography(MultiPointM, 4326) + geog_20 Geometry @db.Geography(MultiPointZM, 4326) + geog_21 Geometry @db.Geography(MultiLineString, 4326) + geog_22 Geometry @db.Geography(MultiLineStringZ, 4326) + geog_23 Geometry @db.Geography(MultiLineStringM, 4326) + geog_24 Geometry @db.Geography(MultiLineStringZM, 4326) + geog_25 Geometry @db.Geography(MultiPolygon, 4326) + geog_26 Geometry @db.Geography(MultiPolygonZ, 4326) + geog_27 Geometry @db.Geography(MultiPolygonM, 4326) + geog_28 Geometry @db.Geography(MultiPolygonZM, 4326) + geog_29 Geometry @db.Geography(GeometryCollection, 4326) + geog_30 Geometry @db.Geography(GeometryCollectionZ, 4326) + geog_31 Geometry @db.Geography(GeometryCollectionM, 4326) + geog_32 Geometry @db.Geography(GeometryCollectionZM, 4326) + geog_33 Geometry @db.Geography(CircularString, 4326) + geog_34 Geometry @db.Geography(CircularStringZ, 4326) + geog_35 Geometry @db.Geography(CircularStringM, 4326) + geog_36 Geometry @db.Geography(CircularStringZM, 4326) + geog_37 Geometry @db.Geography(CompoundCurve, 4326) + geog_38 Geometry @db.Geography(CompoundCurveZ, 4326) + geog_39 Geometry @db.Geography(CompoundCurveM, 4326) + geog_40 Geometry @db.Geography(CompoundCurveZM, 4326) + geog_41 Geometry @db.Geography(CurvePolygon, 4326) + geog_42 Geometry @db.Geography(CurvePolygonZ, 4326) + geog_43 Geometry @db.Geography(CurvePolygonM, 4326) + geog_44 Geometry @db.Geography(CurvePolygonZM, 4326) + geog_45 Geometry @db.Geography(MultiCurve, 4326) + geog_46 Geometry @db.Geography(MultiCurveZ, 4326) + geog_47 Geometry @db.Geography(MultiCurveM, 4326) + geog_48 Geometry @db.Geography(MultiCurveZM, 4326) + geog_49 Geometry @db.Geography(MultiSurface, 4326) + geog_50 Geometry @db.Geography(MultiSurfaceZ, 4326) + geog_51 Geometry @db.Geography(MultiSurfaceM, 4326) + geog_52 Geometry @db.Geography(MultiSurfaceZM, 4326) + geog_53 Geometry @db.Geography(PolyhedralSurface, 4326) + geog_54 Geometry @db.Geography(PolyhedralSurfaceZ, 4326) + geog_55 Geometry @db.Geography(PolyhedralSurfaceM, 4326) + geog_56 Geometry @db.Geography(PolyhedralSurfaceZM, 4326) + geog_57 Geometry @db.Geography(Tin, 4326) + geog_58 Geometry @db.Geography(TinZ, 4326) + geog_59 Geometry @db.Geography(TinM, 4326) + geog_60 Geometry @db.Geography(TinZM, 4326) + geog_61 Geometry @db.Geography(Triangle, 4326) + geog_62 Geometry @db.Geography(TriangleZ, 4326) + geog_63 Geometry @db.Geography(TriangleM, 4326) + geog_64 Geometry @db.Geography(TriangleZM, 4326) + } + "#}; + + psl::parse_schema(schema).unwrap(); +} + +#[test] +fn should_fail_on_geojson_when_incompatible_geometry_type() { + let dml = indoc! {r#" + datasource db { + provider = "postgres" + url = env("TEST_DATABASE_URL") + } + + model Blog { + id Int @id + geom_01 GeoJson @db.Geometry(CircularString, 4326) + geom_02 GeoJson @db.Geometry(CircularStringZ, 4326) + geom_03 GeoJson @db.Geometry(CircularStringM, 4326) + geom_04 GeoJson @db.Geometry(CircularStringZM, 4326) + geom_05 GeoJson @db.Geometry(CompoundCurve, 4326) + geom_06 GeoJson @db.Geometry(CompoundCurveZ, 4326) + geom_07 GeoJson @db.Geometry(CompoundCurveM, 4326) + geom_08 GeoJson @db.Geometry(CompoundCurveZM, 4326) + geom_09 GeoJson @db.Geometry(CurvePolygon, 4326) + geom_10 GeoJson @db.Geometry(CurvePolygonZ, 4326) + geom_11 GeoJson @db.Geometry(CurvePolygonM, 4326) + geom_12 GeoJson @db.Geometry(CurvePolygonZM, 4326) + geom_13 GeoJson @db.Geometry(MultiCurve, 4326) + geom_14 GeoJson @db.Geometry(MultiCurveZ, 4326) + geom_15 GeoJson @db.Geometry(MultiCurveM, 4326) + geom_16 GeoJson @db.Geometry(MultiCurveZM, 4326) + geom_17 GeoJson @db.Geometry(MultiSurface, 4326) + geom_18 GeoJson @db.Geometry(MultiSurfaceZ, 4326) + geom_19 GeoJson @db.Geometry(MultiSurfaceM, 4326) + geom_20 GeoJson @db.Geometry(MultiSurfaceZM, 4326) + geom_21 GeoJson @db.Geometry(PolyhedralSurface, 4326) + geom_22 GeoJson @db.Geometry(PolyhedralSurfaceZ, 4326) + geom_23 GeoJson @db.Geometry(PolyhedralSurfaceM, 4326) + geom_24 GeoJson @db.Geometry(PolyhedralSurfaceZM, 4326) + geog_01 GeoJson @db.Geography(CircularString, 4326) + geog_02 GeoJson @db.Geography(CircularStringZ, 4326) + geog_03 GeoJson @db.Geography(CircularStringM, 4326) + geog_04 GeoJson @db.Geography(CircularStringZM, 4326) + geog_05 GeoJson @db.Geography(CompoundCurve, 4326) + geog_06 GeoJson @db.Geography(CompoundCurveZ, 4326) + geog_07 GeoJson @db.Geography(CompoundCurveM, 4326) + geog_08 GeoJson @db.Geography(CompoundCurveZM, 4326) + geog_09 GeoJson @db.Geography(CurvePolygon, 4326) + geog_10 GeoJson @db.Geography(CurvePolygonZ, 4326) + geog_11 GeoJson @db.Geography(CurvePolygonM, 4326) + geog_12 GeoJson @db.Geography(CurvePolygonZM, 4326) + geog_13 GeoJson @db.Geography(MultiCurve, 4326) + geog_14 GeoJson @db.Geography(MultiCurveZ, 4326) + geog_15 GeoJson @db.Geography(MultiCurveM, 4326) + geog_16 GeoJson @db.Geography(MultiCurveZM, 4326) + geog_17 GeoJson @db.Geography(MultiSurface, 4326) + geog_18 GeoJson @db.Geography(MultiSurfaceZ, 4326) + geog_19 GeoJson @db.Geography(MultiSurfaceM, 4326) + geog_20 GeoJson @db.Geography(MultiSurfaceZM, 4326) + geog_21 GeoJson @db.Geography(PolyhedralSurface, 4326) + geog_22 GeoJson @db.Geography(PolyhedralSurfaceZ, 4326) + geog_23 GeoJson @db.Geography(PolyhedralSurfaceM, 4326) + geog_24 GeoJson @db.Geography(PolyhedralSurfaceZM, 4326) + } + "#}; + + let expectation = expect![[r#" + error: Argument M is out of range for native type `Geometry(CircularString,4326)` of Postgres: CircularString isn't compatible with GeoJson. + --> schema.prisma:8 +  |  +  7 |  id Int @id +  8 |  geom_01 GeoJson @db.Geometry(CircularString, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CircularStringZ,4326)` of Postgres: CircularStringZ isn't compatible with GeoJson. + --> schema.prisma:9 +  |  +  8 |  geom_01 GeoJson @db.Geometry(CircularString, 4326) +  9 |  geom_02 GeoJson @db.Geometry(CircularStringZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CircularStringM,4326)` of Postgres: CircularStringM isn't compatible with GeoJson. + --> schema.prisma:10 +  |  +  9 |  geom_02 GeoJson @db.Geometry(CircularStringZ, 4326) + 10 |  geom_03 GeoJson @db.Geometry(CircularStringM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CircularStringZM,4326)` of Postgres: CircularStringZM isn't compatible with GeoJson. + --> schema.prisma:11 +  |  + 10 |  geom_03 GeoJson @db.Geometry(CircularStringM, 4326) + 11 |  geom_04 GeoJson @db.Geometry(CircularStringZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CompoundCurve,4326)` of Postgres: CompoundCurve isn't compatible with GeoJson. + --> schema.prisma:12 +  |  + 11 |  geom_04 GeoJson @db.Geometry(CircularStringZM, 4326) + 12 |  geom_05 GeoJson @db.Geometry(CompoundCurve, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CompoundCurveZ,4326)` of Postgres: CompoundCurveZ isn't compatible with GeoJson. + --> schema.prisma:13 +  |  + 12 |  geom_05 GeoJson @db.Geometry(CompoundCurve, 4326) + 13 |  geom_06 GeoJson @db.Geometry(CompoundCurveZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CompoundCurveM,4326)` of Postgres: CompoundCurveM isn't compatible with GeoJson. + --> schema.prisma:14 +  |  + 13 |  geom_06 GeoJson @db.Geometry(CompoundCurveZ, 4326) + 14 |  geom_07 GeoJson @db.Geometry(CompoundCurveM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CompoundCurveZM,4326)` of Postgres: CompoundCurveZM isn't compatible with GeoJson. + --> schema.prisma:15 +  |  + 14 |  geom_07 GeoJson @db.Geometry(CompoundCurveM, 4326) + 15 |  geom_08 GeoJson @db.Geometry(CompoundCurveZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CurvePolygon,4326)` of Postgres: CurvePolygon isn't compatible with GeoJson. + --> schema.prisma:16 +  |  + 15 |  geom_08 GeoJson @db.Geometry(CompoundCurveZM, 4326) + 16 |  geom_09 GeoJson @db.Geometry(CurvePolygon, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CurvePolygonZ,4326)` of Postgres: CurvePolygonZ isn't compatible with GeoJson. + --> schema.prisma:17 +  |  + 16 |  geom_09 GeoJson @db.Geometry(CurvePolygon, 4326) + 17 |  geom_10 GeoJson @db.Geometry(CurvePolygonZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CurvePolygonM,4326)` of Postgres: CurvePolygonM isn't compatible with GeoJson. + --> schema.prisma:18 +  |  + 17 |  geom_10 GeoJson @db.Geometry(CurvePolygonZ, 4326) + 18 |  geom_11 GeoJson @db.Geometry(CurvePolygonM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CurvePolygonZM,4326)` of Postgres: CurvePolygonZM isn't compatible with GeoJson. + --> schema.prisma:19 +  |  + 18 |  geom_11 GeoJson @db.Geometry(CurvePolygonM, 4326) + 19 |  geom_12 GeoJson @db.Geometry(CurvePolygonZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiCurve,4326)` of Postgres: MultiCurve isn't compatible with GeoJson. + --> schema.prisma:20 +  |  + 19 |  geom_12 GeoJson @db.Geometry(CurvePolygonZM, 4326) + 20 |  geom_13 GeoJson @db.Geometry(MultiCurve, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiCurveZ,4326)` of Postgres: MultiCurveZ isn't compatible with GeoJson. + --> schema.prisma:21 +  |  + 20 |  geom_13 GeoJson @db.Geometry(MultiCurve, 4326) + 21 |  geom_14 GeoJson @db.Geometry(MultiCurveZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiCurveM,4326)` of Postgres: MultiCurveM isn't compatible with GeoJson. + --> schema.prisma:22 +  |  + 21 |  geom_14 GeoJson @db.Geometry(MultiCurveZ, 4326) + 22 |  geom_15 GeoJson @db.Geometry(MultiCurveM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiCurveZM,4326)` of Postgres: MultiCurveZM isn't compatible with GeoJson. + --> schema.prisma:23 +  |  + 22 |  geom_15 GeoJson @db.Geometry(MultiCurveM, 4326) + 23 |  geom_16 GeoJson @db.Geometry(MultiCurveZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiSurface,4326)` of Postgres: MultiSurface isn't compatible with GeoJson. + --> schema.prisma:24 +  |  + 23 |  geom_16 GeoJson @db.Geometry(MultiCurveZM, 4326) + 24 |  geom_17 GeoJson @db.Geometry(MultiSurface, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiSurfaceZ,4326)` of Postgres: MultiSurfaceZ isn't compatible with GeoJson. + --> schema.prisma:25 +  |  + 24 |  geom_17 GeoJson @db.Geometry(MultiSurface, 4326) + 25 |  geom_18 GeoJson @db.Geometry(MultiSurfaceZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiSurfaceM,4326)` of Postgres: MultiSurfaceM isn't compatible with GeoJson. + --> schema.prisma:26 +  |  + 25 |  geom_18 GeoJson @db.Geometry(MultiSurfaceZ, 4326) + 26 |  geom_19 GeoJson @db.Geometry(MultiSurfaceM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiSurfaceZM,4326)` of Postgres: MultiSurfaceZM isn't compatible with GeoJson. + --> schema.prisma:27 +  |  + 26 |  geom_19 GeoJson @db.Geometry(MultiSurfaceM, 4326) + 27 |  geom_20 GeoJson @db.Geometry(MultiSurfaceZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(PolyhedralSurface,4326)` of Postgres: PolyhedralSurface isn't compatible with GeoJson. + --> schema.prisma:28 +  |  + 27 |  geom_20 GeoJson @db.Geometry(MultiSurfaceZM, 4326) + 28 |  geom_21 GeoJson @db.Geometry(PolyhedralSurface, 4326) +  |  + error: Argument M is out of range for native type `Geometry(PolyhedralSurfaceZ,4326)` of Postgres: PolyhedralSurfaceZ isn't compatible with GeoJson. + --> schema.prisma:29 +  |  + 28 |  geom_21 GeoJson @db.Geometry(PolyhedralSurface, 4326) + 29 |  geom_22 GeoJson @db.Geometry(PolyhedralSurfaceZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(PolyhedralSurfaceM,4326)` of Postgres: PolyhedralSurfaceM isn't compatible with GeoJson. + --> schema.prisma:30 +  |  + 29 |  geom_22 GeoJson @db.Geometry(PolyhedralSurfaceZ, 4326) + 30 |  geom_23 GeoJson @db.Geometry(PolyhedralSurfaceM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(PolyhedralSurfaceZM,4326)` of Postgres: PolyhedralSurfaceZM isn't compatible with GeoJson. + --> schema.prisma:31 +  |  + 30 |  geom_23 GeoJson @db.Geometry(PolyhedralSurfaceM, 4326) + 31 |  geom_24 GeoJson @db.Geometry(PolyhedralSurfaceZM, 4326) +  |  + error: Argument M is out of range for native type `Geography(CircularString,4326)` of Postgres: CircularString isn't compatible with GeoJson. + --> schema.prisma:32 +  |  + 31 |  geom_24 GeoJson @db.Geometry(PolyhedralSurfaceZM, 4326) + 32 |  geog_01 GeoJson @db.Geography(CircularString, 4326) +  |  + error: Argument M is out of range for native type `Geography(CircularStringZ,4326)` of Postgres: CircularStringZ isn't compatible with GeoJson. + --> schema.prisma:33 +  |  + 32 |  geog_01 GeoJson @db.Geography(CircularString, 4326) + 33 |  geog_02 GeoJson @db.Geography(CircularStringZ, 4326) +  |  + error: Argument M is out of range for native type `Geography(CircularStringM,4326)` of Postgres: CircularStringM isn't compatible with GeoJson. + --> schema.prisma:34 +  |  + 33 |  geog_02 GeoJson @db.Geography(CircularStringZ, 4326) + 34 |  geog_03 GeoJson @db.Geography(CircularStringM, 4326) +  |  + error: Argument M is out of range for native type `Geography(CircularStringZM,4326)` of Postgres: CircularStringZM isn't compatible with GeoJson. + --> schema.prisma:35 +  |  + 34 |  geog_03 GeoJson @db.Geography(CircularStringM, 4326) + 35 |  geog_04 GeoJson @db.Geography(CircularStringZM, 4326) +  |  + error: Argument M is out of range for native type `Geography(CompoundCurve,4326)` of Postgres: CompoundCurve isn't compatible with GeoJson. + --> schema.prisma:36 +  |  + 35 |  geog_04 GeoJson @db.Geography(CircularStringZM, 4326) + 36 |  geog_05 GeoJson @db.Geography(CompoundCurve, 4326) +  |  + error: Argument M is out of range for native type `Geography(CompoundCurveZ,4326)` of Postgres: CompoundCurveZ isn't compatible with GeoJson. + --> schema.prisma:37 +  |  + 36 |  geog_05 GeoJson @db.Geography(CompoundCurve, 4326) + 37 |  geog_06 GeoJson @db.Geography(CompoundCurveZ, 4326) +  |  + error: Argument M is out of range for native type `Geography(CompoundCurveM,4326)` of Postgres: CompoundCurveM isn't compatible with GeoJson. + --> schema.prisma:38 +  |  + 37 |  geog_06 GeoJson @db.Geography(CompoundCurveZ, 4326) + 38 |  geog_07 GeoJson @db.Geography(CompoundCurveM, 4326) +  |  + error: Argument M is out of range for native type `Geography(CompoundCurveZM,4326)` of Postgres: CompoundCurveZM isn't compatible with GeoJson. + --> schema.prisma:39 +  |  + 38 |  geog_07 GeoJson @db.Geography(CompoundCurveM, 4326) + 39 |  geog_08 GeoJson @db.Geography(CompoundCurveZM, 4326) +  |  + error: Argument M is out of range for native type `Geography(CurvePolygon,4326)` of Postgres: CurvePolygon isn't compatible with GeoJson. + --> schema.prisma:40 +  |  + 39 |  geog_08 GeoJson @db.Geography(CompoundCurveZM, 4326) + 40 |  geog_09 GeoJson @db.Geography(CurvePolygon, 4326) +  |  + error: Argument M is out of range for native type `Geography(CurvePolygonZ,4326)` of Postgres: CurvePolygonZ isn't compatible with GeoJson. + --> schema.prisma:41 +  |  + 40 |  geog_09 GeoJson @db.Geography(CurvePolygon, 4326) + 41 |  geog_10 GeoJson @db.Geography(CurvePolygonZ, 4326) +  |  + error: Argument M is out of range for native type `Geography(CurvePolygonM,4326)` of Postgres: CurvePolygonM isn't compatible with GeoJson. + --> schema.prisma:42 +  |  + 41 |  geog_10 GeoJson @db.Geography(CurvePolygonZ, 4326) + 42 |  geog_11 GeoJson @db.Geography(CurvePolygonM, 4326) +  |  + error: Argument M is out of range for native type `Geography(CurvePolygonZM,4326)` of Postgres: CurvePolygonZM isn't compatible with GeoJson. + --> schema.prisma:43 +  |  + 42 |  geog_11 GeoJson @db.Geography(CurvePolygonM, 4326) + 43 |  geog_12 GeoJson @db.Geography(CurvePolygonZM, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiCurve,4326)` of Postgres: MultiCurve isn't compatible with GeoJson. + --> schema.prisma:44 +  |  + 43 |  geog_12 GeoJson @db.Geography(CurvePolygonZM, 4326) + 44 |  geog_13 GeoJson @db.Geography(MultiCurve, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiCurveZ,4326)` of Postgres: MultiCurveZ isn't compatible with GeoJson. + --> schema.prisma:45 +  |  + 44 |  geog_13 GeoJson @db.Geography(MultiCurve, 4326) + 45 |  geog_14 GeoJson @db.Geography(MultiCurveZ, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiCurveM,4326)` of Postgres: MultiCurveM isn't compatible with GeoJson. + --> schema.prisma:46 +  |  + 45 |  geog_14 GeoJson @db.Geography(MultiCurveZ, 4326) + 46 |  geog_15 GeoJson @db.Geography(MultiCurveM, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiCurveZM,4326)` of Postgres: MultiCurveZM isn't compatible with GeoJson. + --> schema.prisma:47 +  |  + 46 |  geog_15 GeoJson @db.Geography(MultiCurveM, 4326) + 47 |  geog_16 GeoJson @db.Geography(MultiCurveZM, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiSurface,4326)` of Postgres: MultiSurface isn't compatible with GeoJson. + --> schema.prisma:48 +  |  + 47 |  geog_16 GeoJson @db.Geography(MultiCurveZM, 4326) + 48 |  geog_17 GeoJson @db.Geography(MultiSurface, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiSurfaceZ,4326)` of Postgres: MultiSurfaceZ isn't compatible with GeoJson. + --> schema.prisma:49 +  |  + 48 |  geog_17 GeoJson @db.Geography(MultiSurface, 4326) + 49 |  geog_18 GeoJson @db.Geography(MultiSurfaceZ, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiSurfaceM,4326)` of Postgres: MultiSurfaceM isn't compatible with GeoJson. + --> schema.prisma:50 +  |  + 49 |  geog_18 GeoJson @db.Geography(MultiSurfaceZ, 4326) + 50 |  geog_19 GeoJson @db.Geography(MultiSurfaceM, 4326) +  |  + error: Argument M is out of range for native type `Geography(MultiSurfaceZM,4326)` of Postgres: MultiSurfaceZM isn't compatible with GeoJson. + --> schema.prisma:51 +  |  + 50 |  geog_19 GeoJson @db.Geography(MultiSurfaceM, 4326) + 51 |  geog_20 GeoJson @db.Geography(MultiSurfaceZM, 4326) +  |  + error: Argument M is out of range for native type `Geography(PolyhedralSurface,4326)` of Postgres: PolyhedralSurface isn't compatible with GeoJson. + --> schema.prisma:52 +  |  + 51 |  geog_20 GeoJson @db.Geography(MultiSurfaceZM, 4326) + 52 |  geog_21 GeoJson @db.Geography(PolyhedralSurface, 4326) +  |  + error: Argument M is out of range for native type `Geography(PolyhedralSurfaceZ,4326)` of Postgres: PolyhedralSurfaceZ isn't compatible with GeoJson. + --> schema.prisma:53 +  |  + 52 |  geog_21 GeoJson @db.Geography(PolyhedralSurface, 4326) + 53 |  geog_22 GeoJson @db.Geography(PolyhedralSurfaceZ, 4326) +  |  + error: Argument M is out of range for native type `Geography(PolyhedralSurfaceM,4326)` of Postgres: PolyhedralSurfaceM isn't compatible with GeoJson. + --> schema.prisma:54 +  |  + 53 |  geog_22 GeoJson @db.Geography(PolyhedralSurfaceZ, 4326) + 54 |  geog_23 GeoJson @db.Geography(PolyhedralSurfaceM, 4326) +  |  + error: Argument M is out of range for native type `Geography(PolyhedralSurfaceZM,4326)` of Postgres: PolyhedralSurfaceZM isn't compatible with GeoJson. + --> schema.prisma:55 +  |  + 54 |  geog_23 GeoJson @db.Geography(PolyhedralSurfaceM, 4326) + 55 |  geog_24 GeoJson @db.Geography(PolyhedralSurfaceZM, 4326) +  |  + "#]]; + + expect_error(dml, &expectation); +} diff --git a/psl/psl/tests/types/sqlite_native_types.rs b/psl/psl/tests/types/sqlite_native_types.rs new file mode 100644 index 000000000000..c9c4133d3a35 --- /dev/null +++ b/psl/psl/tests/types/sqlite_native_types.rs @@ -0,0 +1,487 @@ +use crate::common::*; +use expect_test::expect; + +#[test] +fn sqlite_specific_native_types_are_valid() { + let schema = indoc! {r#" + datasource db { + provider = "sqlite" + url = "file:test.db" + } + + model NativeTypesTest { + id Int @id + geomcol1 Geometry @db.Geometry(Geometry, 4326) + geomcol2 Geometry @db.Geometry(GeometryZ, 4326) + geomcol3 Geometry @db.Geometry(GeometryM, 4326) + geomcol4 Geometry @db.Geometry(GeometryZM, 4326) + geomcol5 Geometry @db.Geometry(Point, 4326) + geomcol6 Geometry @db.Geometry(PointZ, 4326) + geomcol7 Geometry @db.Geometry(PointM, 4326) + geomcol8 Geometry @db.Geometry(PointZM, 4326) + geomcol9 Geometry @db.Geometry(Point, 4326) + geomcol10 Geometry @db.Geometry(PointZ, 4326) + geomcol11 Geometry @db.Geometry(PointM, 4326) + geomcol12 Geometry @db.Geometry(PointZM, 4326) + geomcol13 Geometry @db.Geometry(LineString, 4326) + geomcol14 Geometry @db.Geometry(LineStringZ, 4326) + geomcol15 Geometry @db.Geometry(LineStringM, 4326) + geomcol16 Geometry @db.Geometry(LineStringZM, 4326) + geomcol17 Geometry @db.Geometry(Polygon, 4326) + geomcol18 Geometry @db.Geometry(PolygonZ, 4326) + geomcol19 Geometry @db.Geometry(PolygonM, 4326) + geomcol20 Geometry @db.Geometry(PolygonZM, 4326) + geomcol21 Geometry @db.Geometry(MultiPoint, 4326) + geomcol22 Geometry @db.Geometry(MultiPointZ, 4326) + geomcol23 Geometry @db.Geometry(MultiPointM, 4326) + geomcol24 Geometry @db.Geometry(MultiPointZM, 4326) + geomcol25 Geometry @db.Geometry(MultiLineString, 4326) + geomcol26 Geometry @db.Geometry(MultiLineStringZ, 4326) + geomcol27 Geometry @db.Geometry(MultiLineStringM, 4326) + geomcol28 Geometry @db.Geometry(MultiLineStringZM, 4326) + geomcol29 Geometry @db.Geometry(MultiPolygon, 4326) + geomcol30 Geometry @db.Geometry(MultiPolygonZ, 4326) + geomcol31 Geometry @db.Geometry(MultiPolygonM, 4326) + geomcol32 Geometry @db.Geometry(MultiPolygonZM, 4326) + geomcol33 Geometry @db.Geometry(GeometryCollection, 4326) + geomcol34 Geometry @db.Geometry(GeometryCollectionZ, 4326) + geomcol35 Geometry @db.Geometry(GeometryCollectionM, 4326) + geomcol36 Geometry @db.Geometry(GeometryCollectionZM, 4326) + } + "#}; + + psl::parse_schema(schema).unwrap(); +} + +#[test] +fn should_fail_on_geojson_when_invalid_geometry_type() { + let dml = indoc! {r#" + datasource db { + provider = "sqlite" + url = "file:test.db" + } + + model Blog { + id Int @id + geom Geometry @db.Geometry(Invalid) + } + "#}; + + let expectation = expect![[r#" + error: Expected a geometry type and an optional srid, but found (Invalid). + --> schema.prisma:8 +  |  +  7 |  id Int @id +  8 |  geom Geometry @db.Geometry(Invalid) +  |  + "#]]; + + expect_error(dml, &expectation); +} + +#[test] +fn should_fail_on_geojson_when_non_wgs84_srid() { + let schema = indoc! {r#" + datasource db { + provider = "sqlite" + url = "file:test.db" + } + + model User { + id Int @id + geom GeoJson @db.Geometry(Point, 3857) + } + "#}; + + let expectation = expect![[r#" + error: Argument M is out of range for native type `Geometry(Point,3857)` of sqlite: GeoJson SRID must be 4326. + --> schema.prisma:8 +  |  +  7 |  id Int @id +  8 |  geom GeoJson @db.Geometry(Point, 3857) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn should_fail_on_geometry_when_out_of_bound_srid() { + let schema = indoc! {r#" + datasource db { + provider = "sqlite" + url = "file:test.db" + } + + model User { + id Int @id + geom Geometry @db.Geometry(Point, -2) + } + "#}; + + let expectation = expect![[r#" + error: Argument M is out of range for native type `Geometry(Point,-2)` of sqlite: SRID must be superior or equal to -1. + --> schema.prisma:8 +  |  +  7 |  id Int @id +  8 |  geom Geometry @db.Geometry(Point, -2) +  |  + "#]]; + + expect_error(schema, &expectation); +} + +#[test] +fn should_fail_on_geometry_when_extra_geometry_type() { + let schema = indoc! {r#" + datasource db { + provider = "sqlite" + url = "file:test.db" + } + + model User { + id Int @id + geom_00 Geometry @db.Geometry(CircularString, 4326) + geom_01 Geometry @db.Geometry(CircularStringZ, 4326) + geom_02 Geometry @db.Geometry(CircularStringM, 4326) + geom_03 Geometry @db.Geometry(CircularStringZM, 4326) + geom_04 Geometry @db.Geometry(CompoundCurve, 4326) + geom_05 Geometry @db.Geometry(CompoundCurveZ, 4326) + geom_06 Geometry @db.Geometry(CompoundCurveM, 4326) + geom_07 Geometry @db.Geometry(CompoundCurveZM, 4326) + geom_08 Geometry @db.Geometry(CurvePolygon, 4326) + geom_09 Geometry @db.Geometry(CurvePolygonZ, 4326) + geom_10 Geometry @db.Geometry(CurvePolygonM, 4326) + geom_11 Geometry @db.Geometry(CurvePolygonZM, 4326) + geom_12 Geometry @db.Geometry(MultiCurve, 4326) + geom_13 Geometry @db.Geometry(MultiCurveZ, 4326) + geom_14 Geometry @db.Geometry(MultiCurveM, 4326) + geom_15 Geometry @db.Geometry(MultiCurveZM, 4326) + geom_16 Geometry @db.Geometry(MultiSurface, 4326) + geom_17 Geometry @db.Geometry(MultiSurfaceZ, 4326) + geom_18 Geometry @db.Geometry(MultiSurfaceM, 4326) + geom_19 Geometry @db.Geometry(MultiSurfaceZM, 4326) + geom_20 Geometry @db.Geometry(PolyhedralSurface, 4326) + geom_21 Geometry @db.Geometry(PolyhedralSurfaceZ, 4326) + geom_22 Geometry @db.Geometry(PolyhedralSurfaceM, 4326) + geom_23 Geometry @db.Geometry(PolyhedralSurfaceZM, 4326) + geog_00 Geometry @db.Geography(CircularString, 4326) + geog_01 Geometry @db.Geography(CircularStringZ, 4326) + geog_02 Geometry @db.Geography(CircularStringM, 4326) + geog_03 Geometry @db.Geography(CircularStringZM, 4326) + geog_04 Geometry @db.Geography(CompoundCurve, 4326) + geog_05 Geometry @db.Geography(CompoundCurveZ, 4326) + geog_06 Geometry @db.Geography(CompoundCurveM, 4326) + geog_07 Geometry @db.Geography(CompoundCurveZM, 4326) + geog_08 Geometry @db.Geography(CurvePolygon, 4326) + geog_09 Geometry @db.Geography(CurvePolygonZ, 4326) + geog_10 Geometry @db.Geography(CurvePolygonM, 4326) + geog_11 Geometry @db.Geography(CurvePolygonZM, 4326) + geog_12 Geometry @db.Geography(MultiCurve, 4326) + geog_13 Geometry @db.Geography(MultiCurveZ, 4326) + geog_14 Geometry @db.Geography(MultiCurveM, 4326) + geog_15 Geometry @db.Geography(MultiCurveZM, 4326) + geog_16 Geometry @db.Geography(MultiSurface, 4326) + geog_17 Geometry @db.Geography(MultiSurfaceZ, 4326) + geog_18 Geometry @db.Geography(MultiSurfaceM, 4326) + geog_19 Geometry @db.Geography(MultiSurfaceZM, 4326) + geog_20 Geometry @db.Geography(PolyhedralSurface, 4326) + geog_21 Geometry @db.Geography(PolyhedralSurfaceZ, 4326) + geog_22 Geometry @db.Geography(PolyhedralSurfaceM, 4326) + geog_23 Geometry @db.Geography(PolyhedralSurfaceZM, 4326) + } + "#}; + + let expectation = expect![[r#" + error: Argument M is out of range for native type `Geometry(CircularString,4326)` of sqlite: CircularString isn't supported for the current connector. + --> schema.prisma:8 +  |  +  7 |  id Int @id +  8 |  geom_00 Geometry @db.Geometry(CircularString, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CircularStringZ,4326)` of sqlite: CircularStringZ isn't supported for the current connector. + --> schema.prisma:9 +  |  +  8 |  geom_00 Geometry @db.Geometry(CircularString, 4326) +  9 |  geom_01 Geometry @db.Geometry(CircularStringZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CircularStringM,4326)` of sqlite: CircularStringM isn't supported for the current connector. + --> schema.prisma:10 +  |  +  9 |  geom_01 Geometry @db.Geometry(CircularStringZ, 4326) + 10 |  geom_02 Geometry @db.Geometry(CircularStringM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CircularStringZM,4326)` of sqlite: CircularStringZM isn't supported for the current connector. + --> schema.prisma:11 +  |  + 10 |  geom_02 Geometry @db.Geometry(CircularStringM, 4326) + 11 |  geom_03 Geometry @db.Geometry(CircularStringZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CompoundCurve,4326)` of sqlite: CompoundCurve isn't supported for the current connector. + --> schema.prisma:12 +  |  + 11 |  geom_03 Geometry @db.Geometry(CircularStringZM, 4326) + 12 |  geom_04 Geometry @db.Geometry(CompoundCurve, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CompoundCurveZ,4326)` of sqlite: CompoundCurveZ isn't supported for the current connector. + --> schema.prisma:13 +  |  + 12 |  geom_04 Geometry @db.Geometry(CompoundCurve, 4326) + 13 |  geom_05 Geometry @db.Geometry(CompoundCurveZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CompoundCurveM,4326)` of sqlite: CompoundCurveM isn't supported for the current connector. + --> schema.prisma:14 +  |  + 13 |  geom_05 Geometry @db.Geometry(CompoundCurveZ, 4326) + 14 |  geom_06 Geometry @db.Geometry(CompoundCurveM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CompoundCurveZM,4326)` of sqlite: CompoundCurveZM isn't supported for the current connector. + --> schema.prisma:15 +  |  + 14 |  geom_06 Geometry @db.Geometry(CompoundCurveM, 4326) + 15 |  geom_07 Geometry @db.Geometry(CompoundCurveZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CurvePolygon,4326)` of sqlite: CurvePolygon isn't supported for the current connector. + --> schema.prisma:16 +  |  + 15 |  geom_07 Geometry @db.Geometry(CompoundCurveZM, 4326) + 16 |  geom_08 Geometry @db.Geometry(CurvePolygon, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CurvePolygonZ,4326)` of sqlite: CurvePolygonZ isn't supported for the current connector. + --> schema.prisma:17 +  |  + 16 |  geom_08 Geometry @db.Geometry(CurvePolygon, 4326) + 17 |  geom_09 Geometry @db.Geometry(CurvePolygonZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CurvePolygonM,4326)` of sqlite: CurvePolygonM isn't supported for the current connector. + --> schema.prisma:18 +  |  + 17 |  geom_09 Geometry @db.Geometry(CurvePolygonZ, 4326) + 18 |  geom_10 Geometry @db.Geometry(CurvePolygonM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(CurvePolygonZM,4326)` of sqlite: CurvePolygonZM isn't supported for the current connector. + --> schema.prisma:19 +  |  + 18 |  geom_10 Geometry @db.Geometry(CurvePolygonM, 4326) + 19 |  geom_11 Geometry @db.Geometry(CurvePolygonZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiCurve,4326)` of sqlite: MultiCurve isn't supported for the current connector. + --> schema.prisma:20 +  |  + 19 |  geom_11 Geometry @db.Geometry(CurvePolygonZM, 4326) + 20 |  geom_12 Geometry @db.Geometry(MultiCurve, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiCurveZ,4326)` of sqlite: MultiCurveZ isn't supported for the current connector. + --> schema.prisma:21 +  |  + 20 |  geom_12 Geometry @db.Geometry(MultiCurve, 4326) + 21 |  geom_13 Geometry @db.Geometry(MultiCurveZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiCurveM,4326)` of sqlite: MultiCurveM isn't supported for the current connector. + --> schema.prisma:22 +  |  + 21 |  geom_13 Geometry @db.Geometry(MultiCurveZ, 4326) + 22 |  geom_14 Geometry @db.Geometry(MultiCurveM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiCurveZM,4326)` of sqlite: MultiCurveZM isn't supported for the current connector. + --> schema.prisma:23 +  |  + 22 |  geom_14 Geometry @db.Geometry(MultiCurveM, 4326) + 23 |  geom_15 Geometry @db.Geometry(MultiCurveZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiSurface,4326)` of sqlite: MultiSurface isn't supported for the current connector. + --> schema.prisma:24 +  |  + 23 |  geom_15 Geometry @db.Geometry(MultiCurveZM, 4326) + 24 |  geom_16 Geometry @db.Geometry(MultiSurface, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiSurfaceZ,4326)` of sqlite: MultiSurfaceZ isn't supported for the current connector. + --> schema.prisma:25 +  |  + 24 |  geom_16 Geometry @db.Geometry(MultiSurface, 4326) + 25 |  geom_17 Geometry @db.Geometry(MultiSurfaceZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiSurfaceM,4326)` of sqlite: MultiSurfaceM isn't supported for the current connector. + --> schema.prisma:26 +  |  + 25 |  geom_17 Geometry @db.Geometry(MultiSurfaceZ, 4326) + 26 |  geom_18 Geometry @db.Geometry(MultiSurfaceM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(MultiSurfaceZM,4326)` of sqlite: MultiSurfaceZM isn't supported for the current connector. + --> schema.prisma:27 +  |  + 26 |  geom_18 Geometry @db.Geometry(MultiSurfaceM, 4326) + 27 |  geom_19 Geometry @db.Geometry(MultiSurfaceZM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(PolyhedralSurface,4326)` of sqlite: PolyhedralSurface isn't supported for the current connector. + --> schema.prisma:28 +  |  + 27 |  geom_19 Geometry @db.Geometry(MultiSurfaceZM, 4326) + 28 |  geom_20 Geometry @db.Geometry(PolyhedralSurface, 4326) +  |  + error: Argument M is out of range for native type `Geometry(PolyhedralSurfaceZ,4326)` of sqlite: PolyhedralSurfaceZ isn't supported for the current connector. + --> schema.prisma:29 +  |  + 28 |  geom_20 Geometry @db.Geometry(PolyhedralSurface, 4326) + 29 |  geom_21 Geometry @db.Geometry(PolyhedralSurfaceZ, 4326) +  |  + error: Argument M is out of range for native type `Geometry(PolyhedralSurfaceM,4326)` of sqlite: PolyhedralSurfaceM isn't supported for the current connector. + --> schema.prisma:30 +  |  + 29 |  geom_21 Geometry @db.Geometry(PolyhedralSurfaceZ, 4326) + 30 |  geom_22 Geometry @db.Geometry(PolyhedralSurfaceM, 4326) +  |  + error: Argument M is out of range for native type `Geometry(PolyhedralSurfaceZM,4326)` of sqlite: PolyhedralSurfaceZM isn't supported for the current connector. + --> schema.prisma:31 +  |  + 30 |  geom_22 Geometry @db.Geometry(PolyhedralSurfaceM, 4326) + 31 |  geom_23 Geometry @db.Geometry(PolyhedralSurfaceZM, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:32 +  |  + 31 |  geom_23 Geometry @db.Geometry(PolyhedralSurfaceZM, 4326) + 32 |  geog_00 Geometry @db.Geography(CircularString, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:33 +  |  + 32 |  geog_00 Geometry @db.Geography(CircularString, 4326) + 33 |  geog_01 Geometry @db.Geography(CircularStringZ, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:34 +  |  + 33 |  geog_01 Geometry @db.Geography(CircularStringZ, 4326) + 34 |  geog_02 Geometry @db.Geography(CircularStringM, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:35 +  |  + 34 |  geog_02 Geometry @db.Geography(CircularStringM, 4326) + 35 |  geog_03 Geometry @db.Geography(CircularStringZM, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:36 +  |  + 35 |  geog_03 Geometry @db.Geography(CircularStringZM, 4326) + 36 |  geog_04 Geometry @db.Geography(CompoundCurve, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:37 +  |  + 36 |  geog_04 Geometry @db.Geography(CompoundCurve, 4326) + 37 |  geog_05 Geometry @db.Geography(CompoundCurveZ, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:38 +  |  + 37 |  geog_05 Geometry @db.Geography(CompoundCurveZ, 4326) + 38 |  geog_06 Geometry @db.Geography(CompoundCurveM, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:39 +  |  + 38 |  geog_06 Geometry @db.Geography(CompoundCurveM, 4326) + 39 |  geog_07 Geometry @db.Geography(CompoundCurveZM, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:40 +  |  + 39 |  geog_07 Geometry @db.Geography(CompoundCurveZM, 4326) + 40 |  geog_08 Geometry @db.Geography(CurvePolygon, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:41 +  |  + 40 |  geog_08 Geometry @db.Geography(CurvePolygon, 4326) + 41 |  geog_09 Geometry @db.Geography(CurvePolygonZ, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:42 +  |  + 41 |  geog_09 Geometry @db.Geography(CurvePolygonZ, 4326) + 42 |  geog_10 Geometry @db.Geography(CurvePolygonM, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:43 +  |  + 42 |  geog_10 Geometry @db.Geography(CurvePolygonM, 4326) + 43 |  geog_11 Geometry @db.Geography(CurvePolygonZM, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:44 +  |  + 43 |  geog_11 Geometry @db.Geography(CurvePolygonZM, 4326) + 44 |  geog_12 Geometry @db.Geography(MultiCurve, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:45 +  |  + 44 |  geog_12 Geometry @db.Geography(MultiCurve, 4326) + 45 |  geog_13 Geometry @db.Geography(MultiCurveZ, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:46 +  |  + 45 |  geog_13 Geometry @db.Geography(MultiCurveZ, 4326) + 46 |  geog_14 Geometry @db.Geography(MultiCurveM, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:47 +  |  + 46 |  geog_14 Geometry @db.Geography(MultiCurveM, 4326) + 47 |  geog_15 Geometry @db.Geography(MultiCurveZM, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:48 +  |  + 47 |  geog_15 Geometry @db.Geography(MultiCurveZM, 4326) + 48 |  geog_16 Geometry @db.Geography(MultiSurface, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:49 +  |  + 48 |  geog_16 Geometry @db.Geography(MultiSurface, 4326) + 49 |  geog_17 Geometry @db.Geography(MultiSurfaceZ, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:50 +  |  + 49 |  geog_17 Geometry @db.Geography(MultiSurfaceZ, 4326) + 50 |  geog_18 Geometry @db.Geography(MultiSurfaceM, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:51 +  |  + 50 |  geog_18 Geometry @db.Geography(MultiSurfaceM, 4326) + 51 |  geog_19 Geometry @db.Geography(MultiSurfaceZM, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:52 +  |  + 51 |  geog_19 Geometry @db.Geography(MultiSurfaceZM, 4326) + 52 |  geog_20 Geometry @db.Geography(PolyhedralSurface, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:53 +  |  + 52 |  geog_20 Geometry @db.Geography(PolyhedralSurface, 4326) + 53 |  geog_21 Geometry @db.Geography(PolyhedralSurfaceZ, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:54 +  |  + 53 |  geog_21 Geometry @db.Geography(PolyhedralSurfaceZ, 4326) + 54 |  geog_22 Geometry @db.Geography(PolyhedralSurfaceM, 4326) +  |  + error: Native type Geography is not supported for sqlite connector. + --> schema.prisma:55 +  |  + 54 |  geog_22 Geometry @db.Geography(PolyhedralSurfaceM, 4326) + 55 |  geog_23 Geometry @db.Geography(PolyhedralSurfaceZM, 4326) +  |  + "#]]; + + expect_error(schema, &expectation); +} diff --git a/psl/psl/tests/validation/types/mongodb/invalid_json_usage_in_type.prisma b/psl/psl/tests/validation/types/mongodb/invalid_json_usage_in_type.prisma index dfd6517c2e9f..016b4035b553 100644 --- a/psl/psl/tests/validation/types/mongodb/invalid_json_usage_in_type.prisma +++ b/psl/psl/tests/validation/types/mongodb/invalid_json_usage_in_type.prisma @@ -17,37 +17,37 @@ model A { b B } -// error: Native type Json is not compatible with declared field type Int, expected field type Json. +// error: Native type Json is not compatible with declared field type Int, expected field type Json or GeoJson. // --> schema.prisma:7 //  |  //  6 | type B { //  7 |  a Int @test.Json //  |  -// error: Native type Json is not compatible with declared field type Float, expected field type Json. +// error: Native type Json is not compatible with declared field type Float, expected field type Json or GeoJson. // --> schema.prisma:8 //  |  //  7 |  a Int @test.Json //  8 |  b Float @test.Json //  |  -// error: Native type Json is not compatible with declared field type Bytes, expected field type Json. +// error: Native type Json is not compatible with declared field type Bytes, expected field type Json or GeoJson. // --> schema.prisma:9 //  |  //  8 |  b Float @test.Json //  9 |  c Bytes @test.Json //  |  -// error: Native type Json is not compatible with declared field type Boolean, expected field type Json. +// error: Native type Json is not compatible with declared field type Boolean, expected field type Json or GeoJson. // --> schema.prisma:10 //  |  //  9 |  c Bytes @test.Json // 10 |  d Boolean @test.Json //  |  -// error: Native type Json is not compatible with declared field type DateTime, expected field type Json. +// error: Native type Json is not compatible with declared field type DateTime, expected field type Json or GeoJson. // --> schema.prisma:11 //  |  // 10 |  d Boolean @test.Json // 11 |  e DateTime @test.Json //  |  -// error: Native type Json is not compatible with declared field type Decimal, expected field type Json. +// error: Native type Json is not compatible with declared field type Decimal, expected field type Json or GeoJson. // --> schema.prisma:12 //  |  // 11 |  e DateTime @test.Json diff --git a/quaint/Cargo.toml b/quaint/Cargo.toml index 52a7edf72aca..bd3451cc53a4 100644 --- a/quaint/Cargo.toml +++ b/quaint/Cargo.toml @@ -29,41 +29,16 @@ docs = [] # way to access database-specific methods when you need extra control. expose-drivers = [] -native = [ - "postgresql-native", - "mysql-native", - "mssql-native", - "sqlite-native", -] +native = ["postgresql-native", "mysql-native", "mssql-native", "sqlite-native"] all = ["native", "pooled"] -vendored-openssl = [ - "postgres-native-tls/vendored-openssl", - "mysql_async/vendored-openssl", -] +vendored-openssl = ["postgres-native-tls/vendored-openssl", "mysql_async/vendored-openssl"] -postgresql-native = [ - "postgresql", - "native-tls", - "tokio-postgres", - "postgres-types", - "postgres-native-tls", - "bytes", - "tokio", - "bit-vec", - "lru-cache", - "byteorder", -] +postgresql-native = ["postgresql", "native-tls", "tokio-postgres", "postgres-types", "postgres-native-tls", "bytes", "tokio", "bit-vec", "lru-cache", "byteorder"] postgresql = [] -mssql-native = [ - "mssql", - "tiberius", - "tokio-util", - "tokio/time", - "tokio/net", -] +mssql-native = ["mssql", "tiberius", "tokio-util", "tokio/time", "tokio/net"] mssql = [] mysql-native = ["mysql", "mysql_async", "tokio/time", "lru-cache"] @@ -100,6 +75,9 @@ mobc = { version = "0.8", optional = true } serde = { version = "1.0", optional = true } sqlformat = { version = "0.2.0", optional = true } uuid = { version = "1", features = ["v4"] } +once_cell = "1.3" +regex = "1.10.2" +geozero = { version = "0.11.0", default-features = false, features = ["with-wkb", "with-geojson"] } [dev-dependencies] once_cell = "1.3" @@ -126,8 +104,10 @@ optional = true branch = "vendored-openssl" [dependencies.rusqlite] +# git = "https://github.com/rusqlite/rusqlite" +# rev = "714ce2e17117b2d46485aa12479f8e1802b78ba0" version = "0.29" -features = ["chrono", "column_decltype"] +features = ["chrono", "column_decltype", "load_extension"] optional = true [target.'cfg(not(any(target_os = "macos", target_os = "ios")))'.dependencies.tiberius] diff --git a/quaint/src/ast.rs b/quaint/src/ast.rs index dc634423014a..ccb94b9b5b61 100644 --- a/quaint/src/ast.rs +++ b/quaint/src/ast.rs @@ -31,7 +31,7 @@ mod update; mod values; pub use column::{Column, DefaultValue, TypeDataLength, TypeFamily}; -pub use compare::{Comparable, Compare, JsonCompare, JsonType}; +pub use compare::{Comparable, Compare, GeometryCompare, GeometryType, JsonCompare, JsonType}; pub use conditions::ConditionTree; pub use conjunctive::Conjunctive; pub use cte::{CommonTableExpression, IntoCommonTableExpression}; @@ -53,5 +53,6 @@ pub use select::Select; pub use table::*; pub use union::Union; pub use update::*; +pub use values::GeometryValue; pub(crate) use values::Params; pub use values::{IntoRaw, Raw, Value, ValueType, Values}; diff --git a/quaint/src/ast/column.rs b/quaint/src/ast/column.rs index 836b4ce96527..117610a0fbe2 100644 --- a/quaint/src/ast/column.rs +++ b/quaint/src/ast/column.rs @@ -20,6 +20,8 @@ pub enum TypeFamily { Boolean, Uuid, DateTime, + Geometry(Option), + Geography(Option), Decimal(Option<(u8, u8)>), Bytes(Option), } @@ -29,9 +31,9 @@ pub enum TypeFamily { pub struct Column<'a> { pub name: Cow<'a, str>, pub(crate) table: Option>, - pub(crate) alias: Option>, + pub alias: Option>, pub(crate) default: Option>, - pub(crate) type_family: Option, + pub type_family: Option, /// Whether the column is an enum. pub(crate) is_enum: bool, /// Whether the column is a (scalar) list. diff --git a/quaint/src/ast/compare.rs b/quaint/src/ast/compare.rs index 9c7548303466..8395bb45b389 100644 --- a/quaint/src/ast/compare.rs +++ b/quaint/src/ast/compare.rs @@ -1,6 +1,6 @@ use super::ExpressionKind; use crate::ast::{Column, ConditionTree, Expression}; -use std::borrow::Cow; +use std::{borrow::Cow, fmt}; /// For modeling comparison expressions. #[derive(Debug, Clone, PartialEq)] @@ -39,6 +39,8 @@ pub enum Compare<'a> { /// All json related comparators #[cfg(any(feature = "postgresql", feature = "mysql"))] JsonCompare(JsonCompare<'a>), + /// All geometry related comparators + GeometryCompare(GeometryCompare<'a>), /// `left` @@ to_tsquery(`value`) #[cfg(feature = "postgresql")] Matches(Box>, Cow<'a, str>), @@ -53,6 +55,69 @@ pub enum Compare<'a> { All(Box>), } +#[derive(Debug, Clone, PartialEq)] +pub enum GeometryCompare<'a> { + Empty(Box>), + NotEmpty(Box>), + Valid(Box>), + NotValid(Box>), + Within(Box>, Box>), + NotWithin(Box>, Box>), + Intersects(Box>, Box>), + NotIntersects(Box>, Box>), + TypeEquals(Box>, GeometryType<'a>), + TypeNotEquals(Box>, GeometryType<'a>), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum GeometryType<'a> { + Point, + LineString, + CircularString, + CompoundCurve, + Polygon, + CurvePolygon, + Triangle, + Tin, + MultiPoint, + MultiLineString, + MultiCurve, + MultiPolygon, + MultiSurface, + PolyhedralSurface, + GeometryCollection, + ColumnRef(Box>), +} + +impl<'a> From> for GeometryType<'a> { + fn from(col: Column<'a>) -> Self { + GeometryType::ColumnRef(Box::new(col)) + } +} + +impl fmt::Display for GeometryType<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Point => f.write_str("Point"), + Self::LineString => f.write_str("LineString"), + Self::CircularString => f.write_str("CircularString"), + Self::CompoundCurve => f.write_str("CompoundCurve"), + Self::Polygon => f.write_str("Polygon"), + Self::CurvePolygon => f.write_str("CurvePolygon"), + Self::Triangle => f.write_str("Triangle"), + Self::Tin => f.write_str("Tin"), + Self::MultiPoint => f.write_str("MultiPoint"), + Self::MultiLineString => f.write_str("MultiLineString"), + Self::MultiCurve => f.write_str("MultiCurve"), + Self::MultiPolygon => f.write_str("MultiPolygon"), + Self::MultiSurface => f.write_str("MultiSurface"), + Self::PolyhedralSurface => f.write_str("PolyhedralSurface"), + Self::GeometryCollection => f.write_str("GeometryCollection"), + Self::ColumnRef(_) => f.write_str(""), + } + } +} + #[derive(Debug, Clone, PartialEq)] pub enum JsonCompare<'a> { ArrayContains(Box>, Box>), @@ -737,6 +802,186 @@ pub trait Comparable<'a> { where T: Into>; + /// Tests if the geometry value is empty. + /// + /// ```rust + /// # use quaint::{ast::*, visitor::{Visitor, Sqlite}}; + /// # fn main() -> Result<(), quaint::error::Error> { + /// let query = Select::from_table("users").so_that("geom".geometry_is_empty()); + /// let (sql, _) = Sqlite::build(query)?; + /// + /// assert_eq!("SELECT `users`.* FROM `users` WHERE ST_IsEmpty(`geom`)", sql); + /// # Ok(()) + /// # } + /// ``` + fn geometry_is_empty(self) -> Compare<'a>; + + /// Tests if the geometry value is not empty. + /// + /// ```rust + /// # use quaint::{ast::*, visitor::{Visitor, Sqlite}}; + /// # fn main() -> Result<(), quaint::error::Error> { + /// let query = Select::from_table("users").so_that("geom".geometry_is_not_empty()); + /// let (sql, _) = Sqlite::build(query)?; + /// + /// assert_eq!("SELECT `users`.* FROM `users` WHERE NOT ST_IsEmpty(`geom`)", sql); + /// # Ok(()) + /// # } + /// ``` + fn geometry_is_not_empty(self) -> Compare<'a>; + + /// Tests if the geometry value is valid. + /// + /// ```rust + /// # use quaint::{ast::*, visitor::{Visitor, Sqlite}}; + /// # fn main() -> Result<(), quaint::error::Error> { + /// let query = Select::from_table("users").so_that("geom".geometry_is_valid()); + /// let (sql, _) = Sqlite::build(query)?; + /// + /// assert_eq!("SELECT `users`.* FROM `users` WHERE ST_IsValid(`geom`)", sql); + /// # Ok(()) + /// # } + /// ``` + fn geometry_is_valid(self) -> Compare<'a>; + + /// Tests if the geometry value is not valid. + /// + /// ```rust + /// # use quaint::{ast::*, visitor::{Visitor, Sqlite}}; + /// # fn main() -> Result<(), quaint::error::Error> { + /// let query = Select::from_table("users").so_that("geom".geometry_is_not_valid()); + /// let (sql, _) = Sqlite::build(query)?; + /// + /// assert_eq!("SELECT `users`.* FROM `users` WHERE NOT ST_IsValid(`geom`)", sql); + /// # Ok(()) + /// # } + /// ``` + fn geometry_is_not_valid(self) -> Compare<'a>; + + /// Tests if the left side geometry contains the right side geometry. + /// + /// ```rust + /// # use quaint::{ast::*, visitor::{Visitor, Sqlite}}; + /// # fn main() -> Result<(), quaint::error::Error> { + /// let query = Select::from_table("users").so_that("geom".geometry_within(geom_from_text("POINT(0 0)", 4326, false))); + /// let (sql, params) = Sqlite::build(query)?; + /// + /// assert_eq!("SELECT `users`.* FROM `users` WHERE ST_Within(`geom`,ST_GeomFromText(?,?))", sql); + /// + /// assert_eq!(vec![ + /// Value::from("POINT(0 0)"), + /// Value::from(4326) + /// ], params); + /// + /// # Ok(()) + /// # } + /// ``` + fn geometry_within(self, geom: T) -> Compare<'a> + where + T: Into>; + + /// Tests if the left side geometry doesn't contain the right side geometry. + /// + /// ```rust + /// # use quaint::{ast::*, visitor::{Visitor, Sqlite}}; + /// # fn main() -> Result<(), quaint::error::Error> { + /// let query = Select::from_table("users").so_that("geom".geometry_not_within(geom_from_text("POINT(0 0)", 4326, false))); + /// let (sql, params) = Sqlite::build(query)?; + /// + /// assert_eq!("SELECT `users`.* FROM `users` WHERE NOT ST_Within(`geom`,ST_GeomFromText(?,?))", sql); + /// + /// assert_eq!(vec![ + /// Value::from("POINT(0 0)"), + /// Value::from(4326) + /// ], params); + /// + /// # Ok(()) + /// # } + /// ``` + fn geometry_not_within(self, geom: T) -> Compare<'a> + where + T: Into>; + + /// Tests if the left side geometry intersects the right side geometry. + /// + /// ```rust + /// # use quaint::{ast::*, visitor::{Visitor, Sqlite}}; + /// # fn main() -> Result<(), quaint::error::Error> { + /// let query = Select::from_table("users").so_that("geom".geometry_intersects(geom_from_text("POINT(0 0)", 4326, false))); + /// let (sql, params) = Sqlite::build(query)?; + /// + /// assert_eq!("SELECT `users`.* FROM `users` WHERE ST_Intersects(`geom`,ST_GeomFromText(?,?))", sql); + /// + /// assert_eq!(vec![ + /// Value::from("POINT(0 0)"), + /// Value::from(4326) + /// ], params); + /// + /// # Ok(()) + /// # } + /// ``` + fn geometry_intersects(self, geom: T) -> Compare<'a> + where + T: Into>; + + /// Tests if the left side geometry doesn't intersect the right side geometry. + /// + /// ```rust + /// # use quaint::{ast::*, visitor::{Visitor, Sqlite}}; + /// # fn main() -> Result<(), quaint::error::Error> { + /// let query = Select::from_table("users").so_that("geom".geometry_not_intersects(geom_from_text("POINT(0 0)", 4326, false))); + /// let (sql, params) = Sqlite::build(query)?; + /// + /// assert_eq!("SELECT `users`.* FROM `users` WHERE NOT ST_Intersects(`geom`,ST_GeomFromText(?,?))", sql); + /// + /// assert_eq!(vec![ + /// Value::from("POINT(0 0)"), + /// Value::from(4326) + /// ], params); + /// + /// # Ok(()) + /// # } + /// ``` + fn geometry_not_intersects(self, geom: T) -> Compare<'a> + where + T: Into>; + + /// Tests if the geometry value is of a certain type. + /// + /// ```rust + /// # use quaint::{ast::*, visitor::{Visitor, Sqlite}}; + /// # fn main() -> Result<(), quaint::error::Error> { + /// let query = Select::from_table("users").so_that("geom".geometry_type_equals(GeometryType::Point)); + /// let (sql, params) = Sqlite::build(query)?; + /// + /// assert_eq!("SELECT `users`.* FROM `users` WHERE ST_GeometryType(`geom`) = ?", sql); + /// + /// assert_eq!(vec![Value::from("POINT")], params); + /// # Ok(()) + /// # } + /// ``` + fn geometry_type_equals(self, geom_type: T) -> Compare<'a> + where + T: Into>; + + /// Tests if the geometry value is not of a certain type. + /// + /// ```rust + /// # use quaint::{ast::*, visitor::{Visitor, Sqlite}}; + /// # fn main() -> Result<(), quaint::error::Error> { + /// let query = Select::from_table("users").so_that("geom".geometry_type_not_equals(GeometryType::Point)); + /// let (sql, params) = Sqlite::build(query)?; + /// + /// assert_eq!("SELECT `users`.* FROM `users` WHERE ST_GeometryType(`geom`) != ?", sql); + /// + /// assert_eq!(vec![Value::from("POINT")], params); + /// # Ok(()) + /// # } + /// ``` + fn geometry_type_not_equals(self, geom_type: T) -> Compare<'a> + where + T: Into>; + /// Tests if a full-text search matches a certain query. Use it in combination with the `text_search()` function /// /// ```rust @@ -1065,6 +1310,88 @@ where val.json_type_not_equals(json_type) } + #[allow(clippy::wrong_self_convention)] + fn geometry_is_empty(self) -> Compare<'a> { + let col: Column<'a> = self.into(); + let val: Expression<'a> = col.into(); + val.geometry_is_empty() + } + + #[allow(clippy::wrong_self_convention)] + fn geometry_is_not_empty(self) -> Compare<'a> { + let col: Column<'a> = self.into(); + let val: Expression<'a> = col.into(); + val.geometry_is_not_empty() + } + + #[allow(clippy::wrong_self_convention)] + fn geometry_is_valid(self) -> Compare<'a> { + let col: Column<'a> = self.into(); + let val: Expression<'a> = col.into(); + val.geometry_is_valid() + } + + #[allow(clippy::wrong_self_convention)] + fn geometry_is_not_valid(self) -> Compare<'a> { + let col: Column<'a> = self.into(); + let val: Expression<'a> = col.into(); + val.geometry_is_not_valid() + } + + fn geometry_within(self, geom: T) -> Compare<'a> + where + T: Into>, + { + let col: Column<'a> = self.into(); + let val: Expression<'a> = col.into(); + val.geometry_within(geom) + } + + fn geometry_not_within(self, geom: T) -> Compare<'a> + where + T: Into>, + { + let col: Column<'a> = self.into(); + let val: Expression<'a> = col.into(); + val.geometry_not_within(geom) + } + + fn geometry_intersects(self, geom: T) -> Compare<'a> + where + T: Into>, + { + let col: Column<'a> = self.into(); + let val: Expression<'a> = col.into(); + val.geometry_intersects(geom) + } + + fn geometry_not_intersects(self, geom: T) -> Compare<'a> + where + T: Into>, + { + let col: Column<'a> = self.into(); + let val: Expression<'a> = col.into(); + val.geometry_not_intersects(geom) + } + + fn geometry_type_equals(self, geom_type: T) -> Compare<'a> + where + T: Into>, + { + let col: Column<'a> = self.into(); + let val: Expression<'a> = col.into(); + val.geometry_type_equals(geom_type) + } + + fn geometry_type_not_equals(self, geom_type: T) -> Compare<'a> + where + T: Into>, + { + let col: Column<'a> = self.into(); + let val: Expression<'a> = col.into(); + val.geometry_type_not_equals(geom_type) + } + #[cfg(feature = "postgresql")] fn matches(self, query: T) -> Compare<'a> where diff --git a/quaint/src/ast/expression.rs b/quaint/src/ast/expression.rs index ea4c32a4fb61..4c5e5200605b 100644 --- a/quaint/src/ast/expression.rs +++ b/quaint/src/ast/expression.rs @@ -1,3 +1,4 @@ +use super::compare::{GeometryCompare, GeometryType}; #[cfg(any(feature = "postgresql", feature = "mysql"))] use super::compare::{JsonCompare, JsonType}; use crate::ast::*; @@ -98,6 +99,11 @@ impl<'a> Expression<'a> { self.kind.is_xml_value() } + #[allow(dead_code)] + pub(crate) fn is_geometry_expr(&self) -> bool { + self.kind.is_geometry_expr() + } + #[allow(dead_code)] pub fn is_asterisk(&self) -> bool { matches!(self.kind, ExpressionKind::Asterisk(_)) @@ -234,6 +240,32 @@ impl<'a> ExpressionKind<'a> { _ => false, } } + pub(crate) fn is_geometry_expr(&self) -> bool { + match self { + Self::Parameterized(Value { + typed: ValueType::Geometry(_), + .. + }) => true, + Self::Parameterized(Value { + typed: ValueType::Geography(_), + .. + }) => true, + Self::RawValue(Raw(Value { + typed: ValueType::Geometry(_), + .. + })) => true, + Self::RawValue(Raw(Value { + typed: ValueType::Geography(_), + .. + })) => true, + Self::Column(c) if matches!(c.type_family, Some(TypeFamily::Geography(_) | TypeFamily::Geometry(_))) => { + true + } + Self::Function(f) => f.returns_geometry(), + Self::Value(expr) => expr.is_geometry_expr(), + _ => false, + } + } } /// A quick alias to create an asterisk to a table. @@ -507,6 +539,64 @@ impl<'a> Comparable<'a> for Expression<'a> { Compare::JsonCompare(JsonCompare::TypeNotEquals(Box::new(self), json_type.into())) } + fn geometry_is_empty(self) -> Compare<'a> { + Compare::GeometryCompare(GeometryCompare::Empty(Box::new(self))) + } + + fn geometry_is_not_empty(self) -> Compare<'a> { + Compare::GeometryCompare(GeometryCompare::NotEmpty(Box::new(self))) + } + + fn geometry_is_valid(self) -> Compare<'a> { + Compare::GeometryCompare(GeometryCompare::Valid(Box::new(self))) + } + + fn geometry_is_not_valid(self) -> Compare<'a> { + Compare::GeometryCompare(GeometryCompare::NotValid(Box::new(self))) + } + + fn geometry_type_equals(self, geometry_type: T) -> Compare<'a> + where + T: Into>, + { + Compare::GeometryCompare(GeometryCompare::TypeEquals(Box::new(self), geometry_type.into())) + } + + fn geometry_type_not_equals(self, geometry_type: T) -> Compare<'a> + where + T: Into>, + { + Compare::GeometryCompare(GeometryCompare::TypeNotEquals(Box::new(self), geometry_type.into())) + } + + fn geometry_within(self, geom: T) -> Compare<'a> + where + T: Into>, + { + Compare::GeometryCompare(GeometryCompare::Within(Box::new(self), Box::new(geom.into()))) + } + + fn geometry_not_within(self, geom: T) -> Compare<'a> + where + T: Into>, + { + Compare::GeometryCompare(GeometryCompare::NotWithin(Box::new(self), Box::new(geom.into()))) + } + + fn geometry_intersects(self, geom: T) -> Compare<'a> + where + T: Into>, + { + Compare::GeometryCompare(GeometryCompare::Intersects(Box::new(self), Box::new(geom.into()))) + } + + fn geometry_not_intersects(self, geom: T) -> Compare<'a> + where + T: Into>, + { + Compare::GeometryCompare(GeometryCompare::NotIntersects(Box::new(self), Box::new(geom.into()))) + } + #[cfg(feature = "postgresql")] fn matches(self, query: T) -> Compare<'a> where diff --git a/quaint/src/ast/function.rs b/quaint/src/ast/function.rs index 5b6373795485..753ab26e139b 100644 --- a/quaint/src/ast/function.rs +++ b/quaint/src/ast/function.rs @@ -20,6 +20,9 @@ mod search; mod sum; mod upper; +mod geom_as_text; +mod geom_from_text; + #[cfg(feature = "mysql")] mod uuid; @@ -45,6 +48,9 @@ pub use search::*; pub use sum::*; pub use upper::*; +pub use geom_as_text::*; +pub use geom_from_text::*; + #[cfg(feature = "mysql")] pub use self::uuid::*; @@ -72,6 +78,12 @@ impl<'a> Function<'a> { _ => false, } } + pub fn returns_geometry(&self) -> bool { + match self.typ_ { + FunctionType::GeomFromText(_) => true, + _ => false, + } + } } /// A database function type @@ -108,6 +120,8 @@ pub(crate) enum FunctionType<'a> { UuidToBinSwapped, #[cfg(feature = "mysql")] Uuid, + GeomAsText(GeomAsText<'a>), + GeomFromText(GeomFromText<'a>), } impl<'a> Aliasable<'a> for Function<'a> { @@ -156,3 +170,5 @@ function!( Coalesce, Concat ); + +function!(GeomAsText, GeomFromText); diff --git a/quaint/src/ast/function/geom_as_text.rs b/quaint/src/ast/function/geom_as_text.rs new file mode 100644 index 000000000000..8c189e75f122 --- /dev/null +++ b/quaint/src/ast/function/geom_as_text.rs @@ -0,0 +1,31 @@ +use super::Function; +use crate::ast::Expression; + +/// A represention of the `ST_AsText` function in the database. +#[derive(Debug, Clone, PartialEq)] +pub struct GeomAsText<'a> { + pub(crate) expression: Box>, +} + +/// Read the geometry expression into a EWKT string. +/// +/// ```rust +/// # use quaint::{ast::*, visitor::{Visitor, Sqlite}}; +/// # fn main() -> Result<(), quaint::error::Error> { +/// let query = Select::from_table("users").value(geom_as_text(Column::from("location"))); +/// let (sql, _) = Sqlite::build(query)?; +/// +/// assert_eq!("SELECT AsEWKT(`location`) FROM `users`", sql); +/// # Ok(()) +/// # } +/// ``` +pub fn geom_as_text<'a, E>(expression: E) -> Function<'a> +where + E: Into>, +{ + let fun = GeomAsText { + expression: Box::new(expression.into()), + }; + + fun.into() +} diff --git a/quaint/src/ast/function/geom_from_text.rs b/quaint/src/ast/function/geom_from_text.rs new file mode 100644 index 000000000000..a8b4481f9236 --- /dev/null +++ b/quaint/src/ast/function/geom_from_text.rs @@ -0,0 +1,41 @@ +use super::Function; +use crate::ast::Expression; + +/// A represention of the `ST_AsText` function in the database. +#[derive(Debug, Clone, PartialEq)] +pub struct GeomFromText<'a> { + pub(crate) wkt_expression: Box>, + pub(crate) srid_expression: Box>, + pub(crate) geography: bool, +} + +/// Write a WKT geometry value using built-in database conversion. +/// +/// ```rust +/// # use quaint::{ast::*, visitor::{Visitor, Sqlite}}; +/// # fn main() -> Result<(), quaint::error::Error> { +/// let query = Insert::single_into("users").value("location", geom_from_text("POINT(0 0)", 4326, false)); +/// let (sql, params) = Sqlite::build(query)?; +/// +/// assert_eq!("INSERT INTO `users` (`location`) VALUES (ST_GeomFromText(?,?))", sql); +/// +/// assert_eq!(vec![ +/// Value::from("POINT(0 0)"), +/// Value::from(4326) +/// ], params); +/// # Ok(()) +/// # } +/// ``` +pub fn geom_from_text<'a, G, S>(wkt_expression: G, srid_expression: S, geography: bool) -> Function<'a> +where + G: Into>, + S: Into>, +{ + let fun = GeomFromText { + wkt_expression: Box::new(wkt_expression.into()), + srid_expression: Box::new(srid_expression.into()), + geography, + }; + + fun.into() +} diff --git a/quaint/src/ast/row.rs b/quaint/src/ast/row.rs index e556cee966af..f29a58d9897f 100644 --- a/quaint/src/ast/row.rs +++ b/quaint/src/ast/row.rs @@ -1,3 +1,4 @@ +use super::compare::GeometryType; #[cfg(any(feature = "postgresql", feature = "mysql"))] use super::compare::JsonType; use crate::ast::{Comparable, Compare, Expression}; @@ -363,6 +364,80 @@ impl<'a> Comparable<'a> for Row<'a> { value.json_type_not_equals(json_type) } + #[allow(clippy::wrong_self_convention)] + fn geometry_is_empty(self) -> Compare<'a> { + let value: Expression<'a> = self.into(); + value.geometry_is_empty() + } + + #[allow(clippy::wrong_self_convention)] + fn geometry_is_not_empty(self) -> Compare<'a> { + let value: Expression<'a> = self.into(); + value.geometry_is_not_empty() + } + + #[allow(clippy::wrong_self_convention)] + fn geometry_is_valid(self) -> Compare<'a> { + let value: Expression<'a> = self.into(); + value.geometry_is_valid() + } + + #[allow(clippy::wrong_self_convention)] + fn geometry_is_not_valid(self) -> Compare<'a> { + let value: Expression<'a> = self.into(); + value.geometry_is_not_valid() + } + + fn geometry_within(self, geom: T) -> Compare<'a> + where + T: Into>, + { + let value: Expression<'a> = self.into(); + value.geometry_within(geom) + } + + fn geometry_not_within(self, geom: T) -> Compare<'a> + where + T: Into>, + { + let value: Expression<'a> = self.into(); + value.geometry_not_within(geom) + } + + fn geometry_intersects(self, geom: T) -> Compare<'a> + where + T: Into>, + { + let value: Expression<'a> = self.into(); + value.geometry_intersects(geom) + } + + fn geometry_not_intersects(self, geom: T) -> Compare<'a> + where + T: Into>, + { + let value: Expression<'a> = self.into(); + value.geometry_not_intersects(geom) + } + + fn geometry_type_equals(self, geometry_type: T) -> Compare<'a> + where + T: Into>, + { + let value: Expression<'a> = self.into(); + + value.geometry_type_equals(geometry_type) + } + + fn geometry_type_not_equals(self, geometry_type: T) -> Compare<'a> + where + T: Into>, + { + let value: Expression<'a> = self.into(); + + value.geometry_type_not_equals(geometry_type) + } + #[cfg(feature = "postgresql")] fn matches(self, query: T) -> Compare<'a> where diff --git a/quaint/src/ast/values.rs b/quaint/src/ast/values.rs index a1bf4f41a26d..ad1577bed7cd 100644 --- a/quaint/src/ast/values.rs +++ b/quaint/src/ast/values.rs @@ -4,7 +4,7 @@ use crate::error::{Error, ErrorKind}; use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive}; use chrono::{DateTime, NaiveDate, NaiveTime, Utc}; use serde_json::{Number, Value as JsonValue}; -use std::fmt::Display; +use std::fmt::{Display, Formatter}; use std::{ borrow::{Borrow, Cow}, convert::TryFrom, @@ -13,6 +13,9 @@ use std::{ }; use uuid::Uuid; +use once_cell::sync::Lazy; +use regex::Regex; + /// A value written to the query as-is without parameterization. #[derive(Debug, Clone, PartialEq)] pub struct Raw<'a>(pub(crate) Value<'a>); @@ -33,6 +36,43 @@ where } } +#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] +pub struct GeometryValue { + pub wkt: String, + pub srid: i32, +} + +impl Display for GeometryValue { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.srid { + 0 => (), + srid => write!(f, "SRID={};", srid)?, + } + f.write_str(&self.wkt) + } +} + +impl FromStr for GeometryValue { + type Err = String; + + fn from_str(s: &str) -> Result { + static EWKT_REGEX: Lazy = + Lazy::new(|| Regex::new(r"^(SRID=(?P\d+);)?(?P.+)$").unwrap()); + EWKT_REGEX + .captures(s) + .map(|capture| { + let srid = match capture.name("srid").map(|v| v.as_str().parse::()) { + None => Ok(0), + Some(Ok(srid)) => Ok(srid), + Some(Err(_)) => Err("Invalid SRID"), + }?; + let wkt = capture.name("geometry").map(|v| v.as_str()).unwrap().to_string(); + Ok(GeometryValue { srid, wkt }) + }) + .ok_or("Invalid EWKT".to_string())? + } +} + /// A native-column type, i.e. the connector-specific type of the column. #[derive(Debug, Clone, PartialEq)] pub struct NativeColumnType<'a>(Cow<'a, str>); @@ -209,6 +249,22 @@ impl<'a> Value<'a> { ValueType::xml(value).into_value() } + /// Creates a new geometry value. + pub fn geometry(value: T) -> Self + where + T: Into, + { + ValueType::geometry(value).into_value() + } + + /// Creates a new geography value. + pub fn geography(value: T) -> Self + where + T: Into, + { + ValueType::geography(value).into_value() + } + /// `true` if the `Value` is null. pub fn is_null(&self) -> bool { self.typed.is_null() @@ -472,6 +528,10 @@ impl<'a> Value<'a> { pub fn null_time() -> Self { ValueType::Time(None).into() } + + pub fn null_geometry() -> Self { + ValueType::Time(None).into() + } } impl<'a> Display for Value<'a> { @@ -530,6 +590,10 @@ pub enum ValueType<'a> { Numeric(Option), /// A JSON value. Json(Option), + /// A Geometry value. + Geometry(Option), + /// A Geography value. + Geography(Option), /// A XML value. Xml(Option>), /// An UUID value. @@ -606,6 +670,8 @@ impl<'a> fmt::Display for ValueType<'a> { ValueType::DateTime(val) => val.map(|v| write!(f, "\"{v}\"")), ValueType::Date(val) => val.map(|v| write!(f, "\"{v}\"")), ValueType::Time(val) => val.map(|v| write!(f, "\"{v}\"")), + ValueType::Geometry(val) => val.as_ref().map(|v| write!(f, "\"{v}\"")), + ValueType::Geography(val) => val.as_ref().map(|v| write!(f, "\"{v}\"")), }; match res { @@ -664,6 +730,8 @@ impl<'a> From> for serde_json::Value { ValueType::DateTime(dt) => dt.map(|dt| serde_json::Value::String(dt.to_rfc3339())), ValueType::Date(date) => date.map(|date| serde_json::Value::String(format!("{date}"))), ValueType::Time(time) => time.map(|time| serde_json::Value::String(format!("{time}"))), + ValueType::Geometry(g) => g.map(|g| serde_json::Value::String(g.to_string())), + ValueType::Geography(g) => g.map(|g| serde_json::Value::String(g.to_string())), }; match res { @@ -810,6 +878,22 @@ impl<'a> ValueType<'a> { Self::Json(Some(value)) } + /// Creates a new geometry value. + pub fn geometry(value: T) -> Self + where + T: Into, + { + Self::Geometry(Some(value.into())) + } + + /// Creates a new geometry value. + pub fn geography(value: T) -> Self + where + T: Into, + { + Self::Geography(Some(value.into())) + } + /// Creates a new XML value. pub(crate) fn xml(value: T) -> Self where @@ -839,6 +923,8 @@ impl<'a> ValueType<'a> { Self::Date(d) => d.is_none(), Self::Time(t) => t.is_none(), Self::Json(json) => json.is_none(), + Self::Geometry(geom) => geom.is_none(), + Self::Geography(geom) => geom.is_none(), } } diff --git a/quaint/src/connector/mssql/conversion.rs b/quaint/src/connector/mssql/conversion.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/quaint/src/connector/mssql/native/conversion.rs b/quaint/src/connector/mssql/native/conversion.rs index 870654ad5de3..e41cb5f73357 100644 --- a/quaint/src/connector/mssql/native/conversion.rs +++ b/quaint/src/connector/mssql/native/conversion.rs @@ -26,6 +26,8 @@ impl<'a> IntoSql<'a> for &'a Value<'a> { ValueType::DateTime(val) => val.into_sql(), ValueType::Date(val) => val.into_sql(), ValueType::Time(val) => val.into_sql(), + ValueType::Geometry(_) => panic!("Cannot handle raw Geometry"), + ValueType::Geography(_) => panic!("Cannot handle raw Geography"), } } } diff --git a/quaint/src/connector/mysql/native/conversion.rs b/quaint/src/connector/mysql/native/conversion.rs index 659cc0790c07..85ebeed7fd47 100644 --- a/quaint/src/connector/mysql/native/conversion.rs +++ b/quaint/src/connector/mysql/native/conversion.rs @@ -4,11 +4,12 @@ use crate::{ error::{Error, ErrorKind}, }; use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc}; +use geozero::{wkb::MySQLWkb, wkt::WktStr, ToWkb, ToWkt}; use mysql_async::{ self as my, consts::{ColumnFlags, ColumnType}, }; -use std::convert::TryFrom; +use std::{convert::TryFrom, vec}; pub fn conv_params(params: &[Value<'_>]) -> crate::Result { if params.is_empty() { @@ -68,6 +69,13 @@ pub fn conv_params(params: &[Value<'_>]) -> crate::Result { dt.timestamp_subsec_micros(), ) }), + ValueType::Geometry(g) | ValueType::Geography(g) => g.as_ref().map(|g| { + // TODO@geometry: Improve WKB serialization error handling + WktStr(&g.wkt) + .to_mysql_wkb(Some(g.srid)) + .map(my::Value::Bytes) + .unwrap_or_else(|_| panic!("Couldn't convert value `{g}` into EWKB.")) + }), }; match res { @@ -193,6 +201,10 @@ impl TypeIdentifier for my::Column { self.column_type() == ColumnType::MYSQL_TYPE_JSON } + fn is_geometry(&self) -> bool { + self.column_type() == ColumnType::MYSQL_TYPE_GEOMETRY + } + fn is_enum(&self) -> bool { self.flags() == ColumnFlags::ENUM_FLAG || self.column_type() == ColumnType::MYSQL_TYPE_ENUM } @@ -255,6 +267,14 @@ impl TakeRow for my::Row { [0] => Value::boolean(false), _ => Value::boolean(true), }, + my::Value::Bytes(b) if column.is_geometry() => { + MySQLWkb(b).to_ewkt(None).map(Value::text).map_err(|_| { + let msg = "Could not convert geometry blob to Ewkt"; + let kind = ErrorKind::conversion(msg); + + Error::builder(kind).build() + })? + } // https://dev.mysql.com/doc/internals/en/character-set.html my::Value::Bytes(b) if column.character_set() == 63 => Value::bytes(b), my::Value::Bytes(s) => Value::text(String::from_utf8(s)?), @@ -316,6 +336,7 @@ impl TakeRow for my::Row { t if t.is_time() => Value::null_time(), t if t.is_date() => Value::null_date(), t if t.is_json() => Value::null_json(), + t if t.is_geometry() => Value::null_geometry(), typ => { let msg = format!("Value of type {typ:?} is not supported with the current configuration"); diff --git a/quaint/src/connector/postgres/native/conversion.rs b/quaint/src/connector/postgres/native/conversion.rs index efe4debd9b94..21ed6b2407de 100644 --- a/quaint/src/connector/postgres/native/conversion.rs +++ b/quaint/src/connector/postgres/native/conversion.rs @@ -12,6 +12,7 @@ use bytes::BytesMut; use chrono::{DateTime, NaiveDateTime, Utc}; pub(crate) use decimal::DecimalWrapper; +use geozero::{wkb::Ewkb, ToWkt}; use postgres_types::{FromSql, ToSql, WrongType}; use std::{convert::TryFrom, error::Error as StdError}; use tokio_postgres::{ @@ -54,6 +55,8 @@ pub(crate) fn params_to_types(params: &[Value<'_>]) -> Vec { ValueType::DateTime(_) => PostgresType::TIMESTAMPTZ, ValueType::Date(_) => PostgresType::TIMESTAMP, ValueType::Time(_) => PostgresType::TIME, + ValueType::Geometry(_) => PostgresType::BYTEA, + ValueType::Geography(_) => PostgresType::BYTEA, ValueType::Array(ref arr) => { let arr = arr.as_ref().unwrap(); @@ -90,6 +93,8 @@ pub(crate) fn params_to_types(params: &[Value<'_>]) -> Vec { ValueType::DateTime(_) => PostgresType::TIMESTAMPTZ_ARRAY, ValueType::Date(_) => PostgresType::TIMESTAMP_ARRAY, ValueType::Time(_) => PostgresType::TIME_ARRAY, + ValueType::Geometry(_) => PostgresType::BYTEA, + ValueType::Geography(_) => PostgresType::BYTEA, // In the case of nested arrays, we let PG infer the type ValueType::Array(_) => PostgresType::UNKNOWN, } @@ -99,6 +104,18 @@ pub(crate) fn params_to_types(params: &[Value<'_>]) -> Vec { .collect() } +struct EwktString(pub String); + +impl<'a> FromSql<'a> for EwktString { + fn from_sql(_ty: &PostgresType, raw: &'a [u8]) -> Result> { + Ok(Ewkb(raw.to_owned()).to_ewkt(None).map(EwktString)?) + } + + fn accepts(ty: &PostgresType) -> bool { + matches!(ty.name(), "geometry" | "geography") + } +} + struct XmlString(pub String); impl<'a> FromSql<'a> for XmlString { @@ -502,6 +519,12 @@ impl GetRow for PostgresRow { } None => Value::null_array(), }, + ref x if matches!(x.name(), "geometry" | "geography") => { + match row.try_get::<_, Option>(i)? { + Some(ewkt) => Value::text(ewkt.0), + None => Value::null_text(), + } + } ref x => match x.kind() { Kind::Enum => match row.try_get(i)? { Some(val) => { @@ -854,6 +877,8 @@ impl<'a> ToSql for Value<'a> { Ok(result) }), (ValueType::DateTime(value), _) => value.map(|value| value.naive_utc().to_sql(ty, out)), + (ValueType::Geometry(_), _) => panic!("Cannot handle raw Geometry"), + (ValueType::Geography(_), _) => panic!("Cannot handle raw Geography"), }; match res { diff --git a/quaint/src/connector/sqlite/native/conversion.rs b/quaint/src/connector/sqlite/native/conversion.rs index fced37abca4c..84d0b63b6c2a 100644 --- a/quaint/src/connector/sqlite/native/conversion.rs +++ b/quaint/src/connector/sqlite/native/conversion.rs @@ -9,6 +9,7 @@ use crate::{ error::{Error, ErrorKind}, }; +use geozero::{wkb::SpatiaLiteWkb, ToWkt}; use rusqlite::{ types::{Null, ToSql, ToSqlOutput, ValueRef}, Column, Error as RusqlError, Row as SqliteRow, Rows as SqliteRows, @@ -118,6 +119,20 @@ impl TypeIdentifier for Column<'_> { matches!(self.decl_type(), Some("BOOLEAN") | Some("boolean")) } + fn is_geometry(&self) -> bool { + match self.decl_type() { + Some(n) if n.eq_ignore_ascii_case("GEOMETRY") => true, + Some(n) if n.eq_ignore_ascii_case("POINT") => true, + Some(n) if n.eq_ignore_ascii_case("LINESTRING") => true, + Some(n) if n.eq_ignore_ascii_case("POLYGON") => true, + Some(n) if n.eq_ignore_ascii_case("MULTIPOINT") => true, + Some(n) if n.eq_ignore_ascii_case("MULTILINESTRING") => true, + Some(n) if n.eq_ignore_ascii_case("MULTIPOLYGON") => true, + Some(n) if n.eq_ignore_ascii_case("GEOMETRYCOLLECTION") => true, + _ => false, + } + } + fn is_json(&self) -> bool { false } @@ -148,6 +163,7 @@ impl<'a> GetRow for SqliteRow<'a> { c if c.is_datetime() => Value::null_datetime(), c if c.is_date() => Value::null_date(), c if c.is_bool() => Value::null_boolean(), + c if c.is_geometry() => Value::null_geometry(), c => match c.decl_type() { Some(n) => { let msg = format!("Value {n} not supported"); @@ -221,6 +237,15 @@ impl<'a> GetRow for SqliteRow<'a> { }) })? } + ValueRef::Blob(bytes) if column.is_geometry() => SpatiaLiteWkb(bytes.to_vec()) + .to_ewkt(None) + .map(Value::text) + .map_err(|_| { + let builder = Error::builder(ErrorKind::ConversionError( + "Failed to read contents of SQLite geometry column as WKT".into(), + )); + builder.build() + })?, ValueRef::Text(bytes) => Value::text(String::from_utf8(bytes.to_vec())?), ValueRef::Blob(bytes) => Value::bytes(bytes.to_owned()), }; @@ -285,6 +310,8 @@ impl<'a> ToSql for Value<'a> { date.and_hms_opt(time.hour(), time.minute(), time.second()) }) .map(|dt| ToSqlOutput::from(dt.timestamp_millis())), + ValueType::Geometry(_) => panic!("Cannot handle raw Geometry"), + ValueType::Geography(_) => panic!("Cannot handle raw Geography"), }; match value { diff --git a/quaint/src/connector/sqlite/native/mod.rs b/quaint/src/connector/sqlite/native/mod.rs index 3bf0c46a7db5..9220fe75b517 100644 --- a/quaint/src/connector/sqlite/native/mod.rs +++ b/quaint/src/connector/sqlite/native/mod.rs @@ -7,7 +7,7 @@ mod error; use crate::connector::sqlite::params::SqliteParams; use crate::connector::IsolationLevel; -pub use rusqlite::{params_from_iter, version as sqlite_version}; +pub use rusqlite::{params_from_iter, version as sqlite_version, LoadExtensionGuard}; use crate::{ ast::{Query, Value}, @@ -28,6 +28,18 @@ pub struct Sqlite { pub(crate) client: Mutex, } +fn load_spatialite(conn: &rusqlite::Connection) -> crate::Result<()> { + // Loading Spatialite here isn't ideal, but needed because it has to be + // done for every new pooled connection..? + if let Ok(spatialite_path) = std::env::var("SPATIALITE_PATH") { + unsafe { + let _guard = LoadExtensionGuard::new(conn)?; + conn.load_extension(spatialite_path, None)?; + } + } + Ok(()) +} + impl TryFrom<&str> for Sqlite { type Error = Error; @@ -36,6 +48,7 @@ impl TryFrom<&str> for Sqlite { let file_path = params.file_path; let conn = rusqlite::Connection::open(file_path.as_str())?; + load_spatialite(&conn)?; if let Some(timeout) = params.socket_timeout { conn.busy_timeout(timeout)?; @@ -55,6 +68,7 @@ impl Sqlite { /// Open a new SQLite database in memory. pub fn new_in_memory() -> crate::Result { let client = rusqlite::Connection::open_in_memory()?; + load_spatialite(&client)?; Ok(Sqlite { client: Mutex::new(client), diff --git a/quaint/src/connector/type_identifier.rs b/quaint/src/connector/type_identifier.rs index 9fcc46f61c1c..d8ef1d39f5d2 100644 --- a/quaint/src/connector/type_identifier.rs +++ b/quaint/src/connector/type_identifier.rs @@ -11,6 +11,7 @@ pub(crate) trait TypeIdentifier { fn is_bytes(&self) -> bool; fn is_bool(&self) -> bool; fn is_json(&self) -> bool; + fn is_geometry(&self) -> bool; fn is_enum(&self) -> bool; fn is_null(&self) -> bool; } diff --git a/quaint/src/serde.rs b/quaint/src/serde.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/quaint/src/visitor.rs b/quaint/src/visitor.rs index c205b49dd279..6b4e823ebcc3 100644 --- a/quaint/src/visitor.rs +++ b/quaint/src/visitor.rs @@ -139,6 +139,48 @@ pub trait Visitor<'a> { #[cfg(any(feature = "postgresql", feature = "mysql"))] fn visit_json_unquote(&mut self, json_unquote: JsonUnquote<'a>) -> Result; + fn visit_geom_as_text(&mut self, geom: GeomAsText<'a>) -> Result; + + fn visit_geom_from_text(&mut self, geom: GeomFromText<'a>) -> Result; + + fn visit_geometry_type_equals(&mut self, left: Expression<'a>, right: GeometryType<'a>, not: bool) -> Result; + + fn visit_geometry_empty(&mut self, left: Expression<'a>, not: bool) -> Result { + if not { + self.write("NOT ")?; + } + self.surround_with("ST_IsEmpty(", ")", |s| s.visit_expression(left)) + } + + fn visit_geometry_valid(&mut self, left: Expression<'a>, not: bool) -> Result { + if not { + self.write("NOT ")?; + } + self.surround_with("ST_IsValid(", ")", |s| s.visit_expression(left)) + } + + fn visit_geometry_within(&mut self, left: Expression<'a>, right: Expression<'a>, not: bool) -> Result { + if not { + self.write("NOT ")?; + } + self.surround_with("ST_Within(", ")", |s| { + s.visit_expression(left)?; + s.write(",")?; + s.visit_expression(right) + }) + } + + fn visit_geometry_intersects(&mut self, left: Expression<'a>, right: Expression<'a>, not: bool) -> Result { + if not { + self.write("NOT ")?; + } + self.surround_with("ST_Intersects(", ")", |s| { + s.visit_expression(left)?; + s.write(",")?; + s.visit_expression(right) + }) + } + #[cfg(any(feature = "postgresql", feature = "mysql"))] fn visit_text_search(&mut self, text_search: TextSearch<'a>) -> Result; @@ -175,6 +217,9 @@ pub trait Visitor<'a> { match value.typed { ValueType::Enum(Some(variant), name) => self.visit_parameterized_enum(variant, name), ValueType::EnumArray(Some(variants), name) => self.visit_parameterized_enum_array(variants, name), + ValueType::Geometry(Some(geom)) => self.visit_function(geom_from_text(geom.wkt, geom.srid, false)), + ValueType::Geography(Some(geom)) => self.visit_function(geom_from_text(geom.wkt, geom.srid, true)), + ValueType::Geometry(None) | ValueType::Geography(None) => self.write("NULL"), _ => { self.add_parameter(value); self.parameter_substitution() @@ -950,6 +995,22 @@ pub trait Visitor<'a> { JsonCompare::TypeEquals(left, json_type) => self.visit_json_type_equals(*left, json_type, false), JsonCompare::TypeNotEquals(left, json_type) => self.visit_json_type_equals(*left, json_type, true), }, + Compare::GeometryCompare(geom_compare) => match geom_compare { + GeometryCompare::Empty(left) => self.visit_geometry_empty(*left, false), + GeometryCompare::NotEmpty(left) => self.visit_geometry_empty(*left, true), + GeometryCompare::Valid(left) => self.visit_geometry_valid(*left, false), + GeometryCompare::NotValid(left) => self.visit_geometry_valid(*left, true), + GeometryCompare::Within(left, right) => self.visit_geometry_within(*left, *right, false), + GeometryCompare::NotWithin(left, right) => self.visit_geometry_within(*left, *right, true), + GeometryCompare::Intersects(left, right) => self.visit_geometry_intersects(*left, *right, false), + GeometryCompare::NotIntersects(left, right) => self.visit_geometry_intersects(*left, *right, true), + GeometryCompare::TypeEquals(left, geom_type) => { + self.visit_geometry_type_equals(*left, geom_type, false) + } + GeometryCompare::TypeNotEquals(left, geom_type) => { + self.visit_geometry_type_equals(*left, geom_type, true) + } + }, #[cfg(feature = "postgresql")] Compare::Matches(left, right) => self.visit_matches(*left, right, false), #[cfg(feature = "postgresql")] @@ -1106,6 +1167,12 @@ pub trait Visitor<'a> { FunctionType::Concat(concat) => { self.visit_concat(concat)?; } + FunctionType::GeomAsText(geom) => { + self.visit_geom_as_text(geom)?; + } + FunctionType::GeomFromText(geom) => { + self.visit_geom_from_text(geom)?; + } }; if let Some(alias) = fun.alias { diff --git a/quaint/src/visitor/mssql.rs b/quaint/src/visitor/mssql.rs index bf1550b96c31..ca2d1e5eed55 100644 --- a/quaint/src/visitor/mssql.rs +++ b/quaint/src/visitor/mssql.rs @@ -2,10 +2,7 @@ use super::Visitor; #[cfg(any(feature = "postgresql", feature = "mysql"))] use crate::prelude::{JsonExtract, JsonType, JsonUnquote}; use crate::{ - ast::{ - Column, Comparable, Expression, ExpressionKind, Insert, IntoRaw, Join, JoinData, Joinable, Merge, OnConflict, - Order, Ordering, Row, Table, TypeDataLength, TypeFamily, Values, - }, + ast::*, error::{Error, ErrorKind}, prelude::{Aliasable, Average, Query}, visitor, Value, ValueType, @@ -74,6 +71,8 @@ impl<'a> Mssql<'a> { TypeFamily::Boolean => self.write("BIT"), TypeFamily::Uuid => self.write("UNIQUEIDENTIFIER"), TypeFamily::DateTime => self.write("DATETIMEOFFSET"), + TypeFamily::Geometry(_) => self.write("GEOMETRY"), + TypeFamily::Geography(_) => self.write("GEOGRAPHY"), TypeFamily::Bytes(len) => { self.write("VARBINARY(")?; match len { @@ -176,6 +175,12 @@ impl<'a> Mssql<'a> { Ok(()) } + + fn visit_geometry_equals(&mut self, left: Expression<'a>, right: Expression<'a>, not: bool) -> visitor::Result { + self.surround_with("(", ")", |s| s.visit_expression(left))?; + self.surround_with(".STEquals(", ")", |s| s.visit_expression(right))?; + self.write(if not { " = 0" } else { " = 1" }) + } } impl<'a> Visitor<'a> for Mssql<'a> { @@ -203,10 +208,6 @@ impl<'a> Visitor<'a> for Mssql<'a> { Ok(()) } - fn add_parameter(&mut self, value: Value<'a>) { - self.parameters.push(value) - } - /// A point to modify an incoming query to make it compatible with the /// SQL Server. fn compatibility_modifications(&self, query: Query<'a>) -> Query<'a> { @@ -235,6 +236,7 @@ impl<'a> Visitor<'a> for Mssql<'a> { (left_kind, right_kind) => { let (l_alias, r_alias) = (left.alias, right.alias); let (left_xml, right_xml) = (left_kind.is_xml_value(), right_kind.is_xml_value()); + let (left_geom, right_geom) = (left_kind.is_geometry_expr(), right_kind.is_geometry_expr()); let mut left = Expression::from(left_kind); @@ -248,6 +250,12 @@ impl<'a> Visitor<'a> for Mssql<'a> { right = right.alias(alias); } + if left_geom { + return self.visit_geometry_equals(left, right, false); + } else if right_geom { + return self.visit_geometry_equals(right, left, false); + } + if right_xml { self.surround_with("CAST(", " AS NVARCHAR(MAX))", |x| x.visit_expression(left))?; } else { @@ -276,6 +284,7 @@ impl<'a> Visitor<'a> for Mssql<'a> { (left_kind, right_kind) => { let (l_alias, r_alias) = (left.alias, right.alias); let (left_xml, right_xml) = (left_kind.is_xml_value(), right_kind.is_xml_value()); + let (left_geom, right_geom) = (left_kind.is_geometry_expr(), right_kind.is_geometry_expr()); let mut left = Expression::from(left_kind); @@ -289,6 +298,12 @@ impl<'a> Visitor<'a> for Mssql<'a> { right = right.alias(alias); } + if left_geom { + return self.visit_geometry_equals(left, right, true); + } else if right_geom { + return self.visit_geometry_equals(right, left, true); + } + if right_xml { self.surround_with("CAST(", " AS NVARCHAR(MAX))", |x| x.visit_expression(left))?; } else { @@ -361,6 +376,13 @@ impl<'a> Visitor<'a> for Mssql<'a> { // Style 3 is keep all whitespace + internal DTD processing: // https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?redirectedfrom=MSDN&view=sql-server-ver15#xml-styles ValueType::Xml(cow) => cow.map(|cow| self.write(format!("CONVERT(XML, N'{cow}', 3)"))), + // TODO@geometry: find a way to avoid cloning + ValueType::Geometry(g) => g + .as_ref() + .map(|g| self.visit_function(geom_from_text(g.wkt.clone().raw(), g.srid.raw(), false))), + ValueType::Geography(g) => g + .as_ref() + .map(|g| self.visit_function(geom_from_text(g.wkt.clone().raw(), g.srid.raw(), true))), }; match res { @@ -526,6 +548,10 @@ impl<'a> Visitor<'a> for Mssql<'a> { unimplemented!("Upsert not supported for the underlying database.") } + fn add_parameter(&mut self, value: Value<'a>) { + self.parameters.push(value) + } + fn parameter_substitution(&mut self) -> visitor::Result { self.write("@P")?; self.write(self.parameters.len()) @@ -631,6 +657,50 @@ impl<'a> Visitor<'a> for Mssql<'a> { Ok(()) } + fn visit_geometry_type_equals( + &mut self, + left: Expression<'a>, + geom_type: GeometryType<'a>, + not: bool, + ) -> visitor::Result { + self.surround_with("(", ").STGeometryType()", |s| s.visit_expression(left))?; + + if not { + self.write(" != ")?; + } else { + self.write(" = ")?; + } + + match geom_type { + GeometryType::ColumnRef(column) => { + self.surround_with("(", ").STGeometryType()", |s| s.visit_column(*column)) + } + _ => self.visit_expression(Value::text(geom_type.to_string()).into()), + } + } + + fn visit_geometry_empty(&mut self, left: Expression<'a>, not: bool) -> visitor::Result { + self.surround_with("(", ").STIsEmpty()", |s| s.visit_expression(left))?; + self.write(if not { " = 0" } else { " = 1" }) + } + + fn visit_geometry_valid(&mut self, left: Expression<'a>, not: bool) -> visitor::Result { + self.surround_with("(", ").STIsValid()", |s| s.visit_expression(left))?; + self.write(if not { " = 0" } else { " = 1" }) + } + + fn visit_geometry_within(&mut self, left: Expression<'a>, right: Expression<'a>, not: bool) -> visitor::Result { + self.surround_with("(", ")", |s| s.visit_expression(left))?; + self.surround_with(".STWithin(", ")", |s| s.visit_expression(right))?; + self.write(if not { " = 0" } else { " = 1" }) + } + + fn visit_geometry_intersects(&mut self, left: Expression<'a>, right: Expression<'a>, not: bool) -> visitor::Result { + self.surround_with("(", ")", |s| s.visit_expression(left))?; + self.surround_with(".STIntersects(", ")", |s| s.visit_expression(right))?; + self.write(if not { " = 0" } else { " = 1" }) + } + #[cfg(any(feature = "postgresql", feature = "mysql"))] fn visit_json_extract(&mut self, _json_extract: JsonExtract<'a>) -> visitor::Result { unimplemented!("JSON filtering is not yet supported on MSSQL") @@ -694,6 +764,35 @@ impl<'a> Visitor<'a> for Mssql<'a> { ) -> visitor::Result { unimplemented!("JSON filtering is not yet supported on MSSQL") } + + fn visit_geom_as_text(&mut self, geom: GeomAsText<'a>) -> visitor::Result { + self.write("CASE WHEN ")?; + self.visit_expression(*geom.expression.clone())?; + self.write("IS NULL THEN NULL ELSE ")?; + self.surround_with("CONCAT(", ")", |ref mut s| { + s.write("'SRID=',")?; + s.surround_with("(", ").STSrid", |ref mut s| { + s.visit_expression(*geom.expression.clone()) + })?; + s.write(",';',")?; + s.surround_with("CAST(", " AS VARCHAR(MAX))", |ref mut s| { + s.visit_expression(*geom.expression) + })?; + Ok(()) + })?; + self.write("END")?; + Ok(()) + } + + fn visit_geom_from_text(&mut self, geom: GeomFromText<'a>) -> visitor::Result { + self.write(if geom.geography { "geography" } else { "geometry" })?; + self.surround_with("::STGeomFromText(", ")", |ref mut s| { + s.visit_expression(*geom.wkt_expression)?; + s.write(",")?; + s.visit_expression(*geom.srid_expression)?; + Ok(()) + }) + } } #[cfg(test)] @@ -704,6 +803,7 @@ mod tests { visitor::{Mssql, Visitor}, }; use indoc::indoc; + use std::str::FromStr; fn expected_values<'a, T>(sql: &'static str, params: Vec) -> (String, Vec>) where @@ -1272,6 +1372,22 @@ mod tests { assert!(params.is_empty()); } + #[test] + fn test_raw_geometry() { + let geom = GeometryValue::from_str("SRID=4326;POINT(0 0)").unwrap(); + let (sql, params) = Mssql::build(Select::default().value(Value::geometry(geom).raw())).unwrap(); + assert_eq!("SELECT geometry::STGeomFromText('POINT(0 0)',4326)", sql); + assert!(params.is_empty()); + } + + #[test] + fn test_raw_geography() { + let geom = GeometryValue::from_str("SRID=4326;POINT(0 0)").unwrap(); + let (sql, params) = Mssql::build(Select::default().value(Value::geography(geom).raw())).unwrap(); + assert_eq!("SELECT geography::STGeomFromText('POINT(0 0)',4326)", sql); + assert!(params.is_empty()); + } + #[test] fn test_single_insert() { let insert = Insert::single_into("foo").value("bar", "lol").value("wtf", "meow"); diff --git a/quaint/src/visitor/mysql.rs b/quaint/src/visitor/mysql.rs index 26d0f0d5fd65..2ab3f08abf0e 100644 --- a/quaint/src/visitor/mysql.rs +++ b/quaint/src/visitor/mysql.rs @@ -163,6 +163,13 @@ impl<'a> Visitor<'a> for Mysql<'a> { ValueType::Date(date) => date.map(|date| self.write(format!("'{date}'"))), ValueType::Time(time) => time.map(|time| self.write(format!("'{time}'"))), ValueType::Xml(cow) => cow.as_ref().map(|cow| self.write(format!("'{cow}'"))), + // TODO@geometry: find a way to avoid cloning + ValueType::Geometry(g) => g + .as_ref() + .map(|g| self.visit_function(geom_from_text(g.wkt.clone().raw(), g.srid.raw(), false))), + ValueType::Geography(g) => g + .as_ref() + .map(|g| self.visit_function(geom_from_text(g.wkt.clone().raw(), g.srid.raw(), true))), }; match res { @@ -462,6 +469,32 @@ impl<'a> Visitor<'a> for Mysql<'a> { self.write(")") } + fn visit_geometry_type_equals( + &mut self, + left: Expression<'a>, + geom_type: GeometryType<'a>, + not: bool, + ) -> visitor::Result { + self.write("ST_GeometryType")?; + self.surround_with("(", ")", |s| s.visit_expression(left.clone()))?; + + if not { + self.write(" != ")?; + } else { + self.write(" = ")?; + } + + match geom_type { + GeometryType::ColumnRef(column) => { + self.write("ST_GeometryType")?; + self.surround_with("(", ")", |s| s.visit_column(*column)) + } + _ => self.visit_expression(Value::text(geom_type.to_string().to_uppercase()).into()), + }?; + + Ok(()) + } + fn visit_greater_than(&mut self, left: Expression<'a>, right: Expression<'a>) -> visitor::Result { self.visit_numeric_comparison(left, right, ">")?; @@ -607,6 +640,27 @@ impl<'a> Visitor<'a> for Mysql<'a> { Ok(()) } + + fn visit_geom_as_text(&mut self, geom: GeomAsText<'a>) -> visitor::Result { + self.surround_with("CONCAT(", ")", |ref mut s| { + s.write("'SRID=',")?; + s.surround_with("ST_SRID(", ")", |ref mut s| { + s.visit_expression(*geom.expression.clone()) + })?; + s.write(",';',")?; + s.surround_with("ST_AsText(", ")", |ref mut s| s.visit_expression(*geom.expression))?; + Ok(()) + }) + } + + fn visit_geom_from_text(&mut self, geom: GeomFromText<'a>) -> visitor::Result { + self.surround_with("ST_GeomFromText(", ")", |ref mut s| { + s.visit_expression(*geom.wkt_expression)?; + s.write(",")?; + s.visit_expression(*geom.srid_expression)?; + Ok(()) + }) + } } fn get_target_table(query: Query<'_>) -> Option> { @@ -620,6 +674,7 @@ fn get_target_table(query: Query<'_>) -> Option> { #[cfg(test)] mod tests { use crate::visitor::*; + use std::str::FromStr; fn expected_values<'a, T>(sql: &'static str, params: Vec) -> (String, Vec>) where @@ -802,6 +857,26 @@ mod tests { assert!(params.is_empty()); } + #[test] + fn test_raw_geometry() { + let (sql, params) = Mysql::build( + Select::default().value(Value::geometry(GeometryValue::from_str("SRID=4326;POINT(0 0)").unwrap()).raw()), + ) + .unwrap(); + assert_eq!("SELECT ST_GeomFromText('POINT(0 0)',4326)", sql); + assert!(params.is_empty()); + } + + #[test] + fn test_raw_geography() { + let (sql, params) = Mysql::build( + Select::default().value(Value::geography(GeometryValue::from_str("SRID=4326;POINT(0 0)").unwrap()).raw()), + ) + .unwrap(); + assert_eq!("SELECT ST_GeomFromText('POINT(0 0)',4326)", sql); + assert!(params.is_empty()); + } + #[test] fn test_distinct() { let expected_sql = "SELECT DISTINCT `bar` FROM `test`"; diff --git a/quaint/src/visitor/postgres.rs b/quaint/src/visitor/postgres.rs index 648b3f0dc1ec..edc996103c5a 100644 --- a/quaint/src/visitor/postgres.rs +++ b/quaint/src/visitor/postgres.rs @@ -228,6 +228,13 @@ impl<'a> Visitor<'a> for Postgres<'a> { ValueType::DateTime(dt) => dt.map(|dt| self.write(format!("'{}'", dt.to_rfc3339(),))), ValueType::Date(date) => date.map(|date| self.write(format!("'{date}'"))), ValueType::Time(time) => time.map(|time| self.write(format!("'{time}'"))), + // TODO@geometry: find a way to avoid cloning + ValueType::Geometry(g) => g + .as_ref() + .map(|g| self.visit_function(geom_from_text(g.wkt.clone().raw(), g.srid.raw(), false))), + ValueType::Geography(g) => g + .as_ref() + .map(|g| self.visit_function(geom_from_text(g.wkt.clone().raw(), g.srid.raw(), true))), }; match res { @@ -499,6 +506,30 @@ impl<'a> Visitor<'a> for Postgres<'a> { } } + fn visit_geometry_type_equals( + &mut self, + left: Expression<'a>, + geom_type: GeometryType<'a>, + not: bool, + ) -> visitor::Result { + self.write("ST_GeometryType")?; + self.surround_with("(", ")", |s| s.visit_expression(left))?; + + if not { + self.write(" != ")?; + } else { + self.write(" = ")?; + } + + match geom_type { + GeometryType::ColumnRef(column) => { + self.write("ST_GeometryType")?; + self.surround_with("(", ")", |s| s.visit_column(*column)) + } + _ => self.visit_expression(Value::text(format!("ST_{geom_type}")).into()), + } + } + fn visit_text_search(&mut self, text_search: crate::prelude::TextSearch<'a>) -> visitor::Result { let len = text_search.exprs.len(); self.surround_with("to_tsvector(concat_ws(' ', ", "))", |s| { @@ -655,11 +686,25 @@ impl<'a> Visitor<'a> for Postgres<'a> { Ok(()) } + + fn visit_geom_as_text(&mut self, geom: GeomAsText<'a>) -> visitor::Result { + self.surround_with("ST_AsEWKT(", ")", |s| s.visit_expression(*geom.expression)) + } + + fn visit_geom_from_text(&mut self, geom: GeomFromText<'a>) -> visitor::Result { + self.surround_with("ST_GeomFromText(", ")", |ref mut s| { + s.visit_expression(*geom.wkt_expression)?; + s.write(",")?; + s.visit_expression(*geom.srid_expression)?; + Ok(()) + }) + } } #[cfg(test)] mod tests { use crate::visitor::*; + use std::str::FromStr; fn expected_values<'a, T>(sql: &'static str, params: Vec) -> (String, Vec>) where @@ -1059,6 +1104,22 @@ mod tests { assert!(params.is_empty()); } + #[test] + fn test_raw_geometry() { + let geom = GeometryValue::from_str("SRID=4326;POINT(0 0)").unwrap(); + let (sql, params) = Postgres::build(Select::default().value(Value::geometry(geom).raw())).unwrap(); + assert_eq!("SELECT ST_GeomFromText('POINT(0 0)',4326)", sql); + assert!(params.is_empty()); + } + + #[test] + fn test_raw_geography() { + let geom = GeometryValue::from_str("SRID=4326;POINT(0 0)").unwrap(); + let (sql, params) = Postgres::build(Select::default().value(Value::geography(geom).raw())).unwrap(); + assert_eq!("SELECT ST_GeomFromText('POINT(0 0)',4326)", sql); + assert!(params.is_empty()); + } + #[test] fn test_raw_comparator() { let (sql, _) = Postgres::build(Select::from_table("foo").so_that("bar".compare_raw("ILIKE", "baz%"))).unwrap(); diff --git a/quaint/src/visitor/sqlite.rs b/quaint/src/visitor/sqlite.rs index 9c15ef651694..2dfe6b709f8f 100644 --- a/quaint/src/visitor/sqlite.rs +++ b/quaint/src/visitor/sqlite.rs @@ -117,6 +117,13 @@ impl<'a> Visitor<'a> for Sqlite<'a> { ValueType::Date(date) => date.map(|date| self.write(format!("'{date}'"))), ValueType::Time(time) => time.map(|time| self.write(format!("'{time}'"))), ValueType::Xml(cow) => cow.as_ref().map(|cow| self.write(format!("'{cow}'"))), + // TODO@geometry: find a way to avoid cloning + ValueType::Geometry(g) => g + .as_ref() + .map(|g| self.visit_function(geom_from_text(g.wkt.clone().raw(), g.srid.raw(), false))), + ValueType::Geography(g) => g + .as_ref() + .map(|g| self.visit_function(geom_from_text(g.wkt.clone().raw(), g.srid.raw(), true))), }; match res { @@ -223,14 +230,14 @@ impl<'a> Visitor<'a> for Sqlite<'a> { Ok(()) } - fn parameter_substitution(&mut self) -> visitor::Result { - self.write("?") - } - fn add_parameter(&mut self, value: Value<'a>) { self.parameters.push(value); } + fn parameter_substitution(&mut self) -> visitor::Result { + self.write("?") + } + fn visit_limit_and_offset(&mut self, limit: Option>, offset: Option>) -> visitor::Result { match (limit, offset) { (Some(limit), Some(offset)) => { @@ -274,6 +281,30 @@ impl<'a> Visitor<'a> for Sqlite<'a> { }) } + fn visit_geometry_type_equals( + &mut self, + left: Expression<'a>, + geom_type: GeometryType<'a>, + not: bool, + ) -> visitor::Result { + self.write("ST_GeometryType")?; + self.surround_with("(", ")", |s| s.visit_expression(left.clone()))?; + + if not { + self.write(" != ")?; + } else { + self.write(" = ")?; + } + + match geom_type { + GeometryType::ColumnRef(column) => { + self.write("ST_GeometryType")?; + self.surround_with("(", ")", |s| s.visit_column(*column)) + } + _ => self.visit_expression(Value::text(geom_type.to_string().to_uppercase()).into()), + } + } + #[cfg(any(feature = "postgresql", feature = "mysql"))] fn visit_json_extract(&mut self, _json_extract: JsonExtract<'a>) -> visitor::Result { unimplemented!("JSON filtering is not yet supported on SQLite") @@ -382,11 +413,25 @@ impl<'a> Visitor<'a> for Sqlite<'a> { Ok(()) } + + fn visit_geom_as_text(&mut self, geom: GeomAsText<'a>) -> visitor::Result { + self.surround_with("AsEWKT(", ")", |s| s.visit_expression(*geom.expression)) + } + + fn visit_geom_from_text(&mut self, geom: GeomFromText<'a>) -> visitor::Result { + self.surround_with("ST_GeomFromText(", ")", |ref mut s| { + s.visit_expression(*geom.wkt_expression)?; + s.write(",")?; + s.visit_expression(*geom.srid_expression)?; + Ok(()) + }) + } } #[cfg(test)] mod tests { use crate::{val, visitor::*}; + use std::str::FromStr; fn expected_values<'a, T>(sql: &'static str, params: Vec) -> (String, Vec>) where @@ -937,6 +982,22 @@ mod tests { assert!(params.is_empty()); } + #[test] + fn test_raw_geometry() { + let geom = GeometryValue::from_str("SRID=4326;POINT(0 0)").unwrap(); + let (sql, params) = Postgres::build(Select::default().value(Value::geometry(geom).raw())).unwrap(); + assert_eq!("SELECT ST_GeomFromText('POINT(0 0)',4326)", sql); + assert!(params.is_empty()); + } + + #[test] + fn test_raw_geography() { + let geom = GeometryValue::from_str("SRID=4326;POINT(0 0)").unwrap(); + let (sql, params) = Postgres::build(Select::default().value(Value::geography(geom).raw())).unwrap(); + assert_eq!("SELECT ST_GeomFromText('POINT(0 0)',4326)", sql); + assert!(params.is_empty()); + } + #[test] fn test_default_insert() { let insert = Insert::single_into("foo") diff --git a/query-engine/connector-test-kit-rs/qe-setup/src/lib.rs b/query-engine/connector-test-kit-rs/qe-setup/src/lib.rs index 9756c2efec66..7b588e0dd28e 100644 --- a/query-engine/connector-test-kit-rs/qe-setup/src/lib.rs +++ b/query-engine/connector-test-kit-rs/qe-setup/src/lib.rs @@ -7,10 +7,11 @@ mod mongodb; mod mssql; mod mysql; mod postgres; +mod sqlite; pub use schema_core::schema_connector::ConnectorError; -use self::{cockroachdb::*, mongodb::*, mssql::*, mysql::*, postgres::*}; +use self::{cockroachdb::*, mongodb::*, mssql::*, mysql::*, postgres::*, sqlite::*}; use enumflags2::BitFlags; use psl::{builtin_connectors::*, Datasource}; use schema_core::schema_connector::{ConnectorResult, DiffTarget, SchemaConnector}; @@ -50,12 +51,7 @@ pub async fn setup(prisma_schema: &str, db_schemas: &[&str]) -> ConnectorResult< let mut connector = sql_schema_connector::SqlSchemaConnector::new_mysql(); diff_and_apply(prisma_schema, url, &mut connector).await } - provider if SQLITE.is_provider(provider) => { - std::fs::remove_file(source.url.as_literal().unwrap().trim_start_matches("file:")).ok(); - let mut connector = sql_schema_connector::SqlSchemaConnector::new_sqlite(); - diff_and_apply(prisma_schema, url, &mut connector).await - } - + provider if SQLITE.is_provider(provider) => sqlite_setup(url, source, prisma_schema).await, provider if MONGODB.is_provider(provider) => mongo_setup(prisma_schema, &url).await, x => unimplemented!("Connector {} is not supported yet", x), diff --git a/query-engine/connector-test-kit-rs/qe-setup/src/postgres.rs b/query-engine/connector-test-kit-rs/qe-setup/src/postgres.rs index 6bbba8564cae..2e47a6315982 100644 --- a/query-engine/connector-test-kit-rs/qe-setup/src/postgres.rs +++ b/query-engine/connector-test-kit-rs/qe-setup/src/postgres.rs @@ -38,6 +38,9 @@ pub(crate) async fn postgres_setup(url: String, prisma_schema: &str, db_schemas: .map_err(|e| ConnectorError::from_source(e, ""))?; } + let conn = Quaint::new(parsed_url.as_ref()).await.unwrap(); + conn.raw_cmd("CREATE EXTENSION IF NOT EXISTS postgis").await.ok(); + let mut connector = sql_schema_connector::SqlSchemaConnector::new_postgres(); crate::diff_and_apply(prisma_schema, url, &mut connector).await } diff --git a/query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs b/query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs new file mode 100644 index 000000000000..37085b191ca7 --- /dev/null +++ b/query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs @@ -0,0 +1,11 @@ +use psl::Datasource; +use quaint::{prelude::*, single::Quaint}; +use schema_core::schema_connector::ConnectorResult; + +pub(crate) async fn sqlite_setup(url: String, source: Datasource, prisma_schema: &str) -> ConnectorResult<()> { + std::fs::remove_file(source.url.as_literal().unwrap().trim_start_matches("file:")).ok(); + let mut connector = sql_schema_connector::SqlSchemaConnector::new_sqlite(); + let client = Quaint::new(&url).await.unwrap(); + client.query_raw("SELECT InitSpatialMetaData()", &[]).await.unwrap(); + crate::diff_and_apply(prisma_schema, url, &mut connector).await +} diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/geometry.rs b/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/geometry.rs new file mode 100644 index 000000000000..c52442912461 --- /dev/null +++ b/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/geometry.rs @@ -0,0 +1,25 @@ +use indoc::indoc; + +/// Basic Test model containing a single geometry field. +pub fn geometry() -> String { + let schema = indoc! { + "model TestModel { + #id(id, Int, @id) + geometry GeoJson + }" + }; + + schema.to_owned() +} + +/// Basic Test model containing a single optional geometry field. +pub fn geometry_opt() -> String { + let schema = indoc! { + "model TestModel { + #id(id, Int, @id) + geometry GeoJson? + }" + }; + + schema.to_owned() +} diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/mod.rs b/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/mod.rs index cd9d2d9d1cb2..797235a679cb 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/mod.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/mod.rs @@ -1,11 +1,13 @@ mod basic; mod composites; +mod geometry; mod json; mod many_to_many; mod one_to_many; pub use basic::*; pub use composites::*; +pub use geometry::*; pub use json::*; pub use many_to_many::*; pub use one_to_many::*; diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/metrics.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/metrics.rs index cd270bb334c6..0c1e3788c5fa 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/metrics.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/metrics.rs @@ -35,13 +35,13 @@ mod metrics { let total_operations = get_counter(&json, PRISMA_CLIENT_QUERIES_TOTAL); match runner.connector_version() { - Sqlite(_) => assert_eq!(total_queries, 9), + Sqlite(_) => assert_eq!(total_queries, 10), SqlServer(_) => assert_eq!(total_queries, 17), MongoDb(_) => assert_eq!(total_queries, 5), CockroachDb(_) => (), // not deterministic MySql(_) => assert_eq!(total_queries, 12), Vitess(_) => assert_eq!(total_queries, 11), - Postgres(_) => assert_eq!(total_queries, 7), + Postgres(_) => assert_eq!(total_queries, 9), } assert_eq!(total_operations, 2); diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs new file mode 100644 index 000000000000..fe29e6dc95e2 --- /dev/null +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs @@ -0,0 +1,123 @@ +use query_engine_tests::*; + +#[test_suite( + schema(schema), + capabilities(GeoJsonGeometry), + exclude(Postgres(9, 10, 11, 12, 13, 14, 15, "pgbouncer")) +)] +mod geometry_filter_spec { + use query_engine_tests::run_query; + + fn schema() -> String { + let schema = indoc! { + r#" + model TestModel { + #id(id, Int, @id) + geom GeoJson? + } + "# + }; + + schema.to_owned() + } + + #[connector_test] + async fn basic_where(runner: Runner) -> TestResult<()> { + test_data(&runner).await?; + + insta::assert_snapshot!( + run_query!(&runner, r#"query { findManyTestModel(where: { geom: { equals: "{\"type\":\"Point\",\"coordinates\":[0, 0]}" }}) { id }}"#), + @r###"{"data":{"findManyTestModel":[{"id":1}]}}"### + ); + + insta::assert_snapshot!( + run_query!(&runner, r#"query { findManyTestModel(where: { AND: [{ geom: { not: "{\"type\":\"Point\",\"coordinates\":[0, 0]}" }}, { geom: { not: null }}] }) { id }}"#), + @r###"{"data":{"findManyTestModel":[{"id":2}]}}"### + ); + + insta::assert_snapshot!( + run_query!(&runner, r#"query { findManyTestModel(where: { geom: { not: null }}) { id }}"#), + @r###"{"data":{"findManyTestModel":[{"id":1},{"id":2}]}}"### + ); + + Ok(()) + } + + #[connector_test] + async fn where_shorthands(runner: Runner) -> TestResult<()> { + test_data(&runner).await?; + + insta::assert_snapshot!( + run_query!(&runner, r#"query { findManyTestModel(where: { geom: "{\"type\":\"Point\",\"coordinates\":[0, 0]}" }) { id }}"#), + @r###"{"data":{"findManyTestModel":[{"id":1}]}}"### + ); + + match_connector_result!( + &runner, + r#"query { findManyTestModel(where: { geom: null }) { id }}"#, + // MongoDB excludes undefined fields + MongoDb(_) => vec![r#"{"data":{"findManyTestModel":[]}}"#], + _ => vec![r#"{"data":{"findManyTestModel":[{"id":3}]}}"#] + ); + + Ok(()) + } + + #[connector_test(capabilities(GeometryFiltering))] + async fn geometric_comparison_filters(runner: Runner) -> TestResult<()> { + test_data(&runner).await?; + + // geoWithin + insta::assert_snapshot!( + run_query!(&runner, r#"query { findManyTestModel(where: { geom: { geoWithin: "{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[1,4],[4,4],[4,1],[1,1]]]}" }}) { id }}"#), + @r###"{"data":{"findManyTestModel":[{"id":2}]}}"### + ); + + // Not geoWithin + insta::assert_snapshot!( + run_query!(&runner, r#"query { findManyTestModel(where: { geom: { not: { geoWithin: "{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[1,4],[4,4],[4,1],[1,1]]]}" }}}) { id }}"#), + @r###"{"data":{"findManyTestModel":[{"id":1}]}}"### + ); + + // geoIntersects + insta::assert_snapshot!( + run_query!(&runner, r#"query { findManyTestModel(where: { geom: { geoIntersects: "{\"type\":\"Point\",\"coordinates\":[0, 0]}" }}) { id }}"#), + @r###"{"data":{"findManyTestModel":[{"id":1}]}}"### + ); + + // Not geoIntersects + insta::assert_snapshot!( + run_query!(&runner, r#"query { findManyTestModel(where: { geom: { not: { geoIntersects: "{\"type\":\"Point\",\"coordinates\":[0, 0]}" }}}) { id }}"#), + @r###"{"data":{"findManyTestModel":[{"id":2}]}}"### + ); + + Ok(()) + } + + async fn test_data(runner: &Runner) -> TestResult<()> { + runner + .query(indoc! { r#" + mutation { createOneTestModel(data: { + id: 1, + geom: "{\"type\":\"Point\",\"coordinates\":[0, 0]}", + }) { id }}"# }) + .await? + .assert_success(); + + runner + .query(indoc! { r#" + mutation { createOneTestModel(data: { + id: 2, + geom: "{\"type\":\"Polygon\",\"coordinates\":[[[2,2],[2,3],[3,3],[3,2],[2,2]]]}", + }) { id }}"# }) + .await? + .assert_success(); + + runner + .query(indoc! { r#"mutation { createOneTestModel(data: { id: 3 }) { id }}"# }) + .await? + .assert_success(); + + Ok(()) + } +} diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/mod.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/mod.rs index a87b522614ef..fb303ab558f2 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/mod.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/mod.rs @@ -9,6 +9,7 @@ pub mod field_reference; pub mod filter_regression; pub mod filter_unwrap; pub mod filters; +pub mod geometry_filter; pub mod insensitive_filters; pub mod json; pub mod json_filters; diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mod.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mod.rs index fb814cca9977..ed35a5c04604 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mod.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mod.rs @@ -2,3 +2,4 @@ mod mongodb; mod mysql; mod postgres; mod sql_server; +mod sqlite; diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mongodb.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mongodb.rs index 0ee4912574aa..4cbbc4441eca 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mongodb.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mongodb.rs @@ -17,6 +17,7 @@ mod mongodb { bool Boolean @test.Bool bin Bytes @test.BinData bin_oid Bytes @test.ObjectId + geom GeoJson }"# }; @@ -38,6 +39,7 @@ mod mongodb { bool: true bin: "dGVzdA==" bin_oid: "YeUuxAwj5igGOSD0" + geom: "{\"type\": \"Point\", \"coordinates\": [0, 0]}" } ) { int @@ -49,9 +51,10 @@ mod mongodb { bool bin bin_oid + geom } }"#), - @r###"{"data":{"createOneTestModel":{"int":2147483647,"long":32767,"bInt":"9223372036854775807","float":3.1234,"oid":"61e1425609c85b5e01817cc5","str":"test","bool":true,"bin":"dGVzdA==","bin_oid":"YeUuxAwj5igGOSD0"}}}"### + @r###"{"data":{"createOneTestModel":{"int":2147483647,"long":32767,"bInt":"9223372036854775807","float":3.1234,"oid":"61e1425609c85b5e01817cc5","str":"test","bool":true,"bin":"dGVzdA==","bin_oid":"YeUuxAwj5igGOSD0","geom":"{\"type\":\"Point\",\"coordinates\":[0,0]}"}}}"### ); Ok(()) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mysql.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mysql.rs index 4d3c3137f4a2..4c1c80ee7da0 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mysql.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mysql.rs @@ -430,4 +430,208 @@ mod mysql { Ok(()) } + + fn schema_ewkt_geometry_types() -> String { + let schema = indoc! { + r#"model Model { + #id(id, String, @id, @default(cuid())) + geometry Geometry @test.Geometry + point Geometry @test.Point + line Geometry @test.LineString + poly Geometry @test.Polygon + multipoint Geometry @test.MultiPoint + multiline Geometry @test.MultiLineString + multipoly Geometry @test.MultiPolygon + collection Geometry @test.GeometryCollection + }"# + }; + + schema.to_owned() + } + + // "MySQL native spatial types" should "work" + #[connector_test(schema(schema_ewkt_geometry_types))] + async fn native_ewkt_geometry_types(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geometry: "POINT(1 2)" + point: "POINT(1 2)" + line: "LINESTRING(1 2,3 4)" + poly: "POLYGON((1 2,3 4,5 6,1 2))" + multipoint: "MULTIPOINT(1 2)" + multiline: "MULTILINESTRING((1 2,3 4))" + multipoly: "MULTIPOLYGON(((1 2,3 4,5 6,1 2)))" + collection: "GEOMETRYCOLLECTION(POINT(1 2))" + } + ) { + geometry, + point + line + poly + multipoint + multiline + multipoly + collection + } + }"#), + @r###"{"data":{"createOneModel":{"geometry":"POINT(1 2)","point":"POINT(1 2)","line":"LINESTRING(1 2,3 4)","poly":"POLYGON((1 2,3 4,5 6,1 2))","multipoint":"MULTIPOINT(1 2)","multiline":"MULTILINESTRING((1 2,3 4))","multipoly":"MULTIPOLYGON(((1 2,3 4,5 6,1 2)))","collection":"GEOMETRYCOLLECTION(POINT(1 2))"}}}"### + ); + + Ok(()) + } + + fn schema_geojson_geometry_types() -> String { + let schema = indoc! { + r#"model Model { + #id(id, String, @id, @default(cuid())) + geometry GeoJson @test.Geometry + point GeoJson @test.Point + line GeoJson @test.LineString + poly GeoJson @test.Polygon + multipoint GeoJson @test.MultiPoint + multiline GeoJson @test.MultiLineString + multipoly GeoJson @test.MultiPolygon + collection GeoJson @test.GeometryCollection + }"# + }; + + schema.to_owned() + } + + // "MySQL native spatial types" should "work" + #[connector_test(schema(schema_geojson_geometry_types))] + async fn native_geojson_geometry_types(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geometry: "{\"type\":\"Point\",\"coordinates\":[1,2]}" + point: "{\"type\":\"Point\",\"coordinates\":[1,2]}" + line: "{\"type\":\"LineString\",\"coordinates\":[[1,2],[3,4]]}" + poly: "{\"type\":\"Polygon\",\"coordinates\":[[[1,2],[3,4],[5,6],[1,2]]]}" + multipoint: "{\"type\":\"MultiPoint\",\"coordinates\":[[1,2]]}" + multiline: "{\"type\":\"MultiLineString\",\"coordinates\":[[[1,2],[3,4]]]}" + multipoly: "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[1,2],[3,4],[5,6],[1,2]]]]}" + collection: "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[1,2]}]}" + } + ) { + geometry, + point + line + poly + multipoint + multiline + multipoly + collection + } + }"#), + @r###"{"data":{"createOneModel":{"geometry":"{\"type\": \"Point\", \"coordinates\": [1,2]}","point":"{\"type\": \"Point\", \"coordinates\": [1,2]}","line":"{\"type\": \"LineString\", \"coordinates\": [[1,2],[3,4]]}","poly":"{\"type\": \"Polygon\", \"coordinates\": [[[1,2],[3,4],[5,6],[1,2]]]}","multipoint":"{\"type\": \"MultiPoint\", \"coordinates\": [[1,2]]}","multiline":"{\"type\": \"MultiLineString\", \"coordinates\": [[[1,2],[3,4]]]}","multipoly":"{\"type\": \"MultiPolygon\", \"coordinates\": [[[[1,2],[3,4],[5,6],[1,2]]]]}","collection":"{\"type\": \"GeometryCollection\", \"geometries\": [{\"type\": \"Point\", \"coordinates\": [1,2]}]}"}}}"### + ); + + Ok(()) + } + + fn schema_geometry_srid_types() -> String { + let schema = indoc! { + r#"model Model { + #id(id, String, @id, @default(cuid())) + geometry Geometry @test.Geometry(4326) + point Geometry @test.Point(4326) + line Geometry @test.LineString(4326) + poly Geometry @test.Polygon(4326) + multipoint Geometry @test.MultiPoint(4326) + multiline Geometry @test.MultiLineString(4326) + multipoly Geometry @test.MultiPolygon(4326) + collection Geometry @test.GeometryCollection(4326) + }"# + }; + + schema.to_owned() + } + + // "MySQL native spatial srid types" should "work" + #[connector_test(only(MySQL(8)), schema(schema_geometry_srid_types))] + async fn native_geometry_srid_types(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geometry: "SRID=4326;POINT(1 2)" + point: "SRID=4326;POINT(1 2)" + line: "SRID=4326;LINESTRING(1 2,3 4)" + poly: "SRID=4326;POLYGON((1 2,3 4,5 6,1 2))" + multipoint: "SRID=4326;MULTIPOINT(1 2)" + multiline: "SRID=4326;MULTILINESTRING((1 2,3 4))" + multipoly: "SRID=4326;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))" + collection: "SRID=4326;GEOMETRYCOLLECTION(POINT(1 2))" + } + ) { + geometry + point + line + poly + multipoint + multiline + multipoly + collection + } + }"#), + @r###"{"data":{"createOneModel":{"geometry":"SRID=4326;POINT(1 2)","point":"SRID=4326;POINT(1 2)","line":"SRID=4326;LINESTRING(1 2,3 4)","poly":"SRID=4326;POLYGON((1 2,3 4,5 6,1 2))","multipoint":"SRID=4326;MULTIPOINT(1 2)","multiline":"SRID=4326;MULTILINESTRING((1 2,3 4))","multipoly":"SRID=4326;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))","collection":"SRID=4326;GEOMETRYCOLLECTION(POINT(1 2))"}}}"### + ); + + Ok(()) + } + + fn schema_geojson_srid_geometry_types() -> String { + let schema = indoc! { + r#"model Model { + #id(id, String, @id, @default(cuid())) + geometry GeoJson @test.Geometry(4326) + point GeoJson @test.Point(4326) + line GeoJson @test.LineString(4326) + poly GeoJson @test.Polygon(4326) + multipoint GeoJson @test.MultiPoint(4326) + multiline GeoJson @test.MultiLineString(4326) + multipoly GeoJson @test.MultiPolygon(4326) + collection GeoJson @test.GeometryCollection(4326) + }"# + }; + + schema.to_owned() + } + + // "MySQL native spatial types" should "work" + #[connector_test(schema(schema_geojson_srid_geometry_types))] + async fn native_geojson_srid_geometry_types(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geometry: "{\"type\":\"Point\",\"coordinates\":[1,2]}" + point: "{\"type\":\"Point\",\"coordinates\":[1,2]}" + line: "{\"type\":\"LineString\",\"coordinates\":[[1,2],[3,4]]}" + poly: "{\"type\":\"Polygon\",\"coordinates\":[[[1,2],[3,4],[5,6],[1,2]]]}" + multipoint: "{\"type\":\"MultiPoint\",\"coordinates\":[[1,2]]}" + multiline: "{\"type\":\"MultiLineString\",\"coordinates\":[[[1,2],[3,4]]]}" + multipoly: "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[1,2],[3,4],[5,6],[1,2]]]]}" + collection: "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[1,2]}]}" + } + ) { + geometry, + point + line + poly + multipoint + multiline + multipoly + collection + } + }"#), + @r###"{"data":{"createOneModel":{"geometry":"{\"type\": \"Point\", \"coordinates\": [1,2]}","point":"{\"type\": \"Point\", \"coordinates\": [1,2]}","line":"{\"type\": \"LineString\", \"coordinates\": [[1,2],[3,4]]}","poly":"{\"type\": \"Polygon\", \"coordinates\": [[[1,2],[3,4],[5,6],[1,2]]]}","multipoint":"{\"type\": \"MultiPoint\", \"coordinates\": [[1,2]]}","multiline":"{\"type\": \"MultiLineString\", \"coordinates\": [[[1,2],[3,4]]]}","multipoly":"{\"type\": \"MultiPolygon\", \"coordinates\": [[[[1,2],[3,4],[5,6],[1,2]]]]}","collection":"{\"type\": \"GeometryCollection\", \"geometries\": [{\"type\": \"Point\", \"coordinates\": [1,2]}]}"}}}"### + ); + + Ok(()) + } } diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/postgres.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/postgres.rs index 2d487ec4f137..8a8312c5ebde 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/postgres.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/postgres.rs @@ -346,4 +346,396 @@ mod postgres { Ok(()) } + + fn schema_ewkt_geometry() -> String { + let schema = indoc! { + r#"model Model { + @@schema("test") + #id(id, String, @id, @default(cuid())) + geometry Geometry @test.Geometry(Geometry) + geometry_point Geometry @test.Geometry(Point) + geometry_line Geometry @test.Geometry(LineString) + geometry_poly Geometry @test.Geometry(Polygon) + geometry_multipoint Geometry @test.Geometry(MultiPoint) + geometry_multiline Geometry @test.Geometry(MultiLineString) + geometry_multipoly Geometry @test.Geometry(MultiPolygon) + geometry_collection Geometry @test.Geometry(GeometryCollection) + }"# + }; + + schema.to_owned() + } + + // "PostGIS common geometry types" should "work" + #[connector_test( + only(Postgres("15-postgis"), CockroachDb), + schema(schema_ewkt_geometry), + db_schemas("public", "test") + )] + async fn native_ewkt_geometry(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geometry: "POINT(1 2)" + geometry_point: "POINT(1 2)" + geometry_line: "LINESTRING(1 2,3 4)" + geometry_poly: "POLYGON((1 2,3 4,5 6,1 2))" + geometry_multipoint: "MULTIPOINT(1 2)" + geometry_multiline: "MULTILINESTRING((1 2,3 4))" + geometry_multipoly: "MULTIPOLYGON(((1 2,3 4,5 6,1 2)))" + geometry_collection: "GEOMETRYCOLLECTION(POINT(1 2))" + } + ) { + geometry + geometry_point + geometry_line + geometry_poly + geometry_multipoint + geometry_multiline + geometry_multipoly + geometry_collection + } + }"#), + @r###"{"data":{"createOneModel":{"geometry":"POINT(1 2)","geometry_point":"POINT(1 2)","geometry_line":"LINESTRING(1 2,3 4)","geometry_poly":"POLYGON((1 2,3 4,5 6,1 2))","geometry_multipoint":"MULTIPOINT(1 2)","geometry_multiline":"MULTILINESTRING((1 2,3 4))","geometry_multipoly":"MULTIPOLYGON(((1 2,3 4,5 6,1 2)))","geometry_collection":"GEOMETRYCOLLECTION(POINT(1 2))"}}}"### + ); + + Ok(()) + } + + fn schema_ewkt_geometry_srid() -> String { + let schema = indoc! { + r#"model Model { + @@schema("test") + #id(id, String, @id, @default(cuid())) + geometry Geometry @test.Geometry(Geometry, 3857) + geometry_point Geometry @test.Geometry(Point, 3857) + geometry_line Geometry @test.Geometry(LineString, 3857) + geometry_poly Geometry @test.Geometry(Polygon, 3857) + geometry_multipoint Geometry @test.Geometry(MultiPoint, 3857) + geometry_multiline Geometry @test.Geometry(MultiLineString, 3857) + geometry_multipoly Geometry @test.Geometry(MultiPolygon, 3857) + geometry_collection Geometry @test.Geometry(GeometryCollection, 3857) + }"# + }; + + schema.to_owned() + } + + // "PostGIS common geometry types with srid" should "work" + #[connector_test( + only(Postgres("15-postgis"), CockroachDb), + schema(schema_ewkt_geometry_srid), + db_schemas("public", "test") + )] + async fn native_geometry_srid(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geometry: "SRID=3857;POINT(1 2)" + geometry_point: "SRID=3857;POINT(1 2)" + geometry_line: "SRID=3857;LINESTRING(1 2,3 4)" + geometry_poly: "SRID=3857;POLYGON((1 2,3 4,5 6,1 2))" + geometry_multipoint: "SRID=3857;MULTIPOINT(1 2)" + geometry_multiline: "SRID=3857;MULTILINESTRING((1 2,3 4))" + geometry_multipoly: "SRID=3857;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))" + geometry_collection: "SRID=3857;GEOMETRYCOLLECTION(POINT(1 2))" + } + ) { + geometry + geometry_point + geometry_line + geometry_poly + geometry_multipoint + geometry_multiline + geometry_multipoly + geometry_collection + } + }"#), + @r###"{"data":{"createOneModel":{"geometry":"SRID=3857;POINT(1 2)","geometry_point":"SRID=3857;POINT(1 2)","geometry_line":"SRID=3857;LINESTRING(1 2,3 4)","geometry_poly":"SRID=3857;POLYGON((1 2,3 4,5 6,1 2))","geometry_multipoint":"SRID=3857;MULTIPOINT(1 2)","geometry_multiline":"SRID=3857;MULTILINESTRING((1 2,3 4))","geometry_multipoly":"SRID=3857;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))","geometry_collection":"SRID=3857;GEOMETRYCOLLECTION(POINT(1 2))"}}}"### + ); + + Ok(()) + } + + fn schema_ewkt_geography() -> String { + let schema = indoc! { + r#"model Model { + @@schema("test") + #id(id, String, @id, @default(cuid())) + geography Geometry @test.Geography(Geometry) + geography_point Geometry @test.Geography(Point) + geography_line Geometry @test.Geography(LineString) + geography_poly Geometry @test.Geography(Polygon) + geography_multipoint Geometry @test.Geography(MultiPoint) + geography_multiline Geometry @test.Geography(MultiLineString) + geography_multipoly Geometry @test.Geography(MultiPolygon) + geography_collection Geometry @test.Geography(GeometryCollection) + }"# + }; + + schema.to_owned() + } + + // "PostGIS common geography types" should "work" + #[connector_test( + only(Postgres("15-postgis"), CockroachDb), + schema(schema_ewkt_geography), + db_schemas("public", "test") + )] + async fn native_ewkt_geography(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geography: "SRID=4326;POINT(1 2)" + geography_point: "SRID=4326;POINT(1 2)" + geography_line: "SRID=4326;LINESTRING(1 2,3 4)" + geography_poly: "SRID=4326;POLYGON((1 2,3 4,5 6,1 2))" + geography_multipoint: "SRID=4326;MULTIPOINT(1 2)" + geography_multiline: "SRID=4326;MULTILINESTRING((1 2,3 4))" + geography_multipoly: "SRID=4326;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))" + geography_collection: "SRID=4326;GEOMETRYCOLLECTION(POINT(1 2))" + } + ) { + geography + geography_point + geography_line + geography_poly + geography_multipoint + geography_multiline + geography_multipoly + geography_collection + } + }"#), + @r###"{"data":{"createOneModel":{"geography":"SRID=4326;POINT(1 2)","geography_point":"SRID=4326;POINT(1 2)","geography_line":"SRID=4326;LINESTRING(1 2,3 4)","geography_poly":"SRID=4326;POLYGON((1 2,3 4,5 6,1 2))","geography_multipoint":"SRID=4326;MULTIPOINT(1 2)","geography_multiline":"SRID=4326;MULTILINESTRING((1 2,3 4))","geography_multipoly":"SRID=4326;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))","geography_collection":"SRID=4326;GEOMETRYCOLLECTION(POINT(1 2))"}}}"### + ); + + Ok(()) + } + + fn schema_ewkt_geography_srid() -> String { + let schema = indoc! { + r#"model Model { + @@schema("test") + #id(id, String, @id, @default(cuid())) + geography Geometry @test.Geography(Geometry, 9000) + geography_point Geometry @test.Geography(Point, 9000) + geography_line Geometry @test.Geography(LineString, 9000) + geography_poly Geometry @test.Geography(Polygon, 9000) + geography_multipoint Geometry @test.Geography(MultiPoint, 9000) + geography_multiline Geometry @test.Geography(MultiLineString, 9000) + geography_multipoly Geometry @test.Geography(MultiPolygon, 9000) + geography_collection Geometry @test.Geography(GeometryCollection, 9000) + }"# + }; + + schema.to_owned() + } + + // "PostGIS common geography types with srid" should "work" + #[connector_test( + only(Postgres("15-postgis"), CockroachDb), + schema(schema_ewkt_geography_srid), + db_schemas("public", "test") + )] + async fn native_ewkt_geography_srid(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geography: "SRID=9000;POINT(1 2)" + geography_point: "SRID=9000;POINT(1 2)" + geography_line: "SRID=9000;LINESTRING(1 2,3 4)" + geography_poly: "SRID=9000;POLYGON((1 2,3 4,5 6,1 2))" + geography_multipoint: "SRID=9000;MULTIPOINT(1 2)" + geography_multiline: "SRID=9000;MULTILINESTRING((1 2,3 4))" + geography_multipoly: "SRID=9000;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))" + geography_collection: "SRID=9000;GEOMETRYCOLLECTION(POINT(1 2))" + } + ) { + geography + geography_point + geography_line + geography_poly + geography_multipoint + geography_multiline + geography_multipoly + geography_collection + } + }"#), + @r###"{"data":{"createOneModel":{"geography":"SRID=9000;POINT(1 2)","geography_point":"SRID=9000;POINT(1 2)","geography_line":"SRID=9000;LINESTRING(1 2,3 4)","geography_poly":"SRID=9000;POLYGON((1 2,3 4,5 6,1 2))","geography_multipoint":"SRID=9000;MULTIPOINT(1 2)","geography_multiline":"SRID=9000;MULTILINESTRING((1 2,3 4))","geography_multipoly":"SRID=9000;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))","geography_collection":"SRID=9000;GEOMETRYCOLLECTION(POINT(1 2))"}}}"### + ); + + Ok(()) + } + + fn schema_extra_geometry() -> String { + let schema = indoc! { + r#"model Model { + @@schema("test") + #id(id, String, @id, @default(cuid())) + geometry_triangle Geometry @test.Geometry(Triangle) + geometry_circularstring Geometry @test.Geometry(CircularString) + geometry_compoundcurve Geometry @test.Geometry(CompoundCurve) + geometry_curvepolygon Geometry @test.Geometry(CurvePolygon) + geometry_multicurve Geometry @test.Geometry(MultiCurve) + geometry_multisurface Geometry @test.Geometry(MultiSurface) + geometry_polyhedral Geometry @test.Geometry(PolyhedralSurfaceZ) + geometry_tin Geometry @test.Geometry(Tin) + }"# + }; + + schema.to_owned() + } + + // "PostGIS extra geometry types" should "work" + #[connector_test( + only(Postgres("15-postgis")), + schema(schema_extra_geometry), + db_schemas("public", "test") + )] + async fn native_extra_geometry(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geometry_triangle: "TRIANGLE((0 0,1 1,2 0,0 0))" + geometry_circularstring: "CIRCULARSTRING(0 0,4 0,4 4,0 4,0 0)" + geometry_compoundcurve: "COMPOUNDCURVE(CIRCULARSTRING(0 0,1 1,1 0),(1 0,0 1))" + geometry_curvepolygon: "CURVEPOLYGON(CIRCULARSTRING(0 0,4 0,4 4,0 4,0 0),(1 1,3 3,3 1,1 1))" + geometry_multicurve: "MULTICURVE((0 0,5 5),CIRCULARSTRING(4 0,4 4,8 4))" + geometry_multisurface: "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0,4 0,4 4,0 4,0 0),(1 1,3 3,3 1,1 1)),((10 10,14 12,11 10,10 10),(11 11,11.5 11,11 11.5,11 11)))" + geometry_polyhedral:"POLYHEDRALSURFACE(((0 0 0,1 0 0,0 1 0,0 0 1,0 0 0)))" + geometry_tin: "TIN(((80 130,50 160,80 70,80 130)),((50 160,10 190,10 70,50 160)),((80 70,50 160,10 70,80 70)),((120 160,120 190,50 160,120 160)),((120 190,10 190,50 160,120 190)))" + } + ) { + geometry_triangle + geometry_circularstring + geometry_compoundcurve + geometry_curvepolygon + geometry_multicurve + geometry_multisurface + geometry_polyhedral + geometry_tin + } + }"#), + @r###"{"data":{"createOneModel":{"geometry_triangle":"TRIANGLE((0 0,1 1,2 0,0 0))","geometry_circularstring":"CIRCULARSTRING(0 0,4 0,4 4,0 4,0 0)","geometry_compoundcurve":"COMPOUNDCURVE(CIRCULARSTRING(0 0,1 1,1 0),(1 0,0 1))","geometry_curvepolygon":"CURVEPOLYGON(CIRCULARSTRING(0 0,4 0,4 4,0 4,0 0),(1 1,3 3,3 1,1 1))","geometry_multicurve":"MULTICURVE((0 0,5 5),CIRCULARSTRING(4 0,4 4,8 4))","geometry_multisurface":"MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0,4 0,4 4,0 4,0 0),(1 1,3 3,3 1,1 1)),((10 10,14 12,11 10,10 10),(11 11,11.5 11,11 11.5,11 11)))","geometry_polyhedral":"POLYHEDRALSURFACE(((0 0 0,1 0 0,0 1 0,0 0 1,0 0 0)))","geometry_tin":"TIN(((80 130,50 160,80 70,80 130)),((50 160,10 190,10 70,50 160)),((80 70,50 160,10 70,80 70)),((120 160,120 190,50 160,120 160)),((120 190,10 190,50 160,120 190)))"}}}"### + ); + + Ok(()) + } + + fn schema_geojson_geometry() -> String { + let schema = indoc! { + r#"model Model { + @@schema("test") + #id(id, String, @id, @default(cuid())) + geometry GeoJson @test.Geometry(Geometry, 4326) + geometry_point GeoJson @test.Geometry(Point, 4326) + geometry_line GeoJson @test.Geometry(LineString, 4326) + geometry_poly GeoJson @test.Geometry(Polygon, 4326) + geometry_multipoint GeoJson @test.Geometry(MultiPoint, 4326) + geometry_multiline GeoJson @test.Geometry(MultiLineString, 4326) + geometry_multipoly GeoJson @test.Geometry(MultiPolygon, 4326) + geometry_collection GeoJson @test.Geometry(GeometryCollection, 4326) + }"# + }; + + schema.to_owned() + } + + // "PostGIS common geometry types" should "work" with GeoJSON + #[connector_test( + only(Postgres("15-postgis"), CockroachDb), + schema(schema_geojson_geometry), + db_schemas("public", "test") + )] + async fn native_geojson_geometry(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geometry: "{\"type\":\"Point\",\"coordinates\":[1,2]}" + geometry_point: "{\"type\":\"Point\",\"coordinates\":[1,2]}" + geometry_line: "{\"type\":\"LineString\",\"coordinates\":[[1,2],[3,4]]}" + geometry_poly: "{\"type\":\"Polygon\",\"coordinates\":[[[1,2],[3,4],[5,6],[1,2]]]}" + geometry_multipoint: "{\"type\":\"MultiPoint\",\"coordinates\":[[1,2]]}" + geometry_multiline: "{\"type\":\"MultiLineString\",\"coordinates\":[[[1,2],[3,4]]]}" + geometry_multipoly: "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[1,2],[3,4],[5,6],[1,2]]]]}" + geometry_collection: "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[1,2]}]}" + } + ) { + geometry + geometry_point + geometry_line + geometry_poly + geometry_multipoint + geometry_multiline + geometry_multipoly + geometry_collection + } + }"#), + @r###"{"data":{"createOneModel":{"geometry":"{\"type\": \"Point\", \"coordinates\": [1,2]}","geometry_point":"{\"type\": \"Point\", \"coordinates\": [1,2]}","geometry_line":"{\"type\": \"LineString\", \"coordinates\": [[1,2],[3,4]]}","geometry_poly":"{\"type\": \"Polygon\", \"coordinates\": [[[1,2],[3,4],[5,6],[1,2]]]}","geometry_multipoint":"{\"type\": \"MultiPoint\", \"coordinates\": [[1,2]]}","geometry_multiline":"{\"type\": \"MultiLineString\", \"coordinates\": [[[1,2],[3,4]]]}","geometry_multipoly":"{\"type\": \"MultiPolygon\", \"coordinates\": [[[[1,2],[3,4],[5,6],[1,2]]]]}","geometry_collection":"{\"type\": \"GeometryCollection\", \"geometries\": [{\"type\": \"Point\", \"coordinates\": [1,2]}]}"}}}"### + ); + + Ok(()) + } + + fn schema_geojson_geography() -> String { + let schema = indoc! { + r#"model Model { + @@schema("test") + #id(id, String, @id, @default(cuid())) + geography GeoJson @test.Geography(Geometry, 4326) + geography_point GeoJson @test.Geography(Point, 4326) + geography_line GeoJson @test.Geography(LineString, 4326) + geography_poly GeoJson @test.Geography(Polygon, 4326) + geography_multipoint GeoJson @test.Geography(MultiPoint, 4326) + geography_multiline GeoJson @test.Geography(MultiLineString, 4326) + geography_multipoly GeoJson @test.Geography(MultiPolygon, 4326) + geography_collection GeoJson @test.Geography(GeometryCollection, 4326) + }"# + }; + + schema.to_owned() + } + + // "PostGIS common geometry types" should "work" with GeoJSON + #[connector_test( + only(Postgres("15-postgis"), CockroachDb), + schema(schema_geojson_geography), + db_schemas("public", "test") + )] + async fn native_geojson_geography(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geography: "{\"type\":\"Point\",\"coordinates\":[1,2]}" + geography_point: "{\"type\":\"Point\",\"coordinates\":[1,2]}" + geography_line: "{\"type\":\"LineString\",\"coordinates\":[[1,2],[3,4]]}" + geography_poly: "{\"type\":\"Polygon\",\"coordinates\":[[[1,2],[3,4],[5,6],[1,2]]]}" + geography_multipoint: "{\"type\":\"MultiPoint\",\"coordinates\":[[1,2]]}" + geography_multiline: "{\"type\":\"MultiLineString\",\"coordinates\":[[[1,2],[3,4]]]}" + geography_multipoly: "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[1,2],[3,4],[5,6],[1,2]]]]}" + geography_collection: "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[1,2]}]}" + } + ) { + geography + geography_point + geography_line + geography_poly + geography_multipoint + geography_multiline + geography_multipoly + geography_collection + } + }"#), + @r###"{"data":{"createOneModel":{"geography":"{\"type\": \"Point\", \"coordinates\": [1,2]}","geography_point":"{\"type\": \"Point\", \"coordinates\": [1,2]}","geography_line":"{\"type\": \"LineString\", \"coordinates\": [[1,2],[3,4]]}","geography_poly":"{\"type\": \"Polygon\", \"coordinates\": [[[1,2],[3,4],[5,6],[1,2]]]}","geography_multipoint":"{\"type\": \"MultiPoint\", \"coordinates\": [[1,2]]}","geography_multiline":"{\"type\": \"MultiLineString\", \"coordinates\": [[[1,2],[3,4]]]}","geography_multipoly":"{\"type\": \"MultiPolygon\", \"coordinates\": [[[[1,2],[3,4],[5,6],[1,2]]]]}","geography_collection":"{\"type\": \"GeometryCollection\", \"geometries\": [{\"type\": \"Point\", \"coordinates\": [1,2]}]}"}}}"### + ); + + Ok(()) + } } diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sql_server.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sql_server.rs index 880dd687efd6..38dabaec7fa0 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sql_server.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sql_server.rs @@ -361,8 +361,10 @@ mod sql_server { let schema = indoc! { r#"model Model { #id(id, String, @id, @default(cuid())) - xml String @test.Xml - uuid String @test.UniqueIdentifier + xml String @test.Xml + uuid String @test.UniqueIdentifier + geom Geometry @test.Geometry + geog Geometry @test.Geography }"# }; @@ -378,13 +380,17 @@ mod sql_server { data: { xml: "purr" uuid: "ab309dfd-d041-4110-b162-75d7b95fe989" + geom: "SRID=4326;POINT(1 2)" + geog: "SRID=4326;POINT(1 2)" } ) { xml uuid + geom + geog } }"#), - @r###"{"data":{"createOneModel":{"xml":"purr","uuid":"ab309dfd-d041-4110-b162-75d7b95fe989"}}}"### + @r###"{"data":{"createOneModel":{"xml":"purr","uuid":"ab309dfd-d041-4110-b162-75d7b95fe989","geom":"SRID=4326;POINT (1 2)","geog":"SRID=4326;POINT (1 2)"}}}"### ); Ok(()) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sqlite.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sqlite.rs new file mode 100644 index 000000000000..10fd173a7c99 --- /dev/null +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sqlite.rs @@ -0,0 +1,160 @@ +use query_engine_tests::*; + +#[test_suite(only(Sqlite))] +mod sqlite { + use indoc::indoc; + use query_engine_tests::run_query; + + fn schema_ewkt_geometry() -> String { + let schema = indoc! { + r#"model Model { + #id(id, String, @id, @default(cuid())) + geometry Geometry @test.Geometry(Geometry) + geometry_point Geometry @test.Geometry(Point) + geometry_line Geometry @test.Geometry(LineString) + geometry_poly Geometry @test.Geometry(Polygon) + geometry_multipoint Geometry @test.Geometry(MultiPoint) + geometry_multiline Geometry @test.Geometry(MultiLineString) + geometry_multipoly Geometry @test.Geometry(MultiPolygon) + geometry_collection Geometry @test.Geometry(GeometryCollection) + }"# + }; + + schema.to_owned() + } + + // "Spatialite common geometry types" should "work" + #[connector_test(schema(schema_ewkt_geometry))] + async fn native_ewkt_geometry(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geometry: "POINT(1 2)" + geometry_point: "POINT(1 2)" + geometry_line: "LINESTRING(1 2,3 4)" + geometry_poly: "POLYGON((1 2,3 4,5 6,1 2))" + geometry_multipoint: "MULTIPOINT(1 2)" + geometry_multiline: "MULTILINESTRING((1 2,3 4))" + geometry_multipoly: "MULTIPOLYGON(((1 2,3 4,5 6,1 2)))" + geometry_collection: "GEOMETRYCOLLECTION(POINT(1 2))" + } + ) { + geometry + geometry_point + geometry_line + geometry_poly + geometry_multipoint + geometry_multiline + geometry_multipoly + geometry_collection + } + }"#), + @r###"{"data":{"createOneModel":{"geometry":"POINT(1 2)","geometry_point":"POINT(1 2)","geometry_line":"LINESTRING(1 2,3 4)","geometry_poly":"POLYGON((1 2,3 4,5 6,1 2))","geometry_multipoint":"MULTIPOINT(1 2)","geometry_multiline":"MULTILINESTRING((1 2,3 4))","geometry_multipoly":"MULTIPOLYGON(((1 2,3 4,5 6,1 2)))","geometry_collection":"GEOMETRYCOLLECTION(POINT(1 2))"}}}"### + ); + + Ok(()) + } + + fn schema_ewkt_geometry_srid() -> String { + let schema = indoc! { + r#"model Model { + #id(id, String, @id, @default(cuid())) + geometry Geometry @test.Geometry(Geometry, 3857) + geometry_point Geometry @test.Geometry(Point, 3857) + geometry_line Geometry @test.Geometry(LineString, 3857) + geometry_poly Geometry @test.Geometry(Polygon, 3857) + geometry_multipoint Geometry @test.Geometry(MultiPoint, 3857) + geometry_multiline Geometry @test.Geometry(MultiLineString, 3857) + geometry_multipoly Geometry @test.Geometry(MultiPolygon, 3857) + geometry_collection Geometry @test.Geometry(GeometryCollection, 3857) + }"# + }; + + schema.to_owned() + } + + // "Spatialite common geometry types with srid" should "work" + #[connector_test(schema(schema_ewkt_geometry_srid))] + async fn native_geometry_srid(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geometry: "SRID=3857;POINT(1 2)" + geometry_point: "SRID=3857;POINT(1 2)" + geometry_line: "SRID=3857;LINESTRING(1 2,3 4)" + geometry_poly: "SRID=3857;POLYGON((1 2,3 4,5 6,1 2))" + geometry_multipoint: "SRID=3857;MULTIPOINT(1 2)" + geometry_multiline: "SRID=3857;MULTILINESTRING((1 2,3 4))" + geometry_multipoly: "SRID=3857;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))" + geometry_collection: "SRID=3857;GEOMETRYCOLLECTION(POINT(1 2))" + } + ) { + geometry + geometry_point + geometry_line + geometry_poly + geometry_multipoint + geometry_multiline + geometry_multipoly + geometry_collection + } + }"#), + @r###"{"data":{"createOneModel":{"geometry":"SRID=3857;POINT(1 2)","geometry_point":"SRID=3857;POINT(1 2)","geometry_line":"SRID=3857;LINESTRING(1 2,3 4)","geometry_poly":"SRID=3857;POLYGON((1 2,3 4,5 6,1 2))","geometry_multipoint":"SRID=3857;MULTIPOINT(1 2)","geometry_multiline":"SRID=3857;MULTILINESTRING((1 2,3 4))","geometry_multipoly":"SRID=3857;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))","geometry_collection":"SRID=3857;GEOMETRYCOLLECTION(POINT(1 2))"}}}"### + ); + + Ok(()) + } + + fn schema_geojson_geometry() -> String { + let schema = indoc! { + r#"model Model { + #id(id, String, @id, @default(cuid())) + geometry GeoJson @test.Geometry(Geometry, 4326) + geometry_point GeoJson @test.Geometry(Point, 4326) + geometry_line GeoJson @test.Geometry(LineString, 4326) + geometry_poly GeoJson @test.Geometry(Polygon, 4326) + geometry_multipoint GeoJson @test.Geometry(MultiPoint, 4326) + geometry_multiline GeoJson @test.Geometry(MultiLineString, 4326) + geometry_multipoly GeoJson @test.Geometry(MultiPolygon, 4326) + geometry_collection GeoJson @test.Geometry(GeometryCollection, 4326) + }"# + }; + + schema.to_owned() + } + + // "Spatialite geometry types" should "work" with GeoJSON + #[connector_test(schema(schema_geojson_geometry))] + async fn native_geojson_geometry(runner: Runner) -> TestResult<()> { + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { + createOneModel( + data: { + geometry: "{\"type\":\"Point\",\"coordinates\":[1,2]}" + geometry_point: "{\"type\":\"Point\",\"coordinates\":[1,2]}" + geometry_line: "{\"type\":\"LineString\",\"coordinates\":[[1,2],[3,4]]}" + geometry_poly: "{\"type\":\"Polygon\",\"coordinates\":[[[1,2],[3,4],[5,6],[1,2]]]}" + geometry_multipoint: "{\"type\":\"MultiPoint\",\"coordinates\":[[1,2]]}" + geometry_multiline: "{\"type\":\"MultiLineString\",\"coordinates\":[[[1,2],[3,4]]]}" + geometry_multipoly: "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[1,2],[3,4],[5,6],[1,2]]]]}" + geometry_collection: "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[1,2]}]}" + } + ) { + geometry + geometry_point + geometry_line + geometry_poly + geometry_multipoint + geometry_multiline + geometry_multipoly + geometry_collection + } + }"#), + @r###"{"data":{"createOneModel":{"geometry":"{\"type\": \"Point\", \"coordinates\": [1,2]}","geometry_point":"{\"type\": \"Point\", \"coordinates\": [1,2]}","geometry_line":"{\"type\": \"LineString\", \"coordinates\": [[1,2],[3,4]]}","geometry_poly":"{\"type\": \"Polygon\", \"coordinates\": [[[1,2],[3,4],[5,6],[1,2]]]}","geometry_multipoint":"{\"type\": \"MultiPoint\", \"coordinates\": [[1,2]]}","geometry_multiline":"{\"type\": \"MultiLineString\", \"coordinates\": [[[1,2],[3,4]]]}","geometry_multipoly":"{\"type\": \"MultiPolygon\", \"coordinates\": [[[[1,2],[3,4],[5,6],[1,2]]]]}","geometry_collection":"{\"type\": \"GeometryCollection\", \"geometries\": [{\"type\": \"Point\", \"coordinates\": [1,2]}]}"}}}"### + ); + + Ok(()) + } +} diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs index 1507ea0c082b..d53b98daf871 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs @@ -424,3 +424,36 @@ mod mapped_create { Ok(()) } } + +#[test_suite( + schema(geometry_opt), + capabilities(GeoJsonGeometry), + exclude(Postgres(9, 10, 11, 12, 13, 14, 15, "pgbouncer")) +)] +mod geometry_create { + use query_engine_tests::run_query; + + #[connector_test] + async fn create_geometry(runner: Runner) -> TestResult<()> { + // TODO@geometry: ideally, make geojson generation consistent with SQL connectors + match_connector_result!( + &runner, + r#"mutation { createOneTestModel(data: { id: 1, geometry: "{\"type\": \"Point\", \"coordinates\": [1,2]}" }) { geometry }}"#, + // MongoDB excludes undefined fields + MongoDb(_) => vec![r#"{"data":{"createOneTestModel":{"geometry":"{\"type\":\"Point\",\"coordinates\":[1,2]}"}}}"#], + _ => vec![r#"{"data":{"createOneTestModel":{"geometry":"{\"type\": \"Point\", \"coordinates\": [1,2]}"}}}"#] + ); + + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { createOneTestModel(data: { id: 2, geometry: null }) { geometry }}"#), + @r###"{"data":{"createOneTestModel":{"geometry":null}}}"### + ); + + insta::assert_snapshot!( + run_query!(&runner, r#"mutation { createOneTestModel(data: { id: 3 }) { geometry }}"#), + @r###"{"data":{"createOneTestModel":{"geometry":null}}}"### + ); + + Ok(()) + } +} diff --git a/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mod.rs b/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mod.rs index 6cc6120f71c8..a3b84a9173a8 100644 --- a/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mod.rs +++ b/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mod.rs @@ -108,6 +108,9 @@ pub(crate) fn connection_string( Some(PostgresVersion::V15) if is_ci => { format!("postgresql://postgres:prisma@test-db-postgres-15:5432/{database}") } + Some(PostgresVersion::V15PostGIS) if is_ci => { + format!("postgresql://postgres:prisma@test-db-postgres-15:5432/{database}") + } Some(PostgresVersion::PgBouncer) if is_ci => { format!("postgresql://postgres:prisma@test-db-pgbouncer:6432/{database}&pgbouncer=true") } @@ -121,6 +124,7 @@ pub(crate) fn connection_string( } Some(PostgresVersion::V14) => format!("postgresql://postgres:prisma@127.0.0.1:5437/{database}"), Some(PostgresVersion::V15) => format!("postgresql://postgres:prisma@127.0.0.1:5438/{database}"), + Some(PostgresVersion::V15PostGIS) => format!("postgresql://postgres:prisma@127.0.0.1:5439/{database}"), Some(PostgresVersion::PgBouncer) => { format!("postgresql://postgres:prisma@127.0.0.1:6432/db?{database}&pgbouncer=true") } diff --git a/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/postgres.rs b/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/postgres.rs index 42d0a8c7afdc..0fbcc447be2f 100644 --- a/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/postgres.rs +++ b/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/postgres.rs @@ -35,6 +35,7 @@ pub enum PostgresVersion { V13, V14, V15, + V15PostGIS, PgBouncer, NeonJs, PgJs, @@ -52,6 +53,7 @@ impl TryFrom<&str> for PostgresVersion { "13" => Self::V13, "14" => Self::V14, "15" => Self::V15, + "15-postgis" => Self::V15PostGIS, "pgbouncer" => Self::PgBouncer, "neon.js" => Self::NeonJs, "pg.js" => Self::PgJs, @@ -72,6 +74,7 @@ impl ToString for PostgresVersion { PostgresVersion::V13 => "13", PostgresVersion::V14 => "14", PostgresVersion::V15 => "15", + PostgresVersion::V15PostGIS => "15-postgis", PostgresVersion::PgBouncer => "pgbouncer", PostgresVersion::NeonJs => "neon.js", PostgresVersion::PgJs => "pg.js", diff --git a/query-engine/connector-test-kit-rs/test-configs/postgis15 b/query-engine/connector-test-kit-rs/test-configs/postgis15 new file mode 100644 index 000000000000..8eeacbfe705e --- /dev/null +++ b/query-engine/connector-test-kit-rs/test-configs/postgis15 @@ -0,0 +1,3 @@ +{ + "connector": "postgres", + "version": "15-postgis"} diff --git a/query-engine/connectors/mongodb-query-connector/src/filter.rs b/query-engine/connectors/mongodb-query-connector/src/filter.rs index 64bdadafd6a9..b60179788069 100644 --- a/query-engine/connectors/mongodb-query-connector/src/filter.rs +++ b/query-engine/connectors/mongodb-query-connector/src/filter.rs @@ -242,6 +242,22 @@ impl MongoFilterVisitor { } _ => unimplemented!("Only equality JSON filtering is supported on MongoDB."), }, + ScalarCondition::GeometryWithin(_val) => { + unimplemented!("Geometry filtering is not yet supported on MongoDB") + // doc! { "$geoWithin": [&field_name, self.coerce_to_bson_for_filter(field, val)?] } + } + ScalarCondition::GeometryNotWithin(_val) => { + unimplemented!("Geometry filtering is not yet supported on MongoDB") + // doc! { "$not": { "$geoWithin": [&field_name, self.coerce_to_bson_for_filter(field, val)?] } } + } + ScalarCondition::GeometryIntersects(_val) => { + unimplemented!("Geometry filtering is not yet supported on MongoDB") + // doc! { "$geoIntersects": [&field_name, self.coerce_to_bson_for_filter(field, val)?] } + } + ScalarCondition::GeometryNotIntersects(_val) => { + unimplemented!("Geometry filtering is not yet supported on MongoDB") + // doc! { "$not" : { "$geoIntersects": [&field_name, self.coerce_to_bson_for_filter(field, val)?] } } + } ScalarCondition::IsSet(is_set) => render_is_set(&field_name, is_set), ScalarCondition::Search(_, _) => unimplemented!("Full-text search is not supported yet on MongoDB"), ScalarCondition::NotSearch(_, _) => unimplemented!("Full-text search is not supported yet on MongoDB"), @@ -381,6 +397,18 @@ impl MongoFilterVisitor { ScalarCondition::JsonCompare(_) => Err(MongoError::Unsupported( "JSON filtering is not yet supported on MongoDB".to_string(), )), + ScalarCondition::GeometryWithin(_) => Err(MongoError::Unsupported( + "Geometry Contains insensitive filtering is not yet supported on MongoDB".to_string(), + )), + ScalarCondition::GeometryNotWithin(_) => Err(MongoError::Unsupported( + "Geometry NotContains insensitive filtering is not yet supported on MongoDB".to_string(), + )), + ScalarCondition::GeometryIntersects(_) => Err(MongoError::Unsupported( + "Geometry Intersects insensitive filtering is not yet supported on MongoDB".to_string(), + )), + ScalarCondition::GeometryNotIntersects(_) => Err(MongoError::Unsupported( + "Geometry NotIntersects insensitive filtering is not yet supported on MongoDB".to_string(), + )), ScalarCondition::Search(_, _) | ScalarCondition::NotSearch(_, _) => Err(MongoError::Unsupported( "Full-text search is not supported yet on MongoDB".to_string(), )), diff --git a/query-engine/connectors/mongodb-query-connector/src/value.rs b/query-engine/connectors/mongodb-query-connector/src/value.rs index cf6812d59b6d..62296447acbc 100644 --- a/query-engine/connectors/mongodb-query-connector/src/value.rs +++ b/query-engine/connectors/mongodb-query-connector/src/value.rs @@ -9,7 +9,8 @@ use itertools::Itertools; use mongodb::bson::{oid::ObjectId, spec::BinarySubtype, Binary, Bson, Document, Timestamp}; use psl::builtin_connectors::MongoDbType; use query_structure::{ - CompositeFieldRef, Field, PrismaValue, RelationFieldRef, ScalarFieldRef, SelectedField, TypeIdentifier, + CompositeFieldRef, Field, GeometryFormat, PrismaValue, RelationFieldRef, ScalarFieldRef, SelectedField, + TypeIdentifier, }; use serde_json::Value; use std::{convert::TryFrom, fmt::Display}; @@ -263,6 +264,15 @@ impl IntoBson for (&TypeIdentifier, PrismaValue) { })? } + // Geometry + (TypeIdentifier::Geometry(GeometryFormat::GeoJSON), PrismaValue::GeoJson(json)) => { + let val: Value = serde_json::from_str(&json)?; + Bson::try_from(val).map_err(|_| MongoError::ConversionError { + from: "Stringified JSON".to_owned(), + to: "Mongo BSON (extJSON)".to_owned(), + })? + } + // List values (typ, PrismaValue::List(vals)) => Bson::Array( vals.into_iter() @@ -374,6 +384,11 @@ fn read_scalar_value(bson: Bson, meta: &ScalarOutputMeta) -> crate::Result PrismaValue::Json(serde_json::to_string(&bson.into_relaxed_extjson())?), + // Geometry + (TypeIdentifier::Geometry(GeometryFormat::GeoJSON), bson @ Bson::Document(_)) => { + PrismaValue::GeoJson(serde_json::to_string(&bson.into_relaxed_extjson())?) + } + (ident, bson) => { return Err(MongoError::ConversionError { from: bson.to_string(), diff --git a/query-engine/connectors/sql-query-connector/Cargo.toml b/query-engine/connectors/sql-query-connector/Cargo.toml index fbe04850164d..c3a84ed28a2d 100644 --- a/query-engine/connectors/sql-query-connector/Cargo.toml +++ b/query-engine/connectors/sql-query-connector/Cargo.toml @@ -30,6 +30,8 @@ cuid = { git = "https://github.com/prisma/cuid-rust", branch = "wasm32-support" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] quaint.workspace = true +regex = "1.9.3" +geozero = "0.10.0" [target.'cfg(target_arch = "wasm32")'.dependencies] quaint = { path = "../../../quaint" } diff --git a/query-engine/connectors/sql-query-connector/src/filter/visitor.rs b/query-engine/connectors/sql-query-connector/src/filter/visitor.rs index 1a71cdd824a8..0a84f0a06fa7 100644 --- a/query-engine/connectors/sql-query-connector/src/filter/visitor.rs +++ b/query-engine/connectors/sql-query-connector/src/filter/visitor.rs @@ -962,6 +962,18 @@ fn default_scalar_filter( comparable.not_matches(query) } + ScalarCondition::GeometryWithin(value) => { + comparable.geometry_within(convert_first_value(fields, value, alias, ctx)) + } + ScalarCondition::GeometryNotWithin(value) => { + comparable.geometry_not_within(convert_first_value(fields, value, alias, ctx)) + } + ScalarCondition::GeometryIntersects(value) => { + comparable.geometry_intersects(convert_first_value(fields, value, alias, ctx)) + } + ScalarCondition::GeometryNotIntersects(value) => { + comparable.geometry_not_intersects(convert_first_value(fields, value, alias, ctx)) + } ScalarCondition::JsonCompare(_) => unreachable!(), ScalarCondition::IsSet(_) => unreachable!(), }; @@ -1142,6 +1154,10 @@ fn insensitive_scalar_filter( comparable.not_matches(query) } + ScalarCondition::GeometryWithin(_) => unreachable!(), + ScalarCondition::GeometryNotWithin(_) => unreachable!(), + ScalarCondition::GeometryIntersects(_) => unreachable!(), + ScalarCondition::GeometryNotIntersects(_) => unreachable!(), ScalarCondition::JsonCompare(_) => unreachable!(), ScalarCondition::IsSet(_) => unreachable!(), }; diff --git a/query-engine/connectors/sql-query-connector/src/model_extensions/scalar_field.rs b/query-engine/connectors/sql-query-connector/src/model_extensions/scalar_field.rs index 21612e1a6392..d1dfa3d7a500 100644 --- a/query-engine/connectors/sql-query-connector/src/model_extensions/scalar_field.rs +++ b/query-engine/connectors/sql-query-connector/src/model_extensions/scalar_field.rs @@ -1,8 +1,11 @@ +use std::str::FromStr; + use crate::context::Context; use chrono::Utc; +use geozero::{geojson::GeoJson, ToWkt}; use prisma_value::PrismaValue; use quaint::{ - ast::{EnumName, Value, ValueType}, + ast::{EnumName, GeometryValue, Value, ValueType}, prelude::{EnumVariant, TypeDataLength, TypeFamily}, }; use query_structure::{ScalarField, TypeIdentifier}; @@ -53,6 +56,23 @@ impl ScalarFieldExt for ScalarField { (PrismaValue::Json(s), _) => Value::json(serde_json::from_str::(&s).unwrap()), (PrismaValue::Bytes(b), _) => Value::bytes(b), (PrismaValue::Object(_), _) => unimplemented!(), + (PrismaValue::GeoJson(s), _) => { + let geometry = GeometryValue { + wkt: GeoJson(&s).to_wkt().unwrap(), + srid: 4326, + }; + match self.type_family() { + TypeFamily::Geography(_) => Value::geography(geometry), + _ => Value::geometry(geometry), + } + } + (PrismaValue::Geometry(s), _) => { + let geometry = GeometryValue::from_str(&s).unwrap(); + match self.type_family() { + TypeFamily::Geography(_) => Value::geography(geometry), + _ => Value::geometry(geometry), + } + } (PrismaValue::Null, ident) => match ident { TypeIdentifier::String => Value::null_text(), TypeIdentifier::Float => Value::null_numeric(), @@ -74,6 +94,7 @@ impl ScalarFieldExt for ScalarField { TypeIdentifier::Int => Value::null_int32(), TypeIdentifier::BigInt => Value::null_int64(), TypeIdentifier::Bytes => Value::null_bytes(), + TypeIdentifier::Geometry(_) => Value::null_geometry(), TypeIdentifier::Unsupported => unreachable!("No unsupported field should reach that path"), }, }; @@ -102,6 +123,22 @@ impl ScalarFieldExt for ScalarField { TypeIdentifier::Json => TypeFamily::Text(Some(TypeDataLength::Maximum)), TypeIdentifier::DateTime => TypeFamily::DateTime, TypeIdentifier::Bytes => TypeFamily::Text(parse_scalar_length(self)), + TypeIdentifier::Geometry(_) => { + let type_info = self.native_type().map(|nt| { + let name = nt.name(); + let srid = match nt.args().as_slice() { + [srid] => srid.parse::().ok(), + [_, srid] => srid.parse::().ok(), + _ => None, + }; + (name, srid) + }); + match type_info { + Some(("Geography", srid)) => TypeFamily::Geography(srid), + Some((_, srid)) => TypeFamily::Geometry(srid), + _ => TypeFamily::Geometry(None), + } + } TypeIdentifier::Unsupported => unreachable!("No unsupported field should reach that path"), } } @@ -122,6 +159,9 @@ pub fn convert_lossy<'a>(pv: PrismaValue) -> Value<'a> { PrismaValue::List(l) => Value::array(l.into_iter().map(convert_lossy)), PrismaValue::Json(s) => Value::json(serde_json::from_str(&s).unwrap()), PrismaValue::Bytes(b) => Value::bytes(b), + // TODO@geom: Fix this when we know how to cast GeoJSON to an appropriate DB value + PrismaValue::GeoJson(s) => Value::json(serde_json::from_str(&s).unwrap()), + PrismaValue::Geometry(s) => Value::geometry(GeometryValue::from_str(&s).unwrap()), PrismaValue::Null => Value::null_int32(), // Can't tell which type the null is supposed to be. PrismaValue::Object(_) => unimplemented!(), } diff --git a/query-engine/connectors/sql-query-connector/src/query_builder/read.rs b/query-engine/connectors/sql-query-connector/src/query_builder/read.rs index 3aa91288ea90..3444d929b8c9 100644 --- a/query-engine/connectors/sql-query-connector/src/query_builder/read.rs +++ b/query-engine/connectors/sql-query-connector/src/query_builder/read.rs @@ -4,6 +4,7 @@ use crate::{ }; use connector_interface::{AggregationSelection, RelAggregationSelection}; use itertools::Itertools; +use psl::datamodel_connector::Connector; use quaint::ast::*; use query_structure::*; use tracing::Span; @@ -113,6 +114,14 @@ impl SelectDefinition for QueryArguments { } } +fn get_column_read_expression<'a>(col: Column<'a>, connector: &'a dyn Connector) -> Expression<'a> { + let supports_raw_geom_io = connector.supports_raw_geometry_read(); + match col.type_family { + Some(TypeFamily::Geometry(_) | TypeFamily::Geography(_)) if !supports_raw_geom_io => geom_as_text(col).into(), + _ => col.into(), + } +} + pub(crate) fn get_records( model: &Model, columns: impl Iterator>, @@ -124,10 +133,13 @@ where T: SelectDefinition, { let (select, additional_selection_set) = query.into_select(model, aggr_selections, ctx); - let select = columns.fold(select, |acc, col| acc.column(col)); - - let select = select.append_trace(&Span::current()).add_trace_id(ctx.trace_id); + let select = columns + .map(|c| get_column_read_expression(c, model.dm.schema.connector)) + .fold(select, |acc, col| acc.value(col)) + .append_trace(&Span::current()) + .add_trace_id(ctx.trace_id); + // TODO@geometry: Should we call get_column_read_expression in "additional_selection_set" too ? additional_selection_set .into_iter() .fold(select, |acc, col| acc.value(col)) diff --git a/query-engine/connectors/sql-query-connector/src/query_builder/write.rs b/query-engine/connectors/sql-query-connector/src/query_builder/write.rs index c5bb3e24ddb6..5dd914f5a3b2 100644 --- a/query-engine/connectors/sql-query-connector/src/query_builder/write.rs +++ b/query-engine/connectors/sql-query-connector/src/query_builder/write.rs @@ -31,6 +31,7 @@ pub(crate) fn create_record( insert.value(db_name.to_owned(), field.value(value, ctx)) }); + // TODO@geometry: Should we call geom_as_text in returning statement too ? Insert::from(insert) .returning(selected_fields.as_columns(ctx).map(|c| c.set_is_selected(true))) .append_trace(&Span::current()) diff --git a/query-engine/connectors/sql-query-connector/src/row.rs b/query-engine/connectors/sql-query-connector/src/row.rs index 6f154b1f77dc..a95ab8d859c5 100644 --- a/query-engine/connectors/sql-query-connector/src/row.rs +++ b/query-engine/connectors/sql-query-connector/src/row.rs @@ -2,8 +2,10 @@ use crate::{column_metadata::ColumnMetadata, error::SqlError, value::to_prisma_v use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive}; use chrono::{DateTime, NaiveDate, Utc}; use connector_interface::{coerce_null_to_zero_value, AggregationResult, AggregationSelection}; +use geozero::wkt::WktStr; +use geozero::ToJson; use quaint::{connector::ResultRow, Value, ValueType}; -use query_structure::{ConversionFailure, FieldArity, PrismaValue, Record, TypeIdentifier}; +use query_structure::{ConversionFailure, FieldArity, GeometryFormat, PrismaValue, Record, TypeIdentifier}; use std::{io, str::FromStr}; use uuid::Uuid; @@ -287,6 +289,29 @@ fn row_value_to_prisma_value(p_value: Value, meta: ColumnMetadata<'_>) -> Result ValueType::Bytes(Some(bytes)) => PrismaValue::Bytes(bytes.into()), _ => return Err(create_error(&p_value)), }, + TypeIdentifier::Geometry(GeometryFormat::EWKT) => match p_value.typed { + value if value.is_null() => PrismaValue::Null, + ValueType::Text(Some(ewkt)) => match ewkt.starts_with("SRID=0;") { + true => PrismaValue::Geometry(ewkt[7..].into()), + false => PrismaValue::Geometry(ewkt.into()), + }, + _ => return Err(create_error(&p_value)), + }, + TypeIdentifier::Geometry(GeometryFormat::GeoJSON) => match p_value.typed { + value if value.is_null() => PrismaValue::Null, + // MySQL @<=5.7 and MSSQL cannot return geometry as GeoJSON, so we must serialize + // the ewkt string back to geojson. However, per specification, GeoJSON geometries + // can only be represented with EPSG:4326 projection. Plus WKT can represent more + // spatial types than GeoJSON can, so this operation may fail. + ValueType::Text(Some(ref ewkt)) => match ewkt.starts_with("SRID=4326;") { + true => WktStr(&ewkt[10..]) + .to_json() + .map(PrismaValue::GeoJson) + .map_err(|_| create_error(&p_value))?, + false => return Err(create_error(&p_value)), + }, + _ => return Err(create_error(&p_value)), + }, TypeIdentifier::Unsupported => unreachable!("No unsupported field should reach that path"), }) } diff --git a/query-engine/connectors/sql-query-connector/src/value.rs b/query-engine/connectors/sql-query-connector/src/value.rs index 0929003955f7..888bcda5525e 100644 --- a/query-engine/connectors/sql-query-connector/src/value.rs +++ b/query-engine/connectors/sql-query-connector/src/value.rs @@ -98,6 +98,10 @@ pub fn to_prisma_value<'a, T: Into>>(qv: T) -> crate::Result s .map(|s| PrismaValue::String(s.into_owned())) .unwrap_or(PrismaValue::Null), + + ValueType::Geometry(s) | ValueType::Geography(s) => s + .map(|s| PrismaValue::Geometry(s.to_string())) + .unwrap_or(PrismaValue::Null), }; Ok(val) diff --git a/query-engine/connectors/sql-query-connector/src/value_ext.rs b/query-engine/connectors/sql-query-connector/src/value_ext.rs index a84c9da0380b..011273f6b1b4 100644 --- a/query-engine/connectors/sql-query-connector/src/value_ext.rs +++ b/query-engine/connectors/sql-query-connector/src/value_ext.rs @@ -28,6 +28,8 @@ impl<'a> IntoTypedJsonExtension for quaint::Value<'a> { quaint::ValueType::DateTime(_) => "datetime", quaint::ValueType::Date(_) => "date", quaint::ValueType::Time(_) => "time", + quaint::ValueType::Geometry(_) => "geometry", + quaint::ValueType::Geography(_) => "geography", quaint::ValueType::Array(_) | quaint::ValueType::EnumArray(_, _) => "array", }; diff --git a/query-engine/core/Cargo.toml b/query-engine/core/Cargo.toml index b23050ab7eec..ec20e7415ae4 100644 --- a/query-engine/core/Cargo.toml +++ b/query-engine/core/Cargo.toml @@ -36,6 +36,7 @@ cuid = { git = "https://github.com/prisma/cuid-rust", branch = "wasm32-support" schema = { path = "../schema" } lru = "0.7.7" enumflags2 = "0.7" +geojson = { version = "0.24.1", default-features = false } pin-project = "1" wasm-bindgen-futures = "0.4" diff --git a/query-engine/core/src/constants.rs b/query-engine/core/src/constants.rs index abf320a2969c..62a29f39cf03 100644 --- a/query-engine/core/src/constants.rs +++ b/query-engine/core/src/constants.rs @@ -9,6 +9,8 @@ pub mod custom_types { pub const DECIMAL: &str = "Decimal"; pub const BYTES: &str = "Bytes"; pub const JSON: &str = "Json"; + pub const EWKT_GEOMETRY: &str = "Geometry"; + pub const GEOJSON: &str = "GeoJson"; pub const ENUM: &str = "Enum"; pub const FIELD_REF: &str = "FieldRef"; diff --git a/query-engine/core/src/query_document/parser.rs b/query-engine/core/src/query_document/parser.rs index 79f30e1bd8b7..55af304aa1a5 100644 --- a/query-engine/core/src/query_document/parser.rs +++ b/query-engine/core/src/query_document/parser.rs @@ -3,6 +3,7 @@ use crate::{executor::get_engine_protocol, schema::*}; use bigdecimal::{BigDecimal, ToPrimitive}; use chrono::prelude::*; use core::fmt; +use geojson::GeoJson; use indexmap::{IndexMap, IndexSet}; use query_structure::{DefaultKind, PrismaValue, ValueGeneratorFn}; use std::{borrow::Cow, convert::TryFrom, rc::Rc, str::FromStr}; @@ -355,6 +356,8 @@ impl QueryDocumentParser { (PrismaValue::Bytes(bytes), ScalarType::Bytes) => Ok(PrismaValue::Bytes(bytes)), (PrismaValue::BigInt(b_int), ScalarType::BigInt) => Ok(PrismaValue::BigInt(b_int)), (PrismaValue::DateTime(s), ScalarType::DateTime) => Ok(PrismaValue::DateTime(s)), + (PrismaValue::GeoJson(s), ScalarType::GeoJson) => Ok(PrismaValue::GeoJson(s)), + (PrismaValue::Geometry(s), ScalarType::Geometry) => Ok(PrismaValue::Geometry(s)), (PrismaValue::Null, ScalarType::Null) => Ok(PrismaValue::Null), // String coercion matchers @@ -374,6 +377,13 @@ impl QueryDocumentParser { .parse_datetime(selection_path, argument_path, s.as_str()) .map(PrismaValue::DateTime), + // WKT imput can hardly be validated, since all database vendors wkt dialect + // differ in subtle ways that make them incompatible. + (PrismaValue::String(s), ScalarType::Geometry) => Ok(PrismaValue::Geometry(s)), + (PrismaValue::Json(s) | PrismaValue::String(s), ScalarType::GeoJson) => Ok(PrismaValue::GeoJson( + self.parse_geojson(selection_path, argument_path, &s).map(|_| s)?, + )), + // Int coercion matchers (PrismaValue::Int(i), ScalarType::Int) => Ok(PrismaValue::Int(i)), (PrismaValue::Int(i), ScalarType::Float) => Ok(PrismaValue::Float(BigDecimal::from(i))), @@ -518,6 +528,18 @@ impl QueryDocumentParser { Ok(PrismaValue::List(prisma_values)) } + fn parse_geojson(&self, selection_path: &Path, argument_path: &Path, s: &str) -> QueryParserResult { + s.parse::().map_err(|err| { + ValidationError::invalid_argument_value( + selection_path.segments(), + argument_path.segments(), + s.to_string(), + "GeoJSON String", + Some(Box::new(err)), + ) + }) + } + fn parse_json(&self, selection_path: &Path, argument_path: &Path, s: &str) -> QueryParserResult { serde_json::from_str(s).map_err(|err| { ValidationError::invalid_argument_value( @@ -885,6 +907,8 @@ pub(crate) mod conversions { format!("({})", itertools::join(v.iter().map(prisma_value_to_type_name), ", ")) } PrismaValue::Json(_) => "JSON".to_string(), + PrismaValue::GeoJson(_) => "GeoJSON".to_string(), + PrismaValue::Geometry(_) => "EWKTGeometry".to_string(), PrismaValue::Object(_) => "Object".to_string(), PrismaValue::Null => "Null".to_string(), PrismaValue::DateTime(_) => "DateTime".to_string(), diff --git a/query-engine/core/src/query_graph_builder/extractors/filters/scalar.rs b/query-engine/core/src/query_graph_builder/extractors/filters/scalar.rs index ac84ce06aa21..2f1ec3dc46fc 100644 --- a/query-engine/core/src/query_graph_builder/extractors/filters/scalar.rs +++ b/query-engine/core/src/query_graph_builder/extractors/filters/scalar.rs @@ -174,6 +174,24 @@ impl<'a> ScalarFilterParser<'a> { filters::HAS_SOME => Ok(vec![field.contains_some_element(self.as_condition_list_value(input)?)]), filters::IS_EMPTY => Ok(vec![field.is_empty_list(input.try_into()?)]), + // Geometry-specific filters + filters::GEO_WITHIN => { + if self.reverse() { + Ok(vec![field.geometry_not_within(self.as_condition_value(input, false)?)]) + } else { + Ok(vec![field.geometry_within(self.as_condition_value(input, false)?)]) + } + } + filters::GEO_INTERSECTS => { + if self.reverse() { + Ok(vec![ + field.geometry_not_intersects(self.as_condition_value(input, false)?) + ]) + } else { + Ok(vec![field.geometry_intersects(self.as_condition_value(input, false)?)]) + } + } + // Aggregation filters aggregations::UNDERSCORE_COUNT => self.aggregation_filter(input, Filter::count, true), aggregations::UNDERSCORE_AVG => self.aggregation_filter(input, Filter::average, false), diff --git a/query-engine/core/src/response_ir/internal.rs b/query-engine/core/src/response_ir/internal.rs index 7becb19e768b..6df340075f95 100644 --- a/query-engine/core/src/response_ir/internal.rs +++ b/query-engine/core/src/response_ir/internal.rs @@ -573,6 +573,8 @@ fn convert_prisma_value_graphql_protocol( (ScalarType::DateTime, PrismaValue::DateTime(dt)) => PrismaValue::DateTime(dt), (ScalarType::UUID, PrismaValue::Uuid(u)) => PrismaValue::Uuid(u), (ScalarType::Bytes, PrismaValue::Bytes(b)) => PrismaValue::Bytes(b), + (ScalarType::Geometry, PrismaValue::Geometry(s)) => PrismaValue::Geometry(s), + (ScalarType::GeoJson, PrismaValue::GeoJson(s)) => PrismaValue::GeoJson(s), // The Decimal type doesn't have a corresponding PrismaValue variant. We need to serialize it // to String so that client can deserialize it as Decimal again. @@ -616,6 +618,12 @@ fn convert_prisma_value_json_protocol( (ScalarType::Bytes, PrismaValue::Bytes(x)) => { custom_types::make_object(custom_types::BYTES, PrismaValue::Bytes(x)) } + (ScalarType::Geometry, PrismaValue::Geometry(x)) => { + custom_types::make_object(custom_types::EWKT_GEOMETRY, PrismaValue::Geometry(x)) + } + (ScalarType::GeoJson, PrismaValue::GeoJson(x)) => { + custom_types::make_object(custom_types::GEOJSON, PrismaValue::GeoJson(x)) + } // Identity matchers (ScalarType::String, PrismaValue::String(x)) => PrismaValue::String(x), diff --git a/query-engine/dmmf/src/ast_builders/datamodel_ast_builder.rs b/query-engine/dmmf/src/ast_builders/datamodel_ast_builder.rs index c367695150f6..4e670b5092c7 100644 --- a/query-engine/dmmf/src/ast_builders/datamodel_ast_builder.rs +++ b/query-engine/dmmf/src/ast_builders/datamodel_ast_builder.rs @@ -262,6 +262,8 @@ fn prisma_value_to_serde(value: &PrismaValue) -> serde_json::Value { PrismaValue::Null => serde_json::Value::Null, PrismaValue::Uuid(val) => serde_json::Value::String(val.to_string()), PrismaValue::Json(val) => serde_json::Value::String(val.to_string()), + PrismaValue::GeoJson(val) => serde_json::Value::String(val.to_string()), + PrismaValue::Geometry(val) => serde_json::Value::String(val.to_string()), PrismaValue::List(value_vec) => serde_json::Value::Array(value_vec.iter().map(prisma_value_to_serde).collect()), PrismaValue::Bytes(b) => serde_json::Value::String(encode_bytes(b)), PrismaValue::Object(pairs) => { diff --git a/query-engine/dmmf/src/ast_builders/schema_ast_builder/type_renderer.rs b/query-engine/dmmf/src/ast_builders/schema_ast_builder/type_renderer.rs index dd4f26660440..cc70a239a428 100644 --- a/query-engine/dmmf/src/ast_builders/schema_ast_builder/type_renderer.rs +++ b/query-engine/dmmf/src/ast_builders/schema_ast_builder/type_renderer.rs @@ -49,6 +49,8 @@ pub(super) fn render_output_type<'a>(output_type: &OutputType<'a>, ctx: &mut Ren ScalarType::UUID => "UUID", ScalarType::JsonList => "Json", ScalarType::Bytes => "Bytes", + ScalarType::GeoJson => "GeoJson", + ScalarType::Geometry => "Geometry", }; DmmfTypeReference { diff --git a/query-engine/query-structure/src/field/mod.rs b/query-engine/query-structure/src/field/mod.rs index 45d529c56abf..357727c72609 100644 --- a/query-engine/query-structure/src/field/mod.rs +++ b/query-engine/query-structure/src/field/mod.rs @@ -134,6 +134,12 @@ impl Field { } } +#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)] +pub enum GeometryFormat { + EWKT, + GeoJSON, +} + #[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)] #[allow(clippy::upper_case_acronyms)] pub enum TypeIdentifier { @@ -148,6 +154,7 @@ pub enum TypeIdentifier { Json, DateTime, Bytes, + Geometry(GeometryFormat), Unsupported, } @@ -175,6 +182,8 @@ impl TypeIdentifier { TypeIdentifier::Json => "Json".into(), TypeIdentifier::DateTime => "DateTime".into(), TypeIdentifier::Bytes => "Bytes".into(), + TypeIdentifier::Geometry(GeometryFormat::GeoJSON) => "GeoJson".into(), + TypeIdentifier::Geometry(GeometryFormat::EWKT) => "Geometry".into(), TypeIdentifier::Unsupported => "Unsupported".into(), } } @@ -248,6 +257,8 @@ impl From for TypeIdentifier { ScalarType::Json => Self::Json, ScalarType::Decimal => Self::Decimal, ScalarType::Bytes => Self::Bytes, + ScalarType::Geometry => Self::Geometry(GeometryFormat::EWKT), + ScalarType::GeoJson => Self::Geometry(GeometryFormat::GeoJSON), } } } diff --git a/query-engine/query-structure/src/field/scalar.rs b/query-engine/query-structure/src/field/scalar.rs index b8ef8ab204e2..af854944654a 100644 --- a/query-engine/query-structure/src/field/scalar.rs +++ b/query-engine/query-structure/src/field/scalar.rs @@ -62,6 +62,10 @@ impl ScalarField { self.type_identifier().is_numeric() } + pub fn is_geometry(&self) -> bool { + matches!(self.type_identifier(), TypeIdentifier::Geometry(_)) + } + pub fn container(&self) -> ParentContainer { match self.id { ScalarFieldId::InModel(id) => self.dm.find_model_by_id(self.dm.walk(id).model().id).into(), @@ -259,6 +263,7 @@ pub fn dml_default_kind(default_value: &ast::Expression, scalar_type: Option unreachable!("{:?}", other), } } diff --git a/query-engine/query-structure/src/filter/compare.rs b/query-engine/query-structure/src/filter/compare.rs index 7757965050ad..03d66566c49e 100644 --- a/query-engine/query-structure/src/filter/compare.rs +++ b/query-engine/query-structure/src/filter/compare.rs @@ -61,6 +61,22 @@ pub trait ScalarCompare { where T: Into; + fn geometry_within(&self, val: T) -> Filter + where + T: Into; + + fn geometry_not_within(&self, val: T) -> Filter + where + T: Into; + + fn geometry_intersects(&self, val: T) -> Filter + where + T: Into; + + fn geometry_not_intersects(&self, val: T) -> Filter + where + T: Into; + fn search(&self, val: T) -> Filter where T: Into; diff --git a/query-engine/query-structure/src/filter/scalar/compare.rs b/query-engine/query-structure/src/filter/scalar/compare.rs index efbbb370f664..063eabca3448 100644 --- a/query-engine/query-structure/src/filter/scalar/compare.rs +++ b/query-engine/query-structure/src/filter/scalar/compare.rs @@ -199,6 +199,50 @@ impl ScalarCompare for ScalarFieldRef { mode: QueryMode::Default, }) } + + fn geometry_within(&self, val: T) -> Filter + where + T: Into, + { + Filter::from(ScalarFilter { + projection: ScalarProjection::Single(self.clone()), + condition: ScalarCondition::GeometryWithin(val.into()), + mode: QueryMode::Default, + }) + } + + fn geometry_not_within(&self, val: T) -> Filter + where + T: Into, + { + Filter::from(ScalarFilter { + projection: ScalarProjection::Single(self.clone()), + condition: ScalarCondition::GeometryNotWithin(val.into()), + mode: QueryMode::Default, + }) + } + + fn geometry_intersects(&self, val: T) -> Filter + where + T: Into, + { + Filter::from(ScalarFilter { + projection: ScalarProjection::Single(self.clone()), + condition: ScalarCondition::GeometryIntersects(val.into()), + mode: QueryMode::Default, + }) + } + + fn geometry_not_intersects(&self, val: T) -> Filter + where + T: Into, + { + Filter::from(ScalarFilter { + projection: ScalarProjection::Single(self.clone()), + condition: ScalarCondition::GeometryNotIntersects(val.into()), + mode: QueryMode::Default, + }) + } } impl ScalarCompare for ModelProjection { @@ -399,6 +443,50 @@ impl ScalarCompare for ModelProjection { mode: QueryMode::Default, }) } + + fn geometry_within(&self, val: T) -> Filter + where + T: Into, + { + Filter::from(ScalarFilter { + projection: ScalarProjection::Compound(self.scalar_fields().collect()), + condition: ScalarCondition::GeometryWithin(val.into()), + mode: QueryMode::Default, + }) + } + + fn geometry_not_within(&self, val: T) -> Filter + where + T: Into, + { + Filter::from(ScalarFilter { + projection: ScalarProjection::Compound(self.scalar_fields().collect()), + condition: ScalarCondition::GeometryNotWithin(val.into()), + mode: QueryMode::Default, + }) + } + + fn geometry_intersects(&self, val: T) -> Filter + where + T: Into, + { + Filter::from(ScalarFilter { + projection: ScalarProjection::Compound(self.scalar_fields().collect()), + condition: ScalarCondition::GeometryIntersects(val.into()), + mode: QueryMode::Default, + }) + } + + fn geometry_not_intersects(&self, val: T) -> Filter + where + T: Into, + { + Filter::from(ScalarFilter { + projection: ScalarProjection::Compound(self.scalar_fields().collect()), + condition: ScalarCondition::GeometryNotIntersects(val.into()), + mode: QueryMode::Default, + }) + } } impl ScalarCompare for FieldSelection { @@ -599,4 +687,48 @@ impl ScalarCompare for FieldSelection { mode: QueryMode::Default, }) } + + fn geometry_within(&self, val: T) -> Filter + where + T: Into, + { + Filter::from(ScalarFilter { + projection: ScalarProjection::Compound(self.as_scalar_fields().expect("Todo composites in filters.")), + condition: ScalarCondition::GeometryWithin(val.into()), + mode: QueryMode::Default, + }) + } + + fn geometry_not_within(&self, val: T) -> Filter + where + T: Into, + { + Filter::from(ScalarFilter { + projection: ScalarProjection::Compound(self.as_scalar_fields().expect("Todo composites in filters.")), + condition: ScalarCondition::GeometryNotWithin(val.into()), + mode: QueryMode::Default, + }) + } + + fn geometry_intersects(&self, val: T) -> Filter + where + T: Into, + { + Filter::from(ScalarFilter { + projection: ScalarProjection::Compound(self.as_scalar_fields().expect("Todo composites in filters.")), + condition: ScalarCondition::GeometryIntersects(val.into()), + mode: QueryMode::Default, + }) + } + + fn geometry_not_intersects(&self, val: T) -> Filter + where + T: Into, + { + Filter::from(ScalarFilter { + projection: ScalarProjection::Compound(self.as_scalar_fields().expect("Todo composites in filters.")), + condition: ScalarCondition::GeometryNotIntersects(val.into()), + mode: QueryMode::Default, + }) + } } diff --git a/query-engine/query-structure/src/filter/scalar/condition/mod.rs b/query-engine/query-structure/src/filter/scalar/condition/mod.rs index ff32d3d52219..27b1b5739ecb 100644 --- a/query-engine/query-structure/src/filter/scalar/condition/mod.rs +++ b/query-engine/query-structure/src/filter/scalar/condition/mod.rs @@ -22,6 +22,10 @@ pub enum ScalarCondition { In(ConditionListValue), NotIn(ConditionListValue), JsonCompare(JsonCondition), + GeometryWithin(ConditionValue), + GeometryNotWithin(ConditionValue), + GeometryIntersects(ConditionValue), + GeometryNotIntersects(ConditionValue), Search(ConditionValue, Vec), NotSearch(ConditionValue, Vec), IsSet(bool), @@ -61,6 +65,10 @@ impl ScalarCondition { target_type: json_compare.target_type, }) } + Self::GeometryWithin(v) => Self::GeometryNotWithin(v), + Self::GeometryNotWithin(v) => Self::GeometryWithin(v), + Self::GeometryIntersects(v) => Self::GeometryNotIntersects(v), + Self::GeometryNotIntersects(v) => Self::GeometryIntersects(v), Self::Search(v, fields) => Self::NotSearch(v, fields), Self::NotSearch(v, fields) => Self::Search(v, fields), Self::IsSet(v) => Self::IsSet(!v), @@ -87,6 +95,10 @@ impl ScalarCondition { ScalarCondition::In(v) => v.as_field_ref(), ScalarCondition::NotIn(v) => v.as_field_ref(), ScalarCondition::JsonCompare(json_cond) => json_cond.condition.as_field_ref(), + ScalarCondition::GeometryWithin(v) => v.as_field_ref(), + ScalarCondition::GeometryNotWithin(v) => v.as_field_ref(), + ScalarCondition::GeometryIntersects(v) => v.as_field_ref(), + ScalarCondition::GeometryNotIntersects(v) => v.as_field_ref(), ScalarCondition::Search(v, _) => v.as_field_ref(), ScalarCondition::NotSearch(v, _) => v.as_field_ref(), ScalarCondition::IsSet(_) => None, diff --git a/query-engine/query-structure/src/prisma_value_ext.rs b/query-engine/query-structure/src/prisma_value_ext.rs index 09e052ea844b..9d386531eab1 100644 --- a/query-engine/query-structure/src/prisma_value_ext.rs +++ b/query-engine/query-structure/src/prisma_value_ext.rs @@ -1,5 +1,5 @@ use super::{PrismaValue, TypeIdentifier}; -use crate::DomainError; +use crate::{DomainError, GeometryFormat}; use bigdecimal::ToPrimitive; pub(crate) trait PrismaValueExtensions { @@ -23,6 +23,8 @@ impl PrismaValueExtensions for PrismaValue { (val @ PrismaValue::BigInt(_), TypeIdentifier::BigInt) => val, (val @ PrismaValue::Bytes(_), TypeIdentifier::Bytes) => val, (val @ PrismaValue::Json(_), TypeIdentifier::Json) => val, + (val @ PrismaValue::Geometry(_), TypeIdentifier::Geometry(GeometryFormat::EWKT)) => val, + (val @ PrismaValue::GeoJson(_), TypeIdentifier::Geometry(GeometryFormat::GeoJSON)) => val, // Valid String coercions (PrismaValue::Int(i), TypeIdentifier::String) => PrismaValue::String(format!("{i}")), diff --git a/query-engine/request-handlers/src/protocols/graphql/schema_renderer/type_renderer.rs b/query-engine/request-handlers/src/protocols/graphql/schema_renderer/type_renderer.rs index 7b4d0a370da7..4d3b3e7b4471 100644 --- a/query-engine/request-handlers/src/protocols/graphql/schema_renderer/type_renderer.rs +++ b/query-engine/request-handlers/src/protocols/graphql/schema_renderer/type_renderer.rs @@ -47,6 +47,8 @@ impl<'a> GqlTypeRenderer<'a> { ScalarType::UUID => "UUID", ScalarType::JsonList => "Json", ScalarType::Bytes => "Bytes", + ScalarType::GeoJson => "GeoJson", + ScalarType::Geometry => "Geometry", ScalarType::Null => unreachable!("Null types should not be picked for GQL rendering."), }; @@ -85,6 +87,8 @@ impl<'a> GqlTypeRenderer<'a> { ScalarType::UUID => "UUID", ScalarType::JsonList => "Json", ScalarType::Bytes => "Bytes", + ScalarType::GeoJson => "GeoJson", + ScalarType::Geometry => "Geometry", ScalarType::Null => unreachable!("Null types should not be picked for GQL rendering."), }; diff --git a/query-engine/schema/src/build/input_types/fields/data_input_mapper/update.rs b/query-engine/schema/src/build/input_types/fields/data_input_mapper/update.rs index e6f051b70586..c234d207d0db 100644 --- a/query-engine/schema/src/build/input_types/fields/data_input_mapper/update.rs +++ b/query-engine/schema/src/build/input_types/fields/data_input_mapper/update.rs @@ -39,6 +39,8 @@ impl DataInputFieldMapper for UpdateDataInputFieldMapper { )) } TypeIdentifier::Json => map_scalar_input_type_for_field(ctx, &sf), + // TODO@geometry: Is this the right way ? + TypeIdentifier::Geometry(_) => map_scalar_input_type_for_field(ctx, &sf), TypeIdentifier::DateTime => { InputType::object(update_operations_object_type(ctx, "DateTime", sf.clone(), false)) } diff --git a/query-engine/schema/src/build/input_types/fields/field_filter_types.rs b/query-engine/schema/src/build/input_types/fields/field_filter_types.rs index 84e6faa749ea..54fd16aa1f53 100644 --- a/query-engine/schema/src/build/input_types/fields/field_filter_types.rs +++ b/query-engine/schema/src/build/input_types/fields/field_filter_types.rs @@ -264,6 +264,13 @@ fn full_scalar_filter_type( .chain(inclusion_filters(ctx, mapped_scalar_type.clone(), nullable)) .collect(), + // Inclusion filters are tricky because SQL Server doesn't allow direct equality check between geometries + // so IN ( ... ) filters won't work either. The equality filters are hacked in Quaint, where they are + // converted to .STEquals() expressions + TypeIdentifier::Geometry(_) => geometric_filters(ctx, mapped_scalar_type.clone()) + .chain(equality_filters(mapped_scalar_type.clone(), nullable)) + .collect(), + TypeIdentifier::Unsupported => unreachable!("No unsupported field should reach that path"), }; @@ -462,6 +469,15 @@ fn json_filters(ctx: &'_ QuerySchema) -> impl Iterator> { .into_iter() } +fn geometric_filters<'a>(_ctx: &'a QuerySchema, mapped_type: InputType<'a>) -> impl Iterator> { + let field_types = mapped_type.with_field_ref_input(); + vec![ + input_field(filters::GEO_WITHIN, field_types.clone(), None).optional(), + input_field(filters::GEO_INTERSECTS, field_types.clone(), None).optional(), + ] + .into_iter() +} + fn query_mode_field(ctx: &'_ QuerySchema, nested: bool) -> impl Iterator> { // Limit query mode field to the topmost filter level. // Only build mode field for connectors with insensitive filter support. diff --git a/query-engine/schema/src/build/input_types/mod.rs b/query-engine/schema/src/build/input_types/mod.rs index 14ff37722d6d..e7c8b099f24e 100644 --- a/query-engine/schema/src/build/input_types/mod.rs +++ b/query-engine/schema/src/build/input_types/mod.rs @@ -3,7 +3,7 @@ pub(crate) mod objects; use super::*; use fields::*; -use query_structure::ScalarFieldRef; +use query_structure::{GeometryFormat, ScalarFieldRef}; fn map_scalar_input_type_for_field<'a>(ctx: &'a QuerySchema, field: &ScalarFieldRef) -> InputType<'a> { map_scalar_input_type(ctx, field.type_identifier(), field.is_list()) @@ -19,6 +19,8 @@ fn map_scalar_input_type(ctx: &'_ QuerySchema, typ: TypeIdentifier, list: bool) TypeIdentifier::UUID => InputType::uuid(), TypeIdentifier::DateTime => InputType::date_time(), TypeIdentifier::Json => InputType::json(), + TypeIdentifier::Geometry(GeometryFormat::GeoJSON) => InputType::geojson_geometry(), + TypeIdentifier::Geometry(GeometryFormat::EWKT) => InputType::ewkt_geometry(), TypeIdentifier::Enum(id) => InputType::enum_type(map_schema_enum_type(ctx, id)), TypeIdentifier::Bytes => InputType::bytes(), TypeIdentifier::BigInt => InputType::bigint(), diff --git a/query-engine/schema/src/build/output_types/field.rs b/query-engine/schema/src/build/output_types/field.rs index 29924c9d98c1..00d3a5d793f8 100644 --- a/query-engine/schema/src/build/output_types/field.rs +++ b/query-engine/schema/src/build/output_types/field.rs @@ -1,6 +1,6 @@ use super::*; use input_types::fields::arguments; -use query_structure::{CompositeFieldRef, ScalarFieldRef}; +use query_structure::{CompositeFieldRef, GeometryFormat, ScalarFieldRef}; pub(crate) fn map_output_field(ctx: &'_ QuerySchema, model_field: ModelField) -> OutputField<'_> { let cloned_model_field = model_field.clone(); @@ -34,6 +34,8 @@ pub(crate) fn map_scalar_output_type<'a>(ctx: &'a QuerySchema, typ: &TypeIdentif TypeIdentifier::Boolean => OutputType::boolean(), TypeIdentifier::Enum(e) => OutputType::enum_type(map_schema_enum_type(ctx, *e)), TypeIdentifier::Json => OutputType::json(), + TypeIdentifier::Geometry(GeometryFormat::GeoJSON) => OutputType::geojson_geometry(), + TypeIdentifier::Geometry(GeometryFormat::EWKT) => OutputType::ewkt_geometry(), TypeIdentifier::DateTime => OutputType::date_time(), TypeIdentifier::UUID => OutputType::uuid(), TypeIdentifier::Int => OutputType::int(), diff --git a/query-engine/schema/src/constants.rs b/query-engine/schema/src/constants.rs index 461ce37ef42d..f0c6795fae9b 100644 --- a/query-engine/schema/src/constants.rs +++ b/query-engine/schema/src/constants.rs @@ -106,6 +106,10 @@ pub mod filters { pub const STRING_STARTS_WITH: &str = "string_starts_with"; pub const STRING_ENDS_WITH: &str = "string_ends_with"; pub const JSON_TYPE: &str = "json_type"; + + // geometry filters + pub const GEO_WITHIN: &str = "geoWithin"; + pub const GEO_INTERSECTS: &str = "geoIntersects"; } pub mod aggregations { diff --git a/query-engine/schema/src/input_types.rs b/query-engine/schema/src/input_types.rs index 3a6c0610f600..b133d1622fdc 100644 --- a/query-engine/schema/src/input_types.rs +++ b/query-engine/schema/src/input_types.rs @@ -270,6 +270,14 @@ impl<'a> InputType<'a> { InputType::Scalar(ScalarType::Bytes) } + pub(crate) fn ewkt_geometry() -> InputType<'a> { + InputType::Scalar(ScalarType::Geometry) + } + + pub(crate) fn geojson_geometry() -> InputType<'a> { + InputType::Scalar(ScalarType::GeoJson) + } + pub(crate) fn null() -> InputType<'a> { InputType::Scalar(ScalarType::Null) } diff --git a/query-engine/schema/src/output_types.rs b/query-engine/schema/src/output_types.rs index 32956d01d50b..f42d620f46e6 100644 --- a/query-engine/schema/src/output_types.rs +++ b/query-engine/schema/src/output_types.rs @@ -77,6 +77,14 @@ impl<'a> OutputType<'a> { InnerOutputType::Scalar(ScalarType::Bytes) } + pub(crate) fn ewkt_geometry() -> InnerOutputType<'a> { + InnerOutputType::Scalar(ScalarType::Geometry) + } + + pub(crate) fn geojson_geometry() -> InnerOutputType<'a> { + InnerOutputType::Scalar(ScalarType::GeoJson) + } + /// Attempts to recurse through the type until an object type is found. /// Returns Some(ObjectTypeStrongRef) if ab object type is found, None otherwise. pub fn as_object_type<'b>(&'b self) -> Option<&'b ObjectType<'a>> { diff --git a/query-engine/schema/src/query_schema.rs b/query-engine/schema/src/query_schema.rs index 0324896aea07..d072087e6613 100644 --- a/query-engine/schema/src/query_schema.rs +++ b/query-engine/schema/src/query_schema.rs @@ -313,6 +313,8 @@ pub enum ScalarType { JsonList, UUID, Bytes, + GeoJson, + Geometry, } impl fmt::Display for ScalarType { @@ -330,6 +332,8 @@ impl fmt::Display for ScalarType { ScalarType::UUID => "UUID", ScalarType::JsonList => "Json", ScalarType::Bytes => "Bytes", + ScalarType::GeoJson => "GeoJson", + ScalarType::Geometry => "Geometry", }; f.write_str(typ) diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres.rs index 4942f0f4eff5..659a9dd984d8 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres.rs @@ -535,6 +535,7 @@ pub(crate) enum Circumstances { IsCockroachDb, CockroachWithPostgresNativeTypes, // FIXME: we should really break and remove this CanPartitionTables, + HasPostGIS, } fn disable_postgres_statement_cache(url: &mut Url) -> ConnectorResult<()> { @@ -624,6 +625,10 @@ where } } + if let Ok(_postgis_version) = connection.query_raw("SELECT PostGIS_version();", &[], ¶ms.url).await { + circumstances |= Circumstances::HasPostGIS; + } + if let Some(true) = schema_exists_result .get(0) .and_then(|row| row.at(0).and_then(|value| value.as_bool())) diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/connection.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/connection.rs index 9db4ed56b859..71caa76d1e26 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/connection.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/connection.rs @@ -59,6 +59,10 @@ impl Connection { describer_circumstances |= describer::Circumstances::Cockroach; } + if circumstances.contains(super::Circumstances::HasPostGIS) { + describer_circumstances |= describer::Circumstances::HasPostGIS; + } + if circumstances.contains(super::Circumstances::CockroachWithPostgresNativeTypes) { describer_circumstances |= describer::Circumstances::CockroachWithPostgresNativeTypes; } diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs index 6570682f4d95..2c6ca751c854 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs @@ -20,6 +20,14 @@ pub(crate) struct SqliteFlavour { state: State, } +impl SqliteFlavour { + pub(crate) fn has_spatialite(&self) -> bool { + // TODO@geometry: FIXME! how can we set this at instanciation ? + // currently set to false to avoid too many failing tests + false + } +} + impl Default for SqliteFlavour { fn default() -> Self { SqliteFlavour { state: State::Initial } diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs index 959ed6de8632..bf43f1446f2a 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs @@ -2,7 +2,7 @@ pub(crate) use quaint::connector::rusqlite; -use quaint::connector::{GetRow, ToColumnNames}; +use quaint::connector::{rusqlite::LoadExtensionGuard, GetRow, ToColumnNames}; use schema_connector::{ConnectorError, ConnectorResult}; use sql_schema_describer::{sqlite as describer, DescriberErrorKind, SqlSchema}; use std::sync::Mutex; @@ -10,15 +10,27 @@ use user_facing_errors::schema_engine::ApplyMigrationError; pub(super) struct Connection(Mutex); +fn load_spatialite(conn: &rusqlite::Connection) { + // TODO@geometry: raise an appropriate error when spatialite cannot be loaded instead + if let Ok(spatialite_path) = std::env::var("SPATIALITE_PATH") { + unsafe { + let _guard = LoadExtensionGuard::new(conn).unwrap(); + conn.load_extension(spatialite_path, None).unwrap(); + } + } +} + impl Connection { pub(super) fn new(params: &super::Params) -> ConnectorResult { - Ok(Connection(Mutex::new( - rusqlite::Connection::open(¶ms.file_path).map_err(convert_error)?, - ))) + let conn = rusqlite::Connection::open(¶ms.file_path).map_err(convert_error)?; + load_spatialite(&conn); + Ok(Connection(Mutex::new(conn))) } pub(super) fn new_in_memory() -> Self { - Connection(Mutex::new(rusqlite::Connection::open_in_memory().unwrap())) + let conn = rusqlite::Connection::open_in_memory().unwrap(); + load_spatialite(&conn); + Connection(Mutex::new(conn)) } pub(super) async fn describe_schema(&mut self) -> ConnectorResult { diff --git a/schema-engine/connectors/sql-schema-connector/src/introspection/introspection_pair/scalar_field.rs b/schema-engine/connectors/sql-schema-connector/src/introspection/introspection_pair/scalar_field.rs index e0536b70f432..98bd9406acc9 100644 --- a/schema-engine/connectors/sql-schema-connector/src/introspection/introspection_pair/scalar_field.rs +++ b/schema-engine/connectors/sql-schema-connector/src/introspection/introspection_pair/scalar_field.rs @@ -90,6 +90,7 @@ impl<'a> ScalarFieldPair<'a> { sql::ColumnTypeFamily::DateTime => Cow::from("DateTime"), sql::ColumnTypeFamily::Binary => Cow::from("Bytes"), sql::ColumnTypeFamily::Json => Cow::from("Json"), + sql::ColumnTypeFamily::Geometry => Cow::from("Geometry"), sql::ColumnTypeFamily::Uuid => Cow::from("String"), sql::ColumnTypeFamily::Enum(id) => self.context.enum_prisma_name(*id).prisma_name(), sql::ColumnTypeFamily::Unsupported(ref typ) => Cow::from(typ), @@ -107,6 +108,7 @@ impl<'a> ScalarFieldPair<'a> { sql::ColumnTypeFamily::String => Some(psl::parser_database::ScalarType::String), sql::ColumnTypeFamily::DateTime => Some(psl::parser_database::ScalarType::DateTime), sql::ColumnTypeFamily::Json => Some(psl::parser_database::ScalarType::Json), + sql::ColumnTypeFamily::Geometry => Some(psl::parser_database::ScalarType::Geometry), sql::ColumnTypeFamily::Uuid => Some(psl::parser_database::ScalarType::String), sql::ColumnTypeFamily::Binary => Some(psl::parser_database::ScalarType::Bytes), sql::ColumnTypeFamily::Enum(_) => None, diff --git a/schema-engine/connectors/sql-schema-connector/src/lib.rs b/schema-engine/connectors/sql-schema-connector/src/lib.rs index 66924c2b5a9b..b9a41609b421 100644 --- a/schema-engine/connectors/sql-schema-connector/src/lib.rs +++ b/schema-engine/connectors/sql-schema-connector/src/lib.rs @@ -1,6 +1,6 @@ //! The SQL migration connector. -#![deny(rust_2018_idioms, unsafe_code, missing_docs)] +#![deny(rust_2018_idioms, missing_docs)] mod apply_migration; mod database_schema; diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_renderer.rs b/schema-engine/connectors/sql-schema-connector/src/sql_renderer.rs index 74ee2c573ece..7287de59e492 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_renderer.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_renderer.rs @@ -70,6 +70,11 @@ pub(crate) trait SqlRenderer { /// Render a table creation with the provided table name. fn render_create_table_as(&self, table: TableWalker<'_>, table_name: QuotedWithPrefix<&str>) -> String; + /// Render geometry columns creation (Spatialite only) + fn render_create_geometry_columns(&self, _table: TableWalker<'_>, _table_name: QuotedWithPrefix<&str>) -> String { + unreachable!("unreachable render_create_geometry_columns") + } + fn render_drop_and_recreate_index(&self, _indexes: MigrationPair>) -> Vec { unreachable!("unreachable render_drop_and_recreate_index") } diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/mssql_renderer.rs b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/mssql_renderer.rs index 540542648ea2..7594e2cc752e 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/mssql_renderer.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/mssql_renderer.rs @@ -514,6 +514,8 @@ fn render_column_type(column: sql::TableColumnWalker<'_>) -> Cow<'static, str> { MsSqlType::VarBinary(len) => format!("VARBINARY{len}", len = format_type_param(*len)).into(), MsSqlType::Image => "IMAGE".into(), MsSqlType::Xml => "XML".into(), + MsSqlType::Geometry => "GEOMETRY".into(), + MsSqlType::Geography => "GEOGRAPHY".into(), MsSqlType::UniqueIdentifier => "UNIQUEIDENTIFIER".into(), } } diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/mysql_renderer.rs b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/mysql_renderer.rs index 4f96aea6fd69..617f0915665b 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/mysql_renderer.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/mysql_renderer.rs @@ -408,6 +408,13 @@ fn render_column_type(column: TableColumnWalker<'_>) -> Cow<'static, str> { } } + fn render_srid(input: Option) -> String { + match input { + None => "".to_string(), + Some(srid) => format!(" SRID {srid}"), + } + } + fn render_decimal(input: Option<(u32, u32)>) -> String { match input { None => "".to_string(), @@ -449,6 +456,14 @@ fn render_column_type(column: TableColumnWalker<'_>) -> Cow<'static, str> { MySqlType::UnsignedTinyInt => "TINYINT UNSIGNED".into(), MySqlType::UnsignedMediumInt => "MEDIUMINT UNSIGNED".into(), MySqlType::UnsignedBigInt => "BIGINT UNSIGNED".into(), + MySqlType::Geometry(srid) => format!("GEOMETRY{}", render_srid(*srid)).into(), + MySqlType::Point(srid) => format!("POINT{}", render_srid(*srid)).into(), + MySqlType::LineString(srid) => format!("LINESTRING{}", render_srid(*srid)).into(), + MySqlType::Polygon(srid) => format!("POLYGON{}", render_srid(*srid)).into(), + MySqlType::MultiPoint(srid) => format!("MULTIPOINT{}", render_srid(*srid)).into(), + MySqlType::MultiLineString(srid) => format!("MULTILINESTRING{}", render_srid(*srid)).into(), + MySqlType::MultiPolygon(srid) => format!("MULTIPOLYGON{}", render_srid(*srid)).into(), + MySqlType::GeometryCollection(srid) => format!("GEOMETRYCOLLECTION{}", render_srid(*srid)).into(), } } diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/postgres_renderer.rs b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/postgres_renderer.rs index fdebc14f89b2..cacbcdd79854 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/postgres_renderer.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/postgres_renderer.rs @@ -8,7 +8,7 @@ use crate::{ }, sql_schema_differ::{ColumnChange, ColumnChanges}, }; -use psl::builtin_connectors::{CockroachType, PostgresType}; +use psl::builtin_connectors::{CockroachType, GeometryParams, PostgresType}; use sql_ddl::{ postgres::{self as ddl, PostgresIdentifier}, IndexColumn, SortOrder, @@ -587,6 +587,8 @@ fn render_column_type_postgres(col: TableColumnWalker<'_>) -> Cow<'static, str> PostgresType::Xml => "XML".into(), PostgresType::Json => "JSON".into(), PostgresType::JsonB => "JSONB".into(), + PostgresType::Geometry(geom) => format!("GEOMETRY{}", render_geometry_arg(*geom)).into(), + PostgresType::Geography(geom) => format!("GEOGRAPHY{}", render_geometry_arg(*geom)).into(), }; if t.arity.is_list() { @@ -628,6 +630,8 @@ fn render_column_type_cockroachdb(col: TableColumnWalker<'_>) -> Cow<'static, st CockroachType::VarBit(length) => format!("VARBIT{}", render_optional_args(*length)).into(), CockroachType::Uuid => "UUID".into(), CockroachType::JsonB => "JSONB".into(), + CockroachType::Geometry(geom) => format!("GEOMETRY{}", render_geometry_arg(*geom)).into(), + CockroachType::Geography(geom) => format!("GEOGRAPHY{}", render_geometry_arg(*geom)).into(), }; if t.arity.is_list() { @@ -651,6 +655,14 @@ fn render_decimal_args(input: Option<(u32, u32)>) -> String { } } +fn render_geometry_arg(input: Option) -> String { + match input { + None => "".to_string(), + Some(GeometryParams { ty, srid: 0 }) => format!("({ty})"), + Some(GeometryParams { ty, srid }) => format!("({ty}, {srid})"), + } +} + /// Escape an in-memory string so it becomes a valid string literal with default escaping, i.e. /// replacing `'` characters with `''`. fn escape_string_literal(s: &str) -> Cow<'_, str> { diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/sqlite_renderer.rs b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/sqlite_renderer.rs index b0e79b515814..9485a3a7b9b0 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/sqlite_renderer.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/sqlite_renderer.rs @@ -6,10 +6,11 @@ use crate::{ }; use indoc::formatdoc; use once_cell::sync::Lazy; +use psl::builtin_connectors::SQLiteType; use regex::Regex; use sql_ddl::sqlite as ddl; use sql_schema_describer::{walkers::*, *}; -use std::borrow::Cow; +use std::{fmt::Write, borrow::Cow}; impl SqlRenderer for SqliteFlavour { fn quote<'a>(&self, name: &'a str) -> Quoted<&'a str> { @@ -141,7 +142,36 @@ impl SqlRenderer for SqliteFlavour { .map(|c| c.map(|c| c.name().into()).collect()); } - create_table.to_string() + let create_geometries = &self.render_create_geometry_columns(table, table_name); + if create_geometries.is_empty() { + create_table.to_string() + } else { + create_table.to_string() + "\n;" + create_geometries + } + } + + fn render_create_geometry_columns(&self, table: TableWalker<'_>, table_name: QuotedWithPrefix<&str>) -> String { + // TODO@geometry: RecoverGeometryColumn doesn't error, it returns 1 on success and 0 on failure + // Is that sufficient to signal failure to the migration script or do we need special handing ? + let table_name = table_name.to_string(); + let table_name = &table_name[1..table_name.len() - 1]; // Because we need it as an unqoted string + table + .columns() + .filter(|c| c.column_type_family().is_geometry()) + .fold(String::new(), |mut result, col| { + let column_name = col.name(); + let SQLiteType::Geometry(geom) = col.column_native_type().unwrap(); + let geom = geom.expect("Couldn't get geometry column type informations"); + writeln!( + result, + "SELECT RecoverGeometryColumn('{table_name}', '{column_name}', {srid}, '{ty}', '{dims}');", + srid = geom.srid, + ty = geom.ty.as_2d(), + dims = geom.ty.dimensions(), + ) + .unwrap(); + result + }) } fn render_drop_enum(&self, _: EnumWalker<'_>) -> Vec { @@ -173,8 +203,12 @@ impl SqlRenderer for SqliteFlavour { stmt.push_str("PRAGMA foreign_keys=off"); }); step.render_statement(&mut |stmt| { - stmt.push_str("DROP TABLE "); - stmt.push_display(&Quoted::sqlite_ident(table_name)); + if self.has_spatialite() { + stmt.push_str(&format!("SELECT DropTable(NULL, '{}')", table_name)); + } else { + stmt.push_str("DROP TABLE "); + stmt.push_display(&Quoted::sqlite_ident(table_name)); + } }); step.render_statement(&mut |stmt| { stmt.push_str("PRAGMA foreign_keys=on"); @@ -197,13 +231,21 @@ impl SqlRenderer for SqliteFlavour { copy_current_table_into_new_table(&mut result, redefine_table, tables, &temporary_table_name); - result.push(format!(r#"DROP TABLE "{}""#, tables.previous.name())); - - result.push(format!( - r#"ALTER TABLE "{old_name}" RENAME TO "{new_name}""#, - old_name = temporary_table_name, - new_name = tables.next.name(), - )); + if self.has_spatialite() { + result.push(format!("SELECT DropTable(NULL, '{}')", tables.previous.name())); + result.push(format!( + "SELECT RenameTable('{old_name}', '{new_name}')", + old_name = temporary_table_name, + new_name = tables.next.name(), + )); + } else { + result.push(format!(r#"DROP TABLE "{}""#, tables.previous.name())); + result.push(format!( + r#"ALTER TABLE "{old_name}" RENAME TO "{new_name}""#, + old_name = temporary_table_name, + new_name = tables.next.name(), + )); + } for index in tables.next.indexes().filter(|idx| !idx.is_primary_key()) { result.push(self.render_create_index(index)); @@ -217,7 +259,11 @@ impl SqlRenderer for SqliteFlavour { } fn render_rename_table(&self, _namespace: Option<&str>, name: &str, new_name: &str) -> String { - format!(r#"ALTER TABLE "{name}" RENAME TO "{new_name}""#) + if self.has_spatialite() { + format!("SELECT RenameTable(NULL, '{name}', '{new_name}')") + } else { + format!(r#"ALTER TABLE "{name}" RENAME TO "{new_name}""#) + } } fn render_drop_view(&self, view: ViewWalker<'_>) -> String { @@ -243,6 +289,8 @@ fn render_column_type(t: &ColumnType) -> &str { ColumnTypeFamily::BigInt => "BIGINT", ColumnTypeFamily::String => "TEXT", ColumnTypeFamily::Binary => "BLOB", + // TODO@geometry: Ideally, render 2D native geometry type instead (not necessary) + ColumnTypeFamily::Geometry => "GEOMETRY", ColumnTypeFamily::Json => unreachable!("ColumnTypeFamily::Json on SQLite"), ColumnTypeFamily::Enum(_) => unreachable!("ColumnTypeFamily::Enum on SQLite"), ColumnTypeFamily::Uuid => unimplemented!("ColumnTypeFamily::Uuid on SQLite"), diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_schema_calculator.rs b/schema-engine/connectors/sql-schema-connector/src/sql_schema_calculator.rs index 10a87b96d5a5..3814e8f0f00f 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_schema_calculator.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_schema_calculator.rs @@ -461,6 +461,8 @@ fn push_column_for_builtin_scalar_type( ScalarType::Bytes => sql::ColumnTypeFamily::Binary, ScalarType::Decimal => sql::ColumnTypeFamily::Decimal, ScalarType::BigInt => sql::ColumnTypeFamily::BigInt, + ScalarType::Geometry => sql::ColumnTypeFamily::Geometry, + ScalarType::GeoJson => sql::ColumnTypeFamily::Geometry, }; let native_type = field diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/mssql.rs b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/mssql.rs index 01b89e78d9aa..b183b544c303 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/mssql.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/mssql.rs @@ -123,6 +123,16 @@ fn native_type_change_riskyness(previous: &MsSqlType, next: &MsSqlType) -> Optio use MsSqlTypeParameter::*; let cast = || match previous { + MsSqlType::Geometry => match next { + MsSqlType::Geometry => SafeCast, + _ => NotCastable, + }, + + MsSqlType::Geography => match next { + MsSqlType::Geography => SafeCast, + _ => NotCastable, + }, + // Bit, as in booleans. 1 or 0. MsSqlType::Bit => match next { MsSqlType::TinyInt => SafeCast, diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/mysql.rs b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/mysql.rs index 4f4dea5c7a45..aa42843dd234 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/mysql.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/mysql.rs @@ -158,6 +158,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::Binary(size) => match next { MySqlType::Binary(n) if n == size => return None, @@ -196,6 +205,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option risky(), MySqlType::Date | MySqlType::DateTime(_) | MySqlType::Json | MySqlType::Timestamp(_) => not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::Bit(n) => match next { MySqlType::Bit(m) if n == m => return None, @@ -236,6 +254,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::Blob => match next { MySqlType::Blob => return None, @@ -271,6 +298,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::Char(n) => match next { MySqlType::Char(m) if m == n => return None, @@ -312,6 +348,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option risky(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::Date => match next { MySqlType::Date => return None, @@ -335,6 +380,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), + // To string MySqlType::Binary(_) | MySqlType::Bit(_) @@ -386,6 +440,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), + MySqlType::Timestamp(_) | MySqlType::Time(_) | MySqlType::Date => safe(), }, MySqlType::Decimal(n) => match next { @@ -425,6 +488,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option risky(), MySqlType::DateTime(_) | MySqlType::Timestamp(_) | MySqlType::Date => not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::Double => match next { MySqlType::Double => return None, @@ -469,6 +541,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option safe(), MySqlType::Timestamp(_) | MySqlType::DateTime(_) | MySqlType::Date => not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::Float => match next { MySqlType::Float => return None, @@ -514,6 +595,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option safe(), MySqlType::Timestamp(_) | MySqlType::DateTime(_) | MySqlType::Date => not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::Int => match next { MySqlType::Int => return None, @@ -555,6 +645,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::Json => match next { MySqlType::Json => return None, @@ -594,6 +693,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::LongBlob => match next { MySqlType::LongBlob => return None, @@ -629,6 +737,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::LongText => match next { MySqlType::LongText => return None, @@ -668,6 +785,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option risky(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::MediumBlob => match next { MySqlType::MediumBlob => return None, @@ -703,6 +829,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::MediumInt => match next { MySqlType::MediumInt => return None, @@ -744,6 +879,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::MediumText => match next { MySqlType::MediumText => return None, @@ -783,6 +927,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option risky(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::SmallInt => match next { @@ -820,6 +973,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::Text => match next { @@ -860,6 +1022,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option risky(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::Time(n) => match next { @@ -886,6 +1057,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), + // To numeric MySqlType::BigInt | MySqlType::Bit(_) @@ -937,6 +1117,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), + MySqlType::DateTime(_) | MySqlType::Time(_) | MySqlType::Date => safe(), }, @@ -974,6 +1163,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::TinyInt => match next { @@ -1013,6 +1211,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::TinyText => match next { @@ -1054,6 +1261,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option risky(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::UnsignedBigInt => match next { @@ -1094,6 +1310,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::UnsignedInt => match next { @@ -1134,6 +1359,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::UnsignedMediumInt => match next { MySqlType::UnsignedMediumInt => return None, @@ -1173,6 +1407,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::UnsignedSmallInt => match next { MySqlType::UnsignedSmallInt => return None, @@ -1211,6 +1454,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::UnsignedTinyInt => match next { MySqlType::UnsignedTinyInt => return None, @@ -1251,6 +1503,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::VarBinary(n) => match next { MySqlType::VarBinary(m) if n > m => risky(), @@ -1289,6 +1550,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::VarChar(n) => match next { MySqlType::VarChar(m) if m == n => return None, @@ -1329,6 +1599,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option risky(), + + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), }, MySqlType::Year => match next { MySqlType::Year => return None, @@ -1362,6 +1641,15 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option risky(), + MySqlType::Geometry(_) + | MySqlType::Point(_) + | MySqlType::LineString(_) + | MySqlType::Polygon(_) + | MySqlType::MultiPoint(_) + | MySqlType::MultiLineString(_) + | MySqlType::MultiPolygon(_) + | MySqlType::GeometryCollection(_) => not_castable(), + MySqlType::Date | MySqlType::DateTime(_) | MySqlType::Time(_) @@ -1369,5 +1657,78 @@ fn native_type_change(types: MigrationPair<&MySqlType>) -> Option not_castable(), // out of range }, + // TODO@geometry: MySQL 8+ can actually cast between spatial types (https://dev.mysql.com/doc/refman/8.0/en/cast-functions.html#cast-spatial-types) + MySqlType::Geometry(_) => match next { + MySqlType::TinyBlob + | MySqlType::Blob + | MySqlType::MediumBlob + | MySqlType::LongBlob + | MySqlType::Binary(_) + | MySqlType::VarBinary(_) => risky(), + _ => not_castable(), + }, + MySqlType::Point(_) => match next { + MySqlType::TinyBlob + | MySqlType::Blob + | MySqlType::MediumBlob + | MySqlType::LongBlob + | MySqlType::Binary(_) + | MySqlType::VarBinary(_) => risky(), + _ => not_castable(), + }, + MySqlType::LineString(_) => match next { + MySqlType::TinyBlob + | MySqlType::Blob + | MySqlType::MediumBlob + | MySqlType::LongBlob + | MySqlType::Binary(_) + | MySqlType::VarBinary(_) => risky(), + _ => not_castable(), + }, + MySqlType::Polygon(_) => match next { + MySqlType::TinyBlob + | MySqlType::Blob + | MySqlType::MediumBlob + | MySqlType::LongBlob + | MySqlType::Binary(_) + | MySqlType::VarBinary(_) => risky(), + _ => not_castable(), + }, + MySqlType::MultiPoint(_) => match next { + MySqlType::TinyBlob + | MySqlType::Blob + | MySqlType::MediumBlob + | MySqlType::LongBlob + | MySqlType::Binary(_) + | MySqlType::VarBinary(_) => risky(), + _ => not_castable(), + }, + MySqlType::MultiLineString(_) => match next { + MySqlType::TinyBlob + | MySqlType::Blob + | MySqlType::MediumBlob + | MySqlType::LongBlob + | MySqlType::Binary(_) + | MySqlType::VarBinary(_) => risky(), + _ => not_castable(), + }, + MySqlType::MultiPolygon(_) => match next { + MySqlType::TinyBlob + | MySqlType::Blob + | MySqlType::MediumBlob + | MySqlType::LongBlob + | MySqlType::Binary(_) + | MySqlType::VarBinary(_) => risky(), + _ => not_castable(), + }, + MySqlType::GeometryCollection(_) => match next { + MySqlType::TinyBlob + | MySqlType::Blob + | MySqlType::MediumBlob + | MySqlType::LongBlob + | MySqlType::Binary(_) + | MySqlType::VarBinary(_) => risky(), + _ => not_castable(), + }, }) } diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/postgres.rs b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/postgres.rs index 81db61b7daed..1c64176f15b4 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/postgres.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/postgres.rs @@ -661,6 +661,14 @@ fn postgres_native_type_change_riskyness(previous: &PostgresType, next: &Postgre VarChar(_) | Char(_) => RiskyCast, _ => NotCastable, }, + Geometry(_) => match next { + Geography(_) | Text | Json | VarChar(_) | Char(_) => RiskyCast, + _ => NotCastable, + }, + Geography(_) => match next { + Geometry(_) | Text | Json | VarChar(_) | Char(_) => RiskyCast, + _ => NotCastable, + }, }) }; diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs index 515f26ac2937..a964512c63b8 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs @@ -3,8 +3,83 @@ use crate::{ flavour::SqliteFlavour, migration_pair::MigrationPair, sql_schema_differ::column::ColumnTypeChange, sql_schema_differ::differ_database::DifferDatabase, }; +use once_cell::sync::Lazy; +use regex::RegexSet; use sql_schema_describer::{walkers::TableColumnWalker, ColumnTypeFamily}; +/// These can be tables or views, depending on the PostGIS version. In both cases, they should be ignored. +static SPATIALITE_TABLES_OR_VIEWS: Lazy = Lazy::new(|| { + RegexSet::new([ + "(?i)^data_licenses$", + "(?i)^elementarygeometries$", + "(?i)^geometry_columns$", + "(?i)^geometry_columns_auth$", + "(?i)^geometry_columns_field_infos$", + "(?i)^geometry_columns_statistics$", + "(?i)^geometry_columns_time$", + "(?i)^geom_cols_ref_sys$", + "(?i)^idx_iso_metadata_geometry$", + "(?i)^idx_iso_metadata_geometry_node$", + "(?i)^idx_iso_metadata_geometry_parent$", + "(?i)^idx_iso_metadata_geometry_rowid$", + "(?i)^iso_metadata$", + "(?i)^iso_metadata_reference$", + "(?i)^iso_metadata_view$", + "(?i)^knn2$", + "(?i)^networks$", + "(?i)^raster_coverages$", + "(?i)^raster_coverages_keyword$", + "(?i)^raster_coverages_ref_sys$", + "(?i)^raster_coverages_srid$", + "(?i)^rl2map_configurations$", + "(?i)^rl2map_configurations_view$", + "(?i)^se_external_graphics$", + "(?i)^se_external_graphics_view$", + "(?i)^se_fonts$", + "(?i)^se_fonts_view$", + "(?i)^se_raster_styled_layers$", + "(?i)^se_raster_styled_layers_view$", + "(?i)^se_raster_styles$", + "(?i)^se_raster_styles_view$", + "(?i)^se_vector_styled_layers$", + "(?i)^se_vector_styled_layers_view$", + "(?i)^se_vector_styles$", + "(?i)^se_vector_styles_view$", + "(?i)^spatialindex$", + "(?i)^spatialite_history$", + "(?i)^spatial_ref_sys$", + "(?i)^spatial_ref_sys_all$", + "(?i)^spatial_ref_sys_aux$", + "(?i)^sql_statements_log$", + "(?i)^stored_procedures$", + "(?i)^stored_variables$", + "(?i)^topologies$", + "(?i)^vector_coverages$", + "(?i)^vector_coverages_keyword$", + "(?i)^vector_coverages_ref_sys$", + "(?i)^vector_coverages_srid$", + "(?i)^vector_layers$", + "(?i)^vector_layers_auth$", + "(?i)^vector_layers_field_infos$", + "(?i)^vector_layers_statistics$", + "(?i)^views_geometry_columns$", + "(?i)^views_geometry_columns_auth$", + "(?i)^views_geometry_columns_field_infos$", + "(?i)^views_geometry_columns_statistics$", + "(?i)^virts_geometry_collection$", + "(?i)^virts_geometry_collectionm$", + "(?i)^virts_geometry_columns$", + "(?i)^virts_geometry_columns_auth$", + "(?i)^virts_geometry_columns_field_infos$", + "(?i)^virts_geometry_columns_statistics$", + "(?i)^wms_getcapabilities$", + "(?i)^wms_getmap$", + "(?i)^wms_ref_sys$", + "(?i)^wms_settings$", + ]) + .unwrap() +}); + impl SqlSchemaDifferFlavour for SqliteFlavour { fn can_rename_foreign_key(&self) -> bool { false @@ -62,4 +137,12 @@ impl SqlSchemaDifferFlavour for SqliteFlavour { fn has_unnamed_foreign_keys(&self) -> bool { true } + + fn table_should_be_ignored(&self, table_name: &str) -> bool { + SPATIALITE_TABLES_OR_VIEWS.is_match(table_name) + } + + fn view_should_be_ignored(&self, view_name: &str) -> bool { + SPATIALITE_TABLES_OR_VIEWS.is_match(view_name) + } } diff --git a/schema-engine/sql-introspection-tests/tests/cockroachdb/gin.rs b/schema-engine/sql-introspection-tests/tests/cockroachdb/gin.rs index 148cdb3761bf..cb2a52f91890 100644 --- a/schema-engine/sql-introspection-tests/tests/cockroachdb/gin.rs +++ b/schema-engine/sql-introspection-tests/tests/cockroachdb/gin.rs @@ -21,8 +21,8 @@ async fn gin_unsupported_type(api: &mut TestApi) -> TestResult { let expected = expect![[r#" model A { - id BigInt @id @default(autoincrement()) - data Unsupported("geometry") + id BigInt @id @default(autoincrement()) + data Geometry @@index([data], type: Gin) } diff --git a/schema-engine/sql-introspection-tests/tests/commenting_out/cockroachdb.rs b/schema-engine/sql-introspection-tests/tests/commenting_out/cockroachdb.rs index 888a6037a41c..225a8b5aac14 100644 --- a/schema-engine/sql-introspection-tests/tests/commenting_out/cockroachdb.rs +++ b/schema-engine/sql-introspection-tests/tests/commenting_out/cockroachdb.rs @@ -85,8 +85,7 @@ async fn unsupported_type_keeps_its_usages_cockroach(api: &mut TestApi) -> TestR t.add_column("id", types::primary()); // Geometry/Geography is the only type that is not supported by Prisma, but is also not // indexable (only inverted-indexable). - t.add_column("broken", types::custom("geometry")); - t.add_column("broken2", types::custom("geography")); + t.add_column("broken", types::custom("interval")); }); }) .await?; @@ -95,17 +94,15 @@ async fn unsupported_type_keeps_its_usages_cockroach(api: &mut TestApi) -> TestR *** WARNING *** These fields are not supported by Prisma Client, because Prisma currently does not support their types: - - Model: "Test", field: "broken", original data type: "geometry" - - Model: "Test", field: "broken2", original data type: "geography" + - Model: "Test", field: "broken", original data type: "interval" "#]]; api.expect_warnings(&expected).await; let dm = expect![[r#" model Test { - id BigInt @id @default(autoincrement()) - broken Unsupported("geometry") - broken2 Unsupported("geography") + id BigInt @id @default(autoincrement()) + broken Unsupported("interval") } "#]]; diff --git a/schema-engine/sql-introspection-tests/tests/native_types/mod.rs b/schema-engine/sql-introspection-tests/tests/native_types/mod.rs index 4199b1659cda..261d27f53628 100644 --- a/schema-engine/sql-introspection-tests/tests/native_types/mod.rs +++ b/schema-engine/sql-introspection-tests/tests/native_types/mod.rs @@ -1,3 +1,4 @@ mod mssql; mod mysql; mod postgres; +mod sqlite; diff --git a/schema-engine/sql-introspection-tests/tests/native_types/mssql.rs b/schema-engine/sql-introspection-tests/tests/native_types/mssql.rs index 17424f7c70bf..93e5c5bf1642 100644 --- a/schema-engine/sql-introspection-tests/tests/native_types/mssql.rs +++ b/schema-engine/sql-introspection-tests/tests/native_types/mssql.rs @@ -34,6 +34,8 @@ const TYPES: &[(&str, &str)] = &[ ("image", "Image"), ("text", "Text"), ("ntext", "NText"), + ("geom", "Geometry"), + ("geog", "Geography"), ]; #[test_connector(tags(Mssql))] @@ -88,6 +90,8 @@ async fn native_type_columns_feature_on(api: &mut TestApi) -> TestResult { image Bytes @db.Image text String @db.Text ntext String @db.NText + geom Geometry + geog Geometry @db.Geography } "#}; diff --git a/schema-engine/sql-introspection-tests/tests/native_types/mysql.rs b/schema-engine/sql-introspection-tests/tests/native_types/mysql.rs index a46878d4b0e0..02017171da49 100644 --- a/schema-engine/sql-introspection-tests/tests/native_types/mysql.rs +++ b/schema-engine/sql-introspection-tests/tests/native_types/mysql.rs @@ -42,6 +42,26 @@ const TYPES: &[(&str, &str)] = &[ ("timestampWithPrecision", "Timestamp(3)"), ("year", "Year"), ("json", "Json"), + ("geom", "Geometry"), + ("point", "Point"), + ("line", "LineString"), + ("polygon", "Polygon"), + ("multipoint", "MultiPoint"), + ("multiline", "MultiLineString"), + ("multipolygon", "MultiPolygon"), + ("geometrycollection", "GeometryCollection"), +]; + +const GEOMETRY_SRID_TYPES: &[(&str, &str)] = &[ + ("id", "BigInt Auto_Increment Primary Key"), + ("geom", "Geometry SRID 4326"), + ("point", "Point SRID 4326"), + ("line", "LineString SRID 4326"), + ("polygon", "Polygon SRID 4326"), + ("multipoint", "MultiPoint SRID 4326"), + ("multiline", "MultiLineString SRID 4326"), + ("multipolygon", "MultiPolygon SRID 4326"), + ("geometrycollection", "GeometryCollection SRID 4326"), ]; // NOTE: The MariaDB expectations broke with 10.11.2 @@ -109,6 +129,56 @@ async fn native_type_columns_feature_on(api: &mut TestApi) -> TestResult { timestampWithPrecision DateTime @db.Timestamp(3) year Int @db.Year json {json} + geom Geometry + point Geometry @db.Point + line Geometry @db.LineString + polygon Geometry @db.Polygon + multipoint Geometry @db.MultiPoint + multiline Geometry @db.MultiLineString + multipolygon Geometry @db.MultiPolygon + geometrycollection Geometry @db.GeometryCollection + }} + "#, + }; + + let result = api.introspect().await?; + + println!("EXPECTATION: \n {types:#}"); + println!("RESULT: \n {result:#}"); + + api.assert_eq_datamodels(&types, &result); + + Ok(()) +} + +#[test_connector(tags(Mysql8))] +async fn native_type_geometry_columns_srid_feature_on(api: &mut TestApi) -> TestResult { + let columns: Vec = GEOMETRY_SRID_TYPES + .iter() + .map(|(name, db_type)| format!("`{name}` {db_type} Not Null")) + .collect(); + + api.barrel() + .execute(move |migration| { + migration.create_table("Spatial", move |t| { + for column in &columns { + t.inject_custom(column); + } + }); + }) + .await?; + + let types = formatdoc! {r#" + model Spatial {{ + id BigInt @id @default(autoincrement()) + geom Geometry @db.Geometry(4326) + point Geometry @db.Point(4326) + line Geometry @db.LineString(4326) + polygon Geometry @db.Polygon(4326) + multipoint Geometry @db.MultiPoint(4326) + multiline Geometry @db.MultiLineString(4326) + multipolygon Geometry @db.MultiPolygon(4326) + geometrycollection Geometry @db.GeometryCollection(4326) }} "#, }; diff --git a/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs b/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs index f95c6fcbc890..2ff61e6e3644 100644 --- a/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs +++ b/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs @@ -36,7 +36,145 @@ const TYPES: &[(&str, &str)] = &[ ("inet", "Inet"), ]; -#[test_connector(tags(Postgres), exclude(CockroachDb))] +const GEOMETRY_TYPES: &[(&str, &str)] = &[ + ("geometry", "Geometry"), + ("geometry_geometry", "Geometry(Geometry)"), + ("geometry_geometry_srid", "Geometry(Geometry, 4326)"), + ("geometry_geometry_m", "Geometry(GeometryM)"), + ("geometry_geometry_z", "Geometry(GeometryZ)"), + ("geometry_geometry_zm", "Geometry(GeometryZM)"), + ("geometry_point", "Geometry(Point)"), + ("geometry_point_m", "Geometry(PointM)"), + ("geometry_point_z", "Geometry(PointZ)"), + ("geometry_point_zm", "Geometry(PointZM)"), + ("geometry_linestring", "Geometry(LineString)"), + ("geometry_linestring_m", "Geometry(LineStringM)"), + ("geometry_linestring_z", "Geometry(LineStringZ)"), + ("geometry_linestring_zm", "Geometry(LineStringZM)"), + ("geometry_polygon", "Geometry(Polygon)"), + ("geometry_polygon_m", "Geometry(PolygonM)"), + ("geometry_polygon_z", "Geometry(PolygonZ)"), + ("geometry_polygon_zm", "Geometry(PolygonZM)"), + ("geometry_multipoint", "Geometry(MultiPoint)"), + ("geometry_multipoint_m", "Geometry(MultiPointM)"), + ("geometry_multipoint_z", "Geometry(MultiPointZ)"), + ("geometry_multipoint_zm", "Geometry(MultiPointZM)"), + ("geometry_multilinestring", "Geometry(MultiLineString)"), + ("geometry_multilinestring_m", "Geometry(MultiLineStringM)"), + ("geometry_multilinestring_z", "Geometry(MultiLineStringZ)"), + ("geometry_multilinestring_zm", "Geometry(MultiLineStringZM)"), + ("geometry_multipolygon", "Geometry(MultiPolygon)"), + ("geometry_multipolygon_m", "Geometry(MultiPolygonM)"), + ("geometry_multipolygon_z", "Geometry(MultiPolygonZ)"), + ("geometry_multipolygon_zm", "Geometry(MultiPolygonZM)"), + ("geometry_geometrycollection", "Geometry(GeometryCollection)"), + ("geometry_geometrycollection_m", "Geometry(GeometryCollectionM)"), + ("geometry_geometrycollection_z", "Geometry(GeometryCollectionZ)"), + ("geometry_geometrycollection_zm", "Geometry(GeometryCollectionZM)"), + ("geography", "Geography"), + ("geography_geometry", "Geography(Geometry)"), + ("geography_geometry_srid", "Geography(Geometry, 4326)"), + ("geography_geometry_m", "Geography(GeometryM)"), + ("geography_geometry_z", "Geography(GeometryZ)"), + ("geography_geometry_zm", "Geography(GeometryZM)"), + ("geography_point", "Geography(Point)"), + ("geography_point_m", "Geography(PointM)"), + ("geography_point_z", "Geography(PointZ)"), + ("geography_point_zm", "Geography(PointZM)"), + ("geography_linestring", "Geography(LineString)"), + ("geography_linestring_m", "Geography(LineStringM)"), + ("geography_linestring_z", "Geography(LineStringZ)"), + ("geography_linestring_zm", "Geography(LineStringZM)"), + ("geography_polygon", "Geography(Polygon)"), + ("geography_polygon_m", "Geography(PolygonM)"), + ("geography_polygon_z", "Geography(PolygonZ)"), + ("geography_polygon_zm", "Geography(PolygonZM)"), + ("geography_multipoint", "Geography(MultiPoint)"), + ("geography_multipoint_m", "Geography(MultiPointM)"), + ("geography_multipoint_z", "Geography(MultiPointZ)"), + ("geography_multipoint_zm", "Geography(MultiPointZM)"), + ("geography_multilinestring", "Geography(MultiLineString)"), + ("geography_multilinestring_m", "Geography(MultiLineStringM)"), + ("geography_multilinestring_z", "Geography(MultiLineStringZ)"), + ("geography_multilinestring_zm", "Geography(MultiLineStringZM)"), + ("geography_multipolygon", "Geography(MultiPolygon)"), + ("geography_multipolygon_m", "Geography(MultiPolygonM)"), + ("geography_multipolygon_z", "Geography(MultiPolygonZ)"), + ("geography_multipolygon_zm", "Geography(MultiPolygonZM)"), + ("geography_geometrycollection", "Geography(GeometryCollection)"), + ("geography_geometrycollection_m", "Geography(GeometryCollectionM)"), + ("geography_geometrycollection_z", "Geography(GeometryCollectionZ)"), + ("geography_geometrycollection_zm", "Geography(GeometryCollectionZM)"), +]; + +const GEOMETRY_EXTRA_TYPES: &[(&str, &str)] = &[ + ("geometry_circularstring", "Geometry(CircularString)"), + ("geometry_circularstringm", "Geometry(CircularStringM)"), + ("geometry_circularstringz", "Geometry(CircularStringZ)"), + ("geometry_circularstringzm", "Geometry(CircularStringZM)"), + ("geometry_compoundcurve", "Geometry(CompoundCurve)"), + ("geometry_compoundcurvem", "Geometry(CompoundCurveM)"), + ("geometry_compoundcurvez", "Geometry(CompoundCurveZ)"), + ("geometry_compoundcurvezm", "Geometry(CompoundCurveZM)"), + ("geometry_curvepolygon", "Geometry(CurvePolygon)"), + ("geometry_curvepolygonm", "Geometry(CurvePolygonM)"), + ("geometry_curvepolygonz", "Geometry(CurvePolygonZ)"), + ("geometry_curvepolygonzm", "Geometry(CurvePolygonZM)"), + ("geometry_multicurve", "Geometry(MultiCurve)"), + ("geometry_multicurvem", "Geometry(MultiCurveM)"), + ("geometry_multicurvez", "Geometry(MultiCurveZ)"), + ("geometry_multicurvezm", "Geometry(MultiCurveZM)"), + ("geometry_multisurface", "Geometry(MultiSurface)"), + ("geometry_multisurfacem", "Geometry(MultiSurfaceM)"), + ("geometry_multisurfacez", "Geometry(MultiSurfaceZ)"), + ("geometry_multisurfacezm", "Geometry(MultiSurfaceZM)"), + ("geometry_polyhedralsurface", "Geometry(PolyhedralSurface)"), + ("geometry_polyhedralsurfacem", "Geometry(PolyhedralSurfaceM)"), + ("geometry_polyhedralsurfacez", "Geometry(PolyhedralSurfaceZ)"), + ("geometry_polyhedralsurfacezm", "Geometry(PolyhedralSurfaceZM)"), + ("geometry_triangle", "Geometry(Triangle)"), + ("geometry_trianglem", "Geometry(TriangleM)"), + ("geometry_trianglez", "Geometry(TriangleZ)"), + ("geometry_trianglezm", "Geometry(TriangleZM)"), + ("geometry_tin", "Geometry(Tin)"), + ("geometry_tinm", "Geometry(TinM)"), + ("geometry_tinz", "Geometry(TinZ)"), + ("geometry_tinzm", "Geometry(TinZM)"), + ("geography_circularstring", "Geography(CircularString)"), + ("geography_circularstringm", "Geography(CircularStringM)"), + ("geography_circularstringz", "Geography(CircularStringZ)"), + ("geography_circularstringzm", "Geography(CircularStringZM)"), + ("geography_compoundcurve", "Geography(CompoundCurve)"), + ("geography_compoundcurvem", "Geography(CompoundCurveM)"), + ("geography_compoundcurvez", "Geography(CompoundCurveZ)"), + ("geography_compoundcurvezm", "Geography(CompoundCurveZM)"), + ("geography_curvepolygon", "Geography(CurvePolygon)"), + ("geography_curvepolygonm", "Geography(CurvePolygonM)"), + ("geography_curvepolygonz", "Geography(CurvePolygonZ)"), + ("geography_curvepolygonzm", "Geography(CurvePolygonZM)"), + ("geography_multicurve", "Geography(MultiCurve)"), + ("geography_multicurvem", "Geography(MultiCurveM)"), + ("geography_multicurvez", "Geography(MultiCurveZ)"), + ("geography_multicurvezm", "Geography(MultiCurveZM)"), + ("geography_multisurface", "Geography(MultiSurface)"), + ("geography_multisurfacem", "Geography(MultiSurfaceM)"), + ("geography_multisurfacez", "Geography(MultiSurfaceZ)"), + ("geography_multisurfacezm", "Geography(MultiSurfaceZM)"), + ("geography_polyhedralsurface", "Geography(PolyhedralSurface)"), + ("geography_polyhedralsurfacem", "Geography(PolyhedralSurfaceM)"), + ("geography_polyhedralsurfacez", "Geography(PolyhedralSurfaceZ)"), + ("geography_polyhedralsurfacezm", "Geography(PolyhedralSurfaceZM)"), + ("geography_triangle", "Geography(Triangle)"), + ("geography_trianglem", "Geography(TriangleM)"), + ("geography_trianglez", "Geography(TriangleZ)"), + ("geography_trianglezm", "Geography(TriangleZM)"), + ("geography_tin", "Geography(Tin)"), + ("geography_tinm", "Geography(TinM)"), + ("geography_tinz", "Geography(TinZ)"), + ("geography_tinzm", "Geography(TinZM)"), +]; + +#[test_connector(tags(Postgres), exclude(PostGIS, CockroachDb))] async fn native_type_columns_feature_on(api: &mut TestApi) -> TestResult { let columns: Vec = TYPES .iter() @@ -100,7 +238,238 @@ async fn native_type_columns_feature_on(api: &mut TestApi) -> TestResult { Ok(()) } -#[test_connector(tags(Postgres), exclude(CockroachDb))] +#[test_connector(tags(PostGIS))] +async fn native_type_spatial_columns_feature_on(api: &mut TestApi) -> TestResult { + api.raw_cmd("CREATE EXTENSION IF NOT EXISTS postgis").await; + + let columns: Vec = GEOMETRY_TYPES + .iter() + .map(|(name, db_type)| format!("\"{name}\" {db_type} Not Null")) + .collect(); + + api.barrel() + .execute(move |migration| { + migration.create_table("Spatial", move |t| { + t.inject_custom("id Integer Primary Key"); + for column in &columns { + t.inject_custom(column); + } + }); + }) + .await?; + + let mut types = indoc! {r#" + model Spatial { + id Int @id + geometry Geometry + geometry_geometry Geometry + geometry_geometry_srid Geometry @db.Geometry(Geometry, 4326) + geometry_geometry_m Geometry @db.Geometry(GeometryM) + geometry_geometry_z Geometry @db.Geometry(GeometryZ) + geometry_geometry_zm Geometry @db.Geometry(GeometryZM) + geometry_point Geometry @db.Geometry(Point) + geometry_point_m Geometry @db.Geometry(PointM) + geometry_point_z Geometry @db.Geometry(PointZ) + geometry_point_zm Geometry @db.Geometry(PointZM) + geometry_linestring Geometry @db.Geometry(LineString) + geometry_linestring_m Geometry @db.Geometry(LineStringM) + geometry_linestring_z Geometry @db.Geometry(LineStringZ) + geometry_linestring_zm Geometry @db.Geometry(LineStringZM) + geometry_polygon Geometry @db.Geometry(Polygon) + geometry_polygon_m Geometry @db.Geometry(PolygonM) + geometry_polygon_z Geometry @db.Geometry(PolygonZ) + geometry_polygon_zm Geometry @db.Geometry(PolygonZM) + geometry_multipoint Geometry @db.Geometry(MultiPoint) + geometry_multipoint_m Geometry @db.Geometry(MultiPointM) + geometry_multipoint_z Geometry @db.Geometry(MultiPointZ) + geometry_multipoint_zm Geometry @db.Geometry(MultiPointZM) + geometry_multilinestring Geometry @db.Geometry(MultiLineString) + geometry_multilinestring_m Geometry @db.Geometry(MultiLineStringM) + geometry_multilinestring_z Geometry @db.Geometry(MultiLineStringZ) + geometry_multilinestring_zm Geometry @db.Geometry(MultiLineStringZM) + geometry_multipolygon Geometry @db.Geometry(MultiPolygon) + geometry_multipolygon_m Geometry @db.Geometry(MultiPolygonM) + geometry_multipolygon_z Geometry @db.Geometry(MultiPolygonZ) + geometry_multipolygon_zm Geometry @db.Geometry(MultiPolygonZM) + geometry_geometrycollection Geometry @db.Geometry(GeometryCollection) + geometry_geometrycollection_m Geometry @db.Geometry(GeometryCollectionM) + geometry_geometrycollection_z Geometry @db.Geometry(GeometryCollectionZ) + geometry_geometrycollection_zm Geometry @db.Geometry(GeometryCollectionZM) + geography Geometry @db.Geography(Geometry, 4326) + geography_geometry Geometry @db.Geography(Geometry, 4326) + geography_geometry_srid Geometry @db.Geography(Geometry, 4326) + geography_geometry_m Geometry @db.Geography(GeometryM, 4326) + geography_geometry_z Geometry @db.Geography(GeometryZ, 4326) + geography_geometry_zm Geometry @db.Geography(GeometryZM, 4326) + geography_point Geometry @db.Geography(Point, 4326) + geography_point_m Geometry @db.Geography(PointM, 4326) + geography_point_z Geometry @db.Geography(PointZ, 4326) + geography_point_zm Geometry @db.Geography(PointZM, 4326) + geography_linestring Geometry @db.Geography(LineString, 4326) + geography_linestring_m Geometry @db.Geography(LineStringM, 4326) + geography_linestring_z Geometry @db.Geography(LineStringZ, 4326) + geography_linestring_zm Geometry @db.Geography(LineStringZM, 4326) + geography_polygon Geometry @db.Geography(Polygon, 4326) + geography_polygon_m Geometry @db.Geography(PolygonM, 4326) + geography_polygon_z Geometry @db.Geography(PolygonZ, 4326) + geography_polygon_zm Geometry @db.Geography(PolygonZM, 4326) + geography_multipoint Geometry @db.Geography(MultiPoint, 4326) + geography_multipoint_m Geometry @db.Geography(MultiPointM, 4326) + geography_multipoint_z Geometry @db.Geography(MultiPointZ, 4326) + geography_multipoint_zm Geometry @db.Geography(MultiPointZM, 4326) + geography_multilinestring Geometry @db.Geography(MultiLineString, 4326) + geography_multilinestring_m Geometry @db.Geography(MultiLineStringM, 4326) + geography_multilinestring_z Geometry @db.Geography(MultiLineStringZ, 4326) + geography_multilinestring_zm Geometry @db.Geography(MultiLineStringZM, 4326) + geography_multipolygon Geometry @db.Geography(MultiPolygon, 4326) + geography_multipolygon_m Geometry @db.Geography(MultiPolygonM, 4326) + geography_multipolygon_z Geometry @db.Geography(MultiPolygonZ, 4326) + geography_multipolygon_zm Geometry @db.Geography(MultiPolygonZM, 4326) + geography_geometrycollection Geometry @db.Geography(GeometryCollection, 4326) + geography_geometrycollection_m Geometry @db.Geography(GeometryCollectionM, 4326) + geography_geometrycollection_z Geometry @db.Geography(GeometryCollectionZ, 4326) + geography_geometrycollection_zm Geometry @db.Geography(GeometryCollectionZM, 4326) + } + "#} + .to_string(); + + // TODO@geometry: shouldn't spatial_ref_sys be ignored here ? + if !api.is_cockroach() { + types += indoc!( + r#" + /// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. + model spatial_ref_sys { + srid Int @id + auth_name String? @db.VarChar(256) + auth_srid Int? + srtext String? @db.VarChar(2048) + proj4text String? @db.VarChar(2048) + } + "# + ); + } + + let result = api.introspect().await?; + + println!("EXPECTATION: \n {types:#}"); + println!("RESULT: \n {result:#}"); + + api.assert_eq_datamodels(&types, &result); + + Ok(()) +} + +#[test_connector(tags(PostGIS), exclude(CockroachDb))] +async fn native_type_extra_spatial_columns_feature_on(api: &mut TestApi) -> TestResult { + api.raw_cmd("CREATE EXTENSION IF NOT EXISTS postgis").await; + + let columns: Vec = GEOMETRY_EXTRA_TYPES + .iter() + .map(|(name, db_type)| format!("\"{name}\" {db_type} Not Null")) + .collect(); + + api.barrel() + .execute(move |migration| { + migration.create_table("Spatial", move |t| { + t.inject_custom("id Integer Primary Key"); + for column in &columns { + t.inject_custom(column); + } + }); + }) + .await?; + + let types = indoc! {r#" + model Spatial { + id Int @id + geometry_circularstring Geometry @db.Geometry(CircularString) + geometry_circularstringm Geometry @db.Geometry(CircularStringM) + geometry_circularstringz Geometry @db.Geometry(CircularStringZ) + geometry_circularstringzm Geometry @db.Geometry(CircularStringZM) + geometry_compoundcurve Geometry @db.Geometry(CompoundCurve) + geometry_compoundcurvem Geometry @db.Geometry(CompoundCurveM) + geometry_compoundcurvez Geometry @db.Geometry(CompoundCurveZ) + geometry_compoundcurvezm Geometry @db.Geometry(CompoundCurveZM) + geometry_curvepolygon Geometry @db.Geometry(CurvePolygon) + geometry_curvepolygonm Geometry @db.Geometry(CurvePolygonM) + geometry_curvepolygonz Geometry @db.Geometry(CurvePolygonZ) + geometry_curvepolygonzm Geometry @db.Geometry(CurvePolygonZM) + geometry_multicurve Geometry @db.Geometry(MultiCurve) + geometry_multicurvem Geometry @db.Geometry(MultiCurveM) + geometry_multicurvez Geometry @db.Geometry(MultiCurveZ) + geometry_multicurvezm Geometry @db.Geometry(MultiCurveZM) + geometry_multisurface Geometry @db.Geometry(MultiSurface) + geometry_multisurfacem Geometry @db.Geometry(MultiSurfaceM) + geometry_multisurfacez Geometry @db.Geometry(MultiSurfaceZ) + geometry_multisurfacezm Geometry @db.Geometry(MultiSurfaceZM) + geometry_polyhedralsurface Geometry @db.Geometry(PolyhedralSurface) + geometry_polyhedralsurfacem Geometry @db.Geometry(PolyhedralSurfaceM) + geometry_polyhedralsurfacez Geometry @db.Geometry(PolyhedralSurfaceZ) + geometry_polyhedralsurfacezm Geometry @db.Geometry(PolyhedralSurfaceZM) + geometry_triangle Geometry @db.Geometry(Triangle) + geometry_trianglem Geometry @db.Geometry(TriangleM) + geometry_trianglez Geometry @db.Geometry(TriangleZ) + geometry_trianglezm Geometry @db.Geometry(TriangleZM) + geometry_tin Geometry @db.Geometry(Tin) + geometry_tinm Geometry @db.Geometry(TinM) + geometry_tinz Geometry @db.Geometry(TinZ) + geometry_tinzm Geometry @db.Geometry(TinZM) + geography_circularstring Geometry @db.Geography(CircularString, 4326) + geography_circularstringm Geometry @db.Geography(CircularStringM, 4326) + geography_circularstringz Geometry @db.Geography(CircularStringZ, 4326) + geography_circularstringzm Geometry @db.Geography(CircularStringZM, 4326) + geography_compoundcurve Geometry @db.Geography(CompoundCurve, 4326) + geography_compoundcurvem Geometry @db.Geography(CompoundCurveM, 4326) + geography_compoundcurvez Geometry @db.Geography(CompoundCurveZ, 4326) + geography_compoundcurvezm Geometry @db.Geography(CompoundCurveZM, 4326) + geography_curvepolygon Geometry @db.Geography(CurvePolygon, 4326) + geography_curvepolygonm Geometry @db.Geography(CurvePolygonM, 4326) + geography_curvepolygonz Geometry @db.Geography(CurvePolygonZ, 4326) + geography_curvepolygonzm Geometry @db.Geography(CurvePolygonZM, 4326) + geography_multicurve Geometry @db.Geography(MultiCurve, 4326) + geography_multicurvem Geometry @db.Geography(MultiCurveM, 4326) + geography_multicurvez Geometry @db.Geography(MultiCurveZ, 4326) + geography_multicurvezm Geometry @db.Geography(MultiCurveZM, 4326) + geography_multisurface Geometry @db.Geography(MultiSurface, 4326) + geography_multisurfacem Geometry @db.Geography(MultiSurfaceM, 4326) + geography_multisurfacez Geometry @db.Geography(MultiSurfaceZ, 4326) + geography_multisurfacezm Geometry @db.Geography(MultiSurfaceZM, 4326) + geography_polyhedralsurface Geometry @db.Geography(PolyhedralSurface, 4326) + geography_polyhedralsurfacem Geometry @db.Geography(PolyhedralSurfaceM, 4326) + geography_polyhedralsurfacez Geometry @db.Geography(PolyhedralSurfaceZ, 4326) + geography_polyhedralsurfacezm Geometry @db.Geography(PolyhedralSurfaceZM, 4326) + geography_triangle Geometry @db.Geography(Triangle, 4326) + geography_trianglem Geometry @db.Geography(TriangleM, 4326) + geography_trianglez Geometry @db.Geography(TriangleZ, 4326) + geography_trianglezm Geometry @db.Geography(TriangleZM, 4326) + geography_tin Geometry @db.Geography(Tin, 4326) + geography_tinm Geometry @db.Geography(TinM, 4326) + geography_tinz Geometry @db.Geography(TinZ, 4326) + geography_tinzm Geometry @db.Geography(TinZM, 4326) + } + + /// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. + model spatial_ref_sys { + srid Int @id + auth_name String? @db.VarChar(256) + auth_srid Int? + srtext String? @db.VarChar(2048) + proj4text String? @db.VarChar(2048) + } + "#} + .to_string(); + + let result = api.introspect().await?; + + println!("EXPECTATION: \n {types:#}"); + println!("RESULT: \n {result:#}"); + + api.assert_eq_datamodels(&types, &result); + + Ok(()) +} + +#[test_connector(tags(Postgres), exclude(PostGIS, CockroachDb))] async fn native_type_array_columns_feature_on(api: &mut TestApi) -> TestResult { api.barrel() .execute(move |migration| { diff --git a/schema-engine/sql-introspection-tests/tests/native_types/sqlite.rs b/schema-engine/sql-introspection-tests/tests/native_types/sqlite.rs new file mode 100644 index 000000000000..6226ae2957ea --- /dev/null +++ b/schema-engine/sql-introspection-tests/tests/native_types/sqlite.rs @@ -0,0 +1,91 @@ +use indoc::indoc; +use sql_introspection_tests::test_api::*; + +#[test_connector(tags(Spatialite))] +async fn native_spatial_type_columns_feature_on(api: &mut TestApi) -> TestResult { + let setup = indoc! {r#" + SELECT InitSpatialMetaData(); + + CREATE TABLE "User" ( + id INTEGER PRIMARY KEY + ); + + SELECT + AddGeometryColumn('User', 'geometry_xy', 3857, 'GEOMETRY', 'XY', 0), + AddGeometryColumn('User', 'geometry_xyz', 3857, 'GEOMETRY', 'XYZ', 0), + AddGeometryColumn('User', 'geometry_xym', 3857, 'GEOMETRY', 'XYM', 0), + AddGeometryColumn('User', 'geometry_xyzm', 3857, 'GEOMETRY', 'XYZM', 0), + AddGeometryColumn('User', 'point_xy', 3857, 'POINT', 'XY', 0), + AddGeometryColumn('User', 'point_xyz', 3857, 'POINT', 'XYZ', 0), + AddGeometryColumn('User', 'point_xym', 3857, 'POINT', 'XYM', 0), + AddGeometryColumn('User', 'point_xyzm', 3857, 'POINT', 'XYZM', 0), + AddGeometryColumn('User', 'linestring_xy', 3857, 'LINESTRING', 'XY', 0), + AddGeometryColumn('User', 'linestring_xyz', 3857, 'LINESTRING', 'XYZ', 0), + AddGeometryColumn('User', 'linestring_xym', 3857, 'LINESTRING', 'XYM', 0), + AddGeometryColumn('User', 'linestring_xyzm', 3857, 'LINESTRING', 'XYZM', 0), + AddGeometryColumn('User', 'polygon_xy', 3857, 'POLYGON', 'XY', 0), + AddGeometryColumn('User', 'polygon_xyz', 3857, 'POLYGON', 'XYZ', 0), + AddGeometryColumn('User', 'polygon_xym', 3857, 'POLYGON', 'XYM', 0), + AddGeometryColumn('User', 'polygon_xyzm', 3857, 'POLYGON', 'XYZM', 0), + AddGeometryColumn('User', 'multipoint_xy', 3857, 'MULTIPOINT', 'XY', 0), + AddGeometryColumn('User', 'multipoint_xyz', 3857, 'MULTIPOINT', 'XYZ', 0), + AddGeometryColumn('User', 'multipoint_xym', 3857, 'MULTIPOINT', 'XYM', 0), + AddGeometryColumn('User', 'multipoint_xyzm', 3857, 'MULTIPOINT', 'XYZM', 0), + AddGeometryColumn('User', 'multilinestring_xy', 3857, 'MULTILINESTRING', 'XY', 0), + AddGeometryColumn('User', 'multilinestring_xyz', 3857, 'MULTILINESTRING', 'XYZ', 0), + AddGeometryColumn('User', 'multilinestring_xym', 3857, 'MULTILINESTRING', 'XYM', 0), + AddGeometryColumn('User', 'multilinestring_xyzm', 3857, 'MULTILINESTRING', 'XYZM', 0), + AddGeometryColumn('User', 'multipolygon_xy', 3857, 'MULTIPOLYGON', 'XY', 0), + AddGeometryColumn('User', 'multipolygon_xyz', 3857, 'MULTIPOLYGON', 'XYZ', 0), + AddGeometryColumn('User', 'multipolygon_xym', 3857, 'MULTIPOLYGON', 'XYM', 0), + AddGeometryColumn('User', 'multipolygon_xyzm', 3857, 'MULTIPOLYGON', 'XYZM', 0), + AddGeometryColumn('User', 'geometrycollection_xy', 3857, 'GEOMETRYCOLLECTION', 'XY', 0), + AddGeometryColumn('User', 'geometrycollection_xyz', 3857, 'GEOMETRYCOLLECTION', 'XYZ', 0), + AddGeometryColumn('User', 'geometrycollection_xym', 3857, 'GEOMETRYCOLLECTION', 'XYM', 0), + AddGeometryColumn('User', 'geometrycollection_xyzm', 3857, 'GEOMETRYCOLLECTION', 'XYZM', 0); + "#}; + + api.raw_cmd(setup).await; + + let expectation = expect![[r#" + model User { + id Int @id @default(autoincrement()) + geometry_xy Geometry? @db.Geometry(Geometry, 3857) + geometry_xyz Geometry? @db.Geometry(GeometryZ, 3857) + geometry_xym Geometry? @db.Geometry(GeometryM, 3857) + geometry_xyzm Geometry? @db.Geometry(GeometryZM, 3857) + point_xy Geometry? @db.Geometry(Point, 3857) + point_xyz Geometry? @db.Geometry(PointZ, 3857) + point_xym Geometry? @db.Geometry(PointM, 3857) + point_xyzm Geometry? @db.Geometry(PointZM, 3857) + linestring_xy Geometry? @db.Geometry(LineString, 3857) + linestring_xyz Geometry? @db.Geometry(LineStringZ, 3857) + linestring_xym Geometry? @db.Geometry(LineStringM, 3857) + linestring_xyzm Geometry? @db.Geometry(LineStringZM, 3857) + polygon_xy Geometry? @db.Geometry(Polygon, 3857) + polygon_xyz Geometry? @db.Geometry(PolygonZ, 3857) + polygon_xym Geometry? @db.Geometry(PolygonM, 3857) + polygon_xyzm Geometry? @db.Geometry(PolygonZM, 3857) + multipoint_xy Geometry? @db.Geometry(MultiPoint, 3857) + multipoint_xyz Geometry? @db.Geometry(MultiPointZ, 3857) + multipoint_xym Geometry? @db.Geometry(MultiPointM, 3857) + multipoint_xyzm Geometry? @db.Geometry(MultiPointZM, 3857) + multilinestring_xy Geometry? @db.Geometry(MultiLineString, 3857) + multilinestring_xyz Geometry? @db.Geometry(MultiLineStringZ, 3857) + multilinestring_xym Geometry? @db.Geometry(MultiLineStringM, 3857) + multilinestring_xyzm Geometry? @db.Geometry(MultiLineStringZM, 3857) + multipolygon_xy Geometry? @db.Geometry(MultiPolygon, 3857) + multipolygon_xyz Geometry? @db.Geometry(MultiPolygonZ, 3857) + multipolygon_xym Geometry? @db.Geometry(MultiPolygonM, 3857) + multipolygon_xyzm Geometry? @db.Geometry(MultiPolygonZM, 3857) + geometrycollection_xy Geometry? @db.Geometry(GeometryCollection, 3857) + geometrycollection_xyz Geometry? @db.Geometry(GeometryCollectionZ, 3857) + geometrycollection_xym Geometry? @db.Geometry(GeometryCollectionM, 3857) + geometrycollection_xyzm Geometry? @db.Geometry(GeometryCollectionZM, 3857) + } + "#]]; + + expectation.assert_eq(&api.introspect_dml().await?); + + Ok(()) +} diff --git a/schema-engine/sql-introspection-tests/tests/simple/mssql/geometry_should_be_unsupported.sql b/schema-engine/sql-introspection-tests/tests/simple/mssql/geometry_should_be_unsupported.sql deleted file mode 100644 index e51cdb461df1..000000000000 --- a/schema-engine/sql-introspection-tests/tests/simple/mssql/geometry_should_be_unsupported.sql +++ /dev/null @@ -1,24 +0,0 @@ --- tags=mssql - -CREATE TABLE [dbo].[A] ( - id INT IDENTITY, - location GEOGRAPHY, - CONSTRAINT [A_pkey] PRIMARY KEY (id) -); - - -/* -generator js { - provider = "prisma-client-js" -} - -datasource db { - provider = "sqlserver" - url = env("DATABASE_URL") -} - -model A { - id Int @id @default(autoincrement()) - location Unsupported("geography")? -} -*/ diff --git a/schema-engine/sql-migration-tests/tests/migrations/indexes/cockroachdb/gin.rs b/schema-engine/sql-migration-tests/tests/migrations/indexes/cockroachdb/gin.rs index 96a937399dc6..04e6648d8299 100644 --- a/schema-engine/sql-migration-tests/tests/migrations/indexes/cockroachdb/gin.rs +++ b/schema-engine/sql-migration-tests/tests/migrations/indexes/cockroachdb/gin.rs @@ -70,8 +70,8 @@ fn gin_jsonb_ops(api: TestApi) { fn gin_raw_ops(api: TestApi) { let dm = r#" model A { - id Int @id - data Unsupported("geometry")? + id Int @id + data Geometry? @@index([data], type: Gin) } diff --git a/schema-engine/sql-migration-tests/tests/native_types/mssql.rs b/schema-engine/sql-migration-tests/tests/native_types/mssql.rs index 83d988acb176..89e7fd841633 100644 --- a/schema-engine/sql-migration-tests/tests/native_types/mssql.rs +++ b/schema-engine/sql-migration-tests/tests/native_types/mssql.rs @@ -1875,6 +1875,8 @@ static TYPE_MAPS: Lazy> = Lazy::new(|| { maps.insert("Image", "Bytes"); maps.insert("Xml", "String"); maps.insert("UniqueIdentifier", "String"); + maps.insert("Geometry", "Geometry"); + maps.insert("Geography", "Geometry"); maps }); diff --git a/schema-engine/sql-migration-tests/tests/native_types/mysql.rs b/schema-engine/sql-migration-tests/tests/native_types/mysql.rs index b74f3dd6bac4..0a7c6a67c116 100644 --- a/schema-engine/sql-migration-tests/tests/native_types/mysql.rs +++ b/schema-engine/sql-migration-tests/tests/native_types/mysql.rs @@ -579,51 +579,52 @@ const IMPOSSIBLE_CASTS: Cases = &[ ]; fn native_type_name_to_prisma_scalar_type_name(scalar_type: &str) -> &'static str { - /// Map from native type name to prisma scalar type name. - const TYPES_MAP: &[(&str, &str)] = &[ - ("BigInt", "BigInt"), - ("Binary", "Bytes"), - ("Bit", "Bytes"), - ("Blob", "Bytes"), - ("Char", "String"), - ("Date", "DateTime"), - ("DateTime", "DateTime"), - ("Decimal", "Decimal"), - ("Double", "Float"), - ("Float", "Float"), - ("Int", "Int"), - ("Json", "Json"), - ("LongBlob", "Bytes"), - ("LongText", "String"), - ("MediumBlob", "Bytes"), - ("MediumInt", "Int"), - ("MediumText", "String"), - ("SmallInt", "Int"), - ("Text", "String"), - ("Time", "DateTime"), - ("Timestamp", "DateTime"), - ("TinyBlob", "Bytes"), - ("TinyInt", "Int"), - ("TinyText", "String"), - ("UnsignedBigInt", "BigInt"), - ("UnsignedInt", "Int"), - ("UnsignedMediumInt", "Int"), - ("UnsignedSmallInt", "Int"), - ("UnsignedTinyInt", "Int"), - ("VarBinary", "Bytes"), - ("VarChar", "String"), - ("Year", "Int"), - ]; - let scalar_type = scalar_type.trim_end_matches(|ch: char| [' ', ',', '(', ')'].contains(&ch) || ch.is_ascii_digit()); - let idx = TYPES_MAP - .binary_search_by_key(&scalar_type, |(native, _prisma)| native) - .map_err(|_err| format!("Could not find {scalar_type} in TYPES_MAP")) - .unwrap(); - - TYPES_MAP[idx].1 + match scalar_type { + "BigInt" => "BigInt", + "Binary" => "Bytes", + "Bit" => "Bytes", + "Blob" => "Bytes", + "Char" => "String", + "Date" => "DateTime", + "DateTime" => "DateTime", + "Decimal" => "Decimal", + "Double" => "Float", + "Float" => "Float", + "Int" => "Int", + "Json" => "Json", + "LongBlob" => "Bytes", + "LongText" => "String", + "MediumBlob" => "Bytes", + "MediumInt" => "Int", + "MediumText" => "String", + "SmallInt" => "Int", + "Text" => "String", + "Time" => "DateTime", + "Timestamp" => "DateTime", + "TinyBlob" => "Bytes", + "TinyInt" => "Int", + "TinyText" => "String", + "UnsignedBigInt" => "BigInt", + "UnsignedInt" => "Int", + "UnsignedMediumInt" => "Int", + "UnsignedSmallInt" => "Int", + "UnsignedTinyInt" => "Int", + "VarBinary" => "Bytes", + "VarChar" => "String", + "Year" => "Int", + "Geometry" => "Geometry", + "Point" => "Geometry", + "LineString" => "Geometry", + "Polygon" => "Geometry", + "MultiPoint" => "Geometry", + "MultiLineString" => "Geometry", + "MultiPolygon" => "Geometry", + "GeometryCollection" => "Geometry", + _ => panic!("Could not find {} in TYPES_MAP", scalar_type), + } } fn colnames_for_cases(cases: Cases) -> Vec { diff --git a/schema-engine/sql-schema-describer/src/lib.rs b/schema-engine/sql-schema-describer/src/lib.rs index c5ae677f0e3b..41e36572369c 100644 --- a/schema-engine/sql-schema-describer/src/lib.rs +++ b/schema-engine/sql-schema-describer/src/lib.rs @@ -652,6 +652,8 @@ pub enum ColumnTypeFamily { Binary, /// JSON types. Json, + /// Geometry types. + Geometry, /// UUID types. Uuid, ///Enum @@ -696,6 +698,10 @@ impl ColumnTypeFamily { matches!(self, ColumnTypeFamily::String) } + pub fn is_geometry(&self) -> bool { + matches!(self, ColumnTypeFamily::Geometry) + } + pub fn is_unsupported(&self) -> bool { matches!(self, ColumnTypeFamily::Unsupported(_)) } diff --git a/schema-engine/sql-schema-describer/src/mssql.rs b/schema-engine/sql-schema-describer/src/mssql.rs index 6de97510943d..2727618c3919 100644 --- a/schema-engine/sql-schema-describer/src/mssql.rs +++ b/schema-engine/sql-schema-describer/src/mssql.rs @@ -377,6 +377,7 @@ impl<'a> SqlSchemaDescriber<'a> { }, ColumnTypeFamily::Binary => DefaultValue::db_generated(default_string), ColumnTypeFamily::Json => DefaultValue::db_generated(default_string), + ColumnTypeFamily::Geometry => DefaultValue::db_generated(default_string), ColumnTypeFamily::Uuid => DefaultValue::db_generated(default_string), ColumnTypeFamily::Unsupported(_) => DefaultValue::db_generated(default_string), ColumnTypeFamily::Enum(_) => unreachable!("No enums in MSSQL"), @@ -865,6 +866,8 @@ impl<'a> SqlSchemaDescriber<'a> { "varbinary" => (Binary, Some(MsSqlType::VarBinary(type_parameter))), "image" => (Binary, Some(MsSqlType::Image)), "xml" => (String, Some(MsSqlType::Xml)), + "geometry" => (Geometry, Some(MsSqlType::Geometry)), + "geography" => (Geometry, Some(MsSqlType::Geography)), "uniqueidentifier" => (Uuid, Some(MsSqlType::UniqueIdentifier)), _ => unsupported_type(), }; diff --git a/schema-engine/sql-schema-describer/src/mysql.rs b/schema-engine/sql-schema-describer/src/mysql.rs index 0d5fd98140dd..3eb54d0e42ad 100644 --- a/schema-engine/sql-schema-describer/src/mysql.rs +++ b/schema-engine/sql-schema-describer/src/mysql.rs @@ -89,7 +89,8 @@ impl super::SqlSchemaDescriberBackend for SqlSchemaDescriber<'_> { self.get_constraints(&table_names, &mut sql_schema).await?; - Self::get_all_columns(&table_names, self.conn, schema, &mut sql_schema, &flavour).await?; + self.get_all_columns(&table_names, schema, &mut sql_schema, &flavour) + .await?; push_foreign_keys(schema, &table_names, &mut sql_schema, self.conn).await?; push_indexes(&table_names, schema, &mut sql_schema, self.conn).await?; @@ -358,8 +359,8 @@ impl<'a> SqlSchemaDescriber<'a> { } async fn get_all_columns( + &self, table_ids: &IndexMap, - conn: &dyn Queryable, schema_name: &str, sql_schema: &mut SqlSchema, flavour: &Flavour, @@ -367,7 +368,30 @@ impl<'a> SqlSchemaDescriber<'a> { // We alias all the columns because MySQL column names are case-insensitive in queries, but the // information schema column names became upper-case in MySQL 8, causing the code fetching // the result values by column name below to fail. - let sql = " + let sql_geometry_srid_column = if self.supports_srid_constraints() { + "geom.srs_id" + } else { + "NULL" + }; + + let sql_geometry_information_table = if matches!(flavour, Flavour::MariaDb) { + " + LEFT JOIN information_schema.geometry_columns geom + ON table_schema = geom.g_table_schema + AND table_name = geom.g_table_name + AND column_name = geom.g_geometry_column + " + } else if self.supports_srid_constraints() { + " + LEFT JOIN information_schema.st_geometry_columns geom + USING (table_schema, table_name, column_name) + " + } else { + "" + }; + + let sql = format!( + " SELECT column_name column_name, data_type data_type, @@ -375,20 +399,23 @@ impl<'a> SqlSchemaDescriber<'a> { character_maximum_length character_maximum_length, numeric_precision numeric_precision, numeric_scale numeric_scale, + {sql_geometry_srid_column} geometry_srid, datetime_precision datetime_precision, column_default column_default, is_nullable is_nullable, extra extra, table_name table_name, - IF(column_comment = '', NULL, column_comment) AS column_comment + NULLIF(column_comment, '') AS column_comment FROM information_schema.columns + {sql_geometry_information_table} WHERE table_schema = ? ORDER BY ordinal_position - "; + " + ); let mut table_defaults = Vec::new(); let mut view_defaults = Vec::new(); - let rows = conn.query_raw(sql, &[schema_name.into()]).await?; + let rows = self.conn.query_raw(&sql, &[schema_name.into()]).await?; for col in rows { trace!("Got column: {col:?}"); @@ -424,6 +451,7 @@ impl<'a> SqlSchemaDescriber<'a> { let time_precision = col.get_u32("datetime_precision"); let numeric_precision = col.get_u32("numeric_precision"); let numeric_scale = col.get_u32("numeric_scale"); + let geometry_srid = col.get_u32("geometry_srid"); let precision = Precision { character_maximum_length, @@ -436,9 +464,9 @@ impl<'a> SqlSchemaDescriber<'a> { let tpe = Self::get_column_type( (&table_name, &name), - &data_type, - &full_data_type, + (&data_type, &full_data_type), precision, + geometry_srid, arity, default_value, sql_schema, @@ -513,6 +541,10 @@ impl<'a> SqlSchemaDescriber<'a> { true => Self::dbgenerated_expression(&default_string), false => DefaultValue::db_generated(default_string), }, + ColumnTypeFamily::Geometry => match default_expression { + true => Self::dbgenerated_expression(&default_string), + false => DefaultValue::db_generated(default_string), + }, ColumnTypeFamily::Uuid => match default_expression { true => Self::dbgenerated_expression(&default_string), false => DefaultValue::db_generated(default_string), @@ -603,9 +635,9 @@ impl<'a> SqlSchemaDescriber<'a> { fn get_column_type( (table, column_name): (&str, &str), - data_type: &str, - full_data_type: &str, + (data_type, full_data_type): (&str, &str), precision: Precision, + geometry_srid: Option, arity: ColumnArity, default: Option<&Value<'_>>, sql_schema: &mut SqlSchema, @@ -711,14 +743,20 @@ impl<'a> SqlSchemaDescriber<'a> { "mediumblob" => (ColumnTypeFamily::Binary, Some(MySqlType::MediumBlob)), "longblob" => (ColumnTypeFamily::Binary, Some(MySqlType::LongBlob)), //spatial - "geometry" => (ColumnTypeFamily::Unsupported(full_data_type.into()), None), - "point" => (ColumnTypeFamily::Unsupported(full_data_type.into()), None), - "linestring" => (ColumnTypeFamily::Unsupported(full_data_type.into()), None), - "polygon" => (ColumnTypeFamily::Unsupported(full_data_type.into()), None), - "multipoint" => (ColumnTypeFamily::Unsupported(full_data_type.into()), None), - "multilinestring" => (ColumnTypeFamily::Unsupported(full_data_type.into()), None), - "multipolygon" => (ColumnTypeFamily::Unsupported(full_data_type.into()), None), - "geometrycollection" => (ColumnTypeFamily::Unsupported(full_data_type.into()), None), + "geometry" => (ColumnTypeFamily::Geometry, Some(MySqlType::Geometry(geometry_srid))), + "point" => (ColumnTypeFamily::Geometry, Some(MySqlType::Point(geometry_srid))), + "linestring" => (ColumnTypeFamily::Geometry, Some(MySqlType::LineString(geometry_srid))), + "polygon" => (ColumnTypeFamily::Geometry, Some(MySqlType::Polygon(geometry_srid))), + "multipoint" => (ColumnTypeFamily::Geometry, Some(MySqlType::MultiPoint(geometry_srid))), + "multilinestring" => ( + ColumnTypeFamily::Geometry, + Some(MySqlType::MultiLineString(geometry_srid)), + ), + "multipolygon" => (ColumnTypeFamily::Geometry, Some(MySqlType::MultiPolygon(geometry_srid))), + "geometrycollection" | "geomcollection" => ( + ColumnTypeFamily::Geometry, + Some(MySqlType::GeometryCollection(geometry_srid)), + ), _ => (ColumnTypeFamily::Unsupported(full_data_type.into()), None), }; @@ -815,6 +853,14 @@ impl<'a> SqlSchemaDescriber<'a> { .circumstances .intersects(Circumstances::MySql56 | Circumstances::MySql57 | Circumstances::MariaDb) } + + /// Tests whether the current database supports geometry SRID constraints + fn supports_srid_constraints(&self) -> bool { + // Only MySQL 8 and above supports geometry SRIDs constraints + !self + .circumstances + .intersects(Circumstances::MySql56 | Circumstances::MySql57 | Circumstances::MariaDb) + } } async fn push_foreign_keys( diff --git a/schema-engine/sql-schema-describer/src/postgres.rs b/schema-engine/sql-schema-describer/src/postgres.rs index 211cf8da489a..8283acb79b22 100644 --- a/schema-engine/sql-schema-describer/src/postgres.rs +++ b/schema-engine/sql-schema-describer/src/postgres.rs @@ -13,7 +13,7 @@ use enumflags2::BitFlags; use indexmap::IndexMap; use indoc::indoc; use psl::{ - builtin_connectors::{CockroachType, PostgresType}, + builtin_connectors::{CockroachType, GeometryParams, GeometryType, PostgresType}, datamodel_connector::NativeTypeInstance, }; use quaint::{connector::ResultRow, prelude::Queryable, Value}; @@ -23,6 +23,7 @@ use std::{ collections::{BTreeMap, HashMap}, convert::TryInto, iter::Peekable, + str::FromStr, }; use tracing::trace; @@ -109,6 +110,7 @@ pub enum Circumstances { Cockroach, CockroachWithPostgresNativeTypes, // TODO: this is a temporary workaround CanPartitionTables, + HasPostGIS, } pub struct SqlSchemaDescriber<'a> { @@ -904,9 +906,9 @@ impl<'a> SqlSchemaDescriber<'a> { .circumstances .contains(Circumstances::CockroachWithPostgresNativeTypes) { - get_column_type_cockroachdb(&col, sql_schema) + get_column_type_cockroachdb(&col, sql_schema, &self.circumstances) } else { - get_column_type_postgresql(&col, sql_schema) + get_column_type_postgresql(&col, sql_schema, &self.circumstances) }; let default = col @@ -974,9 +976,29 @@ impl<'a> SqlSchemaDescriber<'a> { Ok(()) } + fn get_geometry_info(col: &str) -> Option { + static GEOM_REGEX: Lazy = + Lazy::new(|| Regex::new(r"^(?Pgeometry|geography)(\((?P.+?)(,(?P\d+))?\))?$").unwrap()); + GEOM_REGEX.captures(col).and_then(|capture| { + let is_geography = capture.name("class").map(|c| c.as_str() == "geography").unwrap(); + let geom_type = capture + .name("type") + .map(|t| GeometryType::from_str(t.as_str())) + .unwrap_or(Ok(GeometryType::default())); + let srid = capture + .name("srid") + .map(|v| v.as_str().parse::()) + .unwrap_or(Ok(if is_geography { 4326 } else { 0 })); + match (geom_type, srid) { + (Ok(ty), Ok(srid)) => Some(GeometryParams { ty, srid }), + _ => None, + } + }) + } + fn get_precision(col: &ResultRow) -> Precision { - let (character_maximum_length, numeric_precision, numeric_scale, time_precision) = - if matches!(col.get_expect_string("data_type").as_str(), "ARRAY") { + match col.get_expect_string("data_type").as_str() { + "ARRAY" => { fn get_single(formatted_type: &str) -> Option { static SINGLE_REGEX: Lazy = Lazy::new(|| Regex::new(r".*\(([0-9]*)\).*\[\]$").unwrap()); @@ -1003,34 +1025,32 @@ impl<'a> SqlSchemaDescriber<'a> { let formatted_type = col.get_expect_string("formatted_type"); let fdt = col.get_expect_string("full_data_type"); - let char_max_length = match fdt.as_str() { + let character_maximum_length = match fdt.as_str() { "_bpchar" | "_varchar" | "_bit" | "_varbit" => get_single(&formatted_type), _ => None, }; - let (num_precision, num_scale) = match fdt.as_str() { + let (numeric_precision, numeric_scale) = match fdt.as_str() { "_numeric" => get_dual(&formatted_type), _ => (None, None), }; - let time = match fdt.as_str() { + let time_precision = match fdt.as_str() { "_timestamptz" | "_timestamp" | "_timetz" | "_time" | "_interval" => get_single(&formatted_type), _ => None, }; - (char_max_length, num_precision, num_scale, time) - } else { - ( - col.get_u32("character_maximum_length"), - col.get_u32("numeric_precision"), - col.get_u32("numeric_scale"), - col.get_u32("datetime_precision"), - ) - }; - - Precision { - character_maximum_length, - numeric_precision, - numeric_scale, - time_precision, + Precision { + character_maximum_length, + numeric_precision, + numeric_scale, + time_precision, + } + } + _ => Precision { + character_maximum_length: col.get_u32("character_maximum_length"), + numeric_precision: col.get_u32("numeric_precision"), + numeric_scale: col.get_u32("numeric_scale"), + time_precision: col.get_u32("datetime_precision"), + }, } } @@ -1535,10 +1555,15 @@ fn index_from_row( } } -fn get_column_type_postgresql(row: &ResultRow, schema: &SqlSchema) -> ColumnType { +fn get_column_type_postgresql( + row: &ResultRow, + schema: &SqlSchema, + circumstances: &BitFlags, +) -> ColumnType { use ColumnTypeFamily::*; let data_type = row.get_expect_string("data_type"); let full_data_type = row.get_expect_string("full_data_type"); + let formatted_type = row.get_expect_string("formatted_type"); let is_required = match row.get_expect_string("is_nullable").to_lowercase().as_ref() { "no" => true, "yes" => false, @@ -1551,6 +1576,10 @@ fn get_column_type_postgresql(row: &ResultRow, schema: &SqlSchema) -> ColumnType false => ColumnArity::Nullable, }; + let geometry = match circumstances.contains(Circumstances::HasPostGIS) { + true => SqlSchemaDescriber::get_geometry_info(&formatted_type), + false => None, + }; let precision = SqlSchemaDescriber::get_precision(row); let unsupported_type = || (Unsupported(full_data_type.clone()), None); let enum_id: Option<_> = match data_type.as_str() { @@ -1610,6 +1639,13 @@ fn get_column_type_postgresql(row: &ResultRow, schema: &SqlSchema) -> ColumnType "tsvector" | "_tsvector" => unsupported_type(), "txid_snapshot" | "_txid_snapshot" => unsupported_type(), "inet" | "_inet" => (String, Some(PostgresType::Inet)), + //postgis + "geometry" => geometry + .map(|_| (Geometry, Some(PostgresType::Geometry(geometry)))) + .unwrap_or_else(unsupported_type), + "geography" => geometry + .map(|_| (Geometry, Some(PostgresType::Geography(geometry)))) + .unwrap_or_else(unsupported_type), //geometric "box" | "_box" => unsupported_type(), "circle" | "_circle" => unsupported_type(), @@ -1629,7 +1665,11 @@ fn get_column_type_postgresql(row: &ResultRow, schema: &SqlSchema) -> ColumnType } // Separate from get_column_type_postgresql because of native types. -fn get_column_type_cockroachdb(row: &ResultRow, schema: &SqlSchema) -> ColumnType { +fn get_column_type_cockroachdb( + row: &ResultRow, + schema: &SqlSchema, + circumstances: &BitFlags, +) -> ColumnType { use ColumnTypeFamily::*; let data_type = row.get_expect_string("data_type"); let full_data_type = row.get_expect_string("full_data_type"); @@ -1645,6 +1685,10 @@ fn get_column_type_cockroachdb(row: &ResultRow, schema: &SqlSchema) -> ColumnTyp false => ColumnArity::Nullable, }; + let geometry_type = match circumstances.contains(Circumstances::HasPostGIS) { + true => SqlSchemaDescriber::get_geometry_info(&data_type), + false => None, + }; let precision = SqlSchemaDescriber::get_precision(row); let unsupported_type = || (Unsupported(full_data_type.clone()), None); let enum_id: Option<_> = match data_type.as_str() { @@ -1699,6 +1743,13 @@ fn get_column_type_cockroachdb(row: &ResultRow, schema: &SqlSchema) -> ColumnTyp "tsvector" | "_tsvector" => unsupported_type(), "txid_snapshot" | "_txid_snapshot" => unsupported_type(), "inet" | "_inet" => (String, Some(CockroachType::Inet)), + //postgis + "geometry" => geometry_type + .map(|_| (Geometry, Some(CockroachType::Geometry(geometry_type)))) + .unwrap_or_else(unsupported_type), + "geography" => geometry_type + .map(|_| (Geometry, Some(CockroachType::Geography(geometry_type)))) + .unwrap_or_else(unsupported_type), //geometric "box" | "_box" => unsupported_type(), "circle" | "_circle" => unsupported_type(), diff --git a/schema-engine/sql-schema-describer/src/postgres/default.rs b/schema-engine/sql-schema-describer/src/postgres/default.rs index 593368d27e66..897ee2df8f6d 100644 --- a/schema-engine/sql-schema-describer/src/postgres/default.rs +++ b/schema-engine/sql-schema-describer/src/postgres/default.rs @@ -94,7 +94,8 @@ pub(super) fn get_default_value(default_string: &str, tpe: &ColumnType) -> Optio fn parser_for_family(family: &ColumnTypeFamily) -> &'static dyn Fn(&mut Parser<'_>) -> Option { match family { - ColumnTypeFamily::String | ColumnTypeFamily::Json => &parse_string_default, + // TODO@geometry: Is this safe ? + ColumnTypeFamily::String | ColumnTypeFamily::Json | ColumnTypeFamily::Geometry => &parse_string_default, ColumnTypeFamily::Int | ColumnTypeFamily::BigInt => &parse_int_default, ColumnTypeFamily::Enum(_) => &parse_enum_default, ColumnTypeFamily::Float | ColumnTypeFamily::Decimal => &parse_float_default, diff --git a/schema-engine/sql-schema-describer/src/postgres/default/c_style_scalar_lists.rs b/schema-engine/sql-schema-describer/src/postgres/default/c_style_scalar_lists.rs index d9e4b9a55e63..3d0561557eb1 100644 --- a/schema-engine/sql-schema-describer/src/postgres/default/c_style_scalar_lists.rs +++ b/schema-engine/sql-schema-describer/src/postgres/default/c_style_scalar_lists.rs @@ -86,6 +86,7 @@ fn parse_literal(s: &str, tpe: &ColumnType) -> Option { ColumnTypeFamily::DateTime | ColumnTypeFamily::Binary | ColumnTypeFamily::Uuid + | ColumnTypeFamily::Geometry | ColumnTypeFamily::Unsupported(_) => None, } } diff --git a/schema-engine/sql-schema-describer/src/sqlite.rs b/schema-engine/sql-schema-describer/src/sqlite.rs index 1f28958605a2..0d23b748c22e 100644 --- a/schema-engine/sql-schema-describer/src/sqlite.rs +++ b/schema-engine/sql-schema-describer/src/sqlite.rs @@ -7,12 +7,24 @@ use crate::{ }; use either::Either; use indexmap::IndexMap; +use psl::{ + builtin_connectors::{GeometryParams, GeometryType, SQLiteType}, + datamodel_connector::NativeTypeInstance, +}; use quaint::{ ast::{Value, ValueType}, connector::{GetRow, ToColumnNames}, prelude::ResultRow, }; -use std::{any::type_name, borrow::Cow, collections::BTreeMap, convert::TryInto, fmt::Debug, path::Path}; +use regex::RegexSet; +use std::{ + any::type_name, + borrow::Cow, + collections::{BTreeMap, HashMap}, + convert::TryInto, + fmt::Debug, + path::Path, +}; use tracing::trace; #[async_trait::async_trait] @@ -107,8 +119,9 @@ impl<'a> SqlSchemaDescriber<'a> { .filter_map(|(name, id)| id.left().map(|id| (name.as_str(), id))) .collect(); + let geometry_columns = get_geometry_columns(self.conn).await; for (container_name, container_id) in &container_ids { - push_columns(container_name, *container_id, &mut schema, self.conn).await?; + push_columns(container_name, *container_id, &geometry_columns, &mut schema, self.conn).await?; if let Either::Left(table_id) = container_id { push_indexes(container_name, *table_id, &mut schema, self.conn).await?; @@ -321,9 +334,47 @@ impl<'a> SqlSchemaDescriber<'a> { } } +async fn get_geometry_columns(conn: &(dyn Connection + Send + Sync)) -> HashMap<(String, String), (GeometryType, i32)> { + let sql = r#" + SELECT + f_table_name, + f_geometry_column, + geometry_type, + srid + FROM + geometry_columns + "#; + let result_set = conn.query_raw(sql, &[]).await; + if result_set.is_err() { + return HashMap::new(); + } + result_set + .unwrap() + .into_iter() + .map(|row| { + ( + ( + row.get_expect_string("f_table_name"), + row.get_expect_string("f_geometry_column"), + ), + ( + // Unwrapping is safe since Spatialite validates the geometry type + u32::try_from(row.get_expect_i64("geometry_type")) + .unwrap() + .try_into() + .unwrap(), + // Unwrapping is safe since Spatialite validates the SRID against the EPSG database + row.get_expect_i64("srid").try_into().unwrap(), + ), + ) + }) + .collect() +} + async fn push_columns( table_name: &str, container_id: Either, + geometry_columns: &HashMap<(String, String), (GeometryType, i32)>, schema: &mut SqlSchema, conn: &(dyn Connection + Send + Sync), ) -> DescriberResult<()> { @@ -340,7 +391,22 @@ async fn push_columns( ColumnArity::Nullable }; - let tpe = get_column_type(row.get_expect_string("type"), arity); + let column_name = row.get_expect_string("name"); + let column_type = row.get_expect_string("type"); + let geometry_info = geometry_columns.get(&(table_name.to_lowercase(), column_name.to_lowercase())); + let tpe = if let Some((ty, srid)) = geometry_info { + ColumnType { + full_data_type: column_type, + family: ColumnTypeFamily::Geometry, + arity, + native_type: Some(NativeTypeInstance::new(SQLiteType::Geometry(Some(GeometryParams { + ty: *ty, + srid: *srid, + })))), + } + } else { + get_column_type(column_type, arity) + }; let default = match row.get("dflt_value") { None => None, @@ -388,6 +454,7 @@ async fn push_columns( } _ => DefaultValue::db_generated(default_string), }, + ColumnTypeFamily::Geometry => DefaultValue::db_generated(default_string), ColumnTypeFamily::Binary => DefaultValue::db_generated(default_string), ColumnTypeFamily::Json => DefaultValue::db_generated(default_string), ColumnTypeFamily::Uuid => DefaultValue::db_generated(default_string), @@ -608,6 +675,7 @@ fn is_system_table(table_name: &str) -> bool { SQLITE_SYSTEM_TABLES .iter() .any(|system_table| table_name == *system_table) + || SPATIALITE_SYSTEM_TABLES.is_match(table_name) } /// See https://www.sqlite.org/fileformat2.html @@ -618,3 +686,76 @@ const SQLITE_SYSTEM_TABLES: &[&str] = &[ "sqlite_stat3", "sqlite_stat4", ]; + +/// These can be tables or views, depending on the Spatialite version. In both cases, they should be ignored. +static SPATIALITE_SYSTEM_TABLES: Lazy = Lazy::new(|| { + RegexSet::new([ + "(?i)^data_licenses$", + "(?i)^elementarygeometries$", + "(?i)^geometry_columns$", + "(?i)^geometry_columns_auth$", + "(?i)^geometry_columns_field_infos$", + "(?i)^geometry_columns_statistics$", + "(?i)^geometry_columns_time$", + "(?i)^geom_cols_ref_sys$", + "(?i)^idx_iso_metadata_geometry$", + "(?i)^idx_iso_metadata_geometry_node$", + "(?i)^idx_iso_metadata_geometry_parent$", + "(?i)^idx_iso_metadata_geometry_rowid$", + "(?i)^iso_metadata$", + "(?i)^iso_metadata_reference$", + "(?i)^iso_metadata_view$", + "(?i)^knn2$", + "(?i)^networks$", + "(?i)^raster_coverages$", + "(?i)^raster_coverages_keyword$", + "(?i)^raster_coverages_ref_sys$", + "(?i)^raster_coverages_srid$", + "(?i)^rl2map_configurations$", + "(?i)^rl2map_configurations_view$", + "(?i)^se_external_graphics$", + "(?i)^se_external_graphics_view$", + "(?i)^se_fonts$", + "(?i)^se_fonts_view$", + "(?i)^se_raster_styled_layers$", + "(?i)^se_raster_styled_layers_view$", + "(?i)^se_raster_styles$", + "(?i)^se_raster_styles_view$", + "(?i)^se_vector_styled_layers$", + "(?i)^se_vector_styled_layers_view$", + "(?i)^se_vector_styles$", + "(?i)^se_vector_styles_view$", + "(?i)^spatialindex$", + "(?i)^spatialite_history$", + "(?i)^spatial_ref_sys$", + "(?i)^spatial_ref_sys_all$", + "(?i)^spatial_ref_sys_aux$", + "(?i)^sql_statements_log$", + "(?i)^stored_procedures$", + "(?i)^stored_variables$", + "(?i)^topologies$", + "(?i)^vector_coverages$", + "(?i)^vector_coverages_keyword$", + "(?i)^vector_coverages_ref_sys$", + "(?i)^vector_coverages_srid$", + "(?i)^vector_layers$", + "(?i)^vector_layers_auth$", + "(?i)^vector_layers_field_infos$", + "(?i)^vector_layers_statistics$", + "(?i)^views_geometry_columns$", + "(?i)^views_geometry_columns_auth$", + "(?i)^views_geometry_columns_field_infos$", + "(?i)^views_geometry_columns_statistics$", + "(?i)^virts_geometry_collection$", + "(?i)^virts_geometry_collectionm$", + "(?i)^virts_geometry_columns$", + "(?i)^virts_geometry_columns_auth$", + "(?i)^virts_geometry_columns_field_infos$", + "(?i)^virts_geometry_columns_statistics$", + "(?i)^wms_getcapabilities$", + "(?i)^wms_getmap$", + "(?i)^wms_ref_sys$", + "(?i)^wms_settings$", + ]) + .unwrap() +}); diff --git a/schema-engine/sql-schema-describer/tests/describers/mssql_describer_tests.rs b/schema-engine/sql-schema-describer/tests/describers/mssql_describer_tests.rs index e875648e5902..4db9b4cdfa2c 100644 --- a/schema-engine/sql-schema-describer/tests/describers/mssql_describer_tests.rs +++ b/schema-engine/sql-schema-describer/tests/describers/mssql_describer_tests.rs @@ -121,6 +121,8 @@ fn all_mssql_column_types_must_work(api: TestApi) { [varbinary_max_col] varbinary(max), [image_col] image, [xml_col] xml, + [geometry_col] geometry, + [geography_col] geography, CONSTRAINT "thepk" PRIMARY KEY (primary_col) ); "#; @@ -667,6 +669,42 @@ fn all_mssql_column_types_must_work(api: TestApi) { description: None, }, ), + ( + TableId( + 0, + ), + Column { + name: "geometry_col", + tpe: ColumnType { + full_data_type: "geometry", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "geography_col", + tpe: ColumnType { + full_data_type: "geography", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), ], foreign_keys: [], table_default_values: [], diff --git a/schema-engine/sql-schema-describer/tests/describers/mysql_describer_tests.rs b/schema-engine/sql-schema-describer/tests/describers/mysql_describer_tests.rs index f0ce2833988e..4725b490f316 100644 --- a/schema-engine/sql-schema-describer/tests/describers/mysql_describer_tests.rs +++ b/schema-engine/sql-schema-describer/tests/describers/mysql_describer_tests.rs @@ -674,11 +674,11 @@ fn all_mysql_column_types_must_work(api: TestApi) { name: "geometry_col", tpe: ColumnType { full_data_type: "geometry", - family: Unsupported( - "geometry", - ), + family: Geometry, arity: Nullable, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -692,11 +692,11 @@ fn all_mysql_column_types_must_work(api: TestApi) { name: "point_col", tpe: ColumnType { full_data_type: "point", - family: Unsupported( - "point", - ), + family: Geometry, arity: Nullable, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -710,11 +710,11 @@ fn all_mysql_column_types_must_work(api: TestApi) { name: "linestring_col", tpe: ColumnType { full_data_type: "linestring", - family: Unsupported( - "linestring", - ), + family: Geometry, arity: Nullable, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -728,11 +728,11 @@ fn all_mysql_column_types_must_work(api: TestApi) { name: "polygon_col", tpe: ColumnType { full_data_type: "polygon", - family: Unsupported( - "polygon", - ), + family: Geometry, arity: Nullable, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -746,11 +746,11 @@ fn all_mysql_column_types_must_work(api: TestApi) { name: "multipoint_col", tpe: ColumnType { full_data_type: "multipoint", - family: Unsupported( - "multipoint", - ), + family: Geometry, arity: Nullable, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -764,11 +764,11 @@ fn all_mysql_column_types_must_work(api: TestApi) { name: "multilinestring_col", tpe: ColumnType { full_data_type: "multilinestring", - family: Unsupported( - "multilinestring", - ), + family: Geometry, arity: Nullable, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -782,11 +782,11 @@ fn all_mysql_column_types_must_work(api: TestApi) { name: "multipolygon_col", tpe: ColumnType { full_data_type: "multipolygon", - family: Unsupported( - "multipolygon", - ), + family: Geometry, arity: Nullable, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -800,11 +800,11 @@ fn all_mysql_column_types_must_work(api: TestApi) { name: "geometrycollection_col", tpe: ColumnType { full_data_type: "geometrycollection", - family: Unsupported( - "geometrycollection", - ), + family: Geometry, arity: Nullable, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -1510,11 +1510,11 @@ fn all_mariadb_column_types_must_work(api: TestApi) { name: "geometry_col", tpe: ColumnType { full_data_type: "geometry", - family: Unsupported( - "geometry", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -1528,11 +1528,11 @@ fn all_mariadb_column_types_must_work(api: TestApi) { name: "point_col", tpe: ColumnType { full_data_type: "point", - family: Unsupported( - "point", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -1546,11 +1546,11 @@ fn all_mariadb_column_types_must_work(api: TestApi) { name: "linestring_col", tpe: ColumnType { full_data_type: "linestring", - family: Unsupported( - "linestring", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -1564,11 +1564,11 @@ fn all_mariadb_column_types_must_work(api: TestApi) { name: "polygon_col", tpe: ColumnType { full_data_type: "polygon", - family: Unsupported( - "polygon", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -1582,11 +1582,11 @@ fn all_mariadb_column_types_must_work(api: TestApi) { name: "multipoint_col", tpe: ColumnType { full_data_type: "multipoint", - family: Unsupported( - "multipoint", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -1600,11 +1600,11 @@ fn all_mariadb_column_types_must_work(api: TestApi) { name: "multilinestring_col", tpe: ColumnType { full_data_type: "multilinestring", - family: Unsupported( - "multilinestring", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -1618,11 +1618,11 @@ fn all_mariadb_column_types_must_work(api: TestApi) { name: "multipolygon_col", tpe: ColumnType { full_data_type: "multipolygon", - family: Unsupported( - "multipolygon", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -1636,11 +1636,11 @@ fn all_mariadb_column_types_must_work(api: TestApi) { name: "geometrycollection_col", tpe: ColumnType { full_data_type: "geometrycollection", - family: Unsupported( - "geometrycollection", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -1737,14 +1737,14 @@ fn all_mysql_8_column_types_must_work(api: TestApi) { `tinyblob_col` tinyblob NOT NULL, `mediumblob_col` mediumblob NOT NULL, `longblob_col` longblob NOT NULL, - `geometry_col` geometry NOT NULL, - `point_col` point NOT NULL, - `linestring_col` linestring NOT NULL, - `polygon_col` polygon NOT NULL, - `multipoint_col` multipoint NOT NULL, - `multilinestring_col` multilinestring NOT NULL, - `multipolygon_col` multipolygon NOT NULL, - `geometrycollection_col` geometrycollection NOT NULL, + `geometry_col` geometry srid 4326 NOT NULL, + `point_col` point srid 4326 NOT NULL, + `linestring_col` linestring srid 4326 NOT NULL, + `polygon_col` polygon srid 4326 NOT NULL, + `multipoint_col` multipoint srid 4326 NOT NULL, + `multilinestring_col` multilinestring srid 4326 NOT NULL, + `multipolygon_col` multipolygon srid 4326 NOT NULL, + `geometrycollection_col` geometrycollection srid 4326 NOT NULL, `json_col` json NOT NULL ); @@ -2339,11 +2339,11 @@ fn all_mysql_8_column_types_must_work(api: TestApi) { name: "geometry_col", tpe: ColumnType { full_data_type: "geometry", - family: Unsupported( - "geometry", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -2357,11 +2357,11 @@ fn all_mysql_8_column_types_must_work(api: TestApi) { name: "point_col", tpe: ColumnType { full_data_type: "point", - family: Unsupported( - "point", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -2375,11 +2375,11 @@ fn all_mysql_8_column_types_must_work(api: TestApi) { name: "linestring_col", tpe: ColumnType { full_data_type: "linestring", - family: Unsupported( - "linestring", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -2393,11 +2393,11 @@ fn all_mysql_8_column_types_must_work(api: TestApi) { name: "polygon_col", tpe: ColumnType { full_data_type: "polygon", - family: Unsupported( - "polygon", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -2411,11 +2411,11 @@ fn all_mysql_8_column_types_must_work(api: TestApi) { name: "multipoint_col", tpe: ColumnType { full_data_type: "multipoint", - family: Unsupported( - "multipoint", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -2429,11 +2429,11 @@ fn all_mysql_8_column_types_must_work(api: TestApi) { name: "multilinestring_col", tpe: ColumnType { full_data_type: "multilinestring", - family: Unsupported( - "multilinestring", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -2447,11 +2447,11 @@ fn all_mysql_8_column_types_must_work(api: TestApi) { name: "multipolygon_col", tpe: ColumnType { full_data_type: "multipolygon", - family: Unsupported( - "multipolygon", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, @@ -2465,11 +2465,11 @@ fn all_mysql_8_column_types_must_work(api: TestApi) { name: "geometrycollection_col", tpe: ColumnType { full_data_type: "geomcollection", - family: Unsupported( - "geomcollection", - ), + family: Geometry, arity: Required, - native_type: None, + native_type: Some( + NativeTypeInstance(..), + ), }, auto_increment: false, description: None, diff --git a/schema-engine/sql-schema-describer/tests/describers/postgres_describer_tests.rs b/schema-engine/sql-schema-describer/tests/describers/postgres_describer_tests.rs index 0ce7cee65812..bf785650ec38 100644 --- a/schema-engine/sql-schema-describer/tests/describers/postgres_describer_tests.rs +++ b/schema-engine/sql-schema-describer/tests/describers/postgres_describer_tests.rs @@ -60,7 +60,7 @@ fn postgres_many_namespaces(api: TestApi) { .assert_namespace("three"); } -#[test_connector(tags(Postgres), exclude(CockroachDb))] +#[test_connector(tags(Postgres), exclude(PostGIS, CockroachDb))] fn views_can_be_described(api: TestApi) { let full_sql = r#" CREATE TABLE a (a_id int); @@ -78,7 +78,7 @@ fn views_can_be_described(api: TestApi) { assert_eq!(expected_sql, view.definition.unwrap()); } -#[test_connector(tags(Postgres), exclude(CockroachDb))] +#[test_connector(tags(Postgres), exclude(PostGIS, CockroachDb))] fn all_postgres_column_types_must_work(api: TestApi) { let sql = r#" CREATE TABLE "User" ( @@ -1104,6 +1104,201 @@ fn all_postgres_column_types_must_work(api: TestApi) { expected_ext.assert_debug_eq(&ext); } +#[test_connector(tags(PostGIS), exclude(CockroachDb))] +fn all_postgis_column_types_must_work(api: TestApi) { + let sql = r#" + CREATE EXTENSION IF NOT EXISTS postgis; + + CREATE TABLE "Spatial" ( + geometry_geometry GEOMETRY(GEOMETRY, 3857), + geometry_geometry_z GEOMETRY(GEOMETRYZ, 3857), + geometry_geometry_m GEOMETRY(GEOMETRYM, 3857), + geometry_geometry_zm GEOMETRY(GEOMETRYZM, 3857), + geometry_point GEOMETRY(POINT, 3857), + geometry_point_z GEOMETRY(POINTZ, 3857), + geometry_point_m GEOMETRY(POINTM, 3857), + geometry_point_zm GEOMETRY(POINTZM, 3857), + geometry_line GEOMETRY(LINESTRING, 3857), + geometry_line_z GEOMETRY(LINESTRINGZ, 3857), + geometry_line_m GEOMETRY(LINESTRINGM, 3857), + geometry_line_zm GEOMETRY(LINESTRINGZM, 3857), + geometry_polygon GEOMETRY(POLYGON, 3857), + geometry_polygon_z GEOMETRY(POLYGONZ, 3857), + geometry_polygon_m GEOMETRY(POLYGONM, 3857), + geometry_polygon_zm GEOMETRY(POLYGONZM, 3857), + geometry_multipoint GEOMETRY(MULTIPOINT, 3857), + geometry_multipoint_z GEOMETRY(MULTIPOINTZ, 3857), + geometry_multipoint_m GEOMETRY(MULTIPOINTM, 3857), + geometry_multipoint_zm GEOMETRY(MULTIPOINTZM, 3857), + geometry_multiline GEOMETRY(MULTILINESTRING, 3857), + geometry_multiline_z GEOMETRY(MULTILINESTRINGZ, 3857), + geometry_multiline_m GEOMETRY(MULTILINESTRINGM, 3857), + geometry_multiline_zm GEOMETRY(MULTILINESTRINGZM, 3857), + geometry_multipolygon GEOMETRY(MULTIPOLYGON, 3857), + geometry_multipolygon_z GEOMETRY(MULTIPOLYGONZ, 3857), + geometry_multipolygon_m GEOMETRY(MULTIPOLYGONM, 3857), + geometry_multipolygon_zm GEOMETRY(MULTIPOLYGONZM, 3857), + geometry_collection GEOMETRY(GEOMETRYCOLLECTION, 3857), + geometry_collection_z GEOMETRY(GEOMETRYCOLLECTIONZ, 3857), + geometry_collection_m GEOMETRY(GEOMETRYCOLLECTIONM, 3857), + geometry_collection_zm GEOMETRY(GEOMETRYCOLLECTIONZM, 3857), + geometry_triangle GEOMETRY(TRIANGLE, 3857), + geometry_triangle_z GEOMETRY(TRIANGLEZ, 3857), + geometry_triangle_m GEOMETRY(TRIANGLEM, 3857), + geometry_triangle_zm GEOMETRY(TRIANGLEZM, 3857), + geometry_circularstring GEOMETRY(CIRCULARSTRING, 3857), + geometry_circularstring_z GEOMETRY(CIRCULARSTRINGZ, 3857), + geometry_circularstring_m GEOMETRY(CIRCULARSTRINGM, 3857), + geometry_circularstring_zm GEOMETRY(CIRCULARSTRINGZM, 3857), + geometry_compoundcurve GEOMETRY(COMPOUNDCURVE, 3857), + geometry_compoundcurve_z GEOMETRY(COMPOUNDCURVEZ, 3857), + geometry_compoundcurve_m GEOMETRY(COMPOUNDCURVEM, 3857), + geometry_compoundcurve_zm GEOMETRY(COMPOUNDCURVEZM, 3857), + geometry_curvepolygon GEOMETRY(CURVEPOLYGON, 3857), + geometry_curvepolygon_z GEOMETRY(CURVEPOLYGONZ, 3857), + geometry_curvepolygon_m GEOMETRY(CURVEPOLYGONM, 3857), + geometry_curvepolygon_zm GEOMETRY(CURVEPOLYGONZM, 3857), + geometry_multicurve GEOMETRY(MULTICURVE, 3857), + geometry_multicurve_z GEOMETRY(MULTICURVEZ, 3857), + geometry_multicurve_m GEOMETRY(MULTICURVEM, 3857), + geometry_multicurve_zm GEOMETRY(MULTICURVEZM, 3857), + geometry_multisurface GEOMETRY(MULTISURFACE, 3857), + geometry_multisurface_z GEOMETRY(MULTISURFACEZ, 3857), + geometry_multisurface_m GEOMETRY(MULTISURFACEM, 3857), + geometry_multisurface_zm GEOMETRY(MULTISURFACEZM, 3857), + geometry_polyhedral GEOMETRY(POLYHEDRALSURFACE, 3857), + geometry_polyhedral_z GEOMETRY(POLYHEDRALSURFACEZ, 3857), + geometry_polyhedral_m GEOMETRY(POLYHEDRALSURFACEM, 3857), + geometry_polyhedral_zm GEOMETRY(POLYHEDRALSURFACEZM, 3857), + geometry_tin GEOMETRY(TIN, 3857), + geometry_tin_z GEOMETRY(TINZ, 3857), + geometry_tin_m GEOMETRY(TINM, 3857), + geometry_tin_zm GEOMETRY(TINZM, 3857), + geography_geometry GEOGRAPHY(GEOMETRY, 9000), + geography_geometry_z GEOGRAPHY(GEOMETRYZ, 9000), + geography_geometry_m GEOGRAPHY(GEOMETRYM, 9000), + geography_geometry_zm GEOGRAPHY(GEOMETRYZM, 9000), + geography_point GEOGRAPHY(POINT, 9000), + geography_point_z GEOGRAPHY(POINTZ, 9000), + geography_point_m GEOGRAPHY(POINTM, 9000), + geography_point_zm GEOGRAPHY(POINTZM, 9000), + geography_line GEOGRAPHY(LINESTRING, 9000), + geography_line_z GEOGRAPHY(LINESTRINGZ, 9000), + geography_line_m GEOGRAPHY(LINESTRINGM, 9000), + geography_line_zm GEOGRAPHY(LINESTRINGZM, 9000), + geography_polygon GEOGRAPHY(POLYGON, 9000), + geography_polygon_z GEOGRAPHY(POLYGONZ, 9000), + geography_polygon_m GEOGRAPHY(POLYGONM, 9000), + geography_polygon_zm GEOGRAPHY(POLYGONZM, 9000), + geography_multipoint GEOGRAPHY(MULTIPOINT, 9000), + geography_multipoint_z GEOGRAPHY(MULTIPOINTZ, 9000), + geography_multipoint_m GEOGRAPHY(MULTIPOINTM, 9000), + geography_multipoint_zm GEOGRAPHY(MULTIPOINTZM, 9000), + geography_multiline GEOGRAPHY(MULTILINESTRING, 9000), + geography_multiline_z GEOGRAPHY(MULTILINESTRINGZ, 9000), + geography_multiline_m GEOGRAPHY(MULTILINESTRINGM, 9000), + geography_multiline_zm GEOGRAPHY(MULTILINESTRINGZM, 9000), + geography_multipolygon GEOGRAPHY(MULTIPOLYGON, 9000), + geography_multipolygon_z GEOGRAPHY(MULTIPOLYGONZ, 9000), + geography_multipolygon_m GEOGRAPHY(MULTIPOLYGONM, 9000), + geography_multipolygon_zm GEOGRAPHY(MULTIPOLYGONZM, 9000), + geography_collection GEOGRAPHY(GEOMETRYCOLLECTION, 9000), + geography_collection_z GEOGRAPHY(GEOMETRYCOLLECTIONZ, 9000), + geography_collection_m GEOGRAPHY(GEOMETRYCOLLECTIONM, 9000), + geography_collection_zm GEOGRAPHY(GEOMETRYCOLLECTIONZM, 9000), + geography_triangle GEOGRAPHY(TRIANGLE, 9000), + geography_triangle_z GEOGRAPHY(TRIANGLEZ, 9000), + geography_triangle_m GEOGRAPHY(TRIANGLEM, 9000), + geography_triangle_zm GEOGRAPHY(TRIANGLEZM, 9000), + geography_circularstring GEOGRAPHY(CIRCULARSTRING, 9000), + geography_circularstring_z GEOGRAPHY(CIRCULARSTRINGZ, 9000), + geography_circularstring_m GEOGRAPHY(CIRCULARSTRINGM, 9000), + geography_circularstring_zm GEOGRAPHY(CIRCULARSTRINGZM, 9000), + geography_compoundcurve GEOGRAPHY(COMPOUNDCURVE, 9000), + geography_compoundcurve_z GEOGRAPHY(COMPOUNDCURVEZ, 9000), + geography_compoundcurve_m GEOGRAPHY(COMPOUNDCURVEM, 9000), + geography_compoundcurve_zm GEOGRAPHY(COMPOUNDCURVEZM, 9000), + geography_curvepolygon GEOGRAPHY(CURVEPOLYGON, 9000), + geography_curvepolygon_z GEOGRAPHY(CURVEPOLYGONZ, 9000), + geography_curvepolygon_m GEOGRAPHY(CURVEPOLYGONM, 9000), + geography_curvepolygon_zm GEOGRAPHY(CURVEPOLYGONZM, 9000), + geography_multicurve GEOGRAPHY(MULTICURVE, 9000), + geography_multicurve_z GEOGRAPHY(MULTICURVEZ, 9000), + geography_multicurve_m GEOGRAPHY(MULTICURVEM, 9000), + geography_multicurve_zm GEOGRAPHY(MULTICURVEZM, 9000), + geography_multisurface GEOGRAPHY(MULTISURFACE, 9000), + geography_multisurface_z GEOGRAPHY(MULTISURFACEZ, 9000), + geography_multisurface_m GEOGRAPHY(MULTISURFACEM, 9000), + geography_multisurface_zm GEOGRAPHY(MULTISURFACEZM, 9000), + geography_polyhedral GEOGRAPHY(POLYHEDRALSURFACE, 9000), + geography_polyhedral_z GEOGRAPHY(POLYHEDRALSURFACEZ, 9000), + geography_polyhedral_m GEOGRAPHY(POLYHEDRALSURFACEM, 9000), + geography_polyhedral_zm GEOGRAPHY(POLYHEDRALSURFACEZM, 9000), + geography_tin GEOGRAPHY(TIN, 9000), + geography_tin_z GEOGRAPHY(TINZ, 9000), + geography_tin_m GEOGRAPHY(TINM, 9000), + geography_tin_zm GEOGRAPHY(TINZM, 9000) + ); + "#; + api.raw_cmd(sql); + // TODO@geometry: FIXME! The shema returned contains EVERY PostGIS functions / procedures, tables, views etc + // It is massive. Is there a way to strip it so we can make this test work ? + // let expectation = expect![[r#""#]]; + // api.expect_schema(expectation); + + if api.connector_tags().contains(Tags::Postgres9) { + return; // sequence max values work differently on postgres 9 + } + + let result = api.describe(); + let ext = extract_ext(&result); + let expected_ext = expect![[r#" + PostgresSchemaExt { + opclasses: [], + indexes: [ + ( + IndexId( + 0, + ), + BTree, + ), + ], + expression_indexes: [], + index_null_position: {}, + constraint_options: { + Index( + IndexId( + 0, + ), + ): BitFlags { + bits: 0b0, + }, + }, + table_options: [ + {}, + {}, + ], + exclude_constraints: [], + sequences: [], + extensions: [ + DatabaseExtension { + name: "plpgsql", + schema: "pg_catalog", + version: "1.0", + relocatable: false, + }, + DatabaseExtension { + name: "postgis", + schema: "prisma-tests", + version: "3.3.4", + relocatable: false, + }, + ], + } + "#]]; + expected_ext.assert_debug_eq(&ext); +} + #[test_connector(tags(Postgres))] fn cross_schema_references_are_not_allowed(api: TestApi) { let schema2 = format!("{}_2", api.schema_name()); diff --git a/schema-engine/sql-schema-describer/tests/describers/postgres_describer_tests/cockroach_describer_tests.rs b/schema-engine/sql-schema-describer/tests/describers/postgres_describer_tests/cockroach_describer_tests.rs index ccbac9fdc7d0..f87fa67aaf09 100644 --- a/schema-engine/sql-schema-describer/tests/describers/postgres_describer_tests/cockroach_describer_tests.rs +++ b/schema-engine/sql-schema-describer/tests/describers/postgres_describer_tests/cockroach_describer_tests.rs @@ -210,6 +210,339 @@ fn all_cockroach_column_types_must_work(api: TestApi) { }); } +#[test_connector(tags(CockroachDb))] +fn all_postgis_column_types_must_work(api: TestApi) { + let migration = r#" + CREATE TABLE "Spatial" ( + geometry_geometry GEOMETRY(GEOMETRY, 3857), + geometry_geometry_m GEOMETRY(GEOMETRYZ, 3857), + geometry_geometry_z GEOMETRY(GEOMETRYM, 3857), + geometry_geometry_zm GEOMETRY(GEOMETRYZM, 3857), + geometry_point GEOMETRY(POINT, 3857), + geometry_point_m GEOMETRY(POINTZ, 3857), + geometry_point_z GEOMETRY(POINTM, 3857), + geometry_point_zm GEOMETRY(POINTZM, 3857), + geometry_line GEOMETRY(LINESTRING, 3857), + geometry_line_m GEOMETRY(LINESTRINGZ, 3857), + geometry_line_z GEOMETRY(LINESTRINGM, 3857), + geometry_line_zm GEOMETRY(LINESTRINGZM, 3857), + geometry_polygon GEOMETRY(POLYGON, 3857), + geometry_polygon_m GEOMETRY(POLYGONZ, 3857), + geometry_polygon_z GEOMETRY(POLYGONM, 3857), + geometry_polygon_zm GEOMETRY(POLYGONZM, 3857), + geometry_multipoint GEOMETRY(MULTIPOINT, 3857), + geometry_multipoint_m GEOMETRY(MULTIPOINTZ, 3857), + geometry_multipoint_z GEOMETRY(MULTIPOINTM, 3857), + geometry_multipoint_zm GEOMETRY(MULTIPOINTZM, 3857), + geometry_multiline GEOMETRY(MULTILINESTRING, 3857), + geometry_multiline_m GEOMETRY(MULTILINESTRINGZ, 3857), + geometry_multiline_z GEOMETRY(MULTILINESTRINGM, 3857), + geometry_multiline_zm GEOMETRY(MULTILINESTRINGZM, 3857), + geometry_multipolygon GEOMETRY(MULTIPOLYGON, 3857), + geometry_multipolygon_m GEOMETRY(MULTIPOLYGONZ, 3857), + geometry_multipolygon_z GEOMETRY(MULTIPOLYGONM, 3857), + geometry_multipolygon_zm GEOMETRY(MULTIPOLYGONZM, 3857), + geometry_collection GEOMETRY(GEOMETRYCOLLECTION, 3857), + geometry_collection_m GEOMETRY(GEOMETRYCOLLECTIONZ, 3857), + geometry_collection_z GEOMETRY(GEOMETRYCOLLECTIONM, 3857), + geometry_collection_zm GEOMETRY(GEOMETRYCOLLECTIONZM, 3857), + geography_geometry GEOGRAPHY(GEOMETRY, 9000), + geography_geometry_m GEOGRAPHY(GEOMETRYZ, 9000), + geography_geometry_z GEOGRAPHY(GEOMETRYM, 9000), + geography_geometry_zm GEOGRAPHY(GEOMETRYZM, 9000), + geography_point GEOGRAPHY(POINT, 9000), + geography_point_m GEOGRAPHY(POINTZ, 9000), + geography_point_z GEOGRAPHY(POINTM, 9000), + geography_point_zm GEOGRAPHY(POINTZM, 9000), + geography_line GEOGRAPHY(LINESTRING, 9000), + geography_line_m GEOGRAPHY(LINESTRINGZ, 9000), + geography_line_z GEOGRAPHY(LINESTRINGM, 9000), + geography_line_zm GEOGRAPHY(LINESTRINGZM, 9000), + geography_polygon GEOGRAPHY(POLYGON, 9000), + geography_polygon_m GEOGRAPHY(POLYGONZ, 9000), + geography_polygon_z GEOGRAPHY(POLYGONM, 9000), + geography_polygon_zm GEOGRAPHY(POLYGONZM, 9000), + geography_multipoint GEOGRAPHY(MULTIPOINT, 9000), + geography_multipoint_m GEOGRAPHY(MULTIPOINTZ, 9000), + geography_multipoint_z GEOGRAPHY(MULTIPOINTM, 9000), + geography_multipoint_zm GEOGRAPHY(MULTIPOINTZM, 9000), + geography_multiline GEOGRAPHY(MULTILINESTRING, 9000), + geography_multiline_m GEOGRAPHY(MULTILINESTRINGZ, 9000), + geography_multiline_z GEOGRAPHY(MULTILINESTRINGM, 9000), + geography_multiline_zm GEOGRAPHY(MULTILINESTRINGZM, 9000), + geography_multipolygon GEOGRAPHY(MULTIPOLYGON, 9000), + geography_multipolygon_m GEOGRAPHY(MULTIPOLYGONZ, 9000), + geography_multipolygon_z GEOGRAPHY(MULTIPOLYGONM, 9000), + geography_multipolygon_zm GEOGRAPHY(MULTIPOLYGONZM, 9000), + geography_collection GEOGRAPHY(GEOMETRYCOLLECTION, 9000), + geography_collection_m GEOGRAPHY(GEOMETRYCOLLECTIONZ, 9000), + geography_collection_z GEOGRAPHY(GEOMETRYCOLLECTIONM, 9000), + geography_collection_zm GEOGRAPHY(GEOMETRYCOLLECTIONZM, 9000) + ); + "#; + + api.raw_cmd(migration); + + api.describe().assert_table("Spatial", |t| { + t.assert_column("geometry_geometry", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_geometry_z", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_geometry_m", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_geometry_zm", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_point", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_point_z", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_point_m", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_point_zm", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_line", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_line_z", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_line_m", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_line_zm", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_polygon", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_polygon_z", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_polygon_m", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_polygon_zm", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_multipoint", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_multipoint_z", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_multipoint_m", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_multipoint_zm", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_multiline", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_multiline_z", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_multiline_m", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_multiline_zm", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_multipolygon", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_multipolygon_z", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_multipolygon_m", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_multipolygon_zm", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_collection", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_collection_z", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_collection_m", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geometry_collection_zm", |c| { + c.assert_full_data_type("geometry") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_geometry", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_geometry_z", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_geometry_m", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_geometry_zm", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_point", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_point_z", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_point_m", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_point_zm", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_line", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_line_z", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_line_m", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_line_zm", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_polygon", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_polygon_z", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_polygon_m", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_polygon_zm", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_multipoint", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_multipoint_z", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_multipoint_m", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_multipoint_zm", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_multiline", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_multiline_z", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_multiline_m", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_multiline_zm", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_multipolygon", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_multipolygon_z", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_multipolygon_m", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_multipolygon_zm", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_collection", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_collection_z", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_collection_m", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + .assert_column("geography_collection_zm", |c| { + c.assert_full_data_type("geography") + .assert_column_type_family(ColumnTypeFamily::Geometry) + }) + }); +} + #[test_connector(tags(CockroachDb))] fn multi_field_indexes_must_be_inferred_in_the_right_order(api: TestApi) { let schema = format!( diff --git a/schema-engine/sql-schema-describer/tests/describers/sqlite_describer_tests.rs b/schema-engine/sql-schema-describer/tests/describers/sqlite_describer_tests.rs index a7928af66edc..7528742f95dd 100644 --- a/schema-engine/sql-schema-describer/tests/describers/sqlite_describer_tests.rs +++ b/schema-engine/sql-schema-describer/tests/describers/sqlite_describer_tests.rs @@ -51,7 +51,7 @@ fn views_can_be_described(api: TestApi) { fn sqlite_column_types_must_work(api: TestApi) { let sql = r#" CREATE TABLE "User" ( - int_col int not null, + int_col int NOT NULL, int4_col INTEGER NOT NULL, text_col TEXT NOT NULL, real_col REAL NOT NULL, @@ -211,6 +211,697 @@ fn sqlite_column_types_must_work(api: TestApi) { api.expect_schema(expectation); } +#[test_connector(tags(Spatialite))] +fn spatialite_column_types_must_work(api: TestApi) { + let sql = r#" + SELECT InitSpatialMetaData(); + + CREATE TABLE "User" ( + primary_col INTEGER PRIMARY KEY + ); + + SELECT + AddGeometryColumn('User', 'geometry_xy', 3857, 'GEOMETRY', 'XY', 0), + AddGeometryColumn('User', 'geometry_xyz', 3857, 'GEOMETRY', 'XYZ', 0), + AddGeometryColumn('User', 'geometry_xym', 3857, 'GEOMETRY', 'XYM', 0), + AddGeometryColumn('User', 'geometry_xyzm', 3857, 'GEOMETRY', 'XYZM', 0), + AddGeometryColumn('User', 'point_xy', 3857, 'POINT', 'XY', 0), + AddGeometryColumn('User', 'point_xyz', 3857, 'POINT', 'XYZ', 0), + AddGeometryColumn('User', 'point_xym', 3857, 'POINT', 'XYM', 0), + AddGeometryColumn('User', 'point_xyzm', 3857, 'POINT', 'XYZM', 0), + AddGeometryColumn('User', 'linestring_xy', 3857, 'LINESTRING', 'XY', 0), + AddGeometryColumn('User', 'linestring_xyz', 3857, 'LINESTRING', 'XYZ', 0), + AddGeometryColumn('User', 'linestring_xym', 3857, 'LINESTRING', 'XYM', 0), + AddGeometryColumn('User', 'linestring_xyzm', 3857, 'LINESTRING', 'XYZM', 0), + AddGeometryColumn('User', 'polygon_xy', 3857, 'POLYGON', 'XY', 0), + AddGeometryColumn('User', 'polygon_xyz', 3857, 'POLYGON', 'XYZ', 0), + AddGeometryColumn('User', 'polygon_xym', 3857, 'POLYGON', 'XYM', 0), + AddGeometryColumn('User', 'polygon_xyzm', 3857, 'POLYGON', 'XYZM', 0), + AddGeometryColumn('User', 'multipoint_xy', 3857, 'MULTIPOINT', 'XY', 0), + AddGeometryColumn('User', 'multipoint_xyz', 3857, 'MULTIPOINT', 'XYZ', 0), + AddGeometryColumn('User', 'multipoint_xym', 3857, 'MULTIPOINT', 'XYM', 0), + AddGeometryColumn('User', 'multipoint_xyzm', 3857, 'MULTIPOINT', 'XYZM', 0), + AddGeometryColumn('User', 'multilinestring_xy', 3857, 'MULTILINESTRING', 'XY', 0), + AddGeometryColumn('User', 'multilinestring_xyz', 3857, 'MULTILINESTRING', 'XYZ', 0), + AddGeometryColumn('User', 'multilinestring_xym', 3857, 'MULTILINESTRING', 'XYM', 0), + AddGeometryColumn('User', 'multilinestring_xyzm', 3857, 'MULTILINESTRING', 'XYZM', 0), + AddGeometryColumn('User', 'multipolygon_xy', 3857, 'MULTIPOLYGON', 'XY', 0), + AddGeometryColumn('User', 'multipolygon_xyz', 3857, 'MULTIPOLYGON', 'XYZ', 0), + AddGeometryColumn('User', 'multipolygon_xym', 3857, 'MULTIPOLYGON', 'XYM', 0), + AddGeometryColumn('User', 'multipolygon_xyzm', 3857, 'MULTIPOLYGON', 'XYZM', 0), + AddGeometryColumn('User', 'geometrycollection_xy', 3857, 'GEOMETRYCOLLECTION', 'XY', 0), + AddGeometryColumn('User', 'geometrycollection_xyz', 3857, 'GEOMETRYCOLLECTION', 'XYZ', 0), + AddGeometryColumn('User', 'geometrycollection_xym', 3857, 'GEOMETRYCOLLECTION', 'XYM', 0), + AddGeometryColumn('User', 'geometrycollection_xyzm', 3857, 'GEOMETRYCOLLECTION', 'XYZM', 0); + "#; + api.raw_cmd(sql); + let expectation = expect![[r#" + SqlSchema { + namespaces: [], + tables: [ + Table { + namespace_id: NamespaceId( + 0, + ), + name: "User", + properties: BitFlags { + bits: 0b0, + }, + description: None, + }, + ], + enums: [], + enum_variants: [], + table_columns: [ + ( + TableId( + 0, + ), + Column { + name: "primary_col", + tpe: ColumnType { + full_data_type: "integer", + family: Int, + arity: Required, + native_type: None, + }, + auto_increment: true, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "geometry_xy", + tpe: ColumnType { + full_data_type: "GEOMETRY", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "geometry_xyz", + tpe: ColumnType { + full_data_type: "GEOMETRY", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "geometry_xym", + tpe: ColumnType { + full_data_type: "GEOMETRY", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "geometry_xyzm", + tpe: ColumnType { + full_data_type: "GEOMETRY", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "point_xy", + tpe: ColumnType { + full_data_type: "POINT", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "point_xyz", + tpe: ColumnType { + full_data_type: "POINT", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "point_xym", + tpe: ColumnType { + full_data_type: "POINT", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "point_xyzm", + tpe: ColumnType { + full_data_type: "POINT", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "linestring_xy", + tpe: ColumnType { + full_data_type: "LINESTRING", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "linestring_xyz", + tpe: ColumnType { + full_data_type: "LINESTRING", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "linestring_xym", + tpe: ColumnType { + full_data_type: "LINESTRING", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "linestring_xyzm", + tpe: ColumnType { + full_data_type: "LINESTRING", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "polygon_xy", + tpe: ColumnType { + full_data_type: "POLYGON", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "polygon_xyz", + tpe: ColumnType { + full_data_type: "POLYGON", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "polygon_xym", + tpe: ColumnType { + full_data_type: "POLYGON", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "polygon_xyzm", + tpe: ColumnType { + full_data_type: "POLYGON", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "multipoint_xy", + tpe: ColumnType { + full_data_type: "MULTIPOINT", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "multipoint_xyz", + tpe: ColumnType { + full_data_type: "MULTIPOINT", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "multipoint_xym", + tpe: ColumnType { + full_data_type: "MULTIPOINT", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "multipoint_xyzm", + tpe: ColumnType { + full_data_type: "MULTIPOINT", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "multilinestring_xy", + tpe: ColumnType { + full_data_type: "MULTILINESTRING", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "multilinestring_xyz", + tpe: ColumnType { + full_data_type: "MULTILINESTRING", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "multilinestring_xym", + tpe: ColumnType { + full_data_type: "MULTILINESTRING", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "multilinestring_xyzm", + tpe: ColumnType { + full_data_type: "MULTILINESTRING", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "multipolygon_xy", + tpe: ColumnType { + full_data_type: "MULTIPOLYGON", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "multipolygon_xyz", + tpe: ColumnType { + full_data_type: "MULTIPOLYGON", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "multipolygon_xym", + tpe: ColumnType { + full_data_type: "MULTIPOLYGON", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "multipolygon_xyzm", + tpe: ColumnType { + full_data_type: "MULTIPOLYGON", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "geometrycollection_xy", + tpe: ColumnType { + full_data_type: "GEOMETRYCOLLECTION", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "geometrycollection_xyz", + tpe: ColumnType { + full_data_type: "GEOMETRYCOLLECTION", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "geometrycollection_xym", + tpe: ColumnType { + full_data_type: "GEOMETRYCOLLECTION", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ( + TableId( + 0, + ), + Column { + name: "geometrycollection_xyzm", + tpe: ColumnType { + full_data_type: "GEOMETRYCOLLECTION", + family: Geometry, + arity: Nullable, + native_type: Some( + NativeTypeInstance(..), + ), + }, + auto_increment: false, + description: None, + }, + ), + ], + foreign_keys: [], + table_default_values: [], + view_default_values: [], + foreign_key_columns: [], + indexes: [ + Index { + table_id: TableId( + 0, + ), + index_name: "", + tpe: PrimaryKey, + }, + ], + index_columns: [ + IndexColumn { + index_id: IndexId( + 0, + ), + column_id: TableColumnId( + 0, + ), + sort_order: None, + length: None, + }, + ], + check_constraints: [], + views: [], + view_columns: [], + procedures: [], + user_defined_types: [], + connector_data: , + } + "#]]; + api.expect_schema(expectation); +} + #[test_connector(tags(Sqlite))] fn sqlite_foreign_key_on_delete_must_be_handled(api: TestApi) { use sql_schema_describer::ForeignKeyAction::*; diff --git a/schema-engine/sql-schema-describer/tests/test_api/mod.rs b/schema-engine/sql-schema-describer/tests/test_api/mod.rs index 17a94589ddbd..68ea95551b8f 100644 --- a/schema-engine/sql-schema-describer/tests/test_api/mod.rs +++ b/schema-engine/sql-schema-describer/tests/test_api/mod.rs @@ -82,16 +82,16 @@ impl TestApi { match self.sql_family() { SqlFamily::Postgres => { use postgres::Circumstances; - sql_schema_describer::postgres::SqlSchemaDescriber::new( - &self.database, - if self.tags.contains(Tags::CockroachDb) { - Circumstances::Cockroach.into() - } else { - Default::default() - }, - ) - .describe(schemas) - .await + let mut circumstances: BitFlags = Default::default(); + if self.tags.contains(Tags::CockroachDb) { + circumstances |= Circumstances::Cockroach + }; + if self.tags.contains(Tags::PostGIS) { + circumstances |= Circumstances::HasPostGIS + }; + sql_schema_describer::postgres::SqlSchemaDescriber::new(&self.database, circumstances) + .describe(schemas) + .await } SqlFamily::Sqlite => { sql_schema_describer::sqlite::SqlSchemaDescriber::new(&self.database) From e605279c9dc7ff404c08b700187a33e21bcd46b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Nitoref?= Date: Fri, 8 Sep 2023 20:24:34 +0200 Subject: [PATCH 02/47] chore: add postgis database to query engine test matrix --- .github/workflows/test-query-engine.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test-query-engine.yml b/.github/workflows/test-query-engine.yml index 24f1032e6929..b86fcbbbab0c 100644 --- a/.github/workflows/test-query-engine.yml +++ b/.github/workflows/test-query-engine.yml @@ -33,6 +33,10 @@ jobs: single_threaded: true connector: 'postgres' version: '15' + - name: 'postgis15' + single_threaded: true + connector: 'postgres' + version: '15-postgis' - name: 'mssql_2022' single_threaded: false connector: 'sqlserver' From 0a750daf017c27d354e126e223c0a3cb9afb6278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Nitoref?= Date: Fri, 8 Sep 2023 21:53:58 +0200 Subject: [PATCH 03/47] fix: mysql geometries should be serialized to wkb by quaint --- quaint/src/visitor/mysql.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/quaint/src/visitor/mysql.rs b/quaint/src/visitor/mysql.rs index 2ab3f08abf0e..1dbd6bca595e 100644 --- a/quaint/src/visitor/mysql.rs +++ b/quaint/src/visitor/mysql.rs @@ -281,6 +281,11 @@ impl<'a> Visitor<'a> for Mysql<'a> { } } + fn visit_parameterized(&mut self, value: Value<'a>) -> visitor::Result { + self.add_parameter(value); + self.parameter_substitution() + } + fn parameter_substitution(&mut self) -> visitor::Result { self.write("?") } From 98440338eb350c28525bbe6b11a551d29b5b53ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Nitoref?= Date: Fri, 8 Sep 2023 22:18:01 +0200 Subject: [PATCH 04/47] fix: failing inequality operator with PostGIS --- quaint/src/visitor/postgres.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/quaint/src/visitor/postgres.rs b/quaint/src/visitor/postgres.rs index edc996103c5a..3697ddffd89d 100644 --- a/quaint/src/visitor/postgres.rs +++ b/quaint/src/visitor/postgres.rs @@ -381,6 +381,12 @@ impl<'a> Visitor<'a> for Postgres<'a> { _ => "", }; + // TODO@geometry: file a bug report to PostGIS ? (ERROR: operator is not unique: geometry <> geometry) + if left.is_geometry_expr() && right.is_geometry_expr() { + self.write("NOT ")?; + return self.visit_equals(left, right) + } + self.visit_expression(left)?; self.write(left_cast)?; self.write(" <> ")?; From b267f617bd0b44c02c40cb802782c877cc770d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Nitoref?= Date: Fri, 8 Sep 2023 22:18:53 +0200 Subject: [PATCH 05/47] fix: PostGIS tests need to happen with the public schema exposed --- quaint/src/visitor/postgres.rs | 2 +- .../src/schemas/geometry.rs | 25 -------- .../query-engine-tests/src/schemas/mod.rs | 2 - .../tests/queries/filters/geometry_filter.rs | 59 +++++++++++++++---- .../writes/top_level_mutations/create.rs | 46 ++++++++++++--- 5 files changed, 88 insertions(+), 46 deletions(-) delete mode 100644 query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/geometry.rs diff --git a/quaint/src/visitor/postgres.rs b/quaint/src/visitor/postgres.rs index 3697ddffd89d..7d58f46ee942 100644 --- a/quaint/src/visitor/postgres.rs +++ b/quaint/src/visitor/postgres.rs @@ -384,7 +384,7 @@ impl<'a> Visitor<'a> for Postgres<'a> { // TODO@geometry: file a bug report to PostGIS ? (ERROR: operator is not unique: geometry <> geometry) if left.is_geometry_expr() && right.is_geometry_expr() { self.write("NOT ")?; - return self.visit_equals(left, right) + return self.visit_equals(left, right); } self.visit_expression(left)?; diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/geometry.rs b/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/geometry.rs deleted file mode 100644 index c52442912461..000000000000 --- a/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/geometry.rs +++ /dev/null @@ -1,25 +0,0 @@ -use indoc::indoc; - -/// Basic Test model containing a single geometry field. -pub fn geometry() -> String { - let schema = indoc! { - "model TestModel { - #id(id, Int, @id) - geometry GeoJson - }" - }; - - schema.to_owned() -} - -/// Basic Test model containing a single optional geometry field. -pub fn geometry_opt() -> String { - let schema = indoc! { - "model TestModel { - #id(id, Int, @id) - geometry GeoJson? - }" - }; - - schema.to_owned() -} diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/mod.rs b/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/mod.rs index 797235a679cb..cd9d2d9d1cb2 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/mod.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/src/schemas/mod.rs @@ -1,13 +1,11 @@ mod basic; mod composites; -mod geometry; mod json; mod many_to_many; mod one_to_many; pub use basic::*; pub use composites::*; -pub use geometry::*; pub use json::*; pub use many_to_many::*; pub use one_to_many::*; diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs index fe29e6dc95e2..a2df18b46580 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs @@ -1,10 +1,6 @@ use query_engine_tests::*; -#[test_suite( - schema(schema), - capabilities(GeoJsonGeometry), - exclude(Postgres(9, 10, 11, 12, 13, 14, 15, "pgbouncer")) -)] +#[test_suite(capabilities(GeoJsonGeometry))] mod geometry_filter_spec { use query_engine_tests::run_query; @@ -21,8 +17,21 @@ mod geometry_filter_spec { schema.to_owned() } - #[connector_test] - async fn basic_where(runner: Runner) -> TestResult<()> { + fn schema_postgres() -> String { + let schema = indoc! { + r#" + model TestModel { + @@schema("test") + #id(id, Int, @id) + geom GeoJson? + } + "# + }; + + schema.to_owned() + } + + async fn basic_where_test(runner: Runner) -> TestResult<()> { test_data(&runner).await?; insta::assert_snapshot!( @@ -43,8 +52,7 @@ mod geometry_filter_spec { Ok(()) } - #[connector_test] - async fn where_shorthands(runner: Runner) -> TestResult<()> { + async fn where_shorthands_test(runner: Runner) -> TestResult<()> { test_data(&runner).await?; insta::assert_snapshot!( @@ -63,8 +71,7 @@ mod geometry_filter_spec { Ok(()) } - #[connector_test(capabilities(GeometryFiltering))] - async fn geometric_comparison_filters(runner: Runner) -> TestResult<()> { + async fn geometric_comparison_filters_test(runner: Runner) -> TestResult<()> { test_data(&runner).await?; // geoWithin @@ -94,6 +101,36 @@ mod geometry_filter_spec { Ok(()) } + #[connector_test(schema(schema), exclude(Postgres))] + async fn basic_where(runner: Runner) -> TestResult<()> { + basic_where_test(runner).await + } + + #[connector_test(schema(schema), exclude(Postgres))] + async fn where_shorthands(runner: Runner) -> TestResult<()> { + where_shorthands_test(runner).await + } + + #[connector_test(schema(schema), exclude(Postgres))] + async fn geometric_comparison_filters(runner: Runner) -> TestResult<()> { + geometric_comparison_filters_test(runner).await + } + + #[connector_test(schema(schema_postgres), db_schemas("public", "test"), only(Postgres("15-postgis")))] + async fn basic_where_postgres(runner: Runner) -> TestResult<()> { + basic_where_test(runner).await + } + + #[connector_test(schema(schema_postgres), db_schemas("public", "test"), only(Postgres("15-postgis")))] + async fn where_shorthands_postgres(runner: Runner) -> TestResult<()> { + where_shorthands_test(runner).await + } + + #[connector_test(schema(schema_postgres), db_schemas("public", "test"), only(Postgres("15-postgis")))] + async fn geometric_comparison_filters_postgres(runner: Runner) -> TestResult<()> { + geometric_comparison_filters_test(runner).await + } + async fn test_data(runner: &Runner) -> TestResult<()> { runner .query(indoc! { r#" diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs index d53b98daf871..1e46dc4adafe 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs @@ -425,16 +425,11 @@ mod mapped_create { } } -#[test_suite( - schema(geometry_opt), - capabilities(GeoJsonGeometry), - exclude(Postgres(9, 10, 11, 12, 13, 14, 15, "pgbouncer")) -)] +#[test_suite(capabilities(GeoJsonGeometry))] mod geometry_create { use query_engine_tests::run_query; - #[connector_test] - async fn create_geometry(runner: Runner) -> TestResult<()> { + async fn create_geometry_test(runner: Runner) -> TestResult<()> { // TODO@geometry: ideally, make geojson generation consistent with SQL connectors match_connector_result!( &runner, @@ -456,4 +451,41 @@ mod geometry_create { Ok(()) } + + fn geometry_opt() -> String { + let schema = indoc! { + r#"model TestModel { + #id(id, Int, @id) + geometry GeoJson? + }"# + }; + + schema.to_owned() + } + + fn geometry_opt_postgres() -> String { + let schema = indoc! { + r#"model TestModel { + @@schema("test") + #id(id, Int, @id) + geometry GeoJson? + }"# + }; + + schema.to_owned() + } + + #[connector_test(schema(geometry_opt), exclude(Postgres))] + async fn create_geometry(runner: Runner) -> TestResult<()> { + create_geometry_test(runner).await + } + + #[connector_test( + schema(geometry_opt_postgres), + db_schemas("public", "test"), + only(Postgres("15-postgis")) + )] + async fn create_geometry_postgres(runner: Runner) -> TestResult<()> { + create_geometry_test(runner).await + } } From 2aaf40be898fdb26dad596494b3b8989fb76d608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Nitoref?= Date: Fri, 8 Sep 2023 23:27:15 +0200 Subject: [PATCH 06/47] fix: add missing GeometryFiltering capability constraint to geometric_comparison_filters --- .../query-engine-tests/tests/queries/filters/geometry_filter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs index a2df18b46580..5c857f14290e 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs @@ -111,7 +111,7 @@ mod geometry_filter_spec { where_shorthands_test(runner).await } - #[connector_test(schema(schema), exclude(Postgres))] + #[connector_test(schema(schema), exclude(Postgres), capabilities(GeometryFiltering))] async fn geometric_comparison_filters(runner: Runner) -> TestResult<()> { geometric_comparison_filters_test(runner).await } From b9f411f9c163355e16483c2a295b672fccdfa81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Nitoref?= Date: Sat, 9 Sep 2023 13:00:12 +0200 Subject: [PATCH 07/47] fix: srid introspection in postgres dialect Geography default SRID is 4326 in PG, but 0 in CRDB. But in both cases, if non 0, the column SRID it will be always explicitly mentioned in the type information column. --- schema-engine/sql-schema-describer/src/postgres.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/schema-engine/sql-schema-describer/src/postgres.rs b/schema-engine/sql-schema-describer/src/postgres.rs index 8283acb79b22..58da2d4b83c8 100644 --- a/schema-engine/sql-schema-describer/src/postgres.rs +++ b/schema-engine/sql-schema-describer/src/postgres.rs @@ -985,10 +985,7 @@ impl<'a> SqlSchemaDescriber<'a> { .name("type") .map(|t| GeometryType::from_str(t.as_str())) .unwrap_or(Ok(GeometryType::default())); - let srid = capture - .name("srid") - .map(|v| v.as_str().parse::()) - .unwrap_or(Ok(if is_geography { 4326 } else { 0 })); + let srid = capture.name("srid").map(|v| v.as_str().parse::()).unwrap_or(Ok(0)); match (geom_type, srid) { (Ok(ty), Ok(srid)) => Some(GeometryParams { ty, srid }), _ => None, From f42f3baeee62130f5dff1fd3a32e6cea281b9a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Nitoref?= Date: Sat, 9 Sep 2023 17:21:43 +0200 Subject: [PATCH 08/47] fix: handle geography default srid value inconsistency in tests --- .../tests/native_types/postgres.rs | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs b/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs index 2ff61e6e3644..edaa48cafcc4 100644 --- a/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs +++ b/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs @@ -39,7 +39,6 @@ const TYPES: &[(&str, &str)] = &[ const GEOMETRY_TYPES: &[(&str, &str)] = &[ ("geometry", "Geometry"), ("geometry_geometry", "Geometry(Geometry)"), - ("geometry_geometry_srid", "Geometry(Geometry, 4326)"), ("geometry_geometry_m", "Geometry(GeometryM)"), ("geometry_geometry_z", "Geometry(GeometryZ)"), ("geometry_geometry_zm", "Geometry(GeometryZM)"), @@ -73,7 +72,6 @@ const GEOMETRY_TYPES: &[(&str, &str)] = &[ ("geometry_geometrycollection_zm", "Geometry(GeometryCollectionZM)"), ("geography", "Geography"), ("geography_geometry", "Geography(Geometry)"), - ("geography_geometry_srid", "Geography(Geometry, 4326)"), ("geography_geometry_m", "Geography(GeometryM)"), ("geography_geometry_z", "Geography(GeometryZ)"), ("geography_geometry_zm", "Geography(GeometryZM)"), @@ -140,38 +138,38 @@ const GEOMETRY_EXTRA_TYPES: &[(&str, &str)] = &[ ("geometry_tinm", "Geometry(TinM)"), ("geometry_tinz", "Geometry(TinZ)"), ("geometry_tinzm", "Geometry(TinZM)"), - ("geography_circularstring", "Geography(CircularString)"), - ("geography_circularstringm", "Geography(CircularStringM)"), - ("geography_circularstringz", "Geography(CircularStringZ)"), - ("geography_circularstringzm", "Geography(CircularStringZM)"), - ("geography_compoundcurve", "Geography(CompoundCurve)"), - ("geography_compoundcurvem", "Geography(CompoundCurveM)"), - ("geography_compoundcurvez", "Geography(CompoundCurveZ)"), - ("geography_compoundcurvezm", "Geography(CompoundCurveZM)"), - ("geography_curvepolygon", "Geography(CurvePolygon)"), - ("geography_curvepolygonm", "Geography(CurvePolygonM)"), - ("geography_curvepolygonz", "Geography(CurvePolygonZ)"), - ("geography_curvepolygonzm", "Geography(CurvePolygonZM)"), - ("geography_multicurve", "Geography(MultiCurve)"), - ("geography_multicurvem", "Geography(MultiCurveM)"), - ("geography_multicurvez", "Geography(MultiCurveZ)"), - ("geography_multicurvezm", "Geography(MultiCurveZM)"), - ("geography_multisurface", "Geography(MultiSurface)"), - ("geography_multisurfacem", "Geography(MultiSurfaceM)"), - ("geography_multisurfacez", "Geography(MultiSurfaceZ)"), - ("geography_multisurfacezm", "Geography(MultiSurfaceZM)"), - ("geography_polyhedralsurface", "Geography(PolyhedralSurface)"), - ("geography_polyhedralsurfacem", "Geography(PolyhedralSurfaceM)"), - ("geography_polyhedralsurfacez", "Geography(PolyhedralSurfaceZ)"), - ("geography_polyhedralsurfacezm", "Geography(PolyhedralSurfaceZM)"), - ("geography_triangle", "Geography(Triangle)"), - ("geography_trianglem", "Geography(TriangleM)"), - ("geography_trianglez", "Geography(TriangleZ)"), - ("geography_trianglezm", "Geography(TriangleZM)"), - ("geography_tin", "Geography(Tin)"), - ("geography_tinm", "Geography(TinM)"), - ("geography_tinz", "Geography(TinZ)"), - ("geography_tinzm", "Geography(TinZM)"), + ("geography_circularstring", "Geography(CircularString, 4326)"), + ("geography_circularstringm", "Geography(CircularStringM, 4326)"), + ("geography_circularstringz", "Geography(CircularStringZ, 4326)"), + ("geography_circularstringzm", "Geography(CircularStringZM, 4326)"), + ("geography_compoundcurve", "Geography(CompoundCurve, 4326)"), + ("geography_compoundcurvem", "Geography(CompoundCurveM, 4326)"), + ("geography_compoundcurvez", "Geography(CompoundCurveZ, 4326)"), + ("geography_compoundcurvezm", "Geography(CompoundCurveZM, 4326)"), + ("geography_curvepolygon", "Geography(CurvePolygon, 4326)"), + ("geography_curvepolygonm", "Geography(CurvePolygonM, 4326)"), + ("geography_curvepolygonz", "Geography(CurvePolygonZ, 4326)"), + ("geography_curvepolygonzm", "Geography(CurvePolygonZM, 4326)"), + ("geography_multicurve", "Geography(MultiCurve, 4326)"), + ("geography_multicurvem", "Geography(MultiCurveM, 4326)"), + ("geography_multicurvez", "Geography(MultiCurveZ, 4326)"), + ("geography_multicurvezm", "Geography(MultiCurveZM, 4326)"), + ("geography_multisurface", "Geography(MultiSurface, 4326)"), + ("geography_multisurfacem", "Geography(MultiSurfaceM, 4326)"), + ("geography_multisurfacez", "Geography(MultiSurfaceZ, 4326)"), + ("geography_multisurfacezm", "Geography(MultiSurfaceZM, 4326)"), + ("geography_polyhedralsurface", "Geography(PolyhedralSurface, 4326)"), + ("geography_polyhedralsurfacem", "Geography(PolyhedralSurfaceM, 4326)"), + ("geography_polyhedralsurfacez", "Geography(PolyhedralSurfaceZ, 4326)"), + ("geography_polyhedralsurfacezm", "Geography(PolyhedralSurfaceZM, 4326)"), + ("geography_triangle", "Geography(Triangle, 4326)"), + ("geography_trianglem", "Geography(TriangleM, 4326)"), + ("geography_trianglez", "Geography(TriangleZ, 4326)"), + ("geography_trianglezm", "Geography(TriangleZM, 4326)"), + ("geography_tin", "Geography(Tin, 4326)"), + ("geography_tinm", "Geography(TinM, 4326)"), + ("geography_tinz", "Geography(TinZ, 4326)"), + ("geography_tinzm", "Geography(TinZM, 4326)"), ]; #[test_connector(tags(Postgres), exclude(PostGIS, CockroachDb))] @@ -263,7 +261,6 @@ async fn native_type_spatial_columns_feature_on(api: &mut TestApi) -> TestResult id Int @id geometry Geometry geometry_geometry Geometry - geometry_geometry_srid Geometry @db.Geometry(Geometry, 4326) geometry_geometry_m Geometry @db.Geometry(GeometryM) geometry_geometry_z Geometry @db.Geometry(GeometryZ) geometry_geometry_zm Geometry @db.Geometry(GeometryZM) @@ -297,7 +294,6 @@ async fn native_type_spatial_columns_feature_on(api: &mut TestApi) -> TestResult geometry_geometrycollection_zm Geometry @db.Geometry(GeometryCollectionZM) geography Geometry @db.Geography(Geometry, 4326) geography_geometry Geometry @db.Geography(Geometry, 4326) - geography_geometry_srid Geometry @db.Geography(Geometry, 4326) geography_geometry_m Geometry @db.Geography(GeometryM, 4326) geography_geometry_z Geometry @db.Geography(GeometryZ, 4326) geography_geometry_zm Geometry @db.Geography(GeometryZM, 4326) From c44ee7a255853be6f0674bafd48bd5bf2aa7ade0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Nitoref?= Date: Sat, 9 Sep 2023 17:37:10 +0200 Subject: [PATCH 09/47] fix: remove unused variable --- schema-engine/sql-schema-describer/src/postgres.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/schema-engine/sql-schema-describer/src/postgres.rs b/schema-engine/sql-schema-describer/src/postgres.rs index 58da2d4b83c8..97575a418461 100644 --- a/schema-engine/sql-schema-describer/src/postgres.rs +++ b/schema-engine/sql-schema-describer/src/postgres.rs @@ -980,7 +980,6 @@ impl<'a> SqlSchemaDescriber<'a> { static GEOM_REGEX: Lazy = Lazy::new(|| Regex::new(r"^(?Pgeometry|geography)(\((?P.+?)(,(?P\d+))?\))?$").unwrap()); GEOM_REGEX.captures(col).and_then(|capture| { - let is_geography = capture.name("class").map(|c| c.as_str() == "geography").unwrap(); let geom_type = capture .name("type") .map(|t| GeometryType::from_str(t.as_str())) From c16fcd7ded2759889670c712969be1856b5b33a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Nitoref?= Date: Sat, 9 Sep 2023 18:34:27 +0200 Subject: [PATCH 10/47] fix: geography srid introspection test in postgres dialect --- .../tests/native_types/postgres.rs | 70 +++++++++---------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs b/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs index edaa48cafcc4..cb99c013f524 100644 --- a/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs +++ b/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs @@ -38,7 +38,7 @@ const TYPES: &[(&str, &str)] = &[ const GEOMETRY_TYPES: &[(&str, &str)] = &[ ("geometry", "Geometry"), - ("geometry_geometry", "Geometry(Geometry)"), + ("geometry_srid", "Geometry(Geometry, 3857)"), ("geometry_geometry_m", "Geometry(GeometryM)"), ("geometry_geometry_z", "Geometry(GeometryZ)"), ("geometry_geometry_zm", "Geometry(GeometryZM)"), @@ -70,39 +70,38 @@ const GEOMETRY_TYPES: &[(&str, &str)] = &[ ("geometry_geometrycollection_m", "Geometry(GeometryCollectionM)"), ("geometry_geometrycollection_z", "Geometry(GeometryCollectionZ)"), ("geometry_geometrycollection_zm", "Geometry(GeometryCollectionZM)"), - ("geography", "Geography"), - ("geography_geometry", "Geography(Geometry)"), - ("geography_geometry_m", "Geography(GeometryM)"), - ("geography_geometry_z", "Geography(GeometryZ)"), - ("geography_geometry_zm", "Geography(GeometryZM)"), - ("geography_point", "Geography(Point)"), - ("geography_point_m", "Geography(PointM)"), - ("geography_point_z", "Geography(PointZ)"), - ("geography_point_zm", "Geography(PointZM)"), - ("geography_linestring", "Geography(LineString)"), - ("geography_linestring_m", "Geography(LineStringM)"), - ("geography_linestring_z", "Geography(LineStringZ)"), - ("geography_linestring_zm", "Geography(LineStringZM)"), - ("geography_polygon", "Geography(Polygon)"), - ("geography_polygon_m", "Geography(PolygonM)"), - ("geography_polygon_z", "Geography(PolygonZ)"), - ("geography_polygon_zm", "Geography(PolygonZM)"), - ("geography_multipoint", "Geography(MultiPoint)"), - ("geography_multipoint_m", "Geography(MultiPointM)"), - ("geography_multipoint_z", "Geography(MultiPointZ)"), - ("geography_multipoint_zm", "Geography(MultiPointZM)"), - ("geography_multilinestring", "Geography(MultiLineString)"), - ("geography_multilinestring_m", "Geography(MultiLineStringM)"), - ("geography_multilinestring_z", "Geography(MultiLineStringZ)"), - ("geography_multilinestring_zm", "Geography(MultiLineStringZM)"), - ("geography_multipolygon", "Geography(MultiPolygon)"), - ("geography_multipolygon_m", "Geography(MultiPolygonM)"), - ("geography_multipolygon_z", "Geography(MultiPolygonZ)"), - ("geography_multipolygon_zm", "Geography(MultiPolygonZM)"), - ("geography_geometrycollection", "Geography(GeometryCollection)"), - ("geography_geometrycollection_m", "Geography(GeometryCollectionM)"), - ("geography_geometrycollection_z", "Geography(GeometryCollectionZ)"), - ("geography_geometrycollection_zm", "Geography(GeometryCollectionZM)"), + ("geography_geometry", "Geography(Geometry, 4326)"), + ("geography_geometry_m", "Geography(GeometryM, 4326)"), + ("geography_geometry_z", "Geography(GeometryZ, 4326)"), + ("geography_geometry_zm", "Geography(GeometryZM, 4326)"), + ("geography_point", "Geography(Point, 4326)"), + ("geography_point_m", "Geography(PointM, 4326)"), + ("geography_point_z", "Geography(PointZ, 4326)"), + ("geography_point_zm", "Geography(PointZM, 4326)"), + ("geography_linestring", "Geography(LineString, 4326)"), + ("geography_linestring_m", "Geography(LineStringM, 4326)"), + ("geography_linestring_z", "Geography(LineStringZ, 4326)"), + ("geography_linestring_zm", "Geography(LineStringZM, 4326)"), + ("geography_polygon", "Geography(Polygon, 4326)"), + ("geography_polygon_m", "Geography(PolygonM, 4326)"), + ("geography_polygon_z", "Geography(PolygonZ, 4326)"), + ("geography_polygon_zm", "Geography(PolygonZM, 4326)"), + ("geography_multipoint", "Geography(MultiPoint, 4326)"), + ("geography_multipoint_m", "Geography(MultiPointM, 4326)"), + ("geography_multipoint_z", "Geography(MultiPointZ, 4326)"), + ("geography_multipoint_zm", "Geography(MultiPointZM, 4326)"), + ("geography_multilinestring", "Geography(MultiLineString, 4326)"), + ("geography_multilinestring_m", "Geography(MultiLineStringM, 4326)"), + ("geography_multilinestring_z", "Geography(MultiLineStringZ, 4326)"), + ("geography_multilinestring_zm", "Geography(MultiLineStringZM, 4326)"), + ("geography_multipolygon", "Geography(MultiPolygon, 4326)"), + ("geography_multipolygon_m", "Geography(MultiPolygonM, 4326)"), + ("geography_multipolygon_z", "Geography(MultiPolygonZ, 4326)"), + ("geography_multipolygon_zm", "Geography(MultiPolygonZM, 4326)"), + ("geography_geometrycollection", "Geography(GeometryCollection, 4326)"), + ("geography_geometrycollection_m", "Geography(GeometryCollectionM, 4326)"), + ("geography_geometrycollection_z", "Geography(GeometryCollectionZ, 4326)"), + ("geography_geometrycollection_zm", "Geography(GeometryCollectionZM, 4326)"), ]; const GEOMETRY_EXTRA_TYPES: &[(&str, &str)] = &[ @@ -260,7 +259,7 @@ async fn native_type_spatial_columns_feature_on(api: &mut TestApi) -> TestResult model Spatial { id Int @id geometry Geometry - geometry_geometry Geometry + geometry_srid Geometry @db.Geometry(Geometry, 3857) geometry_geometry_m Geometry @db.Geometry(GeometryM) geometry_geometry_z Geometry @db.Geometry(GeometryZ) geometry_geometry_zm Geometry @db.Geometry(GeometryZM) @@ -292,7 +291,6 @@ async fn native_type_spatial_columns_feature_on(api: &mut TestApi) -> TestResult geometry_geometrycollection_m Geometry @db.Geometry(GeometryCollectionM) geometry_geometrycollection_z Geometry @db.Geometry(GeometryCollectionZ) geometry_geometrycollection_zm Geometry @db.Geometry(GeometryCollectionZM) - geography Geometry @db.Geography(Geometry, 4326) geography_geometry Geometry @db.Geography(Geometry, 4326) geography_geometry_m Geometry @db.Geography(GeometryM, 4326) geography_geometry_z Geometry @db.Geography(GeometryZ, 4326) From 6f2db0fbf6165fcccf3f11c9691eb519870ccb7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Nitoref?= Date: Sat, 9 Sep 2023 21:14:07 +0200 Subject: [PATCH 11/47] fix: actually fix geography srid introspection test in postgres dialect --- .../tests/native_types/postgres.rs | 5 ++++- .../sql-schema-describer/src/postgres.rs | 22 ++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs b/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs index cb99c013f524..4fa9270c35d4 100644 --- a/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs +++ b/schema-engine/sql-introspection-tests/tests/native_types/postgres.rs @@ -101,7 +101,10 @@ const GEOMETRY_TYPES: &[(&str, &str)] = &[ ("geography_geometrycollection", "Geography(GeometryCollection, 4326)"), ("geography_geometrycollection_m", "Geography(GeometryCollectionM, 4326)"), ("geography_geometrycollection_z", "Geography(GeometryCollectionZ, 4326)"), - ("geography_geometrycollection_zm", "Geography(GeometryCollectionZM, 4326)"), + ( + "geography_geometrycollection_zm", + "Geography(GeometryCollectionZM, 4326)", + ), ]; const GEOMETRY_EXTRA_TYPES: &[(&str, &str)] = &[ diff --git a/schema-engine/sql-schema-describer/src/postgres.rs b/schema-engine/sql-schema-describer/src/postgres.rs index 97575a418461..826955a2e715 100644 --- a/schema-engine/sql-schema-describer/src/postgres.rs +++ b/schema-engine/sql-schema-describer/src/postgres.rs @@ -976,7 +976,10 @@ impl<'a> SqlSchemaDescriber<'a> { Ok(()) } - fn get_geometry_info(col: &str) -> Option { + fn get_geometry_info(col: &str, circumstances: &BitFlags) -> Option { + if !circumstances.contains(Circumstances::HasPostGIS) { + return None; + } static GEOM_REGEX: Lazy = Lazy::new(|| Regex::new(r"^(?Pgeometry|geography)(\((?P.+?)(,(?P\d+))?\))?$").unwrap()); GEOM_REGEX.captures(col).and_then(|capture| { @@ -984,7 +987,12 @@ impl<'a> SqlSchemaDescriber<'a> { .name("type") .map(|t| GeometryType::from_str(t.as_str())) .unwrap_or(Ok(GeometryType::default())); - let srid = capture.name("srid").map(|v| v.as_str().parse::()).unwrap_or(Ok(0)); + let is_cockroach = circumstances.contains(Circumstances::Cockroach); + let is_geography = capture.name("class").map(|c| c.as_str() == "geography").unwrap(); + let srid = capture + .name("srid") + .map(|v| v.as_str().parse::()) + .unwrap_or(Ok(if is_geography && !is_cockroach { 4326 } else { 0 })); match (geom_type, srid) { (Ok(ty), Ok(srid)) => Some(GeometryParams { ty, srid }), _ => None, @@ -1572,10 +1580,7 @@ fn get_column_type_postgresql( false => ColumnArity::Nullable, }; - let geometry = match circumstances.contains(Circumstances::HasPostGIS) { - true => SqlSchemaDescriber::get_geometry_info(&formatted_type), - false => None, - }; + let geometry = SqlSchemaDescriber::get_geometry_info(&formatted_type, circumstances); let precision = SqlSchemaDescriber::get_precision(row); let unsupported_type = || (Unsupported(full_data_type.clone()), None); let enum_id: Option<_> = match data_type.as_str() { @@ -1681,10 +1686,7 @@ fn get_column_type_cockroachdb( false => ColumnArity::Nullable, }; - let geometry_type = match circumstances.contains(Circumstances::HasPostGIS) { - true => SqlSchemaDescriber::get_geometry_info(&data_type), - false => None, - }; + let geometry_type = SqlSchemaDescriber::get_geometry_info(&data_type, circumstances); let precision = SqlSchemaDescriber::get_precision(row); let unsupported_type = || (Unsupported(full_data_type.clone()), None); let enum_id: Option<_> = match data_type.as_str() { From a5097ded06d8535fc8c6de899496e7ca95694213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Nitoref?= Date: Sat, 9 Sep 2023 22:52:28 +0200 Subject: [PATCH 12/47] fix: simplify mysql srid introspection on mysql and fixes vitess tests --- .../sql-schema-describer/src/mysql.rs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/schema-engine/sql-schema-describer/src/mysql.rs b/schema-engine/sql-schema-describer/src/mysql.rs index 3eb54d0e42ad..cd62c2551348 100644 --- a/schema-engine/sql-schema-describer/src/mysql.rs +++ b/schema-engine/sql-schema-describer/src/mysql.rs @@ -369,27 +369,11 @@ impl<'a> SqlSchemaDescriber<'a> { // information schema column names became upper-case in MySQL 8, causing the code fetching // the result values by column name below to fail. let sql_geometry_srid_column = if self.supports_srid_constraints() { - "geom.srs_id" + "srs_id" } else { "NULL" }; - let sql_geometry_information_table = if matches!(flavour, Flavour::MariaDb) { - " - LEFT JOIN information_schema.geometry_columns geom - ON table_schema = geom.g_table_schema - AND table_name = geom.g_table_name - AND column_name = geom.g_geometry_column - " - } else if self.supports_srid_constraints() { - " - LEFT JOIN information_schema.st_geometry_columns geom - USING (table_schema, table_name, column_name) - " - } else { - "" - }; - let sql = format!( " SELECT @@ -407,7 +391,6 @@ impl<'a> SqlSchemaDescriber<'a> { table_name table_name, NULLIF(column_comment, '') AS column_comment FROM information_schema.columns - {sql_geometry_information_table} WHERE table_schema = ? ORDER BY ordinal_position " From 98c913ff9add77a18c5d37432727b12861dda45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Mon, 13 Nov 2023 00:03:01 +0100 Subject: [PATCH 13/47] fix: add missing MySQL version constraint for test requiring support for srid column type constraints --- .../tests/writes/data_types/native_types/mysql.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mysql.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mysql.rs index 4c1c80ee7da0..dd3bcee94d98 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mysql.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/mysql.rs @@ -603,7 +603,7 @@ mod mysql { } // "MySQL native spatial types" should "work" - #[connector_test(schema(schema_geojson_srid_geometry_types))] + #[connector_test(only(MySQL(8)), schema(schema_geojson_srid_geometry_types))] async fn native_geojson_srid_geometry_types(runner: Runner) -> TestResult<()> { insta::assert_snapshot!( run_query!(&runner, r#"mutation { From e287c3844b0e1fbaabcf93c5559e1e63a5083235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Mon, 13 Nov 2023 00:04:14 +0100 Subject: [PATCH 14/47] fix: ignore unsuccessful initialization of SpatiaLite in SQLite query engine test setup --- query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs b/query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs index 37085b191ca7..865c595365c5 100644 --- a/query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs +++ b/query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs @@ -6,6 +6,6 @@ pub(crate) async fn sqlite_setup(url: String, source: Datasource, prisma_schema: std::fs::remove_file(source.url.as_literal().unwrap().trim_start_matches("file:")).ok(); let mut connector = sql_schema_connector::SqlSchemaConnector::new_sqlite(); let client = Quaint::new(&url).await.unwrap(); - client.query_raw("SELECT InitSpatialMetaData()", &[]).await.unwrap(); + client.query_raw("SELECT InitSpatialMetaData()", &[]).await.ok(); crate::diff_and_apply(prisma_schema, url, &mut connector).await } From 4e4308c7c760da0e8e7e33c1616d583c269b5f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Mon, 13 Nov 2023 01:54:10 +0100 Subject: [PATCH 15/47] fix: replace intersection test due to MySQL 5.6 quirk --- .../tests/queries/filters/geometry_filter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs index 5c857f14290e..84f2db907ebb 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs @@ -88,13 +88,13 @@ mod geometry_filter_spec { // geoIntersects insta::assert_snapshot!( - run_query!(&runner, r#"query { findManyTestModel(where: { geom: { geoIntersects: "{\"type\":\"Point\",\"coordinates\":[0, 0]}" }}) { id }}"#), + run_query!(&runner, r#"query { findManyTestModel(where: { geom: { geoIntersects: "{\"type\":\"Polygon\",\"coordinates\":[[[-1,-1],[-1,1],[1,1],[1,-1],[-1,-1]]]}" }}) { id }}"#), @r###"{"data":{"findManyTestModel":[{"id":1}]}}"### ); // Not geoIntersects insta::assert_snapshot!( - run_query!(&runner, r#"query { findManyTestModel(where: { geom: { not: { geoIntersects: "{\"type\":\"Point\",\"coordinates\":[0, 0]}" }}}) { id }}"#), + run_query!(&runner, r#"query { findManyTestModel(where: { geom: { not: { geoIntersects: "{\"type\":\"Polygon\",\"coordinates\":[[[-1,-1],[-1,1],[1,1],[1,-1],[-1,-1]]]}" }}}) { id }}"#), @r###"{"data":{"findManyTestModel":[{"id":2}]}}"### ); From 16f004cc2c4005dfe44a70e99762e779ef928519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Sun, 19 Nov 2023 13:53:17 +0100 Subject: [PATCH 16/47] fix: formatting --- quaint/src/ast/function.rs | 5 +---- quaint/src/ast/values.rs | 3 +-- .../sql-schema-connector/src/sql_renderer/sqlite_renderer.rs | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/quaint/src/ast/function.rs b/quaint/src/ast/function.rs index 753ab26e139b..0cb929782415 100644 --- a/quaint/src/ast/function.rs +++ b/quaint/src/ast/function.rs @@ -79,10 +79,7 @@ impl<'a> Function<'a> { } } pub fn returns_geometry(&self) -> bool { - match self.typ_ { - FunctionType::GeomFromText(_) => true, - _ => false, - } + matches!(self.typ_, FunctionType::GeomFromText(_)) } } diff --git a/quaint/src/ast/values.rs b/quaint/src/ast/values.rs index ad1577bed7cd..7ade18091167 100644 --- a/quaint/src/ast/values.rs +++ b/quaint/src/ast/values.rs @@ -56,8 +56,7 @@ impl FromStr for GeometryValue { type Err = String; fn from_str(s: &str) -> Result { - static EWKT_REGEX: Lazy = - Lazy::new(|| Regex::new(r"^(SRID=(?P\d+);)?(?P.+)$").unwrap()); + static EWKT_REGEX: Lazy = Lazy::new(|| Regex::new(r"^(SRID=(?P\d+);)?(?P.+)$").unwrap()); EWKT_REGEX .captures(s) .map(|capture| { diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/sqlite_renderer.rs b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/sqlite_renderer.rs index 9485a3a7b9b0..695f8c79d604 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/sqlite_renderer.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/sqlite_renderer.rs @@ -10,7 +10,7 @@ use psl::builtin_connectors::SQLiteType; use regex::Regex; use sql_ddl::sqlite as ddl; use sql_schema_describer::{walkers::*, *}; -use std::{fmt::Write, borrow::Cow}; +use std::{borrow::Cow, fmt::Write}; impl SqlRenderer for SqliteFlavour { fn quote<'a>(&self, name: &'a str) -> Quoted<&'a str> { From 71dae2e0a5f196542efa9d381bd0b9aaf0fb54ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Sun, 19 Nov 2023 13:14:57 +0100 Subject: [PATCH 17/47] fix: make Spatialite geometry filters stricter and tighten geometry filter tests --- quaint/src/visitor/sqlite.rs | 20 +++++++++++++++++++ .../tests/queries/filters/geometry_filter.rs | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/quaint/src/visitor/sqlite.rs b/quaint/src/visitor/sqlite.rs index 2dfe6b709f8f..3e1ecdc3c8b4 100644 --- a/quaint/src/visitor/sqlite.rs +++ b/quaint/src/visitor/sqlite.rs @@ -281,6 +281,26 @@ impl<'a> Visitor<'a> for Sqlite<'a> { }) } + fn visit_geometry_within(&mut self, left: Expression<'a>, right: Expression<'a>, not: bool) -> visitor::Result { + self.surround_with("ST_Within(", ")", |s| { + s.visit_expression(left)?; + s.write(",")?; + s.visit_expression(right) + })?; + self.write(if not { " != 1" } else { " = 1" })?; + Ok(()) + } + + fn visit_geometry_intersects(&mut self, left: Expression<'a>, right: Expression<'a>, not: bool) -> visitor::Result { + self.surround_with("ST_Intersects(", ")", |s| { + s.visit_expression(left)?; + s.write(",")?; + s.visit_expression(right) + })?; + self.write(if not { " != 1" } else { " = 1" })?; + Ok(()) + } + fn visit_geometry_type_equals( &mut self, left: Expression<'a>, diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs index 84f2db907ebb..d5d549680c85 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs @@ -82,7 +82,7 @@ mod geometry_filter_spec { // Not geoWithin insta::assert_snapshot!( - run_query!(&runner, r#"query { findManyTestModel(where: { geom: { not: { geoWithin: "{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[1,4],[4,4],[4,1],[1,1]]]}" }}}) { id }}"#), + run_query!(&runner, r#"query { findManyTestModel(where: { AND: [{ geom: { not: { geoWithin: "{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[1,4],[4,4],[4,1],[1,1]]]}" }}}, { geom: { not: null }}]}) { id }}"#), @r###"{"data":{"findManyTestModel":[{"id":1}]}}"### ); @@ -94,7 +94,7 @@ mod geometry_filter_spec { // Not geoIntersects insta::assert_snapshot!( - run_query!(&runner, r#"query { findManyTestModel(where: { geom: { not: { geoIntersects: "{\"type\":\"Polygon\",\"coordinates\":[[[-1,-1],[-1,1],[1,1],[1,-1],[-1,-1]]]}" }}}) { id }}"#), + run_query!(&runner, r#"query { findManyTestModel(where: { AND: [{ geom: { not: { geoIntersects: "{\"type\":\"Polygon\",\"coordinates\":[[[-1,-1],[-1,1],[1,1],[1,-1],[-1,-1]]]}" }}}, { geom: { not: null }}]}) { id }}"#), @r###"{"data":{"findManyTestModel":[{"id":2}]}}"### ); From 93f582c691b9a03e3486b81e25b2afe6c39c7429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Sat, 25 Nov 2023 11:28:32 +0100 Subject: [PATCH 18/47] fix: add SQLite test versions 3 and 3-spatialite, only run query engine geometry tests for spatialite enabled version --- Makefile | 5 +++++ quaint/src/connector/sqlite/native/mod.rs | 8 +++++--- .../tests/queries/filters/geometry_filter.rs | 10 +++++++--- .../order_by_dependent.rs | 2 +- .../writes/data_types/native_types/sqlite.rs | 2 +- .../writes/top_level_mutations/create.rs | 2 +- .../src/connector_tag/mod.rs | 10 +++++++++- .../src/connector_tag/sqlite.rs | 3 +++ .../test-configs/spatialite | 3 +++ .../src/flavour/sqlite.rs | 15 ++++++++++++--- .../src/flavour/sqlite/connection.rs | 8 +++++--- .../src/sql_renderer/sqlite_renderer.rs | 19 +++++++------------ 12 files changed, 59 insertions(+), 28 deletions(-) create mode 100644 query-engine/connector-test-kit-rs/test-configs/spatialite diff --git a/Makefile b/Makefile index 151a58ed4c2b..08737e8de574 100644 --- a/Makefile +++ b/Makefile @@ -87,6 +87,11 @@ start-sqlite: dev-sqlite: cp $(CONFIG_PATH)/sqlite $(CONFIG_FILE) +start-spatialite: + +dev-spatialite: + cp $(CONFIG_PATH)/spatialite $(CONFIG_FILE) + dev-libsql-js: build-qe-napi build-connector-kit-js cp $(CONFIG_PATH)/libsql-js $(CONFIG_FILE) diff --git a/quaint/src/connector/sqlite/native/mod.rs b/quaint/src/connector/sqlite/native/mod.rs index 9220fe75b517..5d2a6a5ef2b5 100644 --- a/quaint/src/connector/sqlite/native/mod.rs +++ b/quaint/src/connector/sqlite/native/mod.rs @@ -32,9 +32,11 @@ fn load_spatialite(conn: &rusqlite::Connection) -> crate::Result<()> { // Loading Spatialite here isn't ideal, but needed because it has to be // done for every new pooled connection..? if let Ok(spatialite_path) = std::env::var("SPATIALITE_PATH") { - unsafe { - let _guard = LoadExtensionGuard::new(conn)?; - conn.load_extension(spatialite_path, None)?; + if !spatialite_path.is_empty() { + unsafe { + let _guard = LoadExtensionGuard::new(conn)?; + conn.load_extension(spatialite_path, None)?; + } } } Ok(()) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs index d5d549680c85..3fc5c3d7a797 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs @@ -101,17 +101,21 @@ mod geometry_filter_spec { Ok(()) } - #[connector_test(schema(schema), exclude(Postgres))] + #[connector_test(schema(schema), exclude(Postgres, Sqlite(3, "libsql.js")))] async fn basic_where(runner: Runner) -> TestResult<()> { basic_where_test(runner).await } - #[connector_test(schema(schema), exclude(Postgres))] + #[connector_test(schema(schema), exclude(Postgres, Sqlite(3, "libsql.js")))] async fn where_shorthands(runner: Runner) -> TestResult<()> { where_shorthands_test(runner).await } - #[connector_test(schema(schema), exclude(Postgres), capabilities(GeometryFiltering))] + #[connector_test( + schema(schema), + exclude(Postgres, Sqlite(3, "libsql.js")), + capabilities(GeometryFiltering) + )] async fn geometric_comparison_filters(runner: Runner) -> TestResult<()> { geometric_comparison_filters_test(runner).await } diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/order_and_pagination/order_by_dependent.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/order_and_pagination/order_by_dependent.rs index b4c6e7b5ef34..2194d7842a04 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/order_and_pagination/order_by_dependent.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/order_and_pagination/order_by_dependent.rs @@ -275,7 +275,7 @@ mod order_by_dependent { } } }"#, - MongoDb(_) | Sqlite(_)=> vec![r#"{"data":{"findManyModelA":[{"id":2,"b":{"c":{"a":{"id":4}}}},{"id":1,"b":{"c":{"a":{"id":3}}}},{"id":3,"b":null},{"id":4,"b":null}]}}"#], + MongoDb(_) | Sqlite(_) => vec![r#"{"data":{"findManyModelA":[{"id":2,"b":{"c":{"a":{"id":4}}}},{"id":1,"b":{"c":{"a":{"id":3}}}},{"id":3,"b":null},{"id":4,"b":null}]}}"#], MySql(_) | CockroachDb(_) => vec![ r#"{"data":{"findManyModelA":[{"id":2,"b":{"c":{"a":{"id":4}}}},{"id":1,"b":{"c":{"a":{"id":3}}}},{"id":4,"b":null},{"id":3,"b":null}]}}"#, r#"{"data":{"findManyModelA":[{"id":2,"b":{"c":{"a":{"id":4}}}},{"id":1,"b":{"c":{"a":{"id":3}}}},{"id":3,"b":null},{"id":4,"b":null}]}}"#, diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sqlite.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sqlite.rs index 10fd173a7c99..338278c9166f 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sqlite.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sqlite.rs @@ -1,6 +1,6 @@ use query_engine_tests::*; -#[test_suite(only(Sqlite))] +#[test_suite(only(Sqlite("3-spatialite")))] mod sqlite { use indoc::indoc; use query_engine_tests::run_query; diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs index 1e46dc4adafe..b018a10d46c4 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs @@ -475,7 +475,7 @@ mod geometry_create { schema.to_owned() } - #[connector_test(schema(geometry_opt), exclude(Postgres))] + #[connector_test(schema(geometry_opt), exclude(Postgres, Sqlite(3, "libsql.js")))] async fn create_geometry(runner: Runner) -> TestResult<()> { create_geometry_test(runner).await } diff --git a/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mod.rs b/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mod.rs index a3b84a9173a8..f3e3fde9b4e2 100644 --- a/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mod.rs +++ b/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mod.rs @@ -169,12 +169,20 @@ pub(crate) fn connection_string( } None => unreachable!("A versioned connector must have a concrete version to run."), }, - ConnectorVersion::Sqlite(_) => { + ConnectorVersion::Sqlite(version) => { let workspace_root = std::env::var("WORKSPACE_ROOT") .unwrap_or_else(|_| ".".to_owned()) .trim_end_matches('/') .to_owned(); + let spatialite_path = std::env::var("SPATIALITE_PATH"); + match (version, spatialite_path) { + (Some(SqliteVersion::V3Spatialite), Err(_)) => { + panic!("SPATIALITE_PATH env var should be set for version 3-spatialite") + } + (None, _) => unreachable!("A versioned connector must have a concrete version to run."), + (_, _) => (), + }; format!("file://{workspace_root}/db/{database}.db") } ConnectorVersion::CockroachDb(v) => { diff --git a/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/sqlite.rs b/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/sqlite.rs index 5f4dab56784a..d88c9443abb2 100644 --- a/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/sqlite.rs +++ b/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/sqlite.rs @@ -29,6 +29,7 @@ impl ConnectorTagInterface for SqliteConnectorTag { #[derive(Clone, Debug, PartialEq, Eq)] pub enum SqliteVersion { V3, + V3Spatialite, LibsqlJS, } @@ -36,6 +37,7 @@ impl ToString for SqliteVersion { fn to_string(&self) -> String { match self { SqliteVersion::V3 => "3".to_string(), + SqliteVersion::V3Spatialite => "3-spatialite".to_string(), SqliteVersion::LibsqlJS => "libsql.js".to_string(), } } @@ -47,6 +49,7 @@ impl TryFrom<&str> for SqliteVersion { fn try_from(s: &str) -> Result { let version = match s { "3" => Self::V3, + "3-spatialite" => Self::V3Spatialite, "libsql.js" => Self::LibsqlJS, _ => return Err(TestError::parse_error(format!("Unknown SQLite version `{s}`"))), }; diff --git a/query-engine/connector-test-kit-rs/test-configs/spatialite b/query-engine/connector-test-kit-rs/test-configs/spatialite new file mode 100644 index 000000000000..b76e95b1bee0 --- /dev/null +++ b/query-engine/connector-test-kit-rs/test-configs/spatialite @@ -0,0 +1,3 @@ +{ + "connector": "sqlite", + "version": "3-spatialite"} diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs index 2c6ca751c854..4e963e4d5f78 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs @@ -22,9 +22,18 @@ pub(crate) struct SqliteFlavour { impl SqliteFlavour { pub(crate) fn has_spatialite(&self) -> bool { - // TODO@geometry: FIXME! how can we set this at instanciation ? - // currently set to false to avoid too many failing tests - false + // TODO@geometry: How can we set this more safely at instanciation ? + // Ideally, we'd want to check if the library can be loaded successfully + // e.g. by checking if `SELECT spatialite_version()` returns something. + // But we might also want to check if a database has had tables and triggers + // created by spatialite even though the user doesn't set SPATIALITE_PATH + // because operations not taking those into account might results in a + // invalid database state (not tested) + if let Ok(spatialite_path) = std::env::var("SPATIALITE_PATH") { + !spatialite_path.is_empty() + } else { + false + } } } diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs index bf43f1446f2a..aa98e95cf45f 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs @@ -13,9 +13,11 @@ pub(super) struct Connection(Mutex); fn load_spatialite(conn: &rusqlite::Connection) { // TODO@geometry: raise an appropriate error when spatialite cannot be loaded instead if let Ok(spatialite_path) = std::env::var("SPATIALITE_PATH") { - unsafe { - let _guard = LoadExtensionGuard::new(conn).unwrap(); - conn.load_extension(spatialite_path, None).unwrap(); + if !spatialite_path.is_empty() { + unsafe { + let _guard = LoadExtensionGuard::new(conn).unwrap(); + conn.load_extension(spatialite_path, None).unwrap(); + } } } } diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/sqlite_renderer.rs b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/sqlite_renderer.rs index 695f8c79d604..0bff2f6b6547 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/sqlite_renderer.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/sqlite_renderer.rs @@ -142,11 +142,15 @@ impl SqlRenderer for SqliteFlavour { .map(|c| c.map(|c| c.name().into()).collect()); } - let create_geometries = &self.render_create_geometry_columns(table, table_name); + let create_geometries = if self.has_spatialite() { + self.render_create_geometry_columns(table, table_name) + } else { + "".to_string() + }; if create_geometries.is_empty() { create_table.to_string() } else { - create_table.to_string() + "\n;" + create_geometries + create_table.to_string() + "\n;" + &create_geometries } } @@ -233,19 +237,10 @@ impl SqlRenderer for SqliteFlavour { if self.has_spatialite() { result.push(format!("SELECT DropTable(NULL, '{}')", tables.previous.name())); - result.push(format!( - "SELECT RenameTable('{old_name}', '{new_name}')", - old_name = temporary_table_name, - new_name = tables.next.name(), - )); } else { result.push(format!(r#"DROP TABLE "{}""#, tables.previous.name())); - result.push(format!( - r#"ALTER TABLE "{old_name}" RENAME TO "{new_name}""#, - old_name = temporary_table_name, - new_name = tables.next.name(), - )); } + result.push(self.render_rename_table(None, &temporary_table_name, tables.next.name())); for index in tables.next.indexes().filter(|idx| !idx.is_primary_key()) { result.push(self.render_create_index(index)); From 6cd70be4f4be6b0dd48de240ecd50bff7e5e0050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Fri, 24 Nov 2023 10:12:08 +0100 Subject: [PATCH 19/47] chore: add Spatialite tests to CI --- .github/workflows/test-query-engine.yml | 10 ++++++++++ .github/workflows/test-schema-engine.yml | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/.github/workflows/test-query-engine.yml b/.github/workflows/test-query-engine.yml index b86fcbbbab0c..03da84f114a9 100644 --- a/.github/workflows/test-query-engine.yml +++ b/.github/workflows/test-query-engine.yml @@ -45,6 +45,10 @@ jobs: single_threaded: false connector: 'sqlite' version: '3' + - name: 'spatialite' + single_threaded: false + connector: 'sqlite' + version: '3-spatialite' - name: 'mongodb_4_2' single_threaded: true connector: 'mongodb' @@ -113,12 +117,18 @@ jobs: - uses: dtolnay/rust-toolchain@stable + - name: Install Spatialite + if: ${{ matrix.database.name == 'spatialite' }} + run: apt install -y libsqlite3-mod-spatialite + - run: export WORKSPACE_ROOT=$(pwd) && cargo test --package query-engine-tests -- --test-threads=1 if: ${{ matrix.database.single_threaded }} env: CLICOLOR_FORCE: 1 + SPATIALITE_PATH: ${{ matrix.database.name == 'spatialite' && 'mod_spatialite' || null }} - run: export WORKSPACE_ROOT=$(pwd) && cargo test --package query-engine-tests -- --test-threads=8 if: ${{ !matrix.database.single_threaded }} env: CLICOLOR_FORCE: 1 + SPATIALITE_PATH: ${{ matrix.database.name == 'spatialite' && 'mod_spatialite' || null }} diff --git a/.github/workflows/test-schema-engine.yml b/.github/workflows/test-schema-engine.yml index 6b392f373ba6..c359abbdb187 100644 --- a/.github/workflows/test-schema-engine.yml +++ b/.github/workflows/test-schema-engine.yml @@ -96,6 +96,8 @@ jobs: url: 'postgresql://prisma@localhost:26257' - name: sqlite url: sqlite + - name: spatialite + url: sqlite - name: vitess_8_0 url: 'mysql://root:prisma@localhost:33807/test' shadow_database_url: 'mysql://root:prisma@localhost:33808/shadow' @@ -118,6 +120,10 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Install Spatialite + if: ${{ matrix.database.name == 'spatialite' }} + run: sudo apt install -y libsqlite3-mod-spatialite + - name: 'Start ${{ matrix.database.name }}' run: make start-${{ matrix.database.name }} @@ -126,18 +132,21 @@ jobs: env: CLICOLOR_FORCE: 1 TEST_DATABASE_URL: ${{ matrix.database.url }} + SPATIALITE_PATH: ${{ matrix.database.name == 'spatialite' && 'mod_spatialite' || null }} - run: cargo test -p sql-schema-describer if: ${{ !matrix.database.single_threaded }} env: CLICOLOR_FORCE: 1 TEST_DATABASE_URL: ${{ matrix.database.url }} + SPATIALITE_PATH: ${{ matrix.database.name == 'spatialite' && 'mod_spatialite' || null }} - run: cargo test -p sql-migration-tests if: ${{ !matrix.database.single_threaded }} env: CLICOLOR_FORCE: 1 TEST_DATABASE_URL: ${{ matrix.database.url }} + SPATIALITE_PATH: ${{ matrix.database.name == 'spatialite' && 'mod_spatialite' || null }} RUST_LOG: debug - run: cargo test -p schema-engine-cli @@ -145,6 +154,7 @@ jobs: env: CLICOLOR_FORCE: 1 TEST_DATABASE_URL: ${{ matrix.database.url }} + SPATIALITE_PATH: ${{ matrix.database.name == 'spatialite' && 'mod_spatialite' || null }} - run: cargo test -p sql-introspection-tests -- --test-threads=1 if: ${{ matrix.database.is_vitess }} From e6185beeee5fa069b9cbd549efe0e270d5dfb1bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Sun, 26 Nov 2023 22:53:41 +0100 Subject: [PATCH 20/47] fix: incorrect dependency order --- query-engine/connectors/sql-query-connector/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/query-engine/connectors/sql-query-connector/Cargo.toml b/query-engine/connectors/sql-query-connector/Cargo.toml index c3a84ed28a2d..41b55fa5fc0e 100644 --- a/query-engine/connectors/sql-query-connector/Cargo.toml +++ b/query-engine/connectors/sql-query-connector/Cargo.toml @@ -27,11 +27,11 @@ uuid.workspace = true opentelemetry = { version = "0.17", features = ["tokio"] } tracing-opentelemetry = "0.17.3" cuid = { git = "https://github.com/prisma/cuid-rust", branch = "wasm32-support" } +regex = "1.9.3" +geozero = "0.10.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] quaint.workspace = true -regex = "1.9.3" -geozero = "0.10.0" [target.'cfg(target_arch = "wasm32")'.dependencies] quaint = { path = "../../../quaint" } From 42e591422311df8c4d58f69a65ebb38a5d01a097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Sun, 26 Nov 2023 22:54:09 +0100 Subject: [PATCH 21/47] fix: add missing Spatialite system table to the list --- .../src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs | 1 + schema-engine/sql-schema-describer/src/sqlite.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs index a964512c63b8..11417ae7a3aa 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs @@ -25,6 +25,7 @@ static SPATIALITE_TABLES_OR_VIEWS: Lazy = Lazy::new(|| { "(?i)^iso_metadata$", "(?i)^iso_metadata_reference$", "(?i)^iso_metadata_view$", + "(?i)^knn$", "(?i)^knn2$", "(?i)^networks$", "(?i)^raster_coverages$", diff --git a/schema-engine/sql-schema-describer/src/sqlite.rs b/schema-engine/sql-schema-describer/src/sqlite.rs index 0d23b748c22e..c7f74ab71bc9 100644 --- a/schema-engine/sql-schema-describer/src/sqlite.rs +++ b/schema-engine/sql-schema-describer/src/sqlite.rs @@ -705,6 +705,7 @@ static SPATIALITE_SYSTEM_TABLES: Lazy = Lazy::new(|| { "(?i)^iso_metadata$", "(?i)^iso_metadata_reference$", "(?i)^iso_metadata_view$", + "(?i)^knn$", "(?i)^knn2$", "(?i)^networks$", "(?i)^raster_coverages$", From e1da2f78add1a5bd58cc75ec3aff0b75d54877d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Sun, 26 Nov 2023 23:13:27 +0100 Subject: [PATCH 22/47] fix: null_geometry return value --- quaint/src/ast/values.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quaint/src/ast/values.rs b/quaint/src/ast/values.rs index 7ade18091167..f2185b8d6c42 100644 --- a/quaint/src/ast/values.rs +++ b/quaint/src/ast/values.rs @@ -529,7 +529,7 @@ impl<'a> Value<'a> { } pub fn null_geometry() -> Self { - ValueType::Time(None).into() + ValueType::Geometry(None).into() } } From 5a255ac6ce686c5a03b5a657474fe1041eb3b535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 28 Nov 2023 03:14:51 +0100 Subject: [PATCH 23/47] refactor: share SQLITE_SYSTEM_TABLES with sql-schema-connector --- .../sql_schema_differ_flavour/sqlite.rs | 81 +-------- .../sql-schema-describer/src/sqlite.rs | 156 +++++++++--------- 2 files changed, 78 insertions(+), 159 deletions(-) diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs index 11417ae7a3aa..d42ef3ad8b2e 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour/sqlite.rs @@ -3,83 +3,8 @@ use crate::{ flavour::SqliteFlavour, migration_pair::MigrationPair, sql_schema_differ::column::ColumnTypeChange, sql_schema_differ::differ_database::DifferDatabase, }; -use once_cell::sync::Lazy; -use regex::RegexSet; -use sql_schema_describer::{walkers::TableColumnWalker, ColumnTypeFamily}; -/// These can be tables or views, depending on the PostGIS version. In both cases, they should be ignored. -static SPATIALITE_TABLES_OR_VIEWS: Lazy = Lazy::new(|| { - RegexSet::new([ - "(?i)^data_licenses$", - "(?i)^elementarygeometries$", - "(?i)^geometry_columns$", - "(?i)^geometry_columns_auth$", - "(?i)^geometry_columns_field_infos$", - "(?i)^geometry_columns_statistics$", - "(?i)^geometry_columns_time$", - "(?i)^geom_cols_ref_sys$", - "(?i)^idx_iso_metadata_geometry$", - "(?i)^idx_iso_metadata_geometry_node$", - "(?i)^idx_iso_metadata_geometry_parent$", - "(?i)^idx_iso_metadata_geometry_rowid$", - "(?i)^iso_metadata$", - "(?i)^iso_metadata_reference$", - "(?i)^iso_metadata_view$", - "(?i)^knn$", - "(?i)^knn2$", - "(?i)^networks$", - "(?i)^raster_coverages$", - "(?i)^raster_coverages_keyword$", - "(?i)^raster_coverages_ref_sys$", - "(?i)^raster_coverages_srid$", - "(?i)^rl2map_configurations$", - "(?i)^rl2map_configurations_view$", - "(?i)^se_external_graphics$", - "(?i)^se_external_graphics_view$", - "(?i)^se_fonts$", - "(?i)^se_fonts_view$", - "(?i)^se_raster_styled_layers$", - "(?i)^se_raster_styled_layers_view$", - "(?i)^se_raster_styles$", - "(?i)^se_raster_styles_view$", - "(?i)^se_vector_styled_layers$", - "(?i)^se_vector_styled_layers_view$", - "(?i)^se_vector_styles$", - "(?i)^se_vector_styles_view$", - "(?i)^spatialindex$", - "(?i)^spatialite_history$", - "(?i)^spatial_ref_sys$", - "(?i)^spatial_ref_sys_all$", - "(?i)^spatial_ref_sys_aux$", - "(?i)^sql_statements_log$", - "(?i)^stored_procedures$", - "(?i)^stored_variables$", - "(?i)^topologies$", - "(?i)^vector_coverages$", - "(?i)^vector_coverages_keyword$", - "(?i)^vector_coverages_ref_sys$", - "(?i)^vector_coverages_srid$", - "(?i)^vector_layers$", - "(?i)^vector_layers_auth$", - "(?i)^vector_layers_field_infos$", - "(?i)^vector_layers_statistics$", - "(?i)^views_geometry_columns$", - "(?i)^views_geometry_columns_auth$", - "(?i)^views_geometry_columns_field_infos$", - "(?i)^views_geometry_columns_statistics$", - "(?i)^virts_geometry_collection$", - "(?i)^virts_geometry_collectionm$", - "(?i)^virts_geometry_columns$", - "(?i)^virts_geometry_columns_auth$", - "(?i)^virts_geometry_columns_field_infos$", - "(?i)^virts_geometry_columns_statistics$", - "(?i)^wms_getcapabilities$", - "(?i)^wms_getmap$", - "(?i)^wms_ref_sys$", - "(?i)^wms_settings$", - ]) - .unwrap() -}); +use sql_schema_describer::{sqlite::SQLITE_SYSTEM_TABLES, walkers::TableColumnWalker, ColumnTypeFamily}; impl SqlSchemaDifferFlavour for SqliteFlavour { fn can_rename_foreign_key(&self) -> bool { @@ -140,10 +65,10 @@ impl SqlSchemaDifferFlavour for SqliteFlavour { } fn table_should_be_ignored(&self, table_name: &str) -> bool { - SPATIALITE_TABLES_OR_VIEWS.is_match(table_name) + SQLITE_SYSTEM_TABLES.is_match(table_name) } fn view_should_be_ignored(&self, view_name: &str) -> bool { - SPATIALITE_TABLES_OR_VIEWS.is_match(view_name) + SQLITE_SYSTEM_TABLES.is_match(view_name) } } diff --git a/schema-engine/sql-schema-describer/src/sqlite.rs b/schema-engine/sql-schema-describer/src/sqlite.rs index c7f74ab71bc9..8804e864f507 100644 --- a/schema-engine/sql-schema-describer/src/sqlite.rs +++ b/schema-engine/sql-schema-describer/src/sqlite.rs @@ -672,91 +672,85 @@ fn unquote_sqlite_string_default(s: &str) -> Cow<'_, str> { /// Returns whether a table is one of the SQLite system tables. fn is_system_table(table_name: &str) -> bool { - SQLITE_SYSTEM_TABLES - .iter() - .any(|system_table| table_name == *system_table) - || SPATIALITE_SYSTEM_TABLES.is_match(table_name) + SQLITE_SYSTEM_TABLES.is_match(table_name) } /// See https://www.sqlite.org/fileformat2.html -const SQLITE_SYSTEM_TABLES: &[&str] = &[ - "sqlite_sequence", - "sqlite_stat1", - "sqlite_stat2", - "sqlite_stat3", - "sqlite_stat4", -]; - -/// These can be tables or views, depending on the Spatialite version. In both cases, they should be ignored. -static SPATIALITE_SYSTEM_TABLES: Lazy = Lazy::new(|| { +pub static SQLITE_SYSTEM_TABLES: Lazy = Lazy::new(|| { RegexSet::new([ - "(?i)^data_licenses$", - "(?i)^elementarygeometries$", - "(?i)^geometry_columns$", - "(?i)^geometry_columns_auth$", - "(?i)^geometry_columns_field_infos$", - "(?i)^geometry_columns_statistics$", - "(?i)^geometry_columns_time$", - "(?i)^geom_cols_ref_sys$", - "(?i)^idx_iso_metadata_geometry$", - "(?i)^idx_iso_metadata_geometry_node$", - "(?i)^idx_iso_metadata_geometry_parent$", - "(?i)^idx_iso_metadata_geometry_rowid$", - "(?i)^iso_metadata$", - "(?i)^iso_metadata_reference$", - "(?i)^iso_metadata_view$", - "(?i)^knn$", - "(?i)^knn2$", - "(?i)^networks$", - "(?i)^raster_coverages$", - "(?i)^raster_coverages_keyword$", - "(?i)^raster_coverages_ref_sys$", - "(?i)^raster_coverages_srid$", - "(?i)^rl2map_configurations$", - "(?i)^rl2map_configurations_view$", - "(?i)^se_external_graphics$", - "(?i)^se_external_graphics_view$", - "(?i)^se_fonts$", - "(?i)^se_fonts_view$", - "(?i)^se_raster_styled_layers$", - "(?i)^se_raster_styled_layers_view$", - "(?i)^se_raster_styles$", - "(?i)^se_raster_styles_view$", - "(?i)^se_vector_styled_layers$", - "(?i)^se_vector_styled_layers_view$", - "(?i)^se_vector_styles$", - "(?i)^se_vector_styles_view$", - "(?i)^spatialindex$", - "(?i)^spatialite_history$", - "(?i)^spatial_ref_sys$", - "(?i)^spatial_ref_sys_all$", - "(?i)^spatial_ref_sys_aux$", - "(?i)^sql_statements_log$", - "(?i)^stored_procedures$", - "(?i)^stored_variables$", - "(?i)^topologies$", - "(?i)^vector_coverages$", - "(?i)^vector_coverages_keyword$", - "(?i)^vector_coverages_ref_sys$", - "(?i)^vector_coverages_srid$", - "(?i)^vector_layers$", - "(?i)^vector_layers_auth$", - "(?i)^vector_layers_field_infos$", - "(?i)^vector_layers_statistics$", - "(?i)^views_geometry_columns$", - "(?i)^views_geometry_columns_auth$", - "(?i)^views_geometry_columns_field_infos$", - "(?i)^views_geometry_columns_statistics$", - "(?i)^virts_geometry_collection$", - "(?i)^virts_geometry_collectionm$", - "(?i)^virts_geometry_columns$", - "(?i)^virts_geometry_columns_auth$", - "(?i)^virts_geometry_columns_field_infos$", - "(?i)^virts_geometry_columns_statistics$", - "(?i)^wms_getcapabilities$", - "(?i)^wms_getmap$", - "(?i)^wms_ref_sys$", - "(?i)^wms_settings$", + "^sqlite_sequence$", + "^sqlite_stat1$", + "^sqlite_stat2$", + "^sqlite_stat3$", + "^sqlite_stat4$", + "^data_licenses$", + // Spatialite generated table and views + "^ElementaryGeometries$", + "^geometry_columns$", + "^geometry_columns_auth$", + "^geometry_columns_field_infos$", + "^geometry_columns_statistics$", + "^geometry_columns_time$", + "^geom_cols_ref_sys$", + "^idx_ISO_metadata_geometry$", + "^idx_ISO_metadata_geometry_node$", + "^idx_ISO_metadata_geometry_parent$", + "^idx_ISO_metadata_geometry_rowid$", + "^ISO_metadata$", + "^ISO_metadata_reference$", + "^ISO_metadata_view$", + "^KNN$", + "^KNN2$", + "^networks$", + "^raster_coverages$", + "^raster_coverages_keyword$", + "^raster_coverages_ref_sys$", + "^raster_coverages_srid$", + "^rl2map_configurations$", + "^rl2map_configurations_view$", + "^SE_external_graphics$", + "^SE_external_graphics_view$", + "^SE_fonts$", + "^SE_fonts_view$", + "^SE_raster_styled_layers$", + "^SE_raster_styled_layers_view$", + "^SE_raster_styles$", + "^SE_raster_styles_view$", + "^SE_vector_styled_layers$", + "^SE_vector_styled_layers_view$", + "^SE_vector_styles$", + "^SE_vector_styles_view$", + "^SpatialIndex$", + "^spatialite_history$", + "^spatial_ref_sys$", + "^spatial_ref_sys_all$", + "^spatial_ref_sys_aux$", + "^sql_statements_log$", + "^stored_procedures$", + "^stored_variables$", + "^topologies$", + "^vector_coverages$", + "^vector_coverages_keyword$", + "^vector_coverages_ref_sys$", + "^vector_coverages_srid$", + "^vector_layers$", + "^vector_layers_auth$", + "^vector_layers_field_infos$", + "^vector_layers_statistics$", + "^views_geometry_columns$", + "^views_geometry_columns_auth$", + "^views_geometry_columns_field_infos$", + "^views_geometry_columns_statistics$", + "^virts_geometry_collection$", + "^virts_geometry_collectionm$", + "^virts_geometry_columns$", + "^virts_geometry_columns_auth$", + "^virts_geometry_columns_field_infos$", + "^virts_geometry_columns_statistics$", + "^wms_getcapabilities$", + "^wms_getmap$", + "^wms_ref_sys$", + "^wms_settings$", ]) .unwrap() }); From 1290ef7ef5cc0508f4784f6090090fa9519ba6e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 28 Nov 2023 03:54:34 +0100 Subject: [PATCH 24/47] refactor: execute InitSpatialMetaData in quaint --- quaint/src/connector/sqlite/native/mod.rs | 3 ++- .../qe-setup/src/sqlite.rs | 3 --- .../src/flavour/sqlite/connection.rs | 18 +++--------------- .../tests/native_types/sqlite.rs | 2 -- .../tests/describers/sqlite_describer_tests.rs | 2 -- 5 files changed, 5 insertions(+), 23 deletions(-) diff --git a/quaint/src/connector/sqlite/native/mod.rs b/quaint/src/connector/sqlite/native/mod.rs index 5d2a6a5ef2b5..4dc8a7d056c1 100644 --- a/quaint/src/connector/sqlite/native/mod.rs +++ b/quaint/src/connector/sqlite/native/mod.rs @@ -28,7 +28,7 @@ pub struct Sqlite { pub(crate) client: Mutex, } -fn load_spatialite(conn: &rusqlite::Connection) -> crate::Result<()> { +pub fn load_spatialite(conn: &rusqlite::Connection) -> crate::Result<()> { // Loading Spatialite here isn't ideal, but needed because it has to be // done for every new pooled connection..? if let Ok(spatialite_path) = std::env::var("SPATIALITE_PATH") { @@ -36,6 +36,7 @@ fn load_spatialite(conn: &rusqlite::Connection) -> crate::Result<()> { unsafe { let _guard = LoadExtensionGuard::new(conn)?; conn.load_extension(spatialite_path, None)?; + conn.query_row("SELECT InitSpatialMetaData(1)", [], |_| Ok(())).unwrap(); } } } diff --git a/query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs b/query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs index 865c595365c5..da9c93d46bc8 100644 --- a/query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs +++ b/query-engine/connector-test-kit-rs/qe-setup/src/sqlite.rs @@ -1,11 +1,8 @@ use psl::Datasource; -use quaint::{prelude::*, single::Quaint}; use schema_core::schema_connector::ConnectorResult; pub(crate) async fn sqlite_setup(url: String, source: Datasource, prisma_schema: &str) -> ConnectorResult<()> { std::fs::remove_file(source.url.as_literal().unwrap().trim_start_matches("file:")).ok(); let mut connector = sql_schema_connector::SqlSchemaConnector::new_sqlite(); - let client = Quaint::new(&url).await.unwrap(); - client.query_raw("SELECT InitSpatialMetaData()", &[]).await.ok(); crate::diff_and_apply(prisma_schema, url, &mut connector).await } diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs index aa98e95cf45f..47ed9ceed103 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs @@ -2,7 +2,7 @@ pub(crate) use quaint::connector::rusqlite; -use quaint::connector::{rusqlite::LoadExtensionGuard, GetRow, ToColumnNames}; +use quaint::connector::{GetRow, ToColumnNames, load_spatialite}; use schema_connector::{ConnectorError, ConnectorResult}; use sql_schema_describer::{sqlite as describer, DescriberErrorKind, SqlSchema}; use std::sync::Mutex; @@ -10,28 +10,16 @@ use user_facing_errors::schema_engine::ApplyMigrationError; pub(super) struct Connection(Mutex); -fn load_spatialite(conn: &rusqlite::Connection) { - // TODO@geometry: raise an appropriate error when spatialite cannot be loaded instead - if let Ok(spatialite_path) = std::env::var("SPATIALITE_PATH") { - if !spatialite_path.is_empty() { - unsafe { - let _guard = LoadExtensionGuard::new(conn).unwrap(); - conn.load_extension(spatialite_path, None).unwrap(); - } - } - } -} - impl Connection { pub(super) fn new(params: &super::Params) -> ConnectorResult { let conn = rusqlite::Connection::open(¶ms.file_path).map_err(convert_error)?; - load_spatialite(&conn); + load_spatialite(&conn).unwrap(); Ok(Connection(Mutex::new(conn))) } pub(super) fn new_in_memory() -> Self { let conn = rusqlite::Connection::open_in_memory().unwrap(); - load_spatialite(&conn); + load_spatialite(&conn).unwrap(); Connection(Mutex::new(conn)) } diff --git a/schema-engine/sql-introspection-tests/tests/native_types/sqlite.rs b/schema-engine/sql-introspection-tests/tests/native_types/sqlite.rs index 6226ae2957ea..0309a16f3aeb 100644 --- a/schema-engine/sql-introspection-tests/tests/native_types/sqlite.rs +++ b/schema-engine/sql-introspection-tests/tests/native_types/sqlite.rs @@ -4,8 +4,6 @@ use sql_introspection_tests::test_api::*; #[test_connector(tags(Spatialite))] async fn native_spatial_type_columns_feature_on(api: &mut TestApi) -> TestResult { let setup = indoc! {r#" - SELECT InitSpatialMetaData(); - CREATE TABLE "User" ( id INTEGER PRIMARY KEY ); diff --git a/schema-engine/sql-schema-describer/tests/describers/sqlite_describer_tests.rs b/schema-engine/sql-schema-describer/tests/describers/sqlite_describer_tests.rs index 7528742f95dd..f75d7df8803c 100644 --- a/schema-engine/sql-schema-describer/tests/describers/sqlite_describer_tests.rs +++ b/schema-engine/sql-schema-describer/tests/describers/sqlite_describer_tests.rs @@ -214,8 +214,6 @@ fn sqlite_column_types_must_work(api: TestApi) { #[test_connector(tags(Spatialite))] fn spatialite_column_types_must_work(api: TestApi) { let sql = r#" - SELECT InitSpatialMetaData(); - CREATE TABLE "User" ( primary_col INTEGER PRIMARY KEY ); From bb7e33c8752fa6048d3a9532bbacc5e447029e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 28 Nov 2023 03:56:29 +0100 Subject: [PATCH 25/47] fix: Spatialite diff migration tests --- .../tests/migrations/diff.rs | 74 +++++++++++++------ 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/schema-engine/sql-migration-tests/tests/migrations/diff.rs b/schema-engine/sql-migration-tests/tests/migrations/diff.rs index b5b9dbb2250d..0a03172798f7 100644 --- a/schema-engine/sql-migration-tests/tests/migrations/diff.rs +++ b/schema-engine/sql-migration-tests/tests/migrations/diff.rs @@ -202,11 +202,19 @@ fn from_schema_datamodel_to_url(mut api: TestApi) { api.diff(input).unwrap(); - let expected_printed_messages = expect![[r#" - [ - "-- DropTable\nPRAGMA foreign_keys=off;\nDROP TABLE \"cows\";\nPRAGMA foreign_keys=on;\n\n-- CreateTable\nCREATE TABLE \"cats\" (\n \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n \"meows\" BOOLEAN DEFAULT true\n);\n", - ] - "#]]; + let expected_printed_messages = if api.tags().contains(test_setup::Tags::Spatialite) { + expect![[r#" + [ + "-- DropTable\nPRAGMA foreign_keys=off;\nSELECT DropTable(NULL, 'cows');\nPRAGMA foreign_keys=on;\n\n-- CreateTable\nCREATE TABLE \"cats\" (\n \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n \"meows\" BOOLEAN DEFAULT true\n);\n", + ] + "#]] + } else { + expect![[r#" + [ + "-- DropTable\nPRAGMA foreign_keys=off;\nDROP TABLE \"cows\";\nPRAGMA foreign_keys=on;\n\n-- CreateTable\nCREATE TABLE \"cats\" (\n \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n \"meows\" BOOLEAN DEFAULT true\n);\n", + ] + "#]] + }; expected_printed_messages.assert_debug_eq(&host.printed_messages.lock().unwrap()); } @@ -249,12 +257,20 @@ fn from_schema_datasource_relative(mut api: TestApi) { }; api.diff(params).unwrap(); - - let expected_printed_messages = expect![[r#" - [ - "-- DropTable\nPRAGMA foreign_keys=off;\nDROP TABLE \"foo\";\nPRAGMA foreign_keys=on;\n", - ] - "#]]; + + let expected_printed_messages = if api.tags().contains(test_setup::Tags::Spatialite) { + expect![[r#" + [ + "-- DropTable\nPRAGMA foreign_keys=off;\nSELECT DropTable(NULL, 'foo');\nPRAGMA foreign_keys=on;\n", + ] + "#]] + } else { + expect![[r#" + [ + "-- DropTable\nPRAGMA foreign_keys=off;\nDROP TABLE \"foo\";\nPRAGMA foreign_keys=on;\n", + ] + "#]] + }; expected_printed_messages.assert_debug_eq(&host.printed_messages.lock().unwrap()); } @@ -306,11 +322,19 @@ fn from_schema_datasource_to_url(mut api: TestApi) { api.diff(input).unwrap(); - let expected_printed_messages = expect![[r#" - [ - "-- DropTable\nPRAGMA foreign_keys=off;\nDROP TABLE \"cows\";\nPRAGMA foreign_keys=on;\n\n-- CreateTable\nCREATE TABLE \"cats\" (\n \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n \"meows\" BOOLEAN DEFAULT true\n);\n", - ] - "#]]; + let expected_printed_messages = if api.tags().contains(test_setup::Tags::Spatialite) { + expect![[r#" + [ + "-- DropTable\nPRAGMA foreign_keys=off;\nSELECT DropTable(NULL, 'cows');\nPRAGMA foreign_keys=on;\n\n-- CreateTable\nCREATE TABLE \"cats\" (\n \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n \"meows\" BOOLEAN DEFAULT true\n);\n", + ] + "#]] + } else { + expect![[r#" + [ + "-- DropTable\nPRAGMA foreign_keys=off;\nDROP TABLE \"cows\";\nPRAGMA foreign_keys=on;\n\n-- CreateTable\nCREATE TABLE \"cats\" (\n \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n \"meows\" BOOLEAN DEFAULT true\n);\n", + ] + "#]] + }; expected_printed_messages.assert_debug_eq(&host.printed_messages.lock().unwrap()); } @@ -348,11 +372,19 @@ fn from_url_to_url(mut api: TestApi) { api.diff(input).unwrap(); - let expected_printed_messages = expect![[r#" - [ - "-- DropTable\nPRAGMA foreign_keys=off;\nDROP TABLE \"cows\";\nPRAGMA foreign_keys=on;\n\n-- CreateTable\nCREATE TABLE \"cats\" (\n \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n \"meows\" BOOLEAN DEFAULT true\n);\n", - ] - "#]]; + let expected_printed_messages = if api.tags().contains(test_setup::Tags::Spatialite) { + expect![[r#" + [ + "-- DropTable\nPRAGMA foreign_keys=off;\nSELECT DropTable(NULL, 'cows');\nPRAGMA foreign_keys=on;\n\n-- CreateTable\nCREATE TABLE \"cats\" (\n \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n \"meows\" BOOLEAN DEFAULT true\n);\n", + ] + "#]] + } else { + expect![[r#" + [ + "-- DropTable\nPRAGMA foreign_keys=off;\nDROP TABLE \"cows\";\nPRAGMA foreign_keys=on;\n\n-- CreateTable\nCREATE TABLE \"cats\" (\n \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n \"meows\" BOOLEAN DEFAULT true\n);\n", + ] + "#]] + }; expected_printed_messages.assert_debug_eq(&host.printed_messages.lock().unwrap()); } From 455b27467a424b466976837c5e77f7221006f609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 28 Nov 2023 04:05:59 +0100 Subject: [PATCH 26/47] fix: formatting --- .../sql-schema-connector/src/flavour/sqlite/connection.rs | 2 +- schema-engine/sql-migration-tests/tests/migrations/diff.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs index 47ed9ceed103..ab578047e8cc 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs @@ -2,7 +2,7 @@ pub(crate) use quaint::connector::rusqlite; -use quaint::connector::{GetRow, ToColumnNames, load_spatialite}; +use quaint::connector::{load_spatialite, GetRow, ToColumnNames}; use schema_connector::{ConnectorError, ConnectorResult}; use sql_schema_describer::{sqlite as describer, DescriberErrorKind, SqlSchema}; use std::sync::Mutex; diff --git a/schema-engine/sql-migration-tests/tests/migrations/diff.rs b/schema-engine/sql-migration-tests/tests/migrations/diff.rs index 0a03172798f7..aa7c4901b3c5 100644 --- a/schema-engine/sql-migration-tests/tests/migrations/diff.rs +++ b/schema-engine/sql-migration-tests/tests/migrations/diff.rs @@ -257,7 +257,7 @@ fn from_schema_datasource_relative(mut api: TestApi) { }; api.diff(params).unwrap(); - + let expected_printed_messages = if api.tags().contains(test_setup::Tags::Spatialite) { expect![[r#" [ From 35a9f15ba90cc36178fcd5706d559b82b32dca56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 28 Nov 2023 10:52:06 +0100 Subject: [PATCH 27/47] fix: revert Sqlite total queries to 9 --- .../query-engine-tests/tests/new/metrics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/metrics.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/metrics.rs index 0c1e3788c5fa..869cb1e57013 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/metrics.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/metrics.rs @@ -35,7 +35,7 @@ mod metrics { let total_operations = get_counter(&json, PRISMA_CLIENT_QUERIES_TOTAL); match runner.connector_version() { - Sqlite(_) => assert_eq!(total_queries, 10), + Sqlite(_) => assert_eq!(total_queries, 9), SqlServer(_) => assert_eq!(total_queries, 17), MongoDb(_) => assert_eq!(total_queries, 5), CockroachDb(_) => (), // not deterministic From 7b8348c5b7becf3bb42da72a2b3f87c0b7699daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 28 Nov 2023 10:54:27 +0100 Subject: [PATCH 28/47] fix: install Spatialite with sudo --- .github/workflows/test-query-engine.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-query-engine.yml b/.github/workflows/test-query-engine.yml index 03da84f114a9..a985e2151ed9 100644 --- a/.github/workflows/test-query-engine.yml +++ b/.github/workflows/test-query-engine.yml @@ -119,7 +119,7 @@ jobs: - name: Install Spatialite if: ${{ matrix.database.name == 'spatialite' }} - run: apt install -y libsqlite3-mod-spatialite + run: sudo apt install -y libsqlite3-mod-spatialite - run: export WORKSPACE_ROOT=$(pwd) && cargo test --package query-engine-tests -- --test-threads=1 if: ${{ matrix.database.single_threaded }} From 31228b58c12ac2d3633d0e8a8fd4f8e15add6ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Wed, 29 Nov 2023 23:14:23 +0100 Subject: [PATCH 29/47] fix: add WGS84_ONLY option to `InitSpatialMetadata` and only call the function if necessary --- quaint/src/connector/sqlite/native/mod.rs | 13 +++++-- .../writes/data_types/native_types/sqlite.rs | 34 +++++++++---------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/quaint/src/connector/sqlite/native/mod.rs b/quaint/src/connector/sqlite/native/mod.rs index 4dc8a7d056c1..157fa527b703 100644 --- a/quaint/src/connector/sqlite/native/mod.rs +++ b/quaint/src/connector/sqlite/native/mod.rs @@ -36,11 +36,20 @@ pub fn load_spatialite(conn: &rusqlite::Connection) -> crate::Result<()> { unsafe { let _guard = LoadExtensionGuard::new(conn)?; conn.load_extension(spatialite_path, None)?; - conn.query_row("SELECT InitSpatialMetaData(1)", [], |_| Ok(())).unwrap(); + } + return match conn.query_row("SELECT CheckSpatialMetaData()", [], |r| r.get(0))? { + 0 => { + match conn.query_row("SELECT InitSpatialMetaData(1, 'WGS84_ONLY')", [], |r| r.get(0))? { + 1 => Ok(()), + _ => Err(Error::builder(ErrorKind::QueryError("Failed to load Spatialite".into())).build()), + } + }, + 3 => Ok(()), + _ => Err(Error::builder(ErrorKind::ConnectionError("Invalid Spatialite State".into())).build()) } } } - Ok(()) + return Ok(()) } impl TryFrom<&str> for Sqlite { diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sqlite.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sqlite.rs index 338278c9166f..5d7a338c98ab 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sqlite.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/data_types/native_types/sqlite.rs @@ -60,14 +60,14 @@ mod sqlite { let schema = indoc! { r#"model Model { #id(id, String, @id, @default(cuid())) - geometry Geometry @test.Geometry(Geometry, 3857) - geometry_point Geometry @test.Geometry(Point, 3857) - geometry_line Geometry @test.Geometry(LineString, 3857) - geometry_poly Geometry @test.Geometry(Polygon, 3857) - geometry_multipoint Geometry @test.Geometry(MultiPoint, 3857) - geometry_multiline Geometry @test.Geometry(MultiLineString, 3857) - geometry_multipoly Geometry @test.Geometry(MultiPolygon, 3857) - geometry_collection Geometry @test.Geometry(GeometryCollection, 3857) + geometry Geometry @test.Geometry(Geometry, 4326) + geometry_point Geometry @test.Geometry(Point, 4326) + geometry_line Geometry @test.Geometry(LineString, 4326) + geometry_poly Geometry @test.Geometry(Polygon, 4326) + geometry_multipoint Geometry @test.Geometry(MultiPoint, 4326) + geometry_multiline Geometry @test.Geometry(MultiLineString, 4326) + geometry_multipoly Geometry @test.Geometry(MultiPolygon, 4326) + geometry_collection Geometry @test.Geometry(GeometryCollection, 4326) }"# }; @@ -81,14 +81,14 @@ mod sqlite { run_query!(&runner, r#"mutation { createOneModel( data: { - geometry: "SRID=3857;POINT(1 2)" - geometry_point: "SRID=3857;POINT(1 2)" - geometry_line: "SRID=3857;LINESTRING(1 2,3 4)" - geometry_poly: "SRID=3857;POLYGON((1 2,3 4,5 6,1 2))" - geometry_multipoint: "SRID=3857;MULTIPOINT(1 2)" - geometry_multiline: "SRID=3857;MULTILINESTRING((1 2,3 4))" - geometry_multipoly: "SRID=3857;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))" - geometry_collection: "SRID=3857;GEOMETRYCOLLECTION(POINT(1 2))" + geometry: "SRID=4326;POINT(1 2)" + geometry_point: "SRID=4326;POINT(1 2)" + geometry_line: "SRID=4326;LINESTRING(1 2,3 4)" + geometry_poly: "SRID=4326;POLYGON((1 2,3 4,5 6,1 2))" + geometry_multipoint: "SRID=4326;MULTIPOINT(1 2)" + geometry_multiline: "SRID=4326;MULTILINESTRING((1 2,3 4))" + geometry_multipoly: "SRID=4326;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))" + geometry_collection: "SRID=4326;GEOMETRYCOLLECTION(POINT(1 2))" } ) { geometry @@ -101,7 +101,7 @@ mod sqlite { geometry_collection } }"#), - @r###"{"data":{"createOneModel":{"geometry":"SRID=3857;POINT(1 2)","geometry_point":"SRID=3857;POINT(1 2)","geometry_line":"SRID=3857;LINESTRING(1 2,3 4)","geometry_poly":"SRID=3857;POLYGON((1 2,3 4,5 6,1 2))","geometry_multipoint":"SRID=3857;MULTIPOINT(1 2)","geometry_multiline":"SRID=3857;MULTILINESTRING((1 2,3 4))","geometry_multipoly":"SRID=3857;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))","geometry_collection":"SRID=3857;GEOMETRYCOLLECTION(POINT(1 2))"}}}"### + @r###"{"data":{"createOneModel":{"geometry":"SRID=4326;POINT(1 2)","geometry_point":"SRID=4326;POINT(1 2)","geometry_line":"SRID=4326;LINESTRING(1 2,3 4)","geometry_poly":"SRID=4326;POLYGON((1 2,3 4,5 6,1 2))","geometry_multipoint":"SRID=4326;MULTIPOINT(1 2)","geometry_multiline":"SRID=4326;MULTILINESTRING((1 2,3 4))","geometry_multipoly":"SRID=4326;MULTIPOLYGON(((1 2,3 4,5 6,1 2)))","geometry_collection":"SRID=4326;GEOMETRYCOLLECTION(POINT(1 2))"}}}"### ); Ok(()) From 8a5288b359fb26f1cc688e037a42ee311019f3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Wed, 29 Nov 2023 23:26:50 +0100 Subject: [PATCH 30/47] fix: formatting --- quaint/src/connector/sqlite/native/mod.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/quaint/src/connector/sqlite/native/mod.rs b/quaint/src/connector/sqlite/native/mod.rs index 157fa527b703..ca2b3c8b4d57 100644 --- a/quaint/src/connector/sqlite/native/mod.rs +++ b/quaint/src/connector/sqlite/native/mod.rs @@ -38,18 +38,16 @@ pub fn load_spatialite(conn: &rusqlite::Connection) -> crate::Result<()> { conn.load_extension(spatialite_path, None)?; } return match conn.query_row("SELECT CheckSpatialMetaData()", [], |r| r.get(0))? { - 0 => { - match conn.query_row("SELECT InitSpatialMetaData(1, 'WGS84_ONLY')", [], |r| r.get(0))? { - 1 => Ok(()), - _ => Err(Error::builder(ErrorKind::QueryError("Failed to load Spatialite".into())).build()), - } + 0 => match conn.query_row("SELECT InitSpatialMetaData(1, 'WGS84_ONLY')", [], |r| r.get(0))? { + 1 => Ok(()), + _ => Err(Error::builder(ErrorKind::QueryError("Failed to load Spatialite".into())).build()), }, 3 => Ok(()), - _ => Err(Error::builder(ErrorKind::ConnectionError("Invalid Spatialite State".into())).build()) - } + _ => Err(Error::builder(ErrorKind::ConnectionError("Invalid Spatialite State".into())).build()), + }; } } - return Ok(()) + Ok(()) } impl TryFrom<&str> for Sqlite { From e0f834dc25d3a4b4c821df9810dfd890335c0b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Thu, 30 Nov 2023 15:33:33 +0100 Subject: [PATCH 31/47] fix: skip geometric_comparison_filters test for MariaDB while we figure it out --- .../tests/queries/filters/geometry_filter.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs index 3fc5c3d7a797..4b4e2a200787 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs @@ -111,9 +111,11 @@ mod geometry_filter_spec { where_shorthands_test(runner).await } + // This test should work for MariaDB but doesn't so we skip it for now, + // see discussion here: https://github.com/prisma/prisma-engines/pull/4208#issuecomment-1828997865 #[connector_test( schema(schema), - exclude(Postgres, Sqlite(3, "libsql.js")), + exclude(Postgres, Sqlite(3, "libsql.js"), MySQL("mariadb")), capabilities(GeometryFiltering) )] async fn geometric_comparison_filters(runner: Runner) -> TestResult<()> { From f7f28926514b68644d20eda22ecb6e2a7259e081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Thu, 30 Nov 2023 16:48:52 +0100 Subject: [PATCH 32/47] refactor: return conversion error in GeometryValue::from_str --- quaint/src/ast/values.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quaint/src/ast/values.rs b/quaint/src/ast/values.rs index f2185b8d6c42..53c5d7e1ca00 100644 --- a/quaint/src/ast/values.rs +++ b/quaint/src/ast/values.rs @@ -53,7 +53,7 @@ impl Display for GeometryValue { } impl FromStr for GeometryValue { - type Err = String; + type Err = Error; fn from_str(s: &str) -> Result { static EWKT_REGEX: Lazy = Lazy::new(|| Regex::new(r"^(SRID=(?P\d+);)?(?P.+)$").unwrap()); @@ -63,12 +63,12 @@ impl FromStr for GeometryValue { let srid = match capture.name("srid").map(|v| v.as_str().parse::()) { None => Ok(0), Some(Ok(srid)) => Ok(srid), - Some(Err(_)) => Err("Invalid SRID"), + Some(Err(_)) => Err(Error::builder(ErrorKind::conversion("Invalid EWKT SRID")).build()), }?; let wkt = capture.name("geometry").map(|v| v.as_str()).unwrap().to_string(); Ok(GeometryValue { srid, wkt }) }) - .ok_or("Invalid EWKT".to_string())? + .ok_or_else(|| Error::builder(ErrorKind::conversion("Invalid EWKT string")).build())? } } From 858653f25eb29f9d99de5c7933e0fb107fd18a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Sun, 4 Aug 2024 21:39:24 +0200 Subject: [PATCH 33/47] refactor: simplify spatialite CI setup --- .github/workflows/test-query-engine-template.yml | 4 +--- .github/workflows/test-schema-engine.yml | 6 +----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-query-engine-template.yml b/.github/workflows/test-query-engine-template.yml index aee9b400c51d..06a35c076662 100644 --- a/.github/workflows/test-query-engine-template.yml +++ b/.github/workflows/test-query-engine-template.yml @@ -68,16 +68,14 @@ jobs: - name: Install Spatialite if: ${{ inputs.name == 'spatialite' }} - run: sudo apt install -y libsqlite3-mod-spatialite + run: sudo apt install -y libsqlite3-mod-spatialite && echo "SPATIALITE_PATH=mod_spatialite" >> $GITHUB_ENV - run: export WORKSPACE_ROOT=$(pwd) && cargo nextest run -p query-engine-tests --partition hash:${{ matrix.partition }} --test-threads=1 if: ${{ inputs.single_threaded }} env: CLICOLOR_FORCE: 1 - SPATIALITE_PATH: ${{ inputs.name == 'spatialite' && 'mod_spatialite' || null }} - run: export WORKSPACE_ROOT=$(pwd) && cargo nextest run -p query-engine-tests --partition hash:${{ matrix.partition }} --test-threads=8 if: ${{ !inputs.single_threaded }} env: CLICOLOR_FORCE: 1 - SPATIALITE_PATH: ${{ inputs.name == 'spatialite' && 'mod_spatialite' || null }} diff --git a/.github/workflows/test-schema-engine.yml b/.github/workflows/test-schema-engine.yml index ec08cdb22c86..5fbb7829a5a6 100644 --- a/.github/workflows/test-schema-engine.yml +++ b/.github/workflows/test-schema-engine.yml @@ -127,7 +127,7 @@ jobs: - name: Install Spatialite if: ${{ matrix.database.name == 'spatialite' }} - run: sudo apt install -y libsqlite3-mod-spatialite + run: sudo apt install -y libsqlite3-mod-spatialite && echo "SPATIALITE_PATH=mod_spatialite" >> $GITHUB_ENV - name: "Start ${{ matrix.database.name }}" run: make start-${{ matrix.database.name }} @@ -137,28 +137,24 @@ jobs: env: CLICOLOR_FORCE: 1 TEST_DATABASE_URL: ${{ matrix.database.url }} - SPATIALITE_PATH: ${{ matrix.database.name == 'spatialite' && 'mod_spatialite' || null }} - run: cargo nextest run -p sql-schema-describer if: ${{ !matrix.database.single_threaded }} env: CLICOLOR_FORCE: 1 TEST_DATABASE_URL: ${{ matrix.database.url }} - SPATIALITE_PATH: ${{ matrix.database.name == 'spatialite' && 'mod_spatialite' || null }} - run: cargo nextest run -p sql-migration-tests if: ${{ !matrix.database.single_threaded }} env: CLICOLOR_FORCE: 1 TEST_DATABASE_URL: ${{ matrix.database.url }} - SPATIALITE_PATH: ${{ matrix.database.name == 'spatialite' && 'mod_spatialite' || null }} - run: cargo nextest run -p schema-engine-cli if: ${{ !matrix.database.single_threaded }} env: CLICOLOR_FORCE: 1 TEST_DATABASE_URL: ${{ matrix.database.url }} - SPATIALITE_PATH: ${{ matrix.database.name == 'spatialite' && 'mod_spatialite' || null }} # # Vitess tests From f7f25c985b11158d1b13a26b663bb4ad5347bceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Wed, 25 Sep 2024 22:39:40 +0200 Subject: [PATCH 34/47] Fix toolchain, features and lint --- psl/psl-core/src/lib.rs | 1 + quaint/Cargo.toml | 1 - query-engine/schema/src/build/utils.rs | 2 +- rust-toolchain.toml | 2 +- schema-engine/connectors/sql-schema-connector/src/flavour.rs | 2 +- .../connectors/sql-schema-connector/src/flavour/sqlite.rs | 2 +- schema-engine/sql-migration-tests/src/test_api.rs | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) diff --git a/psl/psl-core/src/lib.rs b/psl/psl-core/src/lib.rs index addb6316236c..6387ec7cfa72 100644 --- a/psl/psl-core/src/lib.rs +++ b/psl/psl-core/src/lib.rs @@ -1,6 +1,7 @@ #![doc = include_str!("../README.md")] #![deny(rust_2018_idioms, unsafe_code)] #![allow(clippy::derive_partial_eq_without_eq)] +#![allow(incomplete_features)] #![feature(repr128)] pub mod builtin_connectors; diff --git a/quaint/Cargo.toml b/quaint/Cargo.toml index c6b4ad0c1794..7ee632146d91 100644 --- a/quaint/Cargo.toml +++ b/quaint/Cargo.toml @@ -85,7 +85,6 @@ uuid.workspace = true crosstarget-utils = { path = "../libs/crosstarget-utils" } concat-idents = "1.1.5" once_cell = "1.3" -regex = "1.10.2" geozero = { version = "0.11.0", default-features = false, features = ["with-wkb", "with-geojson"] } [dev-dependencies] diff --git a/query-engine/schema/src/build/utils.rs b/query-engine/schema/src/build/utils.rs index d7ee3106d230..56491ec26742 100644 --- a/query-engine/schema/src/build/utils.rs +++ b/query-engine/schema/src/build/utils.rs @@ -66,7 +66,7 @@ pub(crate) fn simple_input_field<'a>( name: impl Into>, field_type: InputType<'a>, default_value: Option, -) -> InputField<'_> { +) -> InputField<'a> { input_field(name, vec![field_type], default_value) } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e48263a13878..9fcff05a670c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.80.1" +channel = "nightly" components = ["clippy", "rustfmt", "rust-src"] targets = [ # WASM target for serverless and edge environments. diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour.rs b/schema-engine/connectors/sql-schema-connector/src/flavour.rs index 3b2e901bcd67..f4ba1264ee80 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour.rs @@ -258,7 +258,7 @@ pub(crate) trait SqlFlavour: &'a mut self, sql: &'a str, params: &'a [quaint::prelude::Value<'a>], - ) -> BoxFuture<'_, ConnectorResult>; + ) -> BoxFuture<'a, ConnectorResult>; fn raw_cmd<'a>(&'a mut self, sql: &'a str) -> BoxFuture<'a, ConnectorResult<()>>; diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs index 2ea1e21f67fb..e3ec9775165f 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs @@ -364,7 +364,7 @@ impl SqlFlavour for SqliteFlavour { migrations: &'a [MigrationDirectory], _shadow_database_connection_string: Option, _namespaces: Option, - ) -> BoxFuture<'_, ConnectorResult> { + ) -> BoxFuture<'a, ConnectorResult> { Box::pin(async move { tracing::debug!("Applying migrations to temporary in-memory SQLite database."); let mut shadow_db_conn = Connection::new_in_memory(); diff --git a/schema-engine/sql-migration-tests/src/test_api.rs b/schema-engine/sql-migration-tests/src/test_api.rs index 6d67bbd98491..7ef12728463b 100644 --- a/schema-engine/sql-migration-tests/src/test_api.rs +++ b/schema-engine/sql-migration-tests/src/test_api.rs @@ -310,7 +310,7 @@ impl TestApi { MarkMigrationRolledBack::new(&mut self.connector, migration_name.into()) } - pub fn migration_persistence<'a>(&'a mut self) -> &mut (dyn MigrationPersistence + 'a) { + pub fn migration_persistence<'a>(&'a mut self) -> &'a mut (dyn MigrationPersistence + 'a) { &mut self.connector } From 0a879b4a4191f546c6b15019d3bd459f3a958bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Mon, 30 Sep 2024 22:52:21 +0200 Subject: [PATCH 35/47] Add JSArgType::Geometry --- query-engine/driver-adapters/src/conversion/js_arg_type.rs | 4 ++++ .../connectors/sql-schema-connector/src/sql_doc_parser.rs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/query-engine/driver-adapters/src/conversion/js_arg_type.rs b/query-engine/driver-adapters/src/conversion/js_arg_type.rs index e1ea7c1c5754..ea66d807c06f 100644 --- a/query-engine/driver-adapters/src/conversion/js_arg_type.rs +++ b/query-engine/driver-adapters/src/conversion/js_arg_type.rs @@ -40,6 +40,8 @@ pub enum JSArgType { Date, /// A time value. Time, + /// A geometry value. + Geometry, } impl core::fmt::Display for JSArgType { @@ -63,6 +65,7 @@ impl core::fmt::Display for JSArgType { JSArgType::DateTime => "DateTime", JSArgType::Date => "Date", JSArgType::Time => "Time", + JSArgType::Geometry => "Geometry", }; write!(f, "{}", s) @@ -89,5 +92,6 @@ pub fn value_to_js_arg_type(value: &quaint::Value) -> JSArgType { quaint::ValueType::DateTime(_) => JSArgType::DateTime, quaint::ValueType::Date(_) => JSArgType::Date, quaint::ValueType::Time(_) => JSArgType::Time, + quaint::ValueType::Geometry(_) | quaint::ValueType::Geography(_) => JSArgType::Geometry, } } diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_doc_parser.rs b/schema-engine/connectors/sql-schema-connector/src/sql_doc_parser.rs index 819621512b30..f6e82eb86a7e 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_doc_parser.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_doc_parser.rs @@ -218,6 +218,8 @@ fn parse_typ_opt<'a>( ScalarType::Json => ColumnType::Json, ScalarType::Bytes => ColumnType::Bytes, ScalarType::Decimal => ColumnType::Numeric, + ScalarType::GeoJson | ScalarType::Geometry => ColumnType::Geometry, + }) .map(ParsedParamType::ColumnType) .or_else(|| { From e8658e95860fbc09de3b0c187001b699f4d6964d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Mon, 30 Sep 2024 23:12:20 +0200 Subject: [PATCH 36/47] Fix: fmt --- prisma-fmt/src/lib.rs | 1 + prisma-schema-wasm/src/lib.rs | 1 + psl/psl-core/src/lib.rs | 1 + .../validation_pipeline/validations/models.rs | 4 ++-- .../connector-test-kit-rs/qe-setup/src/lib.rs | 1 + .../query-tests-setup/src/logging.rs | 1 + .../query-tests-setup/src/schema_gen/parse.rs | 1 + .../src/root_queries/aggregate.rs | 10 +++++----- .../core/src/query_document/parser.rs | 4 ++-- .../core/src/telemetry/capturing/capturer.rs | 1 + .../src/conversion/js_to_quaint.rs | 1 - query-engine/query-engine/src/server/mod.rs | 5 +---- .../schema-connector/src/namespaces.rs | 1 + .../src/sql_doc_parser.rs | 1 - .../src/sql_renderer/postgres_renderer.rs | 2 +- .../sql-migration-tests/src/assertions.rs | 20 ++++++++----------- .../src/commands/schema_push.rs | 10 ++++------ 17 files changed, 31 insertions(+), 34 deletions(-) diff --git a/prisma-fmt/src/lib.rs b/prisma-fmt/src/lib.rs index 3ec5514313bd..7f6da32e3464 100644 --- a/prisma-fmt/src/lib.rs +++ b/prisma-fmt/src/lib.rs @@ -205,6 +205,7 @@ pub fn validate(validate_params: String) -> Result<(), String> { } /// Given a list of Prisma schema files (and their locations), returns the merged schema. +/// /// This is useful for `@prisma/client` generation, where the client needs a single - potentially large - schema, /// while still allowing the user to split their schema copies into multiple files. /// Internally, it uses `[validate]`. diff --git a/prisma-schema-wasm/src/lib.rs b/prisma-schema-wasm/src/lib.rs index 8ef24cbcdfb3..46b9dee6bc68 100644 --- a/prisma-schema-wasm/src/lib.rs +++ b/prisma-schema-wasm/src/lib.rs @@ -124,6 +124,7 @@ pub fn references(schema: String, params: String) -> String { } /// This api is modelled on an LSP [hover request](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-16.md#hover-request-leftwards_arrow_with_hook). +/// /// Input and output are both JSON, the request being a `HoverParams` object /// and the response being a `Hover` object. #[wasm_bindgen] diff --git a/psl/psl-core/src/lib.rs b/psl/psl-core/src/lib.rs index 6387ec7cfa72..dac097aa17ea 100644 --- a/psl/psl-core/src/lib.rs +++ b/psl/psl-core/src/lib.rs @@ -111,6 +111,7 @@ pub fn validate_multi_file(files: &[(String, SourceFile)], connectors: Connector } /// Retrieves a Prisma schema without validating it. +/// /// You should only use this method when actually validating the schema is too expensive /// computationally or in terms of bundle size (e.g., for `query-engine-wasm`). pub fn parse_without_validation(file: SourceFile, connectors: ConnectorRegistry<'_>) -> ValidatedSchema { diff --git a/psl/psl-core/src/validate/validation_pipeline/validations/models.rs b/psl/psl-core/src/validate/validation_pipeline/validations/models.rs index a53063624b2d..bc72226dcb5d 100644 --- a/psl/psl-core/src/validate/validation_pipeline/validations/models.rs +++ b/psl/psl-core/src/validate/validation_pipeline/validations/models.rs @@ -226,12 +226,12 @@ pub(crate) fn primary_key_connector_specific(model: ModelWalker<'_>, ctx: &mut C } if primary_key.fields().len() > 1 && !ctx.has_capability(ConnectorCapability::CompoundIds) { - return ctx.push_error(DatamodelError::new_model_validation_error( + ctx.push_error(DatamodelError::new_model_validation_error( "The current connector does not support compound ids.", container_type, model.name(), primary_key.ast_attribute().span, - )); + )) } } diff --git a/query-engine/connector-test-kit-rs/qe-setup/src/lib.rs b/query-engine/connector-test-kit-rs/qe-setup/src/lib.rs index c1e6842d9bf8..5912ca6b9570 100644 --- a/query-engine/connector-test-kit-rs/qe-setup/src/lib.rs +++ b/query-engine/connector-test-kit-rs/qe-setup/src/lib.rs @@ -60,6 +60,7 @@ fn parse_configuration(datamodel: &str) -> ConnectorResult<(Datasource, String, } /// Database setup for connector-test-kit-rs with Driver Adapters. +/// /// If the external driver adapter requires a migration by means of the JavaScript runtime /// (rather than just the Schema Engine), this function will call [`ExternalInitializer::init_with_migration`]. /// Otherwise, it will call [`ExternalInitializer::init`], and then proceed with the standard diff --git a/query-engine/connector-test-kit-rs/query-tests-setup/src/logging.rs b/query-engine/connector-test-kit-rs/query-tests-setup/src/logging.rs index 5520075e6d30..bb7d15458158 100644 --- a/query-engine/connector-test-kit-rs/query-tests-setup/src/logging.rs +++ b/query-engine/connector-test-kit-rs/query-tests-setup/src/logging.rs @@ -20,6 +20,7 @@ pub fn test_tracing_subscriber(log_config: String, metrics: MetricRegistry, log_ } /// This is a temporary implementation detail for `tracing` logs in tests. +/// /// Instead of going through `std::io::stderr`, it goes through the specific /// local stderr handle used by `eprintln` and `dbg`, allowing logs to appear in /// specific test outputs for readability. diff --git a/query-engine/connector-test-kit-rs/query-tests-setup/src/schema_gen/parse.rs b/query-engine/connector-test-kit-rs/query-tests-setup/src/schema_gen/parse.rs index 509fc188883f..1b91dad84b81 100644 --- a/query-engine/connector-test-kit-rs/query-tests-setup/src/schema_gen/parse.rs +++ b/query-engine/connector-test-kit-rs/query-tests-setup/src/schema_gen/parse.rs @@ -38,6 +38,7 @@ pub fn parse_id(field: &str, json: &serde_json::Value, path: &[&str], meta: &str } /// Parses the JSON result of mutation sent to the Query Engine in order to extract the generated compound ids. +/// /// Returns a string that's already formatted to be included in another query. eg: /// { "id_1_id_2": { id_1: "my_fancy_id_1", id_2: "my_fancy_id_2" } } pub fn parse_compound_id( diff --git a/query-engine/connectors/mongodb-query-connector/src/root_queries/aggregate.rs b/query-engine/connectors/mongodb-query-connector/src/root_queries/aggregate.rs index 05ff57053e95..797e34127f8a 100644 --- a/query-engine/connectors/mongodb-query-connector/src/root_queries/aggregate.rs +++ b/query-engine/connectors/mongodb-query-connector/src/root_queries/aggregate.rs @@ -108,7 +108,7 @@ fn to_aggregation_rows( for field in fields { let meta = selection_meta.get(field.db_name()).unwrap(); - let bson = doc.remove(&format!("count_{}", field.db_name())).unwrap(); + let bson = doc.remove(format!("count_{}", field.db_name())).unwrap(); let field_val = value_from_bson(bson, meta)?; row.push(AggregationResult::Count(Some(field.clone()), field_val)); @@ -117,7 +117,7 @@ fn to_aggregation_rows( AggregationSelection::Average(fields) => { for field in fields { let meta = selection_meta.get(field.db_name()).unwrap(); - let bson = doc.remove(&format!("avg_{}", field.db_name())).unwrap(); + let bson = doc.remove(format!("avg_{}", field.db_name())).unwrap(); let field_val = value_from_bson(bson, meta)?; row.push(AggregationResult::Average(field.clone(), field_val)); @@ -126,7 +126,7 @@ fn to_aggregation_rows( AggregationSelection::Sum(fields) => { for field in fields { let meta = selection_meta.get(field.db_name()).unwrap(); - let bson = doc.remove(&format!("sum_{}", field.db_name())).unwrap(); + let bson = doc.remove(format!("sum_{}", field.db_name())).unwrap(); let field_val = value_from_bson(bson, meta)?; row.push(AggregationResult::Sum(field.clone(), field_val)); @@ -135,7 +135,7 @@ fn to_aggregation_rows( AggregationSelection::Min(fields) => { for field in fields { let meta = selection_meta.get(field.db_name()).unwrap(); - let bson = doc.remove(&format!("min_{}", field.db_name())).unwrap(); + let bson = doc.remove(format!("min_{}", field.db_name())).unwrap(); let field_val = value_from_bson(bson, meta)?; row.push(AggregationResult::Min(field.clone(), field_val)); @@ -144,7 +144,7 @@ fn to_aggregation_rows( AggregationSelection::Max(fields) => { for field in fields { let meta = selection_meta.get(field.db_name()).unwrap(); - let bson = doc.remove(&format!("max_{}", field.db_name())).unwrap(); + let bson = doc.remove(format!("max_{}", field.db_name())).unwrap(); let field_val = value_from_bson(bson, meta)?; row.push(AggregationResult::Max(field.clone(), field_val)); diff --git a/query-engine/core/src/query_document/parser.rs b/query-engine/core/src/query_document/parser.rs index a216d82fea53..f89540ff8ecc 100644 --- a/query-engine/core/src/query_document/parser.rs +++ b/query-engine/core/src/query_document/parser.rs @@ -122,10 +122,10 @@ impl QueryDocumentParser { ) .and_then(move |arguments| { if !selection.nested_selections().is_empty() && schema_field.field_type().is_scalar() { - return Err(ValidationError::selection_set_on_scalar( + Err(ValidationError::selection_set_on_scalar( selection.name().to_string(), selection_path.segments(), - )); + )) } else { // If the output type of the field is an object type of any form, validate the sub selection as well. let nested_fields = schema_field.field_type().as_object_type().map(|obj| { diff --git a/query-engine/core/src/telemetry/capturing/capturer.rs b/query-engine/core/src/telemetry/capturing/capturer.rs index d0d9886acd2b..290a2737f48a 100644 --- a/query-engine/core/src/telemetry/capturing/capturer.rs +++ b/query-engine/core/src/telemetry/capturing/capturer.rs @@ -9,6 +9,7 @@ use opentelemetry::{ }; /// Capturer determines, based on a set of settings and a trace id, how capturing is going to be handled. +/// /// Generally, both the trace id and the settings will be derived from request headers. Thus, a new /// value of this enum is created per request. #[derive(Debug, Clone)] diff --git a/query-engine/driver-adapters/src/conversion/js_to_quaint.rs b/query-engine/driver-adapters/src/conversion/js_to_quaint.rs index 0ae3e4ad2698..66b2c22e4fcd 100644 --- a/query-engine/driver-adapters/src/conversion/js_to_quaint.rs +++ b/query-engine/driver-adapters/src/conversion/js_to_quaint.rs @@ -285,7 +285,6 @@ pub fn js_value_to_quaint( ColumnType::UuidArray => js_array_to_quaint(ColumnType::Uuid, json_value, column_name), // TODO@geometry: Probably need to add geometry value handling here - unimplemented => { todo!("support column type {:?} in column {}", unimplemented, column_name) } diff --git a/query-engine/query-engine/src/server/mod.rs b/query-engine/query-engine/src/server/mod.rs index 01b61a07b6b4..0b6ef2245ad5 100644 --- a/query-engine/query-engine/src/server/mod.rs +++ b/query-engine/query-engine/src/server/mod.rs @@ -227,10 +227,7 @@ async fn metrics_handler(cx: Arc, req: Request) -> Result = match serde_json::from_slice(full_body.as_ref()) { - Ok(map) => map, - Err(_e) => HashMap::new(), - }; + let global_labels: HashMap = serde_json::from_slice(full_body.as_ref()).unwrap_or_default(); let response = if requested_json { let metrics = cx.metrics.to_json(global_labels); diff --git a/schema-engine/connectors/schema-connector/src/namespaces.rs b/schema-engine/connectors/schema-connector/src/namespaces.rs index d35f82df0f0d..f59d8cb3474f 100644 --- a/schema-engine/connectors/schema-connector/src/namespaces.rs +++ b/schema-engine/connectors/schema-connector/src/namespaces.rs @@ -1,6 +1,7 @@ //! Namespaces are used in conjunction with the MultiSchema preview feature. /// A nonempty set of namespaces. +/// /// It is assumed that the namespaces are unique. /// It is often passed around an Option for when /// the namespaces cannot be inferred, or when the MultiSchema preview diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_doc_parser.rs b/schema-engine/connectors/sql-schema-connector/src/sql_doc_parser.rs index f6e82eb86a7e..ebdf6297ceef 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_doc_parser.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_doc_parser.rs @@ -219,7 +219,6 @@ fn parse_typ_opt<'a>( ScalarType::Bytes => ColumnType::Bytes, ScalarType::Decimal => ColumnType::Numeric, ScalarType::GeoJson | ScalarType::Geometry => ColumnType::Geometry, - }) .map(ParsedParamType::ColumnType) .or_else(|| { diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/postgres_renderer.rs b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/postgres_renderer.rs index b63e6798d173..121255b11fb4 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_renderer/postgres_renderer.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_renderer/postgres_renderer.rs @@ -8,7 +8,7 @@ use crate::{ }, sql_schema_differ::{ColumnChange, ColumnChanges}, }; -use psl::builtin_connectors::{CockroachType, PostgresType, geometry::GeometryParams}; +use psl::builtin_connectors::{geometry::GeometryParams, CockroachType, PostgresType}; use sql_ddl::{ postgres::{self as ddl, PostgresIdentifier}, IndexColumn, SortOrder, diff --git a/schema-engine/sql-migration-tests/src/assertions.rs b/schema-engine/sql-migration-tests/src/assertions.rs index e32554a451b5..6a2598edfb38 100644 --- a/schema-engine/sql-migration-tests/src/assertions.rs +++ b/schema-engine/sql-migration-tests/src/assertions.rs @@ -191,13 +191,11 @@ impl SchemaAssertion { } fn print_context(&self) { - match &self.context { - Some(context) => println!("Test failure with context <{}>", context.red()), - None => {} + if let Some(context) = &self.context { + println!("Test failure with context <{}>", context.red()) } - match &self.description { - Some(description) => println!("{}: {}", "Description".bold(), description.italic()), - None => {} + if let Some(description) = &self.description { + println!("{}: {}", "Description".bold(), description.italic()) } } @@ -325,13 +323,11 @@ pub struct TableAssertion<'a> { impl<'a> TableAssertion<'a> { fn print_context(&self) { - match &self.context { - Some(context) => println!("Test failure with context <{}>", context.red()), - None => {} + if let Some(context) = &self.context { + println!("Test failure with context <{}>", context.red()) } - match &self.description { - Some(description) => println!("{}: {}", "Description".bold(), description.italic()), - None => {} + if let Some(description) = &self.description { + println!("{}: {}", "Description".bold(), description.italic()) } } diff --git a/schema-engine/sql-migration-tests/src/commands/schema_push.rs b/schema-engine/sql-migration-tests/src/commands/schema_push.rs index f7442b3a72c4..f20121f7b3aa 100644 --- a/schema-engine/sql-migration-tests/src/commands/schema_push.rs +++ b/schema-engine/sql-migration-tests/src/commands/schema_push.rs @@ -102,13 +102,11 @@ impl SchemaPushAssertion { } pub fn print_context(&self) { - match &self.context { - Some(context) => println!("Test failure with context <{}>", context.red()), - None => {} + if let Some(context) = &self.context { + println!("Test failure with context <{}>", context.red()) } - match &self.description { - Some(description) => println!("{}: {}", "Description".bold(), description.italic()), - None => {} + if let Some(description) = &self.description { + println!("{}: {}", "Description".bold(), description.italic()) } } From f74d4de072d4255d02016e208bc2b4b644cd7cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Mon, 30 Sep 2024 23:26:00 +0200 Subject: [PATCH 37/47] Fix test_native_types_multifile --- prisma-fmt/tests/native_types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma-fmt/tests/native_types.rs b/prisma-fmt/tests/native_types.rs index ef8712ed1a37..b1000b113716 100644 --- a/prisma-fmt/tests/native_types.rs +++ b/prisma-fmt/tests/native_types.rs @@ -38,7 +38,7 @@ fn test_native_types_multifile() { let result = prisma_fmt::native_types(serde_json::to_string(schema).unwrap()); let expected = expect![[ - r#"[{"name":"SmallInt","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Int"]},{"name":"Integer","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Int"]},{"name":"BigInt","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["BigInt"]},{"name":"Decimal","_number_of_args":0,"_number_of_optional_args":2,"prisma_types":["Decimal"]},{"name":"Money","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Decimal"]},{"name":"Inet","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"Oid","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Int"]},{"name":"Citext","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"Real","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Float"]},{"name":"DoublePrecision","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Float"]},{"name":"VarChar","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Char","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Text","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"ByteA","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Bytes"]},{"name":"Timestamp","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Timestamptz","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Date","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["DateTime"]},{"name":"Time","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Timetz","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Boolean","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Boolean"]},{"name":"Bit","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"VarBit","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Uuid","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"Xml","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"Json","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Json"]},{"name":"JsonB","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Json"]}]"# + r#"[{"name":"SmallInt","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Int"]},{"name":"Integer","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Int"]},{"name":"BigInt","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["BigInt"]},{"name":"Decimal","_number_of_args":0,"_number_of_optional_args":2,"prisma_types":["Decimal"]},{"name":"Money","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Decimal"]},{"name":"Inet","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"Oid","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Int"]},{"name":"Citext","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"Real","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Float"]},{"name":"DoublePrecision","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Float"]},{"name":"VarChar","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Char","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Text","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"ByteA","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Bytes"]},{"name":"Timestamp","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Timestamptz","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Date","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["DateTime"]},{"name":"Time","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Timetz","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["DateTime"]},{"name":"Boolean","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Boolean"]},{"name":"Bit","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"VarBit","_number_of_args":0,"_number_of_optional_args":1,"prisma_types":["String"]},{"name":"Uuid","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"Xml","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["String"]},{"name":"Json","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Json"]},{"name":"JsonB","_number_of_args":0,"_number_of_optional_args":0,"prisma_types":["Json"]},{"name":"Geometry","_number_of_args":0,"_number_of_optional_args":2,"prisma_types":["Geometry","GeoJson"]},{"name":"Geography","_number_of_args":0,"_number_of_optional_args":2,"prisma_types":["Geometry","GeoJson"]}]"# ]]; expected.assert_eq(&result); } From 33048e938cab7a94c382769709528566119adc75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 1 Oct 2024 00:05:42 +0200 Subject: [PATCH 38/47] Update wasm toolchain --- query-engine/query-engine-wasm/rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/query-engine/query-engine-wasm/rust-toolchain.toml b/query-engine/query-engine-wasm/rust-toolchain.toml index 5048fd2e74a6..fab6164d265c 100644 --- a/query-engine/query-engine-wasm/rust-toolchain.toml +++ b/query-engine/query-engine-wasm/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2024-05-25" +channel = "nightly-2024-09-29" components = ["clippy", "rustfmt", "rust-src"] targets = [ "wasm32-unknown-unknown", From 56644f3aafadfb9ac58113ae99b854f4ea170605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 1 Oct 2024 00:06:08 +0200 Subject: [PATCH 39/47] Fix PostGIS version test --- .../tests/describers/postgres_describer_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema-engine/sql-schema-describer/tests/describers/postgres_describer_tests.rs b/schema-engine/sql-schema-describer/tests/describers/postgres_describer_tests.rs index ba6c20be1118..3c28da38dfab 100644 --- a/schema-engine/sql-schema-describer/tests/describers/postgres_describer_tests.rs +++ b/schema-engine/sql-schema-describer/tests/describers/postgres_describer_tests.rs @@ -1290,7 +1290,7 @@ fn all_postgis_column_types_must_work(api: TestApi) { DatabaseExtension { name: "postgis", schema: "prisma-tests", - version: "3.4.2", + version: "3.4.3", relocatable: false, }, ], From a3db88e346809175b9499c5755c7e910c891ece0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 1 Oct 2024 00:06:37 +0200 Subject: [PATCH 40/47] Add Geometry handling in quaint from_type_identifier --- quaint/src/connector/column_type.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quaint/src/connector/column_type.rs b/quaint/src/connector/column_type.rs index e379eb24eeb1..15d230082225 100644 --- a/quaint/src/connector/column_type.rs +++ b/quaint/src/connector/column_type.rs @@ -181,6 +181,8 @@ impl ColumnType { ColumnType::Numeric } else if value.is_text() { ColumnType::Text + } else if value.is_geometry() { + ColumnType::Geometry } else { ColumnType::Unknown } From a16d952f056f3e8dbdf17bf40b36bd0622917354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 1 Oct 2024 01:11:04 +0200 Subject: [PATCH 41/47] Use SRID 4326 in Sqlite introspection tests --- .../tests/native_types/sqlite.rs | 128 +++++++++--------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/schema-engine/sql-introspection-tests/tests/native_types/sqlite.rs b/schema-engine/sql-introspection-tests/tests/native_types/sqlite.rs index 0309a16f3aeb..87c1cb0428e2 100644 --- a/schema-engine/sql-introspection-tests/tests/native_types/sqlite.rs +++ b/schema-engine/sql-introspection-tests/tests/native_types/sqlite.rs @@ -9,38 +9,38 @@ async fn native_spatial_type_columns_feature_on(api: &mut TestApi) -> TestResult ); SELECT - AddGeometryColumn('User', 'geometry_xy', 3857, 'GEOMETRY', 'XY', 0), - AddGeometryColumn('User', 'geometry_xyz', 3857, 'GEOMETRY', 'XYZ', 0), - AddGeometryColumn('User', 'geometry_xym', 3857, 'GEOMETRY', 'XYM', 0), - AddGeometryColumn('User', 'geometry_xyzm', 3857, 'GEOMETRY', 'XYZM', 0), - AddGeometryColumn('User', 'point_xy', 3857, 'POINT', 'XY', 0), - AddGeometryColumn('User', 'point_xyz', 3857, 'POINT', 'XYZ', 0), - AddGeometryColumn('User', 'point_xym', 3857, 'POINT', 'XYM', 0), - AddGeometryColumn('User', 'point_xyzm', 3857, 'POINT', 'XYZM', 0), - AddGeometryColumn('User', 'linestring_xy', 3857, 'LINESTRING', 'XY', 0), - AddGeometryColumn('User', 'linestring_xyz', 3857, 'LINESTRING', 'XYZ', 0), - AddGeometryColumn('User', 'linestring_xym', 3857, 'LINESTRING', 'XYM', 0), - AddGeometryColumn('User', 'linestring_xyzm', 3857, 'LINESTRING', 'XYZM', 0), - AddGeometryColumn('User', 'polygon_xy', 3857, 'POLYGON', 'XY', 0), - AddGeometryColumn('User', 'polygon_xyz', 3857, 'POLYGON', 'XYZ', 0), - AddGeometryColumn('User', 'polygon_xym', 3857, 'POLYGON', 'XYM', 0), - AddGeometryColumn('User', 'polygon_xyzm', 3857, 'POLYGON', 'XYZM', 0), - AddGeometryColumn('User', 'multipoint_xy', 3857, 'MULTIPOINT', 'XY', 0), - AddGeometryColumn('User', 'multipoint_xyz', 3857, 'MULTIPOINT', 'XYZ', 0), - AddGeometryColumn('User', 'multipoint_xym', 3857, 'MULTIPOINT', 'XYM', 0), - AddGeometryColumn('User', 'multipoint_xyzm', 3857, 'MULTIPOINT', 'XYZM', 0), - AddGeometryColumn('User', 'multilinestring_xy', 3857, 'MULTILINESTRING', 'XY', 0), - AddGeometryColumn('User', 'multilinestring_xyz', 3857, 'MULTILINESTRING', 'XYZ', 0), - AddGeometryColumn('User', 'multilinestring_xym', 3857, 'MULTILINESTRING', 'XYM', 0), - AddGeometryColumn('User', 'multilinestring_xyzm', 3857, 'MULTILINESTRING', 'XYZM', 0), - AddGeometryColumn('User', 'multipolygon_xy', 3857, 'MULTIPOLYGON', 'XY', 0), - AddGeometryColumn('User', 'multipolygon_xyz', 3857, 'MULTIPOLYGON', 'XYZ', 0), - AddGeometryColumn('User', 'multipolygon_xym', 3857, 'MULTIPOLYGON', 'XYM', 0), - AddGeometryColumn('User', 'multipolygon_xyzm', 3857, 'MULTIPOLYGON', 'XYZM', 0), - AddGeometryColumn('User', 'geometrycollection_xy', 3857, 'GEOMETRYCOLLECTION', 'XY', 0), - AddGeometryColumn('User', 'geometrycollection_xyz', 3857, 'GEOMETRYCOLLECTION', 'XYZ', 0), - AddGeometryColumn('User', 'geometrycollection_xym', 3857, 'GEOMETRYCOLLECTION', 'XYM', 0), - AddGeometryColumn('User', 'geometrycollection_xyzm', 3857, 'GEOMETRYCOLLECTION', 'XYZM', 0); + AddGeometryColumn('User', 'geometry_xy', 4326, 'GEOMETRY', 'XY', 0), + AddGeometryColumn('User', 'geometry_xyz', 4326, 'GEOMETRY', 'XYZ', 0), + AddGeometryColumn('User', 'geometry_xym', 4326, 'GEOMETRY', 'XYM', 0), + AddGeometryColumn('User', 'geometry_xyzm', 4326, 'GEOMETRY', 'XYZM', 0), + AddGeometryColumn('User', 'point_xy', 4326, 'POINT', 'XY', 0), + AddGeometryColumn('User', 'point_xyz', 4326, 'POINT', 'XYZ', 0), + AddGeometryColumn('User', 'point_xym', 4326, 'POINT', 'XYM', 0), + AddGeometryColumn('User', 'point_xyzm', 4326, 'POINT', 'XYZM', 0), + AddGeometryColumn('User', 'linestring_xy', 4326, 'LINESTRING', 'XY', 0), + AddGeometryColumn('User', 'linestring_xyz', 4326, 'LINESTRING', 'XYZ', 0), + AddGeometryColumn('User', 'linestring_xym', 4326, 'LINESTRING', 'XYM', 0), + AddGeometryColumn('User', 'linestring_xyzm', 4326, 'LINESTRING', 'XYZM', 0), + AddGeometryColumn('User', 'polygon_xy', 4326, 'POLYGON', 'XY', 0), + AddGeometryColumn('User', 'polygon_xyz', 4326, 'POLYGON', 'XYZ', 0), + AddGeometryColumn('User', 'polygon_xym', 4326, 'POLYGON', 'XYM', 0), + AddGeometryColumn('User', 'polygon_xyzm', 4326, 'POLYGON', 'XYZM', 0), + AddGeometryColumn('User', 'multipoint_xy', 4326, 'MULTIPOINT', 'XY', 0), + AddGeometryColumn('User', 'multipoint_xyz', 4326, 'MULTIPOINT', 'XYZ', 0), + AddGeometryColumn('User', 'multipoint_xym', 4326, 'MULTIPOINT', 'XYM', 0), + AddGeometryColumn('User', 'multipoint_xyzm', 4326, 'MULTIPOINT', 'XYZM', 0), + AddGeometryColumn('User', 'multilinestring_xy', 4326, 'MULTILINESTRING', 'XY', 0), + AddGeometryColumn('User', 'multilinestring_xyz', 4326, 'MULTILINESTRING', 'XYZ', 0), + AddGeometryColumn('User', 'multilinestring_xym', 4326, 'MULTILINESTRING', 'XYM', 0), + AddGeometryColumn('User', 'multilinestring_xyzm', 4326, 'MULTILINESTRING', 'XYZM', 0), + AddGeometryColumn('User', 'multipolygon_xy', 4326, 'MULTIPOLYGON', 'XY', 0), + AddGeometryColumn('User', 'multipolygon_xyz', 4326, 'MULTIPOLYGON', 'XYZ', 0), + AddGeometryColumn('User', 'multipolygon_xym', 4326, 'MULTIPOLYGON', 'XYM', 0), + AddGeometryColumn('User', 'multipolygon_xyzm', 4326, 'MULTIPOLYGON', 'XYZM', 0), + AddGeometryColumn('User', 'geometrycollection_xy', 4326, 'GEOMETRYCOLLECTION', 'XY', 0), + AddGeometryColumn('User', 'geometrycollection_xyz', 4326, 'GEOMETRYCOLLECTION', 'XYZ', 0), + AddGeometryColumn('User', 'geometrycollection_xym', 4326, 'GEOMETRYCOLLECTION', 'XYM', 0), + AddGeometryColumn('User', 'geometrycollection_xyzm', 4326, 'GEOMETRYCOLLECTION', 'XYZM', 0); "#}; api.raw_cmd(setup).await; @@ -48,38 +48,38 @@ async fn native_spatial_type_columns_feature_on(api: &mut TestApi) -> TestResult let expectation = expect![[r#" model User { id Int @id @default(autoincrement()) - geometry_xy Geometry? @db.Geometry(Geometry, 3857) - geometry_xyz Geometry? @db.Geometry(GeometryZ, 3857) - geometry_xym Geometry? @db.Geometry(GeometryM, 3857) - geometry_xyzm Geometry? @db.Geometry(GeometryZM, 3857) - point_xy Geometry? @db.Geometry(Point, 3857) - point_xyz Geometry? @db.Geometry(PointZ, 3857) - point_xym Geometry? @db.Geometry(PointM, 3857) - point_xyzm Geometry? @db.Geometry(PointZM, 3857) - linestring_xy Geometry? @db.Geometry(LineString, 3857) - linestring_xyz Geometry? @db.Geometry(LineStringZ, 3857) - linestring_xym Geometry? @db.Geometry(LineStringM, 3857) - linestring_xyzm Geometry? @db.Geometry(LineStringZM, 3857) - polygon_xy Geometry? @db.Geometry(Polygon, 3857) - polygon_xyz Geometry? @db.Geometry(PolygonZ, 3857) - polygon_xym Geometry? @db.Geometry(PolygonM, 3857) - polygon_xyzm Geometry? @db.Geometry(PolygonZM, 3857) - multipoint_xy Geometry? @db.Geometry(MultiPoint, 3857) - multipoint_xyz Geometry? @db.Geometry(MultiPointZ, 3857) - multipoint_xym Geometry? @db.Geometry(MultiPointM, 3857) - multipoint_xyzm Geometry? @db.Geometry(MultiPointZM, 3857) - multilinestring_xy Geometry? @db.Geometry(MultiLineString, 3857) - multilinestring_xyz Geometry? @db.Geometry(MultiLineStringZ, 3857) - multilinestring_xym Geometry? @db.Geometry(MultiLineStringM, 3857) - multilinestring_xyzm Geometry? @db.Geometry(MultiLineStringZM, 3857) - multipolygon_xy Geometry? @db.Geometry(MultiPolygon, 3857) - multipolygon_xyz Geometry? @db.Geometry(MultiPolygonZ, 3857) - multipolygon_xym Geometry? @db.Geometry(MultiPolygonM, 3857) - multipolygon_xyzm Geometry? @db.Geometry(MultiPolygonZM, 3857) - geometrycollection_xy Geometry? @db.Geometry(GeometryCollection, 3857) - geometrycollection_xyz Geometry? @db.Geometry(GeometryCollectionZ, 3857) - geometrycollection_xym Geometry? @db.Geometry(GeometryCollectionM, 3857) - geometrycollection_xyzm Geometry? @db.Geometry(GeometryCollectionZM, 3857) + geometry_xy Geometry? @db.Geometry(Geometry, 4326) + geometry_xyz Geometry? @db.Geometry(GeometryZ, 4326) + geometry_xym Geometry? @db.Geometry(GeometryM, 4326) + geometry_xyzm Geometry? @db.Geometry(GeometryZM, 4326) + point_xy Geometry? @db.Geometry(Point, 4326) + point_xyz Geometry? @db.Geometry(PointZ, 4326) + point_xym Geometry? @db.Geometry(PointM, 4326) + point_xyzm Geometry? @db.Geometry(PointZM, 4326) + linestring_xy Geometry? @db.Geometry(LineString, 4326) + linestring_xyz Geometry? @db.Geometry(LineStringZ, 4326) + linestring_xym Geometry? @db.Geometry(LineStringM, 4326) + linestring_xyzm Geometry? @db.Geometry(LineStringZM, 4326) + polygon_xy Geometry? @db.Geometry(Polygon, 4326) + polygon_xyz Geometry? @db.Geometry(PolygonZ, 4326) + polygon_xym Geometry? @db.Geometry(PolygonM, 4326) + polygon_xyzm Geometry? @db.Geometry(PolygonZM, 4326) + multipoint_xy Geometry? @db.Geometry(MultiPoint, 4326) + multipoint_xyz Geometry? @db.Geometry(MultiPointZ, 4326) + multipoint_xym Geometry? @db.Geometry(MultiPointM, 4326) + multipoint_xyzm Geometry? @db.Geometry(MultiPointZM, 4326) + multilinestring_xy Geometry? @db.Geometry(MultiLineString, 4326) + multilinestring_xyz Geometry? @db.Geometry(MultiLineStringZ, 4326) + multilinestring_xym Geometry? @db.Geometry(MultiLineStringM, 4326) + multilinestring_xyzm Geometry? @db.Geometry(MultiLineStringZM, 4326) + multipolygon_xy Geometry? @db.Geometry(MultiPolygon, 4326) + multipolygon_xyz Geometry? @db.Geometry(MultiPolygonZ, 4326) + multipolygon_xym Geometry? @db.Geometry(MultiPolygonM, 4326) + multipolygon_xyzm Geometry? @db.Geometry(MultiPolygonZM, 4326) + geometrycollection_xy Geometry? @db.Geometry(GeometryCollection, 4326) + geometrycollection_xyz Geometry? @db.Geometry(GeometryCollectionZ, 4326) + geometrycollection_xym Geometry? @db.Geometry(GeometryCollectionM, 4326) + geometrycollection_xyzm Geometry? @db.Geometry(GeometryCollectionZM, 4326) } "#]]; From ff3e9aa6bee36e28e9337280fb3f79e7cfd62bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 1 Oct 2024 01:30:22 +0200 Subject: [PATCH 42/47] Update test unknown_type_mysql --- .../query-engine-tests/tests/raw/sql/typed_output.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/typed_output.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/typed_output.rs index 980392054bfd..4ab5884cd460 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/typed_output.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/typed_output.rs @@ -441,10 +441,10 @@ mod typed_output { } #[connector_test(schema(generic), only(Mysql))] - async fn unknown_type_mysql(runner: Runner) -> TestResult<()> { + async fn geometry_type_mysql(runner: Runner) -> TestResult<()> { insta::assert_snapshot!( run_query!(&runner, fmt_query_raw(r#"SELECT POINT(1, 1);"#, vec![])), - @r###"{"data":{"queryRaw":{"columns":["POINT(1, 1)"],"types":["bytes"],"rows":[["AAAAAAEBAAAAAAAAAAAA8D8AAAAAAADwPw=="]]}}}"### + @r###"{"data":{"queryRaw":{"columns":["POINT(1, 1)"],"types":["geometry"],"rows":[["POINT(1 1)"]}}}"### ); Ok(()) From 9f205edb71f8f82d3bbd291bcf995d3623617b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 1 Oct 2024 01:45:39 +0200 Subject: [PATCH 43/47] Add GeoJson handling in MongoDB into_bson --- .../connectors/mongodb-query-connector/src/value.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/query-engine/connectors/mongodb-query-connector/src/value.rs b/query-engine/connectors/mongodb-query-connector/src/value.rs index 58533cee4120..7c8a675818d9 100644 --- a/query-engine/connectors/mongodb-query-connector/src/value.rs +++ b/query-engine/connectors/mongodb-query-connector/src/value.rs @@ -199,6 +199,16 @@ impl IntoBson for (&MongoDbType, PrismaValue) { })? } + // Geometry + (MongoDbType::Json, PrismaValue::GeoJson(json)) => { + let val: Value = serde_json::from_str(&json)?; + + Bson::try_from(val).map_err(|_| MongoError::ConversionError { + from: "Stringified GeoJSON".to_owned(), + to: "Mongo BSON (extJSON)".to_owned(), + })? + } + // Unhandled conversions (mdb_type, p_val) => { return Err(MongoError::ConversionError { @@ -271,7 +281,7 @@ impl IntoBson for (&TypeIdentifier, PrismaValue) { (TypeIdentifier::Geometry(GeometryFormat::GeoJSON), PrismaValue::GeoJson(json)) => { let val: Value = serde_json::from_str(&json)?; Bson::try_from(val).map_err(|_| MongoError::ConversionError { - from: "Stringified JSON".to_owned(), + from: "Stringified GeoJSON".to_owned(), to: "Mongo BSON (extJSON)".to_owned(), })? } From 7d6d9603adb42c7048e630d358c1cf604839bf55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 1 Oct 2024 01:56:21 +0200 Subject: [PATCH 44/47] Fix geometry_type_mysql --- .../query-engine-tests/tests/raw/sql/typed_output.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/typed_output.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/typed_output.rs index 4ab5884cd460..6edda2a7d396 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/typed_output.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/raw/sql/typed_output.rs @@ -444,7 +444,7 @@ mod typed_output { async fn geometry_type_mysql(runner: Runner) -> TestResult<()> { insta::assert_snapshot!( run_query!(&runner, fmt_query_raw(r#"SELECT POINT(1, 1);"#, vec![])), - @r###"{"data":{"queryRaw":{"columns":["POINT(1, 1)"],"types":["geometry"],"rows":[["POINT(1 1)"]}}}"### + @r###"{"data":{"queryRaw":{"columns":["POINT(1, 1)"],"types":["geometry"],"rows":[["POINT(1 1)"]]}}}"### ); Ok(()) From 2b35a946bee4b585af1e424f8bdee1cea20bcc57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 1 Oct 2024 02:09:16 +0200 Subject: [PATCH 45/47] Use SRID 4326 in spatialite describer tests --- .../describers/sqlite_describer_tests.rs | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/schema-engine/sql-schema-describer/tests/describers/sqlite_describer_tests.rs b/schema-engine/sql-schema-describer/tests/describers/sqlite_describer_tests.rs index f75d7df8803c..05619cc3b71a 100644 --- a/schema-engine/sql-schema-describer/tests/describers/sqlite_describer_tests.rs +++ b/schema-engine/sql-schema-describer/tests/describers/sqlite_describer_tests.rs @@ -219,38 +219,38 @@ fn spatialite_column_types_must_work(api: TestApi) { ); SELECT - AddGeometryColumn('User', 'geometry_xy', 3857, 'GEOMETRY', 'XY', 0), - AddGeometryColumn('User', 'geometry_xyz', 3857, 'GEOMETRY', 'XYZ', 0), - AddGeometryColumn('User', 'geometry_xym', 3857, 'GEOMETRY', 'XYM', 0), - AddGeometryColumn('User', 'geometry_xyzm', 3857, 'GEOMETRY', 'XYZM', 0), - AddGeometryColumn('User', 'point_xy', 3857, 'POINT', 'XY', 0), - AddGeometryColumn('User', 'point_xyz', 3857, 'POINT', 'XYZ', 0), - AddGeometryColumn('User', 'point_xym', 3857, 'POINT', 'XYM', 0), - AddGeometryColumn('User', 'point_xyzm', 3857, 'POINT', 'XYZM', 0), - AddGeometryColumn('User', 'linestring_xy', 3857, 'LINESTRING', 'XY', 0), - AddGeometryColumn('User', 'linestring_xyz', 3857, 'LINESTRING', 'XYZ', 0), - AddGeometryColumn('User', 'linestring_xym', 3857, 'LINESTRING', 'XYM', 0), - AddGeometryColumn('User', 'linestring_xyzm', 3857, 'LINESTRING', 'XYZM', 0), - AddGeometryColumn('User', 'polygon_xy', 3857, 'POLYGON', 'XY', 0), - AddGeometryColumn('User', 'polygon_xyz', 3857, 'POLYGON', 'XYZ', 0), - AddGeometryColumn('User', 'polygon_xym', 3857, 'POLYGON', 'XYM', 0), - AddGeometryColumn('User', 'polygon_xyzm', 3857, 'POLYGON', 'XYZM', 0), - AddGeometryColumn('User', 'multipoint_xy', 3857, 'MULTIPOINT', 'XY', 0), - AddGeometryColumn('User', 'multipoint_xyz', 3857, 'MULTIPOINT', 'XYZ', 0), - AddGeometryColumn('User', 'multipoint_xym', 3857, 'MULTIPOINT', 'XYM', 0), - AddGeometryColumn('User', 'multipoint_xyzm', 3857, 'MULTIPOINT', 'XYZM', 0), - AddGeometryColumn('User', 'multilinestring_xy', 3857, 'MULTILINESTRING', 'XY', 0), - AddGeometryColumn('User', 'multilinestring_xyz', 3857, 'MULTILINESTRING', 'XYZ', 0), - AddGeometryColumn('User', 'multilinestring_xym', 3857, 'MULTILINESTRING', 'XYM', 0), - AddGeometryColumn('User', 'multilinestring_xyzm', 3857, 'MULTILINESTRING', 'XYZM', 0), - AddGeometryColumn('User', 'multipolygon_xy', 3857, 'MULTIPOLYGON', 'XY', 0), - AddGeometryColumn('User', 'multipolygon_xyz', 3857, 'MULTIPOLYGON', 'XYZ', 0), - AddGeometryColumn('User', 'multipolygon_xym', 3857, 'MULTIPOLYGON', 'XYM', 0), - AddGeometryColumn('User', 'multipolygon_xyzm', 3857, 'MULTIPOLYGON', 'XYZM', 0), - AddGeometryColumn('User', 'geometrycollection_xy', 3857, 'GEOMETRYCOLLECTION', 'XY', 0), - AddGeometryColumn('User', 'geometrycollection_xyz', 3857, 'GEOMETRYCOLLECTION', 'XYZ', 0), - AddGeometryColumn('User', 'geometrycollection_xym', 3857, 'GEOMETRYCOLLECTION', 'XYM', 0), - AddGeometryColumn('User', 'geometrycollection_xyzm', 3857, 'GEOMETRYCOLLECTION', 'XYZM', 0); + AddGeometryColumn('User', 'geometry_xy', 4326, 'GEOMETRY', 'XY', 0), + AddGeometryColumn('User', 'geometry_xyz', 4326, 'GEOMETRY', 'XYZ', 0), + AddGeometryColumn('User', 'geometry_xym', 4326, 'GEOMETRY', 'XYM', 0), + AddGeometryColumn('User', 'geometry_xyzm', 4326, 'GEOMETRY', 'XYZM', 0), + AddGeometryColumn('User', 'point_xy', 4326, 'POINT', 'XY', 0), + AddGeometryColumn('User', 'point_xyz', 4326, 'POINT', 'XYZ', 0), + AddGeometryColumn('User', 'point_xym', 4326, 'POINT', 'XYM', 0), + AddGeometryColumn('User', 'point_xyzm', 4326, 'POINT', 'XYZM', 0), + AddGeometryColumn('User', 'linestring_xy', 4326, 'LINESTRING', 'XY', 0), + AddGeometryColumn('User', 'linestring_xyz', 4326, 'LINESTRING', 'XYZ', 0), + AddGeometryColumn('User', 'linestring_xym', 4326, 'LINESTRING', 'XYM', 0), + AddGeometryColumn('User', 'linestring_xyzm', 4326, 'LINESTRING', 'XYZM', 0), + AddGeometryColumn('User', 'polygon_xy', 4326, 'POLYGON', 'XY', 0), + AddGeometryColumn('User', 'polygon_xyz', 4326, 'POLYGON', 'XYZ', 0), + AddGeometryColumn('User', 'polygon_xym', 4326, 'POLYGON', 'XYM', 0), + AddGeometryColumn('User', 'polygon_xyzm', 4326, 'POLYGON', 'XYZM', 0), + AddGeometryColumn('User', 'multipoint_xy', 4326, 'MULTIPOINT', 'XY', 0), + AddGeometryColumn('User', 'multipoint_xyz', 4326, 'MULTIPOINT', 'XYZ', 0), + AddGeometryColumn('User', 'multipoint_xym', 4326, 'MULTIPOINT', 'XYM', 0), + AddGeometryColumn('User', 'multipoint_xyzm', 4326, 'MULTIPOINT', 'XYZM', 0), + AddGeometryColumn('User', 'multilinestring_xy', 4326, 'MULTILINESTRING', 'XY', 0), + AddGeometryColumn('User', 'multilinestring_xyz', 4326, 'MULTILINESTRING', 'XYZ', 0), + AddGeometryColumn('User', 'multilinestring_xym', 4326, 'MULTILINESTRING', 'XYM', 0), + AddGeometryColumn('User', 'multilinestring_xyzm', 4326, 'MULTILINESTRING', 'XYZM', 0), + AddGeometryColumn('User', 'multipolygon_xy', 4326, 'MULTIPOLYGON', 'XY', 0), + AddGeometryColumn('User', 'multipolygon_xyz', 4326, 'MULTIPOLYGON', 'XYZ', 0), + AddGeometryColumn('User', 'multipolygon_xym', 4326, 'MULTIPOLYGON', 'XYM', 0), + AddGeometryColumn('User', 'multipolygon_xyzm', 4326, 'MULTIPOLYGON', 'XYZM', 0), + AddGeometryColumn('User', 'geometrycollection_xy', 4326, 'GEOMETRYCOLLECTION', 'XY', 0), + AddGeometryColumn('User', 'geometrycollection_xyz', 4326, 'GEOMETRYCOLLECTION', 'XYZ', 0), + AddGeometryColumn('User', 'geometrycollection_xym', 4326, 'GEOMETRYCOLLECTION', 'XYM', 0), + AddGeometryColumn('User', 'geometrycollection_xyzm', 4326, 'GEOMETRYCOLLECTION', 'XYZM', 0); "#; api.raw_cmd(sql); let expectation = expect![[r#" From fffdaccc12efd4a458de408f179f47d3fa68bd53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 1 Oct 2024 02:48:01 +0200 Subject: [PATCH 46/47] Add cfd1 and libsql.js.wasm to spatialite tests exclude list --- .../tests/queries/filters/geometry_filter.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs index f0442f889812..c60f8d0ce316 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/queries/filters/geometry_filter.rs @@ -101,12 +101,12 @@ mod geometry_filter_spec { Ok(()) } - #[connector_test(schema(schema), exclude(Postgres, Sqlite(3, "libsql.js")))] + #[connector_test(schema(schema), exclude(Postgres, Sqlite(3, "cfd1", "libsql.js", "libsql.js.wasm")))] async fn basic_where(runner: Runner) -> TestResult<()> { basic_where_test(runner).await } - #[connector_test(schema(schema), exclude(Postgres, Sqlite(3, "libsql.js")))] + #[connector_test(schema(schema), exclude(Postgres, Sqlite(3, "cfd1", "libsql.js", "libsql.js.wasm")))] async fn where_shorthands(runner: Runner) -> TestResult<()> { where_shorthands_test(runner).await } @@ -115,7 +115,7 @@ mod geometry_filter_spec { // see discussion here: https://github.com/prisma/prisma-engines/pull/4208#issuecomment-1828997865 #[connector_test( schema(schema), - exclude(Postgres, Sqlite(3, "libsql.js"), MySQL("mariadb")), + exclude(Postgres, Sqlite(3, "cfd1", "libsql.js", "libsql.js.wasm"), MySQL("mariadb")), capabilities(GeometryFiltering) )] async fn geometric_comparison_filters(runner: Runner) -> TestResult<()> { From 0c912c0fc8385f167393d89283bebcf9e20fa13b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aure=CC=80le=20Nitoref?= Date: Tue, 1 Oct 2024 09:56:48 +0200 Subject: [PATCH 47/47] Add cfd1 and libsql.js.wasm to other spatialite tests exclude list --- .../tests/writes/top_level_mutations/create.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs index dafae1cec5c9..9ac52a0e8e0d 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/writes/top_level_mutations/create.rs @@ -475,7 +475,7 @@ mod geometry_create { schema.to_owned() } - #[connector_test(schema(geometry_opt), exclude(Postgres, Sqlite(3, "libsql.js")))] + #[connector_test(schema(geometry_opt), exclude(Postgres, Sqlite(3, "cfd1", "libsql.js", "libsql.js.wasm")))] async fn create_geometry(runner: Runner) -> TestResult<()> { create_geometry_test(runner).await }