From a588e1296288b79df0204397a441f2b3d891b9d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A8le=20Nitoref?= Date: Thu, 3 Aug 2023 18:52:27 +0200 Subject: [PATCH] feat: geospatial types support --- .github/workflows/quaint.yml | 8 +- .github/workflows/schema-engine.yml | 2 + .test_database_urls/postgis_15 | 1 + Cargo.lock | 91 +++ Cargo.toml | 1 + 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 | 11 +- quaint/src/ast.rs | 4 +- quaint/src/ast/column.rs | 6 +- quaint/src/ast/compare.rs | 350 ++++++++- quaint/src/ast/expression.rs | 91 +++ quaint/src/ast/function.rs | 24 + quaint/src/ast/function/geom_as_text.rs | 31 + quaint/src/ast/function/geom_from_text.rs | 41 ++ quaint/src/ast/row.rs | 86 +++ quaint/src/ast/values.rs | 89 ++- quaint/src/connector/mssql/conversion.rs | 4 + quaint/src/connector/mysql/conversion.rs | 27 +- quaint/src/connector/postgres/conversion.rs | 35 + quaint/src/connector/sqlite.rs | 17 +- quaint/src/connector/sqlite/conversion.rs | 32 + quaint/src/connector/type_identifier.rs | 1 + quaint/src/serde.rs | 10 + quaint/src/visitor.rs | 90 ++- quaint/src/visitor/mssql.rs | 143 +++- quaint/src/visitor/mysql.rs | 77 ++ quaint/src/visitor/postgres.rs | 63 ++ quaint/src/visitor/sqlite.rs | 71 +- .../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 + .../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/query-connector/src/compare.rs | 16 + .../src/filter/scalar/compare.rs | 132 ++++ .../src/filter/scalar/condition/mod.rs | 12 + .../connectors/sql-query-connector/Cargo.toml | 2 + .../src/filter_conversion.rs | 16 + .../src/model_extensions/scalar_field.rs | 44 ++ .../src/query_builder/read.rs | 14 +- .../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/prisma-models/src/field/mod.rs | 11 + .../prisma-models/src/field/scalar.rs | 5 + .../prisma-models/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 | 68 +- .../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 +- 154 files changed, 8589 insertions(+), 344 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 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/quaint.yml b/.github/workflows/quaint.yml index d4a840728272..e866076bbd16 100644 --- a/.github/workflows/quaint.yml +++ b/.github/workflows/quaint.yml @@ -17,13 +17,13 @@ jobs: features: - "--lib --features=all" - "--lib --no-default-features --features=sqlite" - - "--lib --no-default-features --features=sqlite --features=chrono --features=json --features=uuid --features=pooled --features=serde-support --features=bigdecimal" + - "--lib --no-default-features --features=sqlite --features=chrono --features=json --features=geometry --features=uuid --features=pooled --features=serde-support --features=bigdecimal" - "--lib --no-default-features --features=postgresql" - - "--lib --no-default-features --features=postgresql --features=chrono --features=json --features=uuid --features=pooled --features=serde-support --features=bigdecimal" + - "--lib --no-default-features --features=postgresql --features=chrono --features=json --features=geometry --features=uuid --features=pooled --features=serde-support --features=bigdecimal" - "--lib --no-default-features --features=mysql" - - "--lib --no-default-features --features=mysql --features=chrono --features=json --features=uuid --features=pooled --features=serde-support --features=bigdecimal" + - "--lib --no-default-features --features=mysql --features=chrono --features=json --features=geometry --features=uuid --features=pooled --features=serde-support --features=bigdecimal" - "--lib --no-default-features --features=mssql" - - "--lib --no-default-features --features=mssql --features=chrono --features=json --features=uuid --features=pooled --features=serde-support --features=bigdecimal" + - "--lib --no-default-features --features=mssql --features=chrono --features=json --features=geometry --features=uuid --features=pooled --features=serde-support --features=bigdecimal" - "--doc --features=all" env: TEST_MYSQL: "mysql://root:prisma@localhost:3306/prisma" diff --git a/.github/workflows/schema-engine.yml b/.github/workflows/schema-engine.yml index 20c263dc17d0..c30bcfbadef8 100644 --- a/.github/workflows/schema-engine.yml +++ b/.github/workflows/schema-engine.yml @@ -84,6 +84,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 c9fb7e1e1498..6c4fae29d998 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" @@ -1486,6 +1495,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" @@ -2036,6 +2096,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" @@ -2724,6 +2790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3460,6 +3527,7 @@ dependencies = [ "connection-string", "either", "futures", + "geozero 0.11.0", "hex", "lru-cache", "metrics 0.18.1", @@ -3467,9 +3535,11 @@ dependencies = [ "mysql_async", "native-tls", "num_cpus", + "once_cell", "percent-encoding", "postgres-native-tls", "postgres-types", + "regex", "rusqlite", "serde_json", "sqlformat", @@ -3531,6 +3601,7 @@ dependencies = [ "cuid", "enumflags2", "futures", + "geojson", "indexmap 1.9.3", "itertools", "lru 0.7.8", @@ -4287,6 +4358,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" @@ -4689,6 +4766,7 @@ dependencies = [ "chrono", "cuid", "futures", + "geozero 0.10.0", "itertools", "once_cell", "opentelemetry", @@ -4698,6 +4776,7 @@ dependencies = [ "quaint", "query-connector", "rand 0.7.3", + "regex", "serde", "serde_json", "thiserror", @@ -6066,6 +6145,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/Cargo.toml b/Cargo.toml index 02e1f7373d04..1cdf9fbfd53d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ features = [ "postgresql", "sqlite", "uuid", + "geometry", ] [profile.dev.package.backtrace] diff --git a/Makefile b/Makefile index 4645b32328e6..319bb90b30b0 100644 --- a/Makefile +++ b/Makefile @@ -119,6 +119,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 -d --remove-orphans cockroach_23_1 diff --git a/docker-compose.yml b/docker-compose.yml index 5f637f7d10a6..a434d601e5ae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -132,6 +132,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 f104d2710f25..f748e6bd02c1 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; @@ -36,7 +36,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 1c698a644b58..a58a3e90c760 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 | @@ -70,6 +77,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; @@ -135,6 +156,9 @@ impl Connector for CockroachDatamodelConnector { CockroachType::JsonB => ScalarType::Json, // Bytes CockroachType::Bytes => ScalarType::Bytes, + // Geometry + CockroachType::Geometry(_) => ScalarType::Geometry, + CockroachType::Geography(_) => ScalarType::Geometry, } } @@ -168,7 +192,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, ) { @@ -188,6 +212,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 d4688438d299..546bcb682ae5 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 | @@ -75,6 +87,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 { @@ -145,6 +160,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, @@ -206,6 +230,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 6cd160c40670..418b2e1a6b51 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 | @@ -79,6 +86,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. @@ -322,6 +343,9 @@ impl Connector for PostgresDatamodelConnector { JsonB => ScalarType::Json, // Bytes ByteA => ScalarType::Bytes, + // Geometry + Geometry(_) => ScalarType::Geometry, + Geography(_) => ScalarType::Geometry, } } @@ -351,7 +375,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, ) { @@ -374,6 +398,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)) + } _ => (), } } @@ -566,6 +600,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 6b66a6c524ca..eb6a855b0abb 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 | @@ -30,6 +39,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 { @@ -61,24 +87,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] { @@ -86,21 +150,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 7bfee8c02916..5a9d05fd5e9e 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 ccda5e087360..9dcf035dcfe2 100644 --- a/quaint/Cargo.toml +++ b/quaint/Cargo.toml @@ -32,6 +32,7 @@ expose-drivers = [] all = [ "chrono", "json", + "geometry", "mssql", "mysql", "pooled", @@ -60,6 +61,7 @@ postgresql = [ ] json = ["serde_json", "base64"] +geometry = ["geozero", "regex", "once_cell"] mssql = ["tiberius", "uuid", "chrono", "tokio-util", "tokio/time", "tokio/net", "either"] mysql = ["mysql_async", "tokio/time", "lru-cache"] pooled = ["mobc"] @@ -88,10 +90,13 @@ lru-cache = { version = "0.1", optional = true } serde_json = { version = "1.0.48", optional = true, features = ["float_roundtrip"] } native-tls = { version = "0.2", optional = true } bit-vec = { version = "0.6.1", optional = true } -bytes = { version = "1.0", optional = true } mobc = { version = "0.8", optional = true } serde = { version = "1.0", optional = true } sqlformat = { version = "0.2.0", optional = true } +bytes = { version = "1.0", optional = true } +regex = { version = "1", optional = true } +once_cell = { version = "1.3", optional = true } +geozero = { version = "0.11.0", optional = true, default-features = false, features = ["with-wkb", "with-geojson"] } [dev-dependencies] once_cell = "1.3" @@ -115,8 +120,10 @@ optional = true branch = "vendored-openssl" [dependencies.rusqlite] +# git = "https://github.com/rusqlite/rusqlite" +# rev = "714ce2e17117b2d46485aa12479f8e1802b78ba0" version = "0.29" -features = ["chrono", "bundled", "column_decltype"] +features = ["chrono", "bundled", "load_extension", "column_decltype"] 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 03f9bc234cd2..d186aa842bbd 100644 --- a/quaint/src/ast.rs +++ b/quaint/src/ast.rs @@ -30,7 +30,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}; @@ -51,5 +51,7 @@ pub use select::Select; pub use table::*; pub use union::Union; pub use update::*; +#[cfg(feature = "geometry")] +pub use values::GeometryValue; pub(crate) use values::Params; pub use values::{IntoRaw, Raw, Value, Values}; diff --git a/quaint/src/ast/column.rs b/quaint/src/ast/column.rs index 87342bd56bcb..4dda7649b7be 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, } /// Defines a default value for a `Column`. diff --git a/quaint/src/ast/compare.rs b/quaint/src/ast/compare.rs index d92843a23557..35bdfc759192 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,9 @@ pub enum Compare<'a> { /// All json related comparators #[cfg(all(feature = "json", any(feature = "postgresql", feature = "mysql")))] JsonCompare(JsonCompare<'a>), + /// All geometry related comparators + #[cfg(feature = "geometry")] + GeometryCompare(GeometryCompare<'a>), /// `left` @@ to_tsquery(`value`) #[cfg(feature = "postgresql")] Matches(Box>, Cow<'a, str>), @@ -53,6 +56,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 +803,196 @@ 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(()) + /// # } + /// ``` + #[cfg(feature = "geometry")] + 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(()) + /// # } + /// ``` + #[cfg(feature = "geometry")] + 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(()) + /// # } + /// ``` + #[cfg(feature = "geometry")] + 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(()) + /// # } + /// ``` + #[cfg(feature = "geometry")] + 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(()) + /// # } + /// ``` + #[cfg(feature = "geometry")] + 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(()) + /// # } + /// ``` + #[cfg(feature = "geometry")] + 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(()) + /// # } + /// ``` + #[cfg(feature = "geometry")] + 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(()) + /// # } + /// ``` + #[cfg(feature = "geometry")] + 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(()) + /// # } + /// ``` + #[cfg(feature = "geometry")] + 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(()) + /// # } + /// ``` + #[cfg(feature = "geometry")] + 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 +1321,98 @@ where val.json_type_not_equals(json_type) } + #[allow(clippy::wrong_self_convention)] + #[cfg(feature = "geometry")] + 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)] + #[cfg(feature = "geometry")] + 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)] + #[cfg(feature = "geometry")] + 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)] + #[cfg(feature = "geometry")] + 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() + } + + #[cfg(feature = "geometry")] + 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) + } + + #[cfg(feature = "geometry")] + 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) + } + + #[cfg(feature = "geometry")] + 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) + } + + #[cfg(feature = "geometry")] + 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) + } + + #[cfg(feature = "geometry")] + 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) + } + + #[cfg(feature = "geometry")] + 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 b3993abc523b..2c7aa35b3056 100644 --- a/quaint/src/ast/expression.rs +++ b/quaint/src/ast/expression.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "geometry")] +use super::compare::{GeometryCompare, GeometryType}; #[cfg(all(feature = "json", any(feature = "postgresql", feature = "mysql")))] use super::compare::{JsonCompare, JsonType}; use crate::ast::*; @@ -93,6 +95,12 @@ impl<'a> Expression<'a> { self.kind.is_xml_value() } + #[allow(dead_code)] + #[cfg(feature = "geometry")] + 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(_)) @@ -226,6 +234,21 @@ impl<'a> ExpressionKind<'a> { _ => false, } } + #[cfg(feature = "geometry")] + pub(crate) fn is_geometry_expr(&self) -> bool { + match self { + Self::Parameterized(Value::Geometry(_)) => true, + Self::Parameterized(Value::Geography(_)) => true, + Self::RawValue(Raw(Value::Geometry(_))) => true, + Self::RawValue(Raw(Value::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. @@ -499,6 +522,74 @@ impl<'a> Comparable<'a> for Expression<'a> { Compare::JsonCompare(JsonCompare::TypeNotEquals(Box::new(self), json_type.into())) } + #[cfg(feature = "geometry")] + fn geometry_is_empty(self) -> Compare<'a> { + Compare::GeometryCompare(GeometryCompare::Empty(Box::new(self))) + } + + #[cfg(feature = "geometry")] + fn geometry_is_not_empty(self) -> Compare<'a> { + Compare::GeometryCompare(GeometryCompare::NotEmpty(Box::new(self))) + } + + #[cfg(feature = "geometry")] + fn geometry_is_valid(self) -> Compare<'a> { + Compare::GeometryCompare(GeometryCompare::Valid(Box::new(self))) + } + + #[cfg(feature = "geometry")] + fn geometry_is_not_valid(self) -> Compare<'a> { + Compare::GeometryCompare(GeometryCompare::NotValid(Box::new(self))) + } + + #[cfg(feature = "geometry")] + fn geometry_type_equals(self, geometry_type: T) -> Compare<'a> + where + T: Into>, + { + Compare::GeometryCompare(GeometryCompare::TypeEquals(Box::new(self), geometry_type.into())) + } + + #[cfg(feature = "geometry")] + fn geometry_type_not_equals(self, geometry_type: T) -> Compare<'a> + where + T: Into>, + { + Compare::GeometryCompare(GeometryCompare::TypeNotEquals(Box::new(self), geometry_type.into())) + } + + #[cfg(feature = "geometry")] + fn geometry_within(self, geom: T) -> Compare<'a> + where + T: Into>, + { + Compare::GeometryCompare(GeometryCompare::Within(Box::new(self), Box::new(geom.into()))) + } + + #[cfg(feature = "geometry")] + fn geometry_not_within(self, geom: T) -> Compare<'a> + where + T: Into>, + { + Compare::GeometryCompare(GeometryCompare::NotWithin(Box::new(self), Box::new(geom.into()))) + } + + #[cfg(feature = "geometry")] + fn geometry_intersects(self, geom: T) -> Compare<'a> + where + T: Into>, + { + Compare::GeometryCompare(GeometryCompare::Intersects(Box::new(self), Box::new(geom.into()))) + } + + #[cfg(feature = "geometry")] + 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 123b95566b30..e818565eeb05 100644 --- a/quaint/src/ast/function.rs +++ b/quaint/src/ast/function.rs @@ -20,6 +20,11 @@ mod search; mod sum; mod upper; +#[cfg(feature = "geometry")] +mod geom_as_text; +#[cfg(feature = "geometry")] +mod geom_from_text; + #[cfg(feature = "mysql")] mod uuid; @@ -45,6 +50,11 @@ pub use search::*; pub use sum::*; pub use upper::*; +#[cfg(feature = "geometry")] +pub use geom_as_text::*; +#[cfg(feature = "geometry")] +pub use geom_from_text::*; + #[cfg(feature = "mysql")] pub use self::uuid::*; @@ -72,6 +82,13 @@ impl<'a> Function<'a> { _ => false, } } + pub fn returns_geometry(&self) -> bool { + match self.typ_ { + #[cfg(feature = "geometry")] + FunctionType::GeomFromText(_) => true, + _ => false, + } + } } /// A database function type @@ -108,6 +125,10 @@ pub(crate) enum FunctionType<'a> { UuidToBinSwapped, #[cfg(feature = "mysql")] Uuid, + #[cfg(feature = "geometry")] + GeomAsText(GeomAsText<'a>), + #[cfg(feature = "geometry")] + GeomFromText(GeomFromText<'a>), } impl<'a> Aliasable<'a> for Function<'a> { @@ -156,3 +177,6 @@ function!( Coalesce, Concat ); + +#[cfg(feature = "geometry")] +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 3022b9127758..4f0e22d0c2fa 100644 --- a/quaint/src/ast/row.rs +++ b/quaint/src/ast/row.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "geometry")] +use super::compare::GeometryType; #[cfg(all(feature = "json", any(feature = "postgresql", feature = "mysql")))] use super::compare::JsonType; use crate::ast::{Comparable, Compare, Expression}; @@ -363,6 +365,90 @@ impl<'a> Comparable<'a> for Row<'a> { value.json_type_not_equals(json_type) } + #[cfg(feature = "geometry")] + #[allow(clippy::wrong_self_convention)] + fn geometry_is_empty(self) -> Compare<'a> { + let value: Expression<'a> = self.into(); + value.geometry_is_empty() + } + + #[cfg(feature = "geometry")] + #[allow(clippy::wrong_self_convention)] + fn geometry_is_not_empty(self) -> Compare<'a> { + let value: Expression<'a> = self.into(); + value.geometry_is_not_empty() + } + + #[cfg(feature = "geometry")] + #[allow(clippy::wrong_self_convention)] + fn geometry_is_valid(self) -> Compare<'a> { + let value: Expression<'a> = self.into(); + value.geometry_is_valid() + } + + #[cfg(feature = "geometry")] + #[allow(clippy::wrong_self_convention)] + fn geometry_is_not_valid(self) -> Compare<'a> { + let value: Expression<'a> = self.into(); + value.geometry_is_not_valid() + } + + #[cfg(feature = "geometry")] + fn geometry_within(self, geom: T) -> Compare<'a> + where + T: Into>, + { + let value: Expression<'a> = self.into(); + value.geometry_within(geom) + } + + #[cfg(feature = "geometry")] + fn geometry_not_within(self, geom: T) -> Compare<'a> + where + T: Into>, + { + let value: Expression<'a> = self.into(); + value.geometry_not_within(geom) + } + + #[cfg(feature = "geometry")] + fn geometry_intersects(self, geom: T) -> Compare<'a> + where + T: Into>, + { + let value: Expression<'a> = self.into(); + value.geometry_intersects(geom) + } + + #[cfg(feature = "geometry")] + fn geometry_not_intersects(self, geom: T) -> Compare<'a> + where + T: Into>, + { + let value: Expression<'a> = self.into(); + value.geometry_not_intersects(geom) + } + + #[cfg(feature = "geometry")] + 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) + } + + #[cfg(feature = "geometry")] + 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 5296146646a7..7a214feecbf1 100644 --- a/quaint/src/ast/values.rs +++ b/quaint/src/ast/values.rs @@ -16,6 +16,13 @@ use std::{ #[cfg(feature = "uuid")] use uuid::Uuid; +#[cfg(feature = "geometry")] +use once_cell::sync::Lazy; +#[cfg(feature = "geometry")] +use regex::Regex; +#[cfg(feature = "geometry")] +use std::fmt::{Display, Formatter}; + /// A value written to the query as-is without parameterization. #[derive(Debug, Clone, PartialEq)] pub struct Raw<'a>(pub(crate) Value<'a>); @@ -36,6 +43,46 @@ where } } +#[cfg(feature = "geometry")] +#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] +pub struct GeometryValue { + pub wkt: String, + pub srid: i32, +} + +#[cfg(feature = "geometry")] +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) + } +} + +#[cfg(feature = "geometry")] +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 value we must parameterize for the prepared statement. Null values should be /// defined by their corresponding type variants with a `None` value for best /// compatibility. @@ -61,14 +108,22 @@ pub enum Value<'a> { Char(Option), /// An array value (PostgreSQL). Array(Option>>), - /// A numeric value. #[cfg(feature = "bigdecimal")] #[cfg_attr(feature = "docs", doc(cfg(feature = "bigdecimal")))] + /// A numeric value. Numeric(Option), #[cfg(feature = "json")] #[cfg_attr(feature = "docs", doc(cfg(feature = "json")))] /// A JSON value. Json(Option), + #[cfg(feature = "geometry")] + #[cfg_attr(feature = "docs", doc(cfg(feature = "geometry")))] + /// A Geometry value. + Geometry(Option), + #[cfg(feature = "geometry")] + #[cfg_attr(feature = "docs", doc(cfg(feature = "geometry")))] + /// A Geography value. + Geography(Option), /// A XML value. Xml(Option>), #[cfg(feature = "uuid")] @@ -145,6 +200,10 @@ impl<'a> fmt::Display for Value<'a> { Value::Date(val) => val.map(|v| write!(f, "\"{v}\"")), #[cfg(feature = "chrono")] Value::Time(val) => val.map(|v| write!(f, "\"{v}\"")), + #[cfg(feature = "geometry")] + Value::Geometry(val) => val.as_ref().map(|v| write!(f, "\"{v}\"")), + #[cfg(feature = "geometry")] + Value::Geography(val) => val.as_ref().map(|v| write!(f, "\"{v}\"")), }; match res { @@ -188,6 +247,10 @@ impl<'a> From> for serde_json::Value { Value::Numeric(d) => d.map(|d| serde_json::to_value(d.to_f64().unwrap()).unwrap()), #[cfg(feature = "json")] Value::Json(v) => v, + #[cfg(feature = "geometry")] + Value::Geometry(g) => g.map(|g| serde_json::Value::String(g.to_string())), + #[cfg(feature = "geometry")] + Value::Geography(g) => g.map(|g| serde_json::Value::String(g.to_string())), #[cfg(feature = "uuid")] Value::Uuid(u) => u.map(|u| serde_json::Value::String(u.hyphenated().to_string())), #[cfg(feature = "chrono")] @@ -331,6 +394,26 @@ impl<'a> Value<'a> { Value::Json(Some(value)) } + /// Creates a new geometry value. + #[cfg(feature = "geometry")] + #[cfg_attr(feature = "docs", doc(cfg(feature = "geometry")))] + pub fn geometry(value: T) -> Self + where + T: Into, + { + Value::Geometry(Some(value.into())) + } + + /// Creates a new geometry value. + #[cfg(feature = "geometry")] + #[cfg_attr(feature = "docs", doc(cfg(feature = "geometry")))] + pub fn geography(value: T) -> Self + where + T: Into, + { + Value::Geography(Some(value.into())) + } + /// Creates a new XML value. pub fn xml(value: T) -> Self where @@ -365,6 +448,10 @@ impl<'a> Value<'a> { Value::Time(t) => t.is_none(), #[cfg(feature = "json")] Value::Json(json) => json.is_none(), + #[cfg(feature = "geometry")] + Value::Geometry(s) => s.is_none(), + #[cfg(feature = "geometry")] + Value::Geography(s) => s.is_none(), } } diff --git a/quaint/src/connector/mssql/conversion.rs b/quaint/src/connector/mssql/conversion.rs index 60e3f4ab6eb7..fb5ad0ee21b4 100644 --- a/quaint/src/connector/mssql/conversion.rs +++ b/quaint/src/connector/mssql/conversion.rs @@ -26,6 +26,10 @@ impl<'a> IntoSql<'a> for &'a Value<'a> { Value::Numeric(val) => (*val).to_sql(), #[cfg(feature = "json")] Value::Json(val) => val.as_ref().map(|val| serde_json::to_string(&val).unwrap()).into_sql(), + #[cfg(feature = "geometry")] + Value::Geometry(_) => panic!("Cannot handle raw Geometry"), + #[cfg(feature = "geometry")] + Value::Geography(_) => panic!("Cannot handle raw Geography"), #[cfg(feature = "uuid")] Value::Uuid(val) => val.into_sql(), #[cfg(feature = "chrono")] diff --git a/quaint/src/connector/mysql/conversion.rs b/quaint/src/connector/mysql/conversion.rs index ea634f8dc87f..1329167eb294 100644 --- a/quaint/src/connector/mysql/conversion.rs +++ b/quaint/src/connector/mysql/conversion.rs @@ -5,11 +5,13 @@ use crate::{ }; #[cfg(feature = "chrono")] use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc}; +#[cfg(feature = "geometry")] +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() { @@ -74,6 +76,14 @@ pub fn conv_params(params: &[Value<'_>]) -> crate::Result { dt.timestamp_subsec_micros(), ) }), + #[cfg(feature = "geometry")] + Value::Geometry(g) | Value::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 { @@ -199,6 +209,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 } @@ -263,6 +277,15 @@ impl TakeRow for my::Row { [0] => Value::boolean(false), _ => Value::boolean(true), }, + #[cfg(feature = "geometry")] + 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)?), @@ -330,6 +353,8 @@ impl TakeRow for my::Row { t if t.is_date() => Value::Date(None), #[cfg(feature = "json")] t if t.is_json() => Value::Json(None), + #[cfg(feature = "geometry")] + t if t.is_geometry() => Value::Geometry(None), typ => { let msg = format!("Value of type {typ:?} is not supported with the current configuration"); diff --git a/quaint/src/connector/postgres/conversion.rs b/quaint/src/connector/postgres/conversion.rs index b4b0a256ad11..efce758c13bd 100644 --- a/quaint/src/connector/postgres/conversion.rs +++ b/quaint/src/connector/postgres/conversion.rs @@ -14,6 +14,8 @@ use bytes::BytesMut; use chrono::{DateTime, NaiveDateTime, Utc}; #[cfg(feature = "bigdecimal")] pub(crate) use decimal::DecimalWrapper; +#[cfg(feature = "geometry")] +use geozero::{wkb::Ewkb, ToWkt}; use postgres_types::{FromSql, ToSql, WrongType}; use std::{convert::TryFrom, error::Error as StdError}; use tokio_postgres::{ @@ -63,6 +65,10 @@ pub(crate) fn params_to_types(params: &[Value<'_>]) -> Vec { Value::Date(_) => PostgresType::TIMESTAMP, #[cfg(feature = "chrono")] Value::Time(_) => PostgresType::TIME, + #[cfg(feature = "geometry")] + Value::Geometry(_) => PostgresType::BYTEA, + #[cfg(feature = "geometry")] + Value::Geography(_) => PostgresType::BYTEA, Value::Array(ref arr) => { let arr = arr.as_ref().unwrap(); @@ -105,6 +111,10 @@ pub(crate) fn params_to_types(params: &[Value<'_>]) -> Vec { Value::Date(_) => PostgresType::TIMESTAMP_ARRAY, #[cfg(feature = "chrono")] Value::Time(_) => PostgresType::TIME_ARRAY, + #[cfg(feature = "geometry")] + Value::Geometry(_) => PostgresType::BYTEA, + #[cfg(feature = "geometry")] + Value::Geography(_) => PostgresType::BYTEA, // In the case of nested arrays, we let PG infer the type Value::Array(_) => PostgresType::UNKNOWN, } @@ -114,6 +124,20 @@ pub(crate) fn params_to_types(params: &[Value<'_>]) -> Vec { .collect() } +#[cfg(feature = "geometry")] +struct EwktString(pub String); + +#[cfg(feature = "geometry")] +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 { @@ -542,6 +566,13 @@ impl GetRow for PostgresRow { } None => Value::Array(None), }, + #[cfg(feature = "geometry")] + ref x if matches!(x.name(), "geometry" | "geography") => { + match row.try_get::<_, Option>(i)? { + Some(ewkt) => Value::text(ewkt.0), + None => Value::Text(None), + } + } ref x => match x.kind() { Kind::Enum(_) => match row.try_get(i)? { Some(val) => { @@ -841,6 +872,10 @@ impl<'a> ToSql for Value<'a> { parsed_ip_addr.to_sql(ty, out) }) } + #[cfg(feature = "geometry")] + (Value::Geometry(_), _) => panic!("Cannot handle raw Geometry"), + #[cfg(feature = "geometry")] + (Value::Geography(_), _) => panic!("Cannot handle raw Geography"), #[cfg(feature = "json")] (Value::Text(string), &PostgresType::JSON) | (Value::Text(string), &PostgresType::JSONB) => string .as_ref() diff --git a/quaint/src/connector/sqlite.rs b/quaint/src/connector/sqlite.rs index da85697a5936..434e52b5a0f8 100644 --- a/quaint/src/connector/sqlite.rs +++ b/quaint/src/connector/sqlite.rs @@ -1,6 +1,7 @@ mod conversion; mod error; +use rusqlite::LoadExtensionGuard; pub use rusqlite::{params_from_iter, version as sqlite_version}; use super::IsolationLevel; @@ -41,6 +42,18 @@ pub struct SqliteParams { pub max_idle_connection_lifetime: Option, } +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 SqliteParams { type Error = Error; @@ -134,7 +147,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)?; }; @@ -153,7 +166,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/sqlite/conversion.rs b/quaint/src/connector/sqlite/conversion.rs index 68442d2a7202..457525ce7f8c 100644 --- a/quaint/src/connector/sqlite/conversion.rs +++ b/quaint/src/connector/sqlite/conversion.rs @@ -9,6 +9,8 @@ use crate::{ error::{Error, ErrorKind}, }; +#[cfg(feature = "geometry")] +use geozero::{wkb::SpatiaLiteWkb, ToWkt}; use rusqlite::{ types::{Null, ToSql, ToSqlOutput, ValueRef}, Column, Error as RusqlError, Row as SqliteRow, Rows as SqliteRows, @@ -119,6 +121,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 } @@ -152,6 +168,8 @@ impl<'a> GetRow for SqliteRow<'a> { #[cfg(feature = "chrono")] c if c.is_date() => Value::Date(None), c if c.is_bool() => Value::Boolean(None), + #[cfg(feature = "geometry")] + c if c.is_geometry() => Value::Geometry(None), c => match c.decl_type() { Some(n) => { let msg = format!("Value {n} not supported"); @@ -229,6 +247,16 @@ impl<'a> GetRow for SqliteRow<'a> { }) })? } + #[cfg(feature = "geometry")] + 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()), }; @@ -299,6 +327,10 @@ impl<'a> ToSql for Value<'a> { date.and_hms_opt(time.hour(), time.minute(), time.second()) }) .map(|dt| ToSqlOutput::from(dt.timestamp_millis())), + #[cfg(feature = "geometry")] + Value::Geometry(_) => panic!("Cannot handle raw Geometry"), + #[cfg(feature = "geometry")] + Value::Geography(_) => panic!("Cannot handle raw Geography"), }; match value { 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 index aa26bb98c2b0..479ded4a7bdc 100644 --- a/quaint/src/serde.rs +++ b/quaint/src/serde.rs @@ -140,6 +140,16 @@ impl<'de> Deserializer<'de> for ValueDeserializer<'de> { #[cfg(feature = "uuid")] Value::Uuid(None) => visitor.visit_none(), + #[cfg(feature = "geometry")] + Value::Geometry(Some(geom)) => visitor.visit_string(geom.to_string()), + #[cfg(feature = "geometry")] + Value::Geometry(None) => visitor.visit_none(), + + #[cfg(feature = "geometry")] + Value::Geography(Some(geom)) => visitor.visit_string(geom.to_string()), + #[cfg(feature = "geometry")] + Value::Geography(None) => visitor.visit_none(), + #[cfg(feature = "json")] Value::Json(Some(value)) => { let de = value.into_deserializer(); diff --git a/quaint/src/visitor.rs b/quaint/src/visitor.rs index 9f4d9bcb5bcd..46d0d8aa8d34 100644 --- a/quaint/src/visitor.rs +++ b/quaint/src/visitor.rs @@ -139,6 +139,55 @@ pub trait Visitor<'a> { #[cfg(all(feature = "json", any(feature = "postgresql", feature = "mysql")))] fn visit_json_unquote(&mut self, json_unquote: JsonUnquote<'a>) -> Result; + #[cfg(feature = "geometry")] + fn visit_geom_as_text(&mut self, geom: GeomAsText<'a>) -> Result; + + #[cfg(feature = "geometry")] + fn visit_geom_from_text(&mut self, geom: GeomFromText<'a>) -> Result; + + #[cfg(feature = "geometry")] + fn visit_geometry_type_equals(&mut self, left: Expression<'a>, right: GeometryType<'a>, not: bool) -> Result; + + #[cfg(feature = "geometry")] + 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)) + } + + #[cfg(feature = "geometry")] + 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)) + } + + #[cfg(feature = "geometry")] + 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) + }) + } + + #[cfg(feature = "geometry")] + 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; @@ -150,8 +199,20 @@ pub trait Visitor<'a> { /// A visit to a value we parameterize fn visit_parameterized(&mut self, value: Value<'a>) -> Result { - self.add_parameter(value); - self.parameter_substitution() + match value { + #[cfg(feature = "geometry")] + Value::Geometry(Some(geom)) => self.visit_function(geom_from_text(geom.wkt, geom.srid, false)), + #[cfg(feature = "geometry")] + Value::Geography(Some(geom)) => self.visit_function(geom_from_text(geom.wkt, geom.srid, true)), + #[cfg(feature = "geometry")] + Value::Geometry(None) => self.write("NULL"), + #[cfg(feature = "geometry")] + Value::Geography(None) => self.write("NULL"), + _ => { + self.add_parameter(value); + self.parameter_substitution() + } + } } /// The join statements in the query @@ -922,6 +983,23 @@ 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), }, + #[cfg(feature = "geometry")] + 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")] @@ -1066,6 +1144,14 @@ pub trait Visitor<'a> { FunctionType::Concat(concat) => { self.visit_concat(concat)?; } + #[cfg(feature = "geometry")] + FunctionType::GeomAsText(geom) => { + self.visit_geom_as_text(geom)?; + } + #[cfg(feature = "geometry")] + 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 111b43ed8ebe..ae4eb6f01053 100644 --- a/quaint/src/visitor/mssql.rs +++ b/quaint/src/visitor/mssql.rs @@ -2,10 +2,7 @@ use super::Visitor; #[cfg(all(feature = "json", 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, @@ -75,6 +72,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 { @@ -177,6 +176,13 @@ impl<'a> Mssql<'a> { Ok(()) } + + #[cfg(feature = "geometry")] + 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> { @@ -204,10 +210,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> { @@ -236,6 +238,8 @@ 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()); + #[cfg(feature = "geometry")] + let (left_geom, right_geom) = (left_kind.is_geometry_expr(), right_kind.is_geometry_expr()); let mut left = Expression::from(left_kind); @@ -249,6 +253,13 @@ impl<'a> Visitor<'a> for Mssql<'a> { right = right.alias(alias); } + #[cfg(feature = "geometry")] + 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 { @@ -277,6 +288,8 @@ 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()); + #[cfg(feature = "geometry")] + let (left_geom, right_geom) = (left_kind.is_geometry_expr(), right_kind.is_geometry_expr()); let mut left = Expression::from(left_kind); @@ -290,6 +303,13 @@ impl<'a> Visitor<'a> for Mssql<'a> { right = right.alias(alias); } + #[cfg(feature = "geometry")] + 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 { @@ -339,6 +359,10 @@ impl<'a> Visitor<'a> for Mssql<'a> { return Err(builder.build()); } + #[cfg(feature = "geometry")] + Value::Geometry(g) => g.map(|g| self.visit_function(geom_from_text(g.wkt.raw(), g.srid.raw(), false))), + #[cfg(feature = "geometry")] + Value::Geography(g) => g.map(|g| self.visit_function(geom_from_text(g.wkt.raw(), g.srid.raw(), true))), #[cfg(feature = "json")] Value::Json(j) => j.map(|j| self.write(format!("'{}'", serde_json::to_string(&j).unwrap()))), #[cfg(feature = "bigdecimal")] @@ -531,6 +555,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()) @@ -636,6 +664,55 @@ impl<'a> Visitor<'a> for Mssql<'a> { Ok(()) } + #[cfg(feature = "geometry")] + 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()), + } + } + + #[cfg(feature = "geometry")] + 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" }) + } + + #[cfg(feature = "geometry")] + 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" }) + } + + #[cfg(feature = "geometry")] + 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" }) + } + + #[cfg(feature = "geometry")] + 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(all(feature = "json", 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") @@ -699,6 +776,37 @@ impl<'a> Visitor<'a> for Mssql<'a> { ) -> visitor::Result { unimplemented!("JSON filtering is not yet supported on MSSQL") } + + #[cfg(feature = "geometry")] + 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(()) + } + + #[cfg(feature = "geometry")] + 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)] @@ -709,6 +817,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 @@ -1279,6 +1388,24 @@ mod tests { assert!(params.is_empty()); } + #[test] + #[cfg(feature = "geometry")] + 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] + #[cfg(feature = "geometry")] + 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 68bc62ec617f..8fc8af66688b 100644 --- a/quaint/src/visitor/mysql.rs +++ b/quaint/src/visitor/mysql.rs @@ -161,6 +161,10 @@ impl<'a> Visitor<'a> for Mysql<'a> { } None => None, }, + #[cfg(feature = "geometry")] + Value::Geometry(g) => g.map(|g| self.visit_function(geom_from_text(g.wkt.raw(), g.srid.raw(), false))), + #[cfg(feature = "geometry")] + Value::Geography(g) => g.map(|g| self.visit_function(geom_from_text(g.wkt.raw(), g.srid.raw(), true))), #[cfg(feature = "uuid")] Value::Uuid(uuid) => uuid.map(|uuid| self.write(format!("'{}'", uuid.hyphenated()))), #[cfg(feature = "chrono")] @@ -469,6 +473,33 @@ impl<'a> Visitor<'a> for Mysql<'a> { self.write(")") } + #[cfg(feature = "geometry")] + 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, ">")?; @@ -614,6 +645,29 @@ impl<'a> Visitor<'a> for Mysql<'a> { Ok(()) } + + #[cfg(feature = "geometry")] + 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(()) + }) + } + + #[cfg(feature = "geometry")] + 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> { @@ -627,6 +681,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 @@ -811,6 +866,28 @@ mod tests { assert!(params.is_empty()); } + #[test] + #[cfg(feature = "geometry")] + 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] + #[cfg(feature = "geometry")] + 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 8dc02180881b..9b9e26b735f8 100644 --- a/quaint/src/visitor/postgres.rs +++ b/quaint/src/visitor/postgres.rs @@ -107,6 +107,10 @@ impl<'a> Visitor<'a> for Postgres<'a> { }), #[cfg(feature = "json")] Value::Json(j) => j.map(|j| self.write(format!("'{}'", serde_json::to_string(&j).unwrap()))), + #[cfg(feature = "geometry")] + Value::Geometry(g) => g.map(|g| self.visit_function(geom_from_text(g.wkt.raw(), g.srid.raw(), false))), + #[cfg(feature = "geometry")] + Value::Geography(g) => g.map(|g| self.visit_function(geom_from_text(g.wkt.raw(), g.srid.raw(), true))), #[cfg(feature = "bigdecimal")] Value::Numeric(r) => r.map(|r| self.write(r)), #[cfg(feature = "uuid")] @@ -392,6 +396,31 @@ impl<'a> Visitor<'a> for Postgres<'a> { } } + #[cfg(feature = "geometry")] + 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| { @@ -520,11 +549,27 @@ impl<'a> Visitor<'a> for Postgres<'a> { Ok(()) } + + #[cfg(feature = "geometry")] + fn visit_geom_as_text(&mut self, geom: GeomAsText<'a>) -> visitor::Result { + self.surround_with("ST_AsEWKT(", ")", |s| s.visit_expression(*geom.expression)) + } + + #[cfg(feature = "geometry")] + 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 @@ -930,6 +975,24 @@ mod tests { assert!(params.is_empty()); } + #[test] + #[cfg(feature = "geometry")] + 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] + #[cfg(feature = "geometry")] + 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 91d3240df67d..9f5e183243eb 100644 --- a/quaint/src/visitor/sqlite.rs +++ b/quaint/src/visitor/sqlite.rs @@ -122,6 +122,10 @@ impl<'a> Visitor<'a> for Sqlite<'a> { #[cfg(feature = "chrono")] Value::Time(time) => time.map(|time| self.write(format!("'{time}'"))), Value::Xml(cow) => cow.map(|cow| self.write(format!("'{cow}'"))), + #[cfg(feature = "geometry")] + Value::Geometry(g) => g.map(|g| self.visit_function(geom_from_text(g.wkt.raw(), g.srid.raw(), false))), + #[cfg(feature = "geometry")] + Value::Geography(g) => g.map(|g| self.visit_function(geom_from_text(g.wkt.raw(), g.srid.raw(), true))), }; match res { @@ -228,14 +232,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)) => { @@ -279,6 +283,31 @@ impl<'a> Visitor<'a> for Sqlite<'a> { }) } + #[cfg(feature = "geometry")] + 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(all(feature = "json", 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") @@ -387,11 +416,27 @@ impl<'a> Visitor<'a> for Sqlite<'a> { Ok(()) } + + #[cfg(feature = "geometry")] + fn visit_geom_as_text(&mut self, geom: GeomAsText<'a>) -> visitor::Result { + self.surround_with("AsEWKT(", ")", |s| s.visit_expression(*geom.expression)) + } + + #[cfg(feature = "geometry")] + 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 @@ -944,6 +989,24 @@ mod tests { assert!(params.is_empty()); } + #[test] + #[cfg(feature = "geometry")] + 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] + #[cfg(feature = "geometry")] + 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/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 c29b12d5b73f..5399feddf935 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 @@ -401,4 +401,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 55cdb5a85bed..bd7f8042d8db 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 @@ -439,3 +439,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 035acedb5696..77c3190da3e6 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 @@ -104,6 +104,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") } @@ -115,6 +118,7 @@ pub(crate) fn connection_string( Some(PostgresVersion::V13) => format!("postgresql://postgres:prisma@127.0.0.1:5435/{database}"), 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 039231a3f74e..33a643fd65a1 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, } @@ -50,6 +51,7 @@ impl TryFrom<&str> for PostgresVersion { "13" => Self::V13, "14" => Self::V14, "15" => Self::V15, + "15-postgis" => Self::V15PostGIS, "pgbouncer" => Self::PgBouncer, _ => return Err(TestError::parse_error(format!("Unknown Postgres version `{s}`"))), }; @@ -68,6 +70,7 @@ impl ToString for PostgresVersion { PostgresVersion::V13 => "13", PostgresVersion::V14 => "14", PostgresVersion::V15 => "15", + PostgresVersion::V15PostGIS => "15-postgis", PostgresVersion::PgBouncer => "pgbouncer", } .to_owned() 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 44ca06cf875b..0fcd4a175505 100644 --- a/query-engine/connectors/mongodb-query-connector/src/filter.rs +++ b/query-engine/connectors/mongodb-query-connector/src/filter.rs @@ -247,6 +247,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"), @@ -386,6 +402,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 cf984ad76830..6f367f7c9060 100644 --- a/query-engine/connectors/mongodb-query-connector/src/value.rs +++ b/query-engine/connectors/mongodb-query-connector/src/value.rs @@ -8,7 +8,8 @@ use chrono::{TimeZone, Utc}; use itertools::Itertools; use mongodb::bson::{oid::ObjectId, spec::BinarySubtype, Binary, Bson, Document, Timestamp}; use prisma_models::{ - CompositeFieldRef, Field, PrismaValue, RelationFieldRef, ScalarFieldRef, SelectedField, TypeIdentifier, + CompositeFieldRef, Field, GeometryFormat, PrismaValue, RelationFieldRef, ScalarFieldRef, SelectedField, + TypeIdentifier, }; use psl::builtin_connectors::MongoDbType; use serde_json::Value; @@ -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/query-connector/src/compare.rs b/query-engine/connectors/query-connector/src/compare.rs index 783d847939c4..9dd1d0d93ac9 100644 --- a/query-engine/connectors/query-connector/src/compare.rs +++ b/query-engine/connectors/query-connector/src/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/connectors/query-connector/src/filter/scalar/compare.rs b/query-engine/connectors/query-connector/src/filter/scalar/compare.rs index f93798441027..d540fd14c59b 100644 --- a/query-engine/connectors/query-connector/src/filter/scalar/compare.rs +++ b/query-engine/connectors/query-connector/src/filter/scalar/compare.rs @@ -200,6 +200,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 { @@ -400,6 +444,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 { @@ -600,4 +688,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/connectors/query-connector/src/filter/scalar/condition/mod.rs b/query-engine/connectors/query-connector/src/filter/scalar/condition/mod.rs index 4845fab126f1..868a68cf4f92 100644 --- a/query-engine/connectors/query-connector/src/filter/scalar/condition/mod.rs +++ b/query-engine/connectors/query-connector/src/filter/scalar/condition/mod.rs @@ -23,6 +23,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), @@ -62,6 +66,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), @@ -88,6 +96,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/connectors/sql-query-connector/Cargo.toml b/query-engine/connectors/sql-query-connector/Cargo.toml index 5fe3052f2e8d..3a0feb5d55dd 100644 --- a/query-engine/connectors/sql-query-connector/Cargo.toml +++ b/query-engine/connectors/sql-query-connector/Cargo.toml @@ -25,6 +25,8 @@ uuid.workspace = true opentelemetry = { version = "0.17", features = ["tokio"] } tracing-opentelemetry = "0.17.3" quaint.workspace = true +regex = "1.9.3" +geozero = "0.10.0" [dependencies.connector-interface] package = "query-connector" diff --git a/query-engine/connectors/sql-query-connector/src/filter_conversion.rs b/query-engine/connectors/sql-query-connector/src/filter_conversion.rs index a95df0ce5aa1..a77b61a2a79c 100644 --- a/query-engine/connectors/sql-query-connector/src/filter_conversion.rs +++ b/query-engine/connectors/sql-query-connector/src/filter_conversion.rs @@ -749,6 +749,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!(), }; @@ -929,6 +941,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 429cff058241..20f73414eff0 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,7 +1,11 @@ +use std::str::FromStr; + use chrono::Utc; +use geozero::{geojson::GeoJson, ToWkt}; use prisma_models::{ScalarField, TypeIdentifier}; use prisma_value::PrismaValue; use quaint::{ + ast::GeometryValue, ast::Value, prelude::{TypeDataLength, TypeFamily}, }; @@ -24,6 +28,23 @@ impl ScalarFieldExt for ScalarField { (PrismaValue::Uuid(u), _) => u.to_string().into(), (PrismaValue::List(l), _) => Value::Array(Some(l.into_iter().map(|x| self.value(x)).collect())), (PrismaValue::Json(s), _) => Value::Json(Some(serde_json::from_str::(&s).unwrap())), + (PrismaValue::GeoJson(s), _) => { + let geometry = GeometryValue { + wkt: GeoJson(&s).to_wkt().unwrap(), + srid: 4326, + }; + match self.type_family() { + TypeFamily::Geography(_) => Value::Geography(Some(geometry)), + _ => Value::Geometry(Some(geometry)), + } + } + (PrismaValue::Geometry(s), _) => { + let geometry = GeometryValue::from_str(&s).unwrap(); + match self.type_family() { + TypeFamily::Geography(_) => Value::Geography(Some(geometry)), + _ => Value::Geometry(Some(geometry)), + } + } (PrismaValue::Bytes(b), _) => Value::Bytes(Some(b.into())), (PrismaValue::Object(_), _) => unimplemented!(), (PrismaValue::Null, ident) => match ident { @@ -38,6 +59,10 @@ impl ScalarFieldExt for ScalarField { TypeIdentifier::Int => Value::Int32(None), TypeIdentifier::BigInt => Value::Int64(None), TypeIdentifier::Bytes => Value::Bytes(None), + TypeIdentifier::Geometry(_) => match self.type_family() { + TypeFamily::Geography(_) => Value::Geography(None), + _ => Value::Geometry(None), + }, TypeIdentifier::Unsupported => unreachable!("No unsupported field should reach that path"), }, } @@ -64,6 +89,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"), } } @@ -84,6 +125,9 @@ pub fn convert_lossy<'a>(pv: PrismaValue) -> Value<'a> { PrismaValue::List(l) => Value::Array(Some(l.into_iter().map(convert_lossy).collect())), PrismaValue::Json(s) => Value::Json(serde_json::from_str(&s).unwrap()), PrismaValue::Bytes(b) => Value::Bytes(Some(b.into())), + // 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(Some(GeometryValue::from_str(&s).unwrap())), PrismaValue::Null => Value::Int32(None), // 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 2aa0a80169de..c5b62b5477f8 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 @@ -5,6 +5,7 @@ use crate::{ use connector_interface::{filter::Filter, AggregationSelection, QueryArguments, RelAggregationSelection}; use itertools::Itertools; use prisma_models::*; +use psl::datamodel_connector::Connector; use quaint::ast::*; use tracing::Span; @@ -105,6 +106,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>, @@ -116,10 +125,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 = columns + .map(|c| get_column_read_expression(c, model.dm.schema.connector)) + .fold(select, |acc, col| acc.value(col)); let select = select.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 c18cb9bd6613..a70140f265a7 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)) }); + // TODO@geometry: Should we call geom_as_text in returning statement too ? Insert::from(insert) .returning(selected_fields.as_columns(ctx)) .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 9dfd05751c56..9383a11e8bfa 100644 --- a/query-engine/connectors/sql-query-connector/src/row.rs +++ b/query-engine/connectors/sql-query-connector/src/row.rs @@ -2,7 +2,9 @@ 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 prisma_models::{ConversionFailure, FieldArity, PrismaValue, Record, TypeIdentifier}; +use geozero::wkt::WktStr; +use geozero::ToJson; +use prisma_models::{ConversionFailure, FieldArity, GeometryFormat, PrismaValue, Record, TypeIdentifier}; use quaint::{ast::Value, connector::ResultRow}; use std::{io, str::FromStr}; use uuid::Uuid; @@ -162,6 +164,29 @@ fn row_value_to_prisma_value(p_value: Value, meta: ColumnMetadata<'_>) -> Result Value::Json(Some(json)) => PrismaValue::Json(json.to_string()), _ => return Err(create_error(&p_value)), }, + TypeIdentifier::Geometry(GeometryFormat::EWKT) => match p_value { + value if value.is_null() => PrismaValue::Null, + Value::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 { + 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. + Value::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::UUID => match p_value { value if value.is_null() => PrismaValue::Null, Value::Text(Some(uuid)) => PrismaValue::Uuid(Uuid::parse_str(&uuid)?), diff --git a/query-engine/connectors/sql-query-connector/src/value.rs b/query-engine/connectors/sql-query-connector/src/value.rs index 086314ed7419..a40f514884b0 100644 --- a/query-engine/connectors/sql-query-connector/src/value.rs +++ b/query-engine/connectors/sql-query-connector/src/value.rs @@ -87,6 +87,10 @@ pub fn to_prisma_value(quaint_value: Value<'_>) -> crate::Result { Value::Xml(s) => s .map(|s| PrismaValue::String(s.into_owned())) .unwrap_or(PrismaValue::Null), + + Value::Geometry(s) | Value::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 1d9a82427592..bac9e8bc2ebb 100644 --- a/query-engine/connectors/sql-query-connector/src/value_ext.rs +++ b/query-engine/connectors/sql-query-connector/src/value_ext.rs @@ -29,6 +29,8 @@ impl<'a> IntoTypedJsonExtension for quaint::Value<'a> { quaint::Value::Date(_) => "date", quaint::Value::Time(_) => "time", quaint::Value::Array(_) => "array", + quaint::Value::Geometry(_) => "geometry", + quaint::Value::Geography(_) => "geography", }; type_name.to_owned() diff --git a/query-engine/core/Cargo.toml b/query-engine/core/Cargo.toml index c9700bb85f19..764cbe68a8c7 100644 --- a/query-engine/core/Cargo.toml +++ b/query-engine/core/Cargo.toml @@ -33,4 +33,5 @@ cuid = "1.2" schema = { path = "../schema" } lru = "0.7.7" enumflags2 = "0.7" +geojson = { version = "0.24.1", default-features = false } diff --git a/query-engine/core/src/constants.rs b/query-engine/core/src/constants.rs index 6d185a1c6866..7f0914314a4b 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 58a814692271..2674adf49d54 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 prisma_models::{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))), @@ -516,6 +526,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( @@ -883,6 +905,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 0c3a100b7af9..aa98a6669028 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 @@ -178,6 +178,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 d07d625b4fe6..67007f793099 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 1cc66275e8ca..9bae79397d2f 100644 --- a/query-engine/dmmf/src/ast_builders/datamodel_ast_builder.rs +++ b/query-engine/dmmf/src/ast_builders/datamodel_ast_builder.rs @@ -264,6 +264,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/prisma-models/src/field/mod.rs b/query-engine/prisma-models/src/field/mod.rs index d05b6d7d6361..c7f679cc6f27 100644 --- a/query-engine/prisma-models/src/field/mod.rs +++ b/query-engine/prisma-models/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(), } } @@ -243,6 +252,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/prisma-models/src/field/scalar.rs b/query-engine/prisma-models/src/field/scalar.rs index cc3b3533322b..949c65b34752 100644 --- a/query-engine/prisma-models/src/field/scalar.rs +++ b/query-engine/prisma-models/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(), @@ -257,6 +261,7 @@ pub fn dml_default_kind(default_value: &ast::Expression, scalar_type: Option unreachable!("{:?}", other), } } diff --git a/query-engine/prisma-models/src/prisma_value_ext.rs b/query-engine/prisma-models/src/prisma_value_ext.rs index 09e052ea844b..9d386531eab1 100644 --- a/query-engine/prisma-models/src/prisma_value_ext.rs +++ b/query-engine/prisma-models/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 a9b0395d2e00..3afa4125774e 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 af2c77d006b4..65acaa288524 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 f34da22f40af..b6b10e1477ea 100644 --- a/query-engine/schema/src/build/input_types/mod.rs +++ b/query-engine/schema/src/build/input_types/mod.rs @@ -4,7 +4,7 @@ pub(crate) mod objects; use super::*; use crate::*; use fields::*; -use prisma_models::ScalarFieldRef; +use prisma_models::{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()) @@ -20,6 +20,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 2fb5bce366df..b10aa8f82dcd 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 prisma_models::{CompositeFieldRef, ScalarFieldRef}; +use prisma_models::{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 176a31a60a16..75b19e7d04c1 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 7aa949f79083..25543402c5b3 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 f48572db32fb..3b199ce09e98 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 d8620ff18eeb..a958efad6ac6 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..9fd12a8cabe8 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,6 +6,7 @@ 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::*, *}; @@ -141,7 +142,34 @@ 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()) + .map(|c| { + let column_name = c.name(); + let SQLiteType::Geometry(geom) = c.column_native_type().unwrap(); + let geom = geom.expect("Couldn't get geometry column type informations"); + format!( + "SELECT RecoverGeometryColumn('{table_name}', '{column_name}', {srid}, '{ty}', '{dims}');\n", + srid = geom.srid, + ty = geom.ty.as_2d(), + dims = geom.ty.dimensions(), + ) + }) + .collect() } fn render_drop_enum(&self, _: EnumWalker<'_>) -> Vec { @@ -173,8 +201,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 +229,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 +257,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 +287,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 32ac24688601..dce097be1283 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 9144313af8ed..285c65426ca5 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 10a6bd76cb53..46c57557f64d 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::Array}; @@ -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> { @@ -930,9 +932,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 @@ -1000,9 +1002,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()); @@ -1029,34 +1051,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"), + }, } } @@ -1593,10 +1613,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, @@ -1609,6 +1634,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() { @@ -1668,6 +1697,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(), @@ -1687,7 +1723,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"); @@ -1703,6 +1743,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() { @@ -1757,6 +1801,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 3073be2b4daa..f165ed722ce8 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, 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, @@ -385,6 +451,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), @@ -605,6 +672,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 @@ -615,3 +683,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)