diff --git a/quaint/src/ast/function.rs b/quaint/src/ast/function.rs index 6be8489c3a2..c83eba5458d 100644 --- a/quaint/src/ast/function.rs +++ b/quaint/src/ast/function.rs @@ -62,7 +62,9 @@ impl Function<'_> { } } -/// A database function type +/// A database function type. +/// Not every function is supported by every database. +/// TODO: Use `cfg` compilation flags to enable/disable functions based on the database family. #[derive(Debug, Clone, PartialEq)] pub(crate) enum FunctionType<'a> { RowToJson(RowToJson<'a>), diff --git a/quaint/src/visitor.rs b/quaint/src/visitor.rs index f11fd361acc..c91da1e5559 100644 --- a/quaint/src/visitor.rs +++ b/quaint/src/visitor.rs @@ -121,6 +121,8 @@ pub trait Visitor<'a> { /// Visit a non-parameterized value. fn visit_raw_value(&mut self, value: Value<'a>) -> Result; + // TODO: JSON functions such as this one should only be required when + // `#[cfg(any(feature = "postgresql", feature = "mysql"))]` or similar filters apply. fn visit_json_extract(&mut self, json_extract: JsonExtract<'a>) -> Result; fn visit_json_extract_last_array_item(&mut self, extract: JsonExtractLastArrayElem<'a>) -> Result; diff --git a/quaint/src/visitor/mssql.rs b/quaint/src/visitor/mssql.rs index a3887b4cfae..ec39fde54f7 100644 --- a/quaint/src/visitor/mssql.rs +++ b/quaint/src/visitor/mssql.rs @@ -1,5 +1,4 @@ use super::{NativeColumnType, Visitor}; -#[cfg(any(feature = "postgresql", feature = "mysql"))] use crate::prelude::{JsonArrayAgg, JsonBuildObject, JsonExtract, JsonType, JsonUnquote}; use crate::{ ast::{ @@ -672,12 +671,10 @@ impl<'a> Visitor<'a> for Mssql<'a> { Ok(()) } - #[cfg(any(feature = "postgresql", feature = "mysql"))] fn visit_json_extract(&mut self, _json_extract: JsonExtract<'a>) -> visitor::Result { unimplemented!("JSON filtering is not yet supported on MSSQL") } - #[cfg(any(feature = "postgresql", feature = "mysql"))] fn visit_json_array_contains( &mut self, _left: Expression<'a>, @@ -687,32 +684,26 @@ impl<'a> Visitor<'a> for Mssql<'a> { unimplemented!("JSON filtering is not yet supported on MSSQL") } - #[cfg(any(feature = "postgresql", feature = "mysql"))] fn visit_json_type_equals(&mut self, _left: Expression<'a>, _json_type: JsonType, _not: bool) -> visitor::Result { unimplemented!("JSON_TYPE is not yet supported on MSSQL") } - #[cfg(any(feature = "postgresql", feature = "mysql"))] fn visit_json_unquote(&mut self, _json_unquote: JsonUnquote<'a>) -> visitor::Result { unimplemented!("JSON filtering is not yet supported on MSSQL") } - #[cfg(feature = "postgresql")] fn visit_json_array_agg(&mut self, _array_agg: JsonArrayAgg<'a>) -> visitor::Result { unimplemented!("JSON_AGG is not yet supported on MSSQL") } - #[cfg(feature = "postgresql")] fn visit_json_build_object(&mut self, _build_obj: JsonBuildObject<'a>) -> visitor::Result { unimplemented!("JSON_BUILD_OBJECT is not yet supported on MSSQL") } - #[cfg(feature = "postgresql")] fn visit_text_search(&mut self, _text_search: crate::prelude::TextSearch<'a>) -> visitor::Result { unimplemented!("Full-text search is not yet supported on MSSQL") } - #[cfg(feature = "postgresql")] fn visit_matches( &mut self, _left: Expression<'a>, @@ -722,7 +713,6 @@ impl<'a> Visitor<'a> for Mssql<'a> { unimplemented!("Full-text search is not yet supported on MSSQL") } - #[cfg(feature = "postgresql")] fn visit_text_search_relevance( &mut self, _text_search_relevance: crate::prelude::TextSearchRelevance<'a>, @@ -730,7 +720,6 @@ impl<'a> Visitor<'a> for Mssql<'a> { unimplemented!("Full-text search is not yet supported on MSSQL") } - #[cfg(any(feature = "postgresql", feature = "mysql"))] fn visit_json_extract_last_array_item( &mut self, _extract: crate::prelude::JsonExtractLastArrayElem<'a>, @@ -738,7 +727,6 @@ impl<'a> Visitor<'a> for Mssql<'a> { unimplemented!("JSON filtering is not yet supported on MSSQL") } - #[cfg(any(feature = "postgresql", feature = "mysql"))] fn visit_json_extract_first_array_item( &mut self, _extract: crate::prelude::JsonExtractFirstArrayElem<'a>, diff --git a/query-engine/connector-test-kit-rs/qe-setup/Cargo.toml b/query-engine/connector-test-kit-rs/qe-setup/Cargo.toml index dc225369c87..fa49b3715a9 100644 --- a/query-engine/connector-test-kit-rs/qe-setup/Cargo.toml +++ b/query-engine/connector-test-kit-rs/qe-setup/Cargo.toml @@ -8,7 +8,9 @@ psl.workspace = true quaint.workspace = true mongodb-client = { path = "../../../libs/mongodb-client" } schema-core = { path = "../../../schema-engine/core" } -sql-schema-connector = { path = "../../../schema-engine/connectors/sql-schema-connector" } +sql-schema-connector = { path = "../../../schema-engine/connectors/sql-schema-connector", features = [ + "all-native", +] } test-setup = { path = "../../../libs/test-setup" } enumflags2.workspace = true serde.workspace = true diff --git a/schema-engine/connectors/sql-schema-connector/Cargo.toml b/schema-engine/connectors/sql-schema-connector/Cargo.toml index 4bd4d2b677d..695e573bdb5 100644 --- a/schema-engine/connectors/sql-schema-connector/Cargo.toml +++ b/schema-engine/connectors/sql-schema-connector/Cargo.toml @@ -5,34 +5,44 @@ version = "0.1.0" [features] vendored-openssl = ["quaint/vendored-openssl"] +postgresql = ["psl/postgresql", "quaint/postgresql", "schema-connector/postgresql", "sql-schema-describer/postgresql"] +postgresql-native = ["postgresql", "quaint/postgresql-native", "quaint/pooled"] +sqlite = ["psl/sqlite", "quaint/sqlite", "schema-connector/sqlite", "sql-schema-describer/sqlite"] +sqlite-native = ["sqlite", "quaint/sqlite-native", "quaint/pooled", "quaint/expose-drivers", "sqlx-sqlite", "sqlx-core"] +mysql = ["psl/mysql", "quaint/mysql", "schema-connector/mysql", "sql-schema-describer/mysql"] +mysql-native = ["mysql", "quaint/mysql-native", "quaint/pooled"] +mssql = ["psl/mssql", "quaint/mssql", "schema-connector/mssql", "sql-schema-describer/mssql"] +mssql-native = ["mssql", "quaint/mssql-native", "quaint/pooled"] +cockroachdb = ["psl/cockroachdb", "quaint/postgresql", "schema-connector/cockroachdb", "sql-schema-describer/cockroachdb"] +cockroachdb-native = ["cockroachdb", "quaint/postgresql-native", "quaint/pooled"] +all-native = [ + "vendored-openssl", + "quaint/fmt-sql", + "postgresql-native", + "sqlite-native", + "mysql-native", + "mssql-native", + "cockroachdb-native", + "schema-connector/all-native", + "sql-schema-describer/all-native", + "user-facing-errors/all-native", +] [dependencies] psl.workspace = true -quaint = { workspace = true, features = [ - "all-native", - "expose-drivers", - "pooled", - "fmt-sql", -] } -tokio.workspace = true +quaint.workspace = true +tokio = { version = "1", features = ["macros", "sync", "io-util", "time"] } serde.workspace = true indoc.workspace = true uuid.workspace = true indexmap.workspace = true prisma-value = { path = "../../../libs/prisma-value" } -schema-connector = { path = "../schema-connector", features = [ - "all-native", -] } -sql-schema-describer = { path = "../../sql-schema-describer", features = [ - "all-native", -] } +schema-connector = { path = "../schema-connector" } +sql-schema-describer = { path = "../../sql-schema-describer" } datamodel-renderer = { path = "../../datamodel-renderer" } sql-ddl = { path = "../../../libs/sql-ddl" } -user-facing-errors = { path = "../../../libs/user-facing-errors", features = [ - "sql", - "all-native", -] } +user-facing-errors = { path = "../../../libs/user-facing-errors", features = ["sql"] } chrono.workspace = true connection-string.workspace = true @@ -47,7 +57,8 @@ either = "1.6" sqlformat = "0.2.1" sqlparser = "0.32.0" versions = "6.1.0" -sqlx-sqlite = { version = "0.8.0" } -sqlx-core = "0.8.0" +sqlx-sqlite = { version = "0.8.0", optional = true } +sqlx-core = { version = "0.8.0", optional = true } + [dev-dependencies] expect-test = "1" diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour.rs b/schema-engine/connectors/sql-schema-connector/src/flavour.rs index fd6774b26d9..9fbfe6a816c 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour.rs @@ -2,14 +2,28 @@ //! in order to avoid cluttering the connector with conditionals. This is a private implementation //! detail of the SQL connector. +#[cfg(feature = "mssql")] mod mssql; + +#[cfg(feature = "mysql")] mod mysql; + +#[cfg(any(feature = "postgresql", feature = "cockroachdb"))] mod postgres; + +#[cfg(feature = "sqlite")] mod sqlite; +#[cfg(feature = "mssql")] pub(crate) use mssql::MssqlFlavour; + +#[cfg(feature = "mysql")] pub(crate) use mysql::MysqlFlavour; + +#[cfg(any(feature = "postgresql", feature = "cockroachdb"))] pub(crate) use postgres::PostgresFlavour; + +#[cfg(feature = "sqlite")] pub(crate) use sqlite::SqliteFlavour; use crate::{ diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/mssql.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/mssql.rs index 92843aae628..6d721bfd172 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/mssql.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/mssql.rs @@ -1,7 +1,15 @@ -mod connection; -mod shadow_db; +#[cfg(feature = "mssql-native")] +mod native; + +#[cfg(not(feature = "mssql-native"))] +mod wasm; + +#[cfg(feature = "mssql-native")] +use native::{generic_apply_migration_script, shadow_db, Connection}; + +#[cfg(not(feature = "mssql-native"))] +use wasm::{generic_apply_migration_script, shadow_db, Connection}; -use self::connection::*; use crate::SqlFlavour; use connection_string::JdbcString; use indoc::formatdoc; diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/connection.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/native/mod.rs similarity index 99% rename from schema-engine/connectors/sql-schema-connector/src/flavour/mssql/connection.rs rename to schema-engine/connectors/sql-schema-connector/src/flavour/mssql/native/mod.rs index 580c3a18638..2c12cc91b4b 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/connection.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/native/mod.rs @@ -1,5 +1,7 @@ //! All the quaint-wrangling for the mssql connector should happen here. +pub(super) mod shadow_db; + use quaint::{ connector::{self, MssqlUrl}, prelude::{ConnectionInfo, NativeConnectionInfo, Queryable}, diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/shadow_db.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/native/shadow_db.rs similarity index 84% rename from schema-engine/connectors/sql-schema-connector/src/flavour/mssql/shadow_db.rs rename to schema-engine/connectors/sql-schema-connector/src/flavour/mssql/native/shadow_db.rs index 6ff9643324a..104b29f8eb8 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/shadow_db.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/native/shadow_db.rs @@ -1,8 +1,8 @@ -use crate::flavour::*; -use schema_connector::{migrations_directory::MigrationDirectory, ConnectorResult}; +use crate::flavour::{MssqlFlavour, SqlFlavour}; +use schema_connector::{migrations_directory::MigrationDirectory, ConnectorError, ConnectorResult, Namespaces}; use sql_schema_describer::SqlSchema; -pub(super) async fn sql_schema_from_migrations_history( +pub async fn sql_schema_from_migrations_history( migrations: &[MigrationDirectory], mut shadow_db: MssqlFlavour, namespaces: Option, diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/wasm/mod.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/wasm/mod.rs new file mode 100644 index 00000000000..08055cec593 --- /dev/null +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/wasm/mod.rs @@ -0,0 +1,66 @@ +//! All the quaint-wrangling for the mssql connector should happen here. + +pub(super) mod shadow_db; + +use enumflags2::BitFlags; +use quaint::connector::{ColumnType, DescribedColumn, DescribedParameter, GetRow, MssqlUrl, ToColumnNames}; +use schema_connector::{BoxFuture, ConnectorError, ConnectorResult, Namespaces}; +use sql_schema_describer::{mssql as describer, DescriberErrorKind, SqlSchema}; +use user_facing_errors::schema_engine::ApplyMigrationError; + +// TODO: use ExternalConnector here. +pub(super) struct Connection(); + +impl Connection { + pub(super) async fn new(connection_str: &str) -> ConnectorResult { + panic!("[sql-schema-connector::flavour::mssql::wasm] Not implemented"); + } + + #[tracing::instrument(skip(self, params))] + pub(super) async fn describe_schema( + &mut self, + params: &super::Params, + namespaces: Option, + ) -> ConnectorResult { + panic!("[sql-schema-connector::flavour::mssql::wasm] Not implemented"); + } + + pub(super) async fn raw_cmd(&mut self, sql: &str, params: &super::Params) -> ConnectorResult<()> { + tracing::debug!(query_type = "raw_cmd", sql); + panic!("[sql-schema-connector::flavour::mssql::wasm] Not implemented"); + } + + pub(super) async fn version(&mut self, params: &super::Params) -> ConnectorResult> { + tracing::debug!(query_type = "version"); + panic!("[sql-schema-connector::flavour::mssql::wasm] Not implemented"); + } + + pub(super) async fn query( + &mut self, + query: quaint::ast::Query<'_>, + conn_params: &super::Params, + ) -> ConnectorResult { + use quaint::visitor::Visitor; + let (sql, params) = quaint::visitor::Mssql::build(query).unwrap(); + self.query_raw(&sql, ¶ms, conn_params).await + } + + pub(super) async fn query_raw( + &mut self, + sql: &str, + params: &[quaint::prelude::Value<'_>], + conn_params: &super::Params, + ) -> ConnectorResult { + tracing::debug!(query_type = "query_raw", sql); + panic!("[sql-schema-connector::flavour::mssql::wasm] Not implemented"); + } +} + +pub(super) async fn generic_apply_migration_script( + migration_name: &str, + script: &str, + conn: &mut Connection, +) -> ConnectorResult<()> { + tracing::debug!(query_type = "raw_cmd", script); + panic!("[sql-schema-connector::flavour::mssql::wasm] Not implemented"); +} diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/wasm/shadow_db.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/wasm/shadow_db.rs new file mode 100644 index 00000000000..161c84d92a5 --- /dev/null +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/mssql/wasm/shadow_db.rs @@ -0,0 +1,12 @@ +use crate::flavour::{MssqlFlavour, SqlFlavour}; +use schema_connector::Namespaces; +use schema_connector::{migrations_directory::MigrationDirectory, ConnectorResult}; +use sql_schema_describer::SqlSchema; + +pub async fn sql_schema_from_migrations_history( + migrations: &[MigrationDirectory], + mut shadow_db: MssqlFlavour, + namespaces: Option, +) -> ConnectorResult { + panic!("[sql-schema-connector::flavour::mssql::wasm] Not implemented"); +} diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/mysql.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/mysql.rs index e169d825e9d..7e8ba2d208f 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/mysql.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/mysql.rs @@ -1,7 +1,15 @@ -mod connection; -mod shadow_db; +#[cfg(feature = "mysql-native")] +mod native; + +#[cfg(not(feature = "mysql-native"))] +mod wasm; + +#[cfg(feature = "mysql-native")] +use native::{shadow_db, Connection}; + +#[cfg(not(feature = "mysql-native"))] +use wasm::{shadow_db, Connection}; -use self::connection::*; use crate::{error::SystemDatabase, flavour::SqlFlavour}; use enumflags2::BitFlags; use indoc::indoc; diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/connection.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/native/mod.rs similarity index 99% rename from schema-engine/connectors/sql-schema-connector/src/flavour/mysql/connection.rs rename to schema-engine/connectors/sql-schema-connector/src/flavour/mysql/native/mod.rs index fd470fc298d..05d7526bea6 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/connection.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/native/mod.rs @@ -1,5 +1,7 @@ //! All the quaint-wrangling for the mysql connector should happen here. +pub(super) mod shadow_db; + use enumflags2::BitFlags; use quaint::{ connector::{ diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/shadow_db.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/native/shadow_db.rs similarity index 83% rename from schema-engine/connectors/sql-schema-connector/src/flavour/mysql/shadow_db.rs rename to schema-engine/connectors/sql-schema-connector/src/flavour/mysql/native/shadow_db.rs index 9441768c191..860b32ab893 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/shadow_db.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/native/shadow_db.rs @@ -1,8 +1,8 @@ -use crate::flavour::*; +use crate::flavour::{mysql, MysqlFlavour, SqlFlavour}; use schema_connector::{migrations_directory::MigrationDirectory, ConnectorResult}; use sql_schema_describer::SqlSchema; -pub(super) async fn sql_schema_from_migrations_history( +pub async fn sql_schema_from_migrations_history( migrations: &[MigrationDirectory], mut shadow_db: MysqlFlavour, ) -> ConnectorResult { @@ -14,7 +14,7 @@ pub(super) async fn sql_schema_from_migrations_history( migration.migration_name() ); - super::scan_migration_script_impl(&script); + mysql::scan_migration_script_impl(&script); shadow_db .apply_migration_script(migration.migration_name(), &script) diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/wasm/mod.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/wasm/mod.rs new file mode 100644 index 00000000000..d498be514d6 --- /dev/null +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/wasm/mod.rs @@ -0,0 +1,79 @@ +//! All the quaint-wrangling for the mysql connector should happen here. + +pub(super) mod shadow_db; + +use enumflags2::BitFlags; +use quaint::connector::{ColumnType, DescribedColumn, DescribedParameter, GetRow, MysqlUrl, ToColumnNames}; +use schema_connector::{BoxFuture, ConnectorError, ConnectorResult}; +use sql_schema_describer::{mysql as describer, DescriberErrorKind, SqlSchema}; +use user_facing_errors::schema_engine::ApplyMigrationError; + +// TODO: use ExternalConnector here. +pub(super) struct Connection(); + +impl Connection { + pub(super) async fn new(url: url::Url) -> ConnectorResult { + // TODO: establish a connection, gather the version string, + // determine whether it is a CockroachDB instance or not. + panic!("[sql-schema-connector::flavour::mysql::wasm] Not implemented"); + } + + #[tracing::instrument(skip(self, circumstances, params))] + pub(super) async fn describe_schema( + &mut self, + circumstances: BitFlags, + params: &super::Params, + ) -> ConnectorResult { + panic!("[sql-schema-connector::flavour::mysql::wasm] Not implemented"); + } + + pub(super) async fn raw_cmd(&mut self, sql: &str, _url: &MysqlUrl) -> ConnectorResult<()> { + tracing::debug!(query_type = "raw_cmd", sql); + panic!("[sql-schema-connector::flavour::mysql::wasm] Not implemented"); + } + + pub(super) async fn version(&mut self, url: &MysqlUrl) -> ConnectorResult> { + tracing::debug!(query_type = "version"); + panic!("[sql-schema-connector::flavour::mysql::wasm] Not implemented"); + } + + pub(super) async fn query( + &mut self, + query: quaint::ast::Query<'_>, + url: &MysqlUrl, + ) -> ConnectorResult { + use quaint::visitor::Visitor; + let (sql, params) = quaint::visitor::Mysql::build(query).unwrap(); + self.query_raw(&sql, ¶ms, url).await + } + + pub(super) async fn query_raw( + &mut self, + sql: &str, + params: &[quaint::prelude::Value<'_>], + _url: &MysqlUrl, + ) -> ConnectorResult { + tracing::debug!(query_type = "query_raw", sql); + panic!("[sql-schema-connector::flavour::mysql::wasm] Not implemented"); + } + + pub(super) async fn describe_query( + &mut self, + sql: &str, + _url: &MysqlUrl, + circumstances: BitFlags, + ) -> ConnectorResult { + tracing::debug!(query_type = "describe_query", sql); + panic!("[sql-schema-connector::flavour::mysql::wasm] Not implemented"); + } + + pub(super) async fn apply_migration_script( + &mut self, + migration_name: &str, + script: &str, + circumstances: BitFlags, + ) -> ConnectorResult<()> { + tracing::debug!(query_type = "raw_cmd", script); + panic!("[sql-schema-connector::flavour::mysql::wasm] Not implemented"); + } +} diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/wasm/shadow_db.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/wasm/shadow_db.rs new file mode 100644 index 00000000000..b8d291cddf7 --- /dev/null +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/mysql/wasm/shadow_db.rs @@ -0,0 +1,10 @@ +use crate::flavour::{MysqlFlavour, SqlFlavour}; +use schema_connector::{migrations_directory::MigrationDirectory, ConnectorResult}; +use sql_schema_describer::SqlSchema; + +pub async fn sql_schema_from_migrations_history( + migrations: &[MigrationDirectory], + mut shadow_db: MysqlFlavour, +) -> ConnectorResult { + panic!("[sql-schema-connector::flavour::mysql::wasm] Not implemented"); +} 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 02752e491ee..e1c23ed121d 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres.rs @@ -1,14 +1,21 @@ -mod connection; -mod shadow_db; +#[cfg(feature = "postgresql-native")] +mod native; + +#[cfg(not(feature = "postgresql-native"))] +mod wasm; + +#[cfg(feature = "postgresql-native")] +use native::{shadow_db, Connection}; + +#[cfg(not(feature = "postgresql-native"))] +use wasm::{shadow_db, Connection}; -use self::connection::*; use crate::SqlFlavour; use enumflags2::BitFlags; use indoc::indoc; use once_cell::sync::Lazy; use quaint::{ connector::{PostgresUrl, PostgresWebSocketUrl}, - prelude::NativeConnectionInfo, Value, }; use schema_connector::{ @@ -88,9 +95,10 @@ impl MigratePostgresUrl { } } -impl From for NativeConnectionInfo { +#[cfg(feature = "postgresql-native")] +impl From for quaint::prelude::NativeConnectionInfo { fn from(value: MigratePostgresUrl) -> Self { - NativeConnectionInfo::Postgres(value.0) + quaint::prelude::NativeConnectionInfo::Postgres(value.0) } } @@ -128,6 +136,14 @@ impl std::fmt::Debug for PostgresFlavour { } impl PostgresFlavour { + #[cfg(not(feature = "postgresql-native"))] + pub(crate) fn new_external(_adapter: std::sync::Arc) -> Self { + PostgresFlavour { + state: State::Initial, + provider: PostgresProvider::PostgreSql, + } + } + pub(crate) fn new_postgres() -> Self { PostgresFlavour { state: State::Initial, @@ -564,6 +580,7 @@ impl SqlFlavour for PostgresFlavour { fn version(&mut self) -> BoxFuture<'_, ConnectorResult>> { with_connection(self, |params, _circumstances, connection| async move { + // TODO: the `url` used here isn't Wasm-compatible. connection.version(¶ms.url).await }) } diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/connection.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/native/mod.rs similarity index 99% rename from schema-engine/connectors/sql-schema-connector/src/flavour/postgres/connection.rs rename to schema-engine/connectors/sql-schema-connector/src/flavour/postgres/native/mod.rs index 59325574d79..e541689bc69 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/connection.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/native/mod.rs @@ -1,5 +1,7 @@ //! All the quaint-wrangling for the postgres connector should happen here. +pub(super) mod shadow_db; + use enumflags2::BitFlags; use indoc::indoc; use psl::PreviewFeature; diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/shadow_db.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/native/shadow_db.rs similarity index 84% rename from schema-engine/connectors/sql-schema-connector/src/flavour/postgres/shadow_db.rs rename to schema-engine/connectors/sql-schema-connector/src/flavour/postgres/native/shadow_db.rs index 4ef7b2a29b5..8e024bbf7b2 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/shadow_db.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/native/shadow_db.rs @@ -1,8 +1,9 @@ -use crate::flavour::*; +use crate::flavour::{PostgresFlavour, SqlFlavour}; use schema_connector::{migrations_directory::MigrationDirectory, ConnectorResult}; +use schema_connector::{ConnectorError, Namespaces}; use sql_schema_describer::SqlSchema; -pub(super) async fn sql_schema_from_migrations_history( +pub async fn sql_schema_from_migrations_history( migrations: &[MigrationDirectory], mut shadow_db: PostgresFlavour, namespaces: Option, diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/wasm/mod.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/wasm/mod.rs new file mode 100644 index 00000000000..7baca93f41f --- /dev/null +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/wasm/mod.rs @@ -0,0 +1,76 @@ +//! All the quaint-wrangling for the postgres connector should happen here. + +pub(super) mod shadow_db; + +use enumflags2::BitFlags; +use quaint::connector::{ColumnType, DescribedColumn, DescribedParameter, GetRow, ToColumnNames}; +use schema_connector::{BoxFuture, ConnectorError, ConnectorResult, Namespaces}; +use sql_schema_describer::{postgres as describer, DescriberErrorKind, SqlSchema}; +use user_facing_errors::schema_engine::ApplyMigrationError; + +use super::MigratePostgresUrl; + +// TODO: use ExternalConnector here. +pub(super) struct Connection(); + +impl Connection { + pub(super) async fn new(url: url::Url) -> ConnectorResult { + // TODO: establish a connection, gather the version string, + // determine whether it is a CockroachDB instance or not. + panic!("[sql-schema-connector::flavour::postgres::wasm] Not implemented"); + } + + #[tracing::instrument(skip(self, circumstances, params))] + pub(super) async fn describe_schema( + &mut self, + circumstances: BitFlags, + params: &super::Params, + namespaces: Option, + ) -> ConnectorResult { + panic!("[sql-schema-connector::flavour::postgres::wasm] Not implemented"); + } + + pub(super) async fn raw_cmd(&mut self, sql: &str, _url: &MigratePostgresUrl) -> ConnectorResult<()> { + tracing::debug!(query_type = "raw_cmd", sql); + panic!("[sql-schema-connector::flavour::postgres::wasm] Not implemented"); + } + + pub(super) async fn version(&mut self, url: &MigratePostgresUrl) -> ConnectorResult> { + tracing::debug!(query_type = "version"); + panic!("[sql-schema-connector::flavour::postgres::wasm] Not implemented"); + } + + pub(super) async fn query( + &mut self, + query: quaint::ast::Query<'_>, + url: &MigratePostgresUrl, + ) -> ConnectorResult { + use quaint::visitor::Visitor; + let (sql, params) = quaint::visitor::Postgres::build(query).unwrap(); + self.query_raw(&sql, ¶ms, url).await + } + + pub(super) async fn query_raw( + &mut self, + sql: &str, + params: &[quaint::prelude::Value<'_>], + _url: &MigratePostgresUrl, + ) -> ConnectorResult { + tracing::debug!(query_type = "query_raw", sql); + panic!("[sql-schema-connector::flavour::postgres::wasm] Not implemented"); + } + + pub(super) async fn describe_query( + &mut self, + sql: &str, + _url: &MigratePostgresUrl, + ) -> ConnectorResult { + tracing::debug!(query_type = "describe_query", sql); + panic!("[sql-schema-connector::flavour::postgres::wasm] Not implemented"); + } + + pub(super) async fn apply_migration_script(&mut self, migration_name: &str, script: &str) -> ConnectorResult<()> { + tracing::debug!(query_type = "raw_cmd", script); + panic!("[sql-schema-connector::flavour::postgres::wasm] Not implemented"); + } +} diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/wasm/shadow_db.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/wasm/shadow_db.rs new file mode 100644 index 00000000000..fc8a5d8183b --- /dev/null +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/postgres/wasm/shadow_db.rs @@ -0,0 +1,12 @@ +use crate::flavour::{PostgresFlavour, SqlFlavour}; +use schema_connector::Namespaces; +use schema_connector::{migrations_directory::MigrationDirectory, ConnectorResult}; +use sql_schema_describer::SqlSchema; + +pub async fn sql_schema_from_migrations_history( + migrations: &[MigrationDirectory], + mut shadow_db: PostgresFlavour, + namespaces: Option, +) -> ConnectorResult { + panic!("[sql-schema-connector::flavour::postgres::wasm] Not implemented"); +} 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 08d2a80944b..fab1e982d09 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite.rs @@ -1,13 +1,27 @@ -mod connection; +#[cfg(feature = "sqlite-native")] +mod native; + +#[cfg(not(feature = "sqlite-native"))] +mod wasm; + +#[cfg(feature = "sqlite-native")] +use native::{ + create_database, drop_database, ensure_connection_validity, generic_apply_migration_script, introspect, reset, + version, Connection, +}; + +#[cfg(not(feature = "sqlite-native"))] +use wasm::{ + create_database, drop_database, ensure_connection_validity, generic_apply_migration_script, introspect, reset, + version, Connection, +}; -use self::connection::*; use crate::flavour::SqlFlavour; use indoc::indoc; use schema_connector::{ migrations_directory::MigrationDirectory, BoxFuture, ConnectorError, ConnectorParams, ConnectorResult, Namespaces, }; use sql_schema_describer::SqlSchema; -use std::path::Path; type State = super::State; @@ -20,6 +34,13 @@ pub(crate) struct SqliteFlavour { state: State, } +impl SqliteFlavour { + #[cfg(not(feature = "sqlite-native"))] + pub(crate) fn new_external(_adapter: std::sync::Arc) -> Self { + SqliteFlavour { state: State::Initial } + } +} + impl Default for SqliteFlavour { fn default() -> Self { SqliteFlavour { state: State::Initial } @@ -74,22 +95,7 @@ impl SqlFlavour for SqliteFlavour { fn create_database(&mut self) -> BoxFuture<'_, ConnectorResult> { Box::pin(async { let params = self.state.get_unwrapped_params(); - let path = Path::new(¶ms.file_path); - - if path.exists() { - return Ok(params.file_path.clone()); - } - - let dir = path.parent(); - - if let Some((dir, false)) = dir.map(|dir| (dir, dir.exists())) { - std::fs::create_dir_all(dir) - .map_err(|err| ConnectorError::from_source(err, "Creating SQLite database parent directory."))?; - } - - Connection::new(params)?; - - Ok(params.file_path.clone()) + create_database(params) }) } @@ -123,10 +129,7 @@ impl SqlFlavour for SqliteFlavour { fn drop_database(&mut self) -> BoxFuture<'_, ConnectorResult<()>> { let params = self.state.get_unwrapped_params(); - let file_path = ¶ms.file_path; - let ret = std::fs::remove_file(file_path).map_err(|err| { - ConnectorError::from_msg(format!("Failed to delete SQLite database at `{file_path}`.\n{err}")) - }); + let ret = drop_database(params); ready(ret) } @@ -136,24 +139,7 @@ impl SqlFlavour for SqliteFlavour { fn ensure_connection_validity(&mut self) -> BoxFuture<'_, ConnectorResult<()>> { let params = self.state.get_unwrapped_params(); - let path = std::path::Path::new(¶ms.file_path); - // we use metadata() here instead of Path::exists() because we want accurate diagnostics: - // if the file is not reachable because of missing permissions, we don't want to return - // that the file doesn't exist. - let result = match std::fs::metadata(path) { - Ok(_) => Ok(()), - Err(error) if error.kind() == std::io::ErrorKind::NotFound => Err(ConnectorError::user_facing( - user_facing_errors::common::DatabaseDoesNotExist::Sqlite { - database_file_name: path - .file_name() - .map(|osstr| osstr.to_string_lossy().into_owned()) - .unwrap_or_else(|| params.file_path.clone()), - database_file_path: params.file_path.clone(), - }, - )), - Err(err) => Err(ConnectorError::from_source(err, "Failed to open SQLite database.")), - }; - + let result = ensure_connection_validity(params); ready(result) } @@ -182,18 +168,18 @@ impl SqlFlavour for SqliteFlavour { let rows = match conn.query_raw(SQL, &[]) { Ok(result) => result, Err(err) => { - if let Some(rusqlite::Error::SqliteFailure( - rusqlite::ffi::Error { + #[cfg(feature = "sqlite-native")] + if let Some(native::rusqlite::Error::SqliteFailure( + native::rusqlite::ffi::Error { extended_code: 1, // table not found .. }, _, - )) = err.source_as::() + )) = err.source_as::() { return Ok(Err(schema_connector::PersistenceNotInitializedError)); - } else { - return Err(err); } + return Err(err); } }; @@ -268,26 +254,9 @@ impl SqlFlavour for SqliteFlavour { fn introspect( &mut self, namespaces: Option, - _ctx: &schema_connector::IntrospectionContext, + ctx: &schema_connector::IntrospectionContext, ) -> BoxFuture<'_, ConnectorResult> { - Box::pin(async move { - if let Some(params) = self.state.params() { - let path = std::path::Path::new(¶ms.file_path); - if std::fs::metadata(path).is_err() { - return Err(ConnectorError::user_facing( - user_facing_errors::common::DatabaseDoesNotExist::Sqlite { - database_file_name: path - .file_name() - .map(|name| name.to_string_lossy().into_owned()) - .unwrap_or_default(), - database_file_path: params.file_path.clone(), - }, - )); - } - } - - self.describe_schema(namespaces).await - }) + introspect(self, namespaces, ctx) } fn raw_cmd<'a>(&'a mut self, sql: &'a str) -> BoxFuture<'a, ConnectorResult<()>> { @@ -296,23 +265,7 @@ impl SqlFlavour for SqliteFlavour { fn reset(&mut self, _namespaces: Option) -> BoxFuture<'_, ConnectorResult<()>> { ready(with_connection(&mut self.state, move |params, connection| { - let file_path = ¶ms.file_path; - - connection.raw_cmd("PRAGMA main.locking_mode=NORMAL")?; - connection.raw_cmd("PRAGMA main.quick_check")?; - - tracing::debug!("Truncating {:?}", file_path); - - std::fs::File::create(file_path).map_err(|io_error| { - ConnectorError::from_source( - io_error, - "Failed to truncate sqlite file. Please check that you have write permissions on the directory.", - ) - })?; - - acquire_lock(connection)?; - - Ok(()) + reset(params, connection) })) } @@ -369,7 +322,7 @@ impl SqlFlavour for SqliteFlavour { } fn version(&mut self) -> BoxFuture<'_, ConnectorResult>> { - ready(Ok(Some(quaint::connector::sqlite_version().to_owned()))) + version(self) } fn search_path(&self) -> &str { diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/native/mod.rs similarity index 65% rename from schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs rename to schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/native/mod.rs index a34cca5cb9e..b3f7ef23d62 100644 --- a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/connection.rs +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/native/mod.rs @@ -2,8 +2,9 @@ pub(crate) use quaint::connector::rusqlite; +use crate::flavour::SqlFlavour; use quaint::connector::{ColumnType, DescribedColumn, DescribedParameter, GetRow, ToColumnNames}; -use schema_connector::{ConnectorError, ConnectorResult}; +use schema_connector::{BoxFuture, ConnectorError, ConnectorResult, Namespaces}; use sql_schema_describer::{sqlite as describer, DescriberErrorKind, SqlSchema}; use sqlx_core::{column::Column, type_info::TypeInfo}; use sqlx_sqlite::SqliteColumn; @@ -151,6 +152,99 @@ pub(super) fn generic_apply_migration_script( }) } +pub(super) fn create_database(params: &super::Params) -> ConnectorResult { + let path = std::path::Path::new(¶ms.file_path); + + if path.exists() { + return Ok(params.file_path.clone()); + } + + let dir = path.parent(); + + if let Some((dir, false)) = dir.map(|dir| (dir, dir.exists())) { + std::fs::create_dir_all(dir) + .map_err(|err| ConnectorError::from_source(err, "Creating SQLite database parent directory."))?; + } + + Connection::new(params)?; + + Ok(params.file_path.clone()) +} + +pub(super) fn drop_database(params: &super::Params) -> ConnectorResult<()> { + let file_path = ¶ms.file_path; + std::fs::remove_file(file_path) + .map_err(|err| ConnectorError::from_msg(format!("Failed to delete SQLite database at `{file_path}`.\n{err}"))) +} + +pub(super) fn ensure_connection_validity(params: &super::Params) -> ConnectorResult<()> { + let path = std::path::Path::new(¶ms.file_path); + // we use metadata() here instead of Path::exists() because we want accurate diagnostics: + // if the file is not reachable because of missing permissions, we don't want to return + // that the file doesn't exist. + match std::fs::metadata(path) { + Ok(_) => Ok(()), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => Err(ConnectorError::user_facing( + user_facing_errors::common::DatabaseDoesNotExist::Sqlite { + database_file_name: path + .file_name() + .map(|osstr| osstr.to_string_lossy().into_owned()) + .unwrap_or_else(|| params.file_path.clone()), + database_file_path: params.file_path.clone(), + }, + )), + Err(err) => Err(ConnectorError::from_source(err, "Failed to open SQLite database.")), + } +} + +pub(super) fn introspect<'a>( + instance: &'a mut super::SqliteFlavour, + namespaces: Option, + _ctx: &schema_connector::IntrospectionContext, +) -> BoxFuture<'a, ConnectorResult> { + // TODO: move to a separate function + Box::pin(async move { + if let Some(params) = instance.state.params() { + let path = std::path::Path::new(¶ms.file_path); + if std::fs::metadata(path).is_err() { + return Err(ConnectorError::user_facing( + user_facing_errors::common::DatabaseDoesNotExist::Sqlite { + database_file_name: path + .file_name() + .map(|name| name.to_string_lossy().into_owned()) + .unwrap_or_default(), + database_file_path: params.file_path.clone(), + }, + )); + } + } + + instance.describe_schema(namespaces).await + }) +} + +pub(super) fn version(_instance: &mut super::SqliteFlavour) -> BoxFuture<'_, ConnectorResult>> { + super::ready(Ok(Some(quaint::connector::sqlite_version().to_owned()))) +} + +pub(super) fn reset(params: &super::Params, connection: &mut Connection) -> ConnectorResult<()> { + let file_path = ¶ms.file_path; + + connection.raw_cmd("PRAGMA main.locking_mode=NORMAL")?; + connection.raw_cmd("PRAGMA main.quick_check")?; + + tracing::debug!("Truncating {:?}", file_path); + + std::fs::File::create(file_path).map_err(|io_error| { + ConnectorError::from_source( + io_error, + "Failed to truncate sqlite file. Please check that you have write permissions on the directory.", + ) + })?; + + super::acquire_lock(connection) +} + fn convert_error(err: rusqlite::Error) -> ConnectorError { ConnectorError::from_source(err, "SQLite database error") } diff --git a/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/wasm/mod.rs b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/wasm/mod.rs new file mode 100644 index 00000000000..9262e6d4a6e --- /dev/null +++ b/schema-engine/connectors/sql-schema-connector/src/flavour/sqlite/wasm/mod.rs @@ -0,0 +1,88 @@ +//! All the quaint-wrangling for the sqlite connector should happen here. + +use quaint::connector::{ColumnType, DescribedColumn, DescribedParameter, GetRow, ToColumnNames}; +use schema_connector::{BoxFuture, ConnectorError, ConnectorResult, Namespaces}; +use sql_schema_describer::{sqlite as describer, DescriberErrorKind, SqlSchema}; +use user_facing_errors::schema_engine::ApplyMigrationError; + +// TODO: use ExternalConnector here. +pub(super) struct Connection(); + +impl Connection { + pub(super) fn new(params: &super::Params) -> ConnectorResult { + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); + } + + pub(super) fn new_in_memory() -> Self { + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); + } + + pub(super) async fn describe_schema(&mut self) -> ConnectorResult { + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); + } + + pub(super) fn raw_cmd(&mut self, sql: &str) -> ConnectorResult<()> { + tracing::debug!(query_type = "raw_cmd", sql); + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); + } + + pub(super) fn query(&mut self, query: quaint::ast::Query<'_>) -> ConnectorResult { + use quaint::visitor::Visitor; + let (sql, params) = quaint::visitor::Sqlite::build(query).unwrap(); + self.query_raw(&sql, ¶ms) + } + + pub(super) fn query_raw( + &mut self, + sql: &str, + params: &[quaint::prelude::Value<'_>], + ) -> ConnectorResult { + tracing::debug!(query_type = "query_raw", sql); + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); + } + + pub(super) fn describe_query( + &mut self, + sql: &str, + params: &super::Params, + ) -> ConnectorResult { + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); + } +} + +pub(super) fn generic_apply_migration_script( + migration_name: &str, + script: &str, + conn: &Connection, +) -> ConnectorResult<()> { + tracing::debug!(query_type = "raw_cmd", sql = script); + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); +} + +pub(super) fn create_database(params: &super::Params) -> ConnectorResult { + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); +} + +pub(super) fn drop_database(params: &super::Params) -> ConnectorResult<()> { + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); +} + +pub(super) fn ensure_connection_validity(params: &super::Params) -> ConnectorResult<()> { + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); +} + +pub(super) fn introspect<'a>( + instance: &'a mut super::SqliteFlavour, + namespaces: Option, + _ctx: &schema_connector::IntrospectionContext, +) -> BoxFuture<'a, ConnectorResult> { + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); +} + +pub(super) fn reset(params: &super::Params, connection: &mut Connection) -> ConnectorResult<()> { + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); +} + +pub(super) fn version(instance: &mut super::SqliteFlavour) -> BoxFuture<'_, ConnectorResult>> { + panic!("[sql-schema-connector::flavour::sqlite::wasm] Not implemented"); +} diff --git a/schema-engine/connectors/sql-schema-connector/src/introspection/datamodel_calculator/context.rs b/schema-engine/connectors/sql-schema-connector/src/introspection/datamodel_calculator/context.rs index ca97019df32..37d9716e18a 100644 --- a/schema-engine/connectors/sql-schema-connector/src/introspection/datamodel_calculator/context.rs +++ b/schema-engine/connectors/sql-schema-connector/src/introspection/datamodel_calculator/context.rs @@ -36,9 +36,13 @@ pub(crate) struct DatamodelCalculatorContext<'a> { impl<'a> DatamodelCalculatorContext<'a> { pub(crate) fn new(ctx: &'a IntrospectionContext, sql_schema: &'a sql::SqlSchema, search_path: &'a str) -> Self { let flavour: Box = match ctx.sql_family() { + #[cfg(any(feature = "postgresql", feature = "cockroachdb"))] SqlFamily::Postgres => Box::new(flavour::PostgresIntrospectionFlavour), + #[cfg(feature = "mysql")] SqlFamily::Mysql => Box::new(flavour::MysqlIntrospectionFlavour), + #[cfg(feature = "sqlite")] SqlFamily::Sqlite => Box::new(flavour::SqliteIntrospectionFlavour), + #[cfg(feature = "mssql")] SqlFamily::Mssql => Box::new(flavour::SqlServerIntrospectionFlavour), }; diff --git a/schema-engine/connectors/sql-schema-connector/src/lib.rs b/schema-engine/connectors/sql-schema-connector/src/lib.rs index 3641eeb9633..c1caa2eb114 100644 --- a/schema-engine/connectors/sql-schema-connector/src/lib.rs +++ b/schema-engine/connectors/sql-schema-connector/src/lib.rs @@ -18,7 +18,7 @@ mod sql_schema_differ; use database_schema::SqlDatabaseSchema; use enumflags2::BitFlags; -use flavour::{MssqlFlavour, MysqlFlavour, PostgresFlavour, SqlFlavour, SqliteFlavour}; +use flavour::SqlFlavour; use migration_pair::MigrationPair; use psl::{datamodel_connector::NativeTypeInstance, parser_database::ScalarType, ValidatedSchema}; use quaint::connector::DescribedQuery; @@ -37,18 +37,38 @@ pub struct SqlSchemaConnector { } impl SqlSchemaConnector { + /// Initialize an external PostgreSQL migration connector. + #[cfg(all(feature = "postgresql", not(feature = "postgresql-native")))] + pub fn new_postgres_external(adapter: Arc) -> Self { + SqlSchemaConnector { + flavour: Box::new(flavour::PostgresFlavour::new_external(adapter)), + host: Arc::new(EmptyHost), + } + } + + /// Initialize an external SQLite migration connector. + #[cfg(all(feature = "sqlite", not(feature = "sqlite-native")))] + pub fn new_sqlite_external(adapter: Arc) -> Self { + SqlSchemaConnector { + flavour: Box::new(flavour::SqliteFlavour::new_external(adapter)), + host: Arc::new(EmptyHost), + } + } + /// Initialize a PostgreSQL migration connector. + #[cfg(feature = "postgresql-native")] pub fn new_postgres() -> Self { SqlSchemaConnector { - flavour: Box::new(PostgresFlavour::new_postgres()), + flavour: Box::new(flavour::PostgresFlavour::new_postgres()), host: Arc::new(EmptyHost), } } /// Initialize a CockroachDb migration connector. + #[cfg(feature = "cockroachdb-native")] pub fn new_cockroach() -> Self { SqlSchemaConnector { - flavour: Box::new(PostgresFlavour::new_cockroach()), + flavour: Box::new(flavour::PostgresFlavour::new_cockroach()), host: Arc::new(EmptyHost), } } @@ -57,33 +77,37 @@ impl SqlSchemaConnector { /// /// Use [`Self::new_postgres()`] or [`Self::new_cockroach()`] instead when the provider is /// explicitly specified by user or already known otherwise. + #[cfg(any(feature = "postgresql-native", feature = "cockroachdb-native"))] pub fn new_postgres_like() -> Self { SqlSchemaConnector { - flavour: Box::::default(), + flavour: Box::::default(), host: Arc::new(EmptyHost), } } /// Initialize a SQLite migration connector. + #[cfg(feature = "sqlite-native")] pub fn new_sqlite() -> Self { SqlSchemaConnector { - flavour: Box::::default(), + flavour: Box::::default(), host: Arc::new(EmptyHost), } } /// Initialize a MySQL migration connector. + #[cfg(feature = "mysql-native")] pub fn new_mysql() -> Self { SqlSchemaConnector { - flavour: Box::::default(), + flavour: Box::::default(), host: Arc::new(EmptyHost), } } /// Initialize a MSSQL migration connector. + #[cfg(feature = "mssql-native")] pub fn new_mssql() -> Self { SqlSchemaConnector { - flavour: Box::::default(), + flavour: Box::::default(), host: Arc::new(EmptyHost), } } @@ -162,6 +186,7 @@ impl SqlSchemaConnector { } impl SchemaConnector for SqlSchemaConnector { + // TODO: this only seems to be used in `sql-migration-tests`. fn set_host(&mut self, host: Arc) { self.host = host; } @@ -184,6 +209,7 @@ impl SchemaConnector for SqlSchemaConnector { fn acquire_lock(&mut self) -> BoxFuture<'_, ConnectorResult<()>> { // If the env is set and non empty or set to `0`, we disable the lock. + // TODO: avoid using `std::env::var` in Wasm. let disable_lock: bool = std::env::var("PRISMA_SCHEMA_DISABLE_ADVISORY_LOCK") .ok() .map(|value| !matches!(value.as_str(), "0" | "")) @@ -214,6 +240,7 @@ impl SchemaConnector for SqlSchemaConnector { self.flavour.ensure_connection_validity() } + // TODO: this only seems to be used in `sql-migration-tests`. fn host(&self) -> &Arc { &self.host } diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_destructive_change_checker/destructive_change_checker_flavour.rs b/schema-engine/connectors/sql-schema-connector/src/sql_destructive_change_checker/destructive_change_checker_flavour.rs index 9f9e6d3704d..6dea521b5e3 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_destructive_change_checker/destructive_change_checker_flavour.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_destructive_change_checker/destructive_change_checker_flavour.rs @@ -1,6 +1,13 @@ +#[cfg(feature = "mssql")] mod mssql; + +#[cfg(feature = "mysql")] mod mysql; + +#[cfg(any(feature = "postgresql", feature = "cockroachdb"))] mod postgres; + +#[cfg(feature = "sqlite")] mod sqlite; use super::{ 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 66d32ef522f..1cc87d9087e 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_renderer.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_renderer.rs @@ -9,9 +9,17 @@ //! statements, this is done later. mod common; + +#[cfg(feature = "mssql")] mod mssql_renderer; + +#[cfg(feature = "mysql")] mod mysql_renderer; + +#[cfg(any(feature = "postgresql", feature = "cockroachdb"))] mod postgres_renderer; + +#[cfg(feature = "sqlite")] mod sqlite_renderer; pub(crate) use common::IteratorJoin; diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_schema_calculator/sql_schema_calculator_flavour.rs b/schema-engine/connectors/sql-schema-connector/src/sql_schema_calculator/sql_schema_calculator_flavour.rs index eaecc07de17..03cb6c9d7bb 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_schema_calculator/sql_schema_calculator_flavour.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_schema_calculator/sql_schema_calculator_flavour.rs @@ -1,6 +1,13 @@ +#[cfg(feature = "mssql")] mod mssql; + +#[cfg(feature = "mysql")] mod mysql; + +#[cfg(any(feature = "postgresql", feature = "cockroachdb"))] mod postgres; + +#[cfg(feature = "sqlite")] mod sqlite; use psl::parser_database::{ast::FieldArity, walkers::*}; diff --git a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour.rs b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour.rs index 8584b9a97e9..35eab824ea0 100644 --- a/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour.rs +++ b/schema-engine/connectors/sql-schema-connector/src/sql_schema_differ/sql_schema_differ_flavour.rs @@ -5,9 +5,16 @@ use sql_schema_describer::{ TableColumnId, }; +#[cfg(feature = "mssql")] mod mssql; + +#[cfg(feature = "mysql")] mod mysql; + +#[cfg(any(feature = "postgresql", feature = "cockroachdb"))] mod postgres; + +#[cfg(feature = "sqlite")] mod sqlite; /// Trait to specialize SQL schema diffing (resulting in migration steps) by SQL backend. diff --git a/schema-engine/core-wasm/Cargo.toml b/schema-engine/core-wasm/Cargo.toml new file mode 100644 index 00000000000..2354cbd1fe1 --- /dev/null +++ b/schema-engine/core-wasm/Cargo.toml @@ -0,0 +1,24 @@ +[package] +edition = "2021" +name = "schema-core-wasm" +version = "0.1.0" + +[dependencies] +psl = { workspace = true, features = ["all"] } +schema-connector = { path = "../connectors/schema-connector" } + +async-trait.workspace = true +chrono.workspace = true +enumflags2.workspace = true +jsonrpc-core = "17.0" +serde.workspace = true +serde_json.workspace = true +tokio.workspace = true +tracing.workspace = true +tracing-subscriber = "0.3" +tracing-futures.workspace = true +url.workspace = true + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen.workspace = true +tsify.workspace = true diff --git a/schema-engine/core-wasm/src/api.rs b/schema-engine/core-wasm/src/api.rs new file mode 100644 index 00000000000..adbdec3dde7 --- /dev/null +++ b/schema-engine/core-wasm/src/api.rs @@ -0,0 +1,23 @@ +//! The external facing programmatic API to the schema engine. + +use crate::CoreResult; + +/// The programmatic, generic, fantastic schema engine API. +/// +/// TODO: should we even be constrained to such a generic API? Likely not. +/// E.g., version() only needs a single database connector, while diff() might need two. +/// Plus, subsequent calls to these API might depend on different database connectors that +/// must be passed in from the outer JavaScript layer. +#[async_trait::async_trait] +pub trait GenericApi: Send + Sync + 'static { + /// Return the database version as a string. + async fn version(&self, params: Option) -> CoreResult; + + /// Make sure the connection to the database is established and valid. + /// Connectors can choose to connect lazily, but this method should force + /// them to connect. + async fn ensure_connection_validity( + &self, + params: EnsureConnectionValidityParams, + ) -> CoreResult; +} diff --git a/schema-engine/core-wasm/src/core_error.rs b/schema-engine/core-wasm/src/core_error.rs new file mode 100644 index 00000000000..cdaa6ca2711 --- /dev/null +++ b/schema-engine/core-wasm/src/core_error.rs @@ -0,0 +1,7 @@ +use schema_connector::ConnectorError; + +/// The result type for schema engine commands +pub type CoreResult = Result; + +/// The top-level error type for schema engine commands +pub type CoreError = ConnectorError; diff --git a/schema-engine/core-wasm/src/lib.rs b/schema-engine/core-wasm/src/lib.rs new file mode 100644 index 00000000000..8d2c599d2cd --- /dev/null +++ b/schema-engine/core-wasm/src/lib.rs @@ -0,0 +1,33 @@ +#![deny(rust_2018_idioms, unsafe_code, missing_docs)] +#![allow(clippy::needless_collect)] // the implementation of that rule is way too eager, it rejects necessary collects +#![allow(clippy::derive_partial_eq_without_eq)] + +//! The top-level library crate for `schema-engine-wasm`. + +mod api; +mod core_error; +mod state; +mod types; + +// Inspired by request_handlers::load_executor::ConnectorKind +pub enum ConnectorKind<'a> { + #[cfg(not(target_arch = "wasm32"))] + Rust { url: String }, + #[cfg(target_arch = "wasm32")] + Js { + adapter: Arc, + active_provider: &'a str, + _phantom: PhantomData<&'a ()>, // required for WASM target, where JS is the only variant and lifetime gets unused + }, +} + +fn connector_for_provider(connector_kind: ConnectorKind<'_>) { + match connector_kind { + #[cfg(not(target_arch = "wasm32"))] + ConnectorKind::Rust { .. } => { + panic!("`core-wasm` only supports JS connectors"); + } + #[cfg(target_arch = "wasm32")] + ConnectorKind::Js { adapter, .. } => {} + } +} diff --git a/schema-engine/core-wasm/src/state.rs b/schema-engine/core-wasm/src/state.rs new file mode 100644 index 00000000000..e88d7594a47 --- /dev/null +++ b/schema-engine/core-wasm/src/state.rs @@ -0,0 +1,45 @@ +use crate::api::GenericApi; +use quaint::connector::ExternalConnector; +use std::sync::Arc; + +// Blanket impl for any Arc where T: ExternalConnector + ?Sized +#[async_trait::async_trait] +impl GenericApi for Arc +where + T: ExternalConnector + ?Sized, +{ + async fn version(&self, _params: Option) -> CoreResult { + Ok("1.0.0".to_string()) + } + + async fn ensure_connection_validity( + &self, + _params: EnsureConnectionValidityParams, + ) -> CoreResult { + Ok(EnsureConnectionValidityResult {}) + } +} + +pub struct EngineState { + adapter: Arc, +} + +impl GenericApi { + pub fn new(adapter: Arc) -> Self { + Self { adapter } + } +} + +#[async_trait::async_trait] +impl GenericApi for EngineState { + async fn version(&self, _params: Option) -> CoreResult { + Ok("1.0.0".to_string()) + } + + async fn ensure_connection_validity( + &self, + _params: EnsureConnectionValidityParams, + ) -> CoreResult { + Ok(EnsureConnectionValidityResult {}) + } +} diff --git a/schema-engine/core-wasm/src/types/common.rs b/schema-engine/core-wasm/src/types/common.rs new file mode 100644 index 00000000000..4fc694d69f6 --- /dev/null +++ b/schema-engine/core-wasm/src/types/common.rs @@ -0,0 +1,48 @@ +/// The path to a live database taken as input. For flexibility, this can be Prisma schemas as strings, or only the +/// connection string. See variants. +#[derive(Debug, Deserialize)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] +#[cfg_attr(target_arch = "wasm32", tsify(from_wasm_abi))] +#[serde(rename_all = "camelCase")] +pub enum DatasourceParam { + /// A container that holds multiple Prisma schema files. + Schema(SchemasContainer), + + /// An object with a `url` field. + ConnectionString(UrlContainer), +} + +/// A container that holds the path and the content of a Prisma schema file. +#[derive(Debug, Deserialize)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] +#[cfg_attr(target_arch = "wasm32", tsify(from_wasm_abi))] +#[serde(rename_all = "camelCase")] +pub struct SchemaContainer { + /// The content of the Prisma schema file. + content: String, + + /// The file name of the Prisma schema file. + path: String, +} + +/// A list of Prisma schema files with a config directory. +#[derive(Debug, Deserialize)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] +#[cfg_attr(target_arch = "wasm32", tsify(from_wasm_abi))] +#[serde(rename_all = "camelCase")] +pub struct SchemasWithConfigDir { + /// A list of Prisma schema files. + files: Vec, + + /// An optional directory containing the config files such as SSL certificates + config_dir: String, +} + +/// An object with a `url` field. +#[derive(Debug, Deserialize)] +#[cfg_attr(target_arch = "wasm32", derive(Tsify))] +#[cfg_attr(target_arch = "wasm32", tsify(from_wasm_abi))] +#[serde(rename_all = "camelCase")] +pub struct UrlContainer { + url: String, +} diff --git a/schema-engine/core-wasm/src/types/get_database_version.rs b/schema-engine/core-wasm/src/types/get_database_version.rs new file mode 100644 index 00000000000..dc64dcd07aa --- /dev/null +++ b/schema-engine/core-wasm/src/types/get_database_version.rs @@ -0,0 +1,9 @@ +//! Get the database version for error reporting. + +pub struct GetDatabaseVersionInput { + pub datasource: DatasourceParam, +} + +pub struct GetDatabaseVersionOutput { + pub version: String, +} diff --git a/schema-engine/core-wasm/src/types/mod.rs b/schema-engine/core-wasm/src/types/mod.rs new file mode 100644 index 00000000000..a30abfdc921 --- /dev/null +++ b/schema-engine/core-wasm/src/types/mod.rs @@ -0,0 +1,2 @@ +pub(crate) use common; +pub(crate) use get_database_version; diff --git a/schema-engine/core/Cargo.toml b/schema-engine/core/Cargo.toml index c16d81b2df1..1bbc15190cb 100644 --- a/schema-engine/core/Cargo.toml +++ b/schema-engine/core/Cargo.toml @@ -9,7 +9,9 @@ schema-connector = { path = "../connectors/schema-connector", features = [ "all-native", ] } mongodb-schema-connector = { path = "../connectors/mongodb-schema-connector" } -sql-schema-connector = { path = "../connectors/sql-schema-connector" } +sql-schema-connector = { path = "../connectors/sql-schema-connector", features = [ + "all-native", +] } user-facing-errors = { path = "../../libs/user-facing-errors", features = [ "quaint", ] } diff --git a/schema-engine/schema-engine-wasm/src/wasm/engine.rs b/schema-engine/schema-engine-wasm/src/wasm/engine.rs index 04fc4f363a7..bc56884d4e9 100644 --- a/schema-engine/schema-engine-wasm/src/wasm/engine.rs +++ b/schema-engine/schema-engine-wasm/src/wasm/engine.rs @@ -89,6 +89,7 @@ impl SchemaEngine { } /// Reset a database to an empty state (no data, no schema). + #[wasm_bindgen] pub async fn reset(&self) -> Result<(), wasm_bindgen::JsError> { Err(wasm_bindgen::JsError::new("Not yet available.")) } diff --git a/schema-engine/sql-introspection-tests/Cargo.toml b/schema-engine/sql-introspection-tests/Cargo.toml index d612d787329..efc01a7e62b 100644 --- a/schema-engine/sql-introspection-tests/Cargo.toml +++ b/schema-engine/sql-introspection-tests/Cargo.toml @@ -7,7 +7,9 @@ edition = "2021" schema-connector = { path = "../connectors/schema-connector", features = [ "all-native", ] } -sql-schema-connector = { path = "../connectors/sql-schema-connector" } +sql-schema-connector = { path = "../connectors/sql-schema-connector", features = [ + "all-native", +] } sql-schema-describer = { path = "../sql-schema-describer", features = [ "all-native", ] } diff --git a/schema-engine/sql-migration-tests/Cargo.toml b/schema-engine/sql-migration-tests/Cargo.toml index b2b3646ef2e..f1584ecb2b8 100644 --- a/schema-engine/sql-migration-tests/Cargo.toml +++ b/schema-engine/sql-migration-tests/Cargo.toml @@ -6,7 +6,9 @@ edition = "2021" [dependencies] psl = { workspace = true, features = ["all"] } schema-core = { path = "../core" } -sql-schema-connector = { path = "../connectors/sql-schema-connector" } +sql-schema-connector = { path = "../connectors/sql-schema-connector", features = [ + "all-native", +] } sql-schema-describer = { path = "../sql-schema-describer", features = [ "all-native", ] }