Skip to content

Commit 82e5178

Browse files
authored
feat: MSSQL driver adapter (#5469)
[ORM-1057](https://linear.app/prisma-company/issue/ORM-1057/support-mssql-driver-adapter-in-the-query-engine) Doesn't add any engines tests yet, this just makes it possible to build a wasm bundle for MSSQL and fixes compilation errors
1 parent 76e8357 commit 82e5178

32 files changed

Lines changed: 403 additions & 283 deletions

File tree

libs/driver-adapters/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edition = "2021"
77
mysql = ["quaint/mysql"]
88
sqlite = ["quaint/sqlite"]
99
postgresql = ["quaint/postgresql"]
10+
mssql = ["quaint/mssql"]
1011

1112
[lints]
1213
workspace = true

libs/driver-adapters/executor/src/query-compiler-wasm.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as wasmPostgres from '../../../../query-compiler/query-compiler-wasm/pkg/postgresql/query_compiler_bg.js'
22
import * as wasmMysql from '../../../../query-compiler/query-compiler-wasm/pkg/mysql/query_compiler_bg.js'
33
import * as wasmSqlite from '../../../../query-compiler/query-compiler-wasm/pkg/sqlite/query_compiler_bg.js'
4+
import * as wasmSqlServer from '../../../../query-compiler/query-compiler-wasm/pkg/sqlserver/query_compiler_bg.js'
45
import fs from 'node:fs/promises'
56
import path from 'node:path'
67
import { __dirname } from './utils.js'
@@ -9,6 +10,7 @@ const wasm = {
910
postgres: wasmPostgres,
1011
mysql: wasmMysql,
1112
sqlite: wasmSqlite,
13+
sqlserver: wasmSqlServer,
1214
}
1315

1416
type EngineName = keyof typeof wasm

libs/driver-adapters/executor/src/query-compiler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { QueryPlanNode } from '@prisma/client-engine-runtime'
55
export type QueryCompilerParams = {
66
// TODO: support multiple datamodels
77
datamodel: string
8-
provider: 'postgres' | 'mysql' | 'sqlite'
8+
provider: 'postgres' | 'mysql' | 'sqlite' | 'sqlserver'
99
connectionInfo: ConnectionInfo
1010
}
1111

libs/driver-adapters/src/conversion/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ pub(crate) mod js_arg;
22
pub(crate) mod js_arg_type;
33
pub(crate) mod js_to_quaint;
44

5+
#[cfg(feature = "mssql")]
6+
pub(crate) mod mssql;
57
#[cfg(feature = "mysql")]
68
pub(crate) mod mysql;
79
#[cfg(feature = "postgresql")]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use crate::conversion::JSArg;
2+
use serde_json::value::Value as JsonValue;
3+
4+
#[rustfmt::skip]
5+
pub fn value_to_js_arg(value: &quaint::Value) -> serde_json::Result<JSArg> {
6+
let res = match (&value.typed, value.native_column_type_name()) {
7+
(quaint::ValueType::DateTime(Some(dt)), _) => JSArg::Value(JsonValue::String(dt.naive_utc().to_string())),
8+
(quaint::ValueType::Json(Some(s)), _) => JSArg::Value(JsonValue::String(serde_json::to_string(s)?)),
9+
(quaint::ValueType::Bytes(Some(bytes)), _) => JSArg::Buffer(bytes.to_vec()),
10+
(quaint::ValueType::Int32(Some(value)), _) => JSArg::SafeInt(*value),
11+
(quaint::ValueType::Numeric(Some(bd)), _) => JSArg::Value(JsonValue::String(bd.to_string())),
12+
(quaint::ValueType::Array(Some(items)), _) => JSArg::Array(
13+
items
14+
.iter()
15+
.map(value_to_js_arg)
16+
.collect::<serde_json::Result<Vec<JSArg>>>()?,
17+
),
18+
(quaint_value, _) => JSArg::from(JsonValue::from(quaint_value.clone())),
19+
};
20+
21+
Ok(res)
22+
}

libs/driver-adapters/src/error.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ use quaint::error::PostgresError;
66

77
#[cfg(feature = "sqlite")]
88
use quaint::error::SqliteError;
9+
10+
#[cfg(feature = "mssql")]
11+
use quaint::error::MssqlError;
12+
913
use serde::Deserialize;
1014

1115
#[cfg(feature = "postgresql")]
@@ -37,6 +41,14 @@ pub struct SqliteErrorDef {
3741
pub message: Option<String>,
3842
}
3943

44+
#[cfg(feature = "mssql")]
45+
#[derive(Deserialize)]
46+
#[serde(remote = "MssqlError")]
47+
pub struct MssqlErrorDef {
48+
pub code: u32,
49+
pub message: String,
50+
}
51+
4052
#[derive(Deserialize)]
4153
#[serde(tag = "kind")]
4254
/// Wrapper for JS-side errors
@@ -91,6 +103,9 @@ pub(crate) enum DriverAdapterError {
91103
cause: String,
92104
},
93105
MissingFullTextSearchIndex,
106+
TransactionAlreadyClosed {
107+
cause: String,
108+
},
94109
#[cfg(feature = "postgresql")]
95110
#[serde(rename = "postgres")]
96111
Postgres(#[serde(with = "PostgresErrorDef")] PostgresError),
@@ -100,6 +115,9 @@ pub(crate) enum DriverAdapterError {
100115
#[cfg(feature = "sqlite")]
101116
#[serde(rename = "sqlite")]
102117
Sqlite(#[serde(with = "SqliteErrorDef")] SqliteError),
118+
#[cfg(feature = "mssql")]
119+
#[serde(rename = "mssql")]
120+
Mssql(#[serde(with = "MssqlErrorDef")] MssqlError),
103121
}
104122

105123
#[derive(Deserialize)]

libs/driver-adapters/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,18 @@ impl From<DriverAdapterError> for QuaintError {
8888
DriverAdapterError::MissingFullTextSearchIndex => {
8989
QuaintError::builder(ErrorKind::MissingFullTextSearchIndex).build()
9090
}
91+
DriverAdapterError::TransactionAlreadyClosed { cause } => {
92+
QuaintError::builder(ErrorKind::TransactionAlreadyClosed(cause)).build()
93+
}
9194
DriverAdapterError::GenericJs { id } => QuaintError::external_error(id),
9295
#[cfg(feature = "postgresql")]
9396
DriverAdapterError::Postgres(e) => e.into(),
9497
#[cfg(feature = "mysql")]
9598
DriverAdapterError::Mysql(e) => e.into(),
9699
#[cfg(feature = "sqlite")]
97100
DriverAdapterError::Sqlite(e) => e.into(),
101+
#[cfg(feature = "mssql")]
102+
DriverAdapterError::Mssql(e) => e.into(),
98103
// in future, more error types would be added and we'll need to convert them to proper QuaintErrors here
99104
}
100105
}

libs/driver-adapters/src/proxy.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ pub(crate) struct DriverProxy {
5656
get_connection_info: Option<AdapterMethod<(), JsConnectionInfo>>,
5757

5858
/// Start a new transaction.
59-
start_transaction: AdapterMethod<Option<String>, JsTransaction>,
59+
start_transaction: AdapterMethod<(), JsTransaction>,
60+
61+
/// Start a new transaction with a specific isolation level.
62+
start_transaction_with_isolation_level: AdapterMethod<String, JsTransaction>,
6063

6164
/// Dispose of the underlying driver.
6265
dispose: AdapterMethod<(), ()>,
@@ -146,6 +149,7 @@ impl DriverProxy {
146149
Ok(Self {
147150
execute_script: get_named_property(object, "executeScript")?,
148151
start_transaction: get_named_property(object, "startTransaction")?,
152+
start_transaction_with_isolation_level: get_named_property(object, "startTransaction")?,
149153
get_connection_info: get_optional_named_property(object, "getConnectionInfo")?,
150154
dispose: get_named_property(object, "dispose")?,
151155
})
@@ -156,10 +160,13 @@ impl DriverProxy {
156160
}
157161

158162
async fn start_transaction_inner(&self, isolation: Option<IsolationLevel>) -> quaint::Result<Box<JsTransaction>> {
159-
let tx = self
160-
.start_transaction
161-
.call_as_async(isolation.map(|lvl| lvl.to_string()))
162-
.await?;
163+
let tx = if let Some(level) = isolation {
164+
self.start_transaction_with_isolation_level
165+
.call_as_async(level.to_string())
166+
.await?
167+
} else {
168+
self.start_transaction.call_as_async(()).await?
169+
};
163170

164171
// Decrement for this gauge is done in JsTransaction::commit/JsTransaction::rollback
165172
// Previously, it was done in JsTransaction::new, similar to the native Transaction.

libs/driver-adapters/src/queryable.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ impl JsBaseQueryable {
5656
AdapterProvider::Postgres => visitor::Postgres::build(q),
5757
#[cfg(feature = "sqlite")]
5858
AdapterProvider::Sqlite => visitor::Sqlite::build(q),
59+
#[cfg(feature = "mssql")]
60+
AdapterProvider::SqlServer => visitor::Mssql::build(q),
5961
}
6062
}
6163

@@ -69,6 +71,8 @@ impl JsBaseQueryable {
6971
AdapterProvider::Sqlite => conversion::sqlite::value_to_js_arg,
7072
#[cfg(feature = "mysql")]
7173
AdapterProvider::Mysql => conversion::mysql::value_to_js_arg,
74+
#[cfg(feature = "mssql")]
75+
AdapterProvider::SqlServer => conversion::mssql::value_to_js_arg,
7276
};
7377

7478
let args = values
@@ -134,16 +138,18 @@ impl QuaintQueryable for JsBaseQueryable {
134138

135139
// Note: Needed by the Wasm Schema Engine only.
136140
async fn version(&self) -> quaint::Result<Option<String>> {
137-
let version_fn: &'static str = match self.provider {
141+
let version_expr: &'static str = match self.provider {
138142
#[cfg(feature = "mysql")]
139-
AdapterProvider::Mysql => visitor::Mysql::version_fn(),
143+
AdapterProvider::Mysql => visitor::Mysql::version_expr(),
140144
#[cfg(feature = "postgresql")]
141-
AdapterProvider::Postgres => visitor::Postgres::version_fn(),
145+
AdapterProvider::Postgres => visitor::Postgres::version_expr(),
142146
#[cfg(feature = "sqlite")]
143-
AdapterProvider::Sqlite => visitor::Sqlite::version_fn(),
147+
AdapterProvider::Sqlite => visitor::Sqlite::version_expr(),
148+
#[cfg(feature = "mssql")]
149+
AdapterProvider::SqlServer => visitor::Mssql::version_expr(),
144150
};
145151

146-
let query = format!(r#"SELECT {}() AS version"#, version_fn);
152+
let query = format!(r#"SELECT {} AS version"#, version_expr);
147153
let rows = self.query_raw(query.as_str(), &[]).await?;
148154

149155
let version_string = rows
@@ -173,6 +179,8 @@ impl QuaintQueryable for JsBaseQueryable {
173179
AdapterProvider::Postgres => false,
174180
#[cfg(feature = "sqlite")]
175181
AdapterProvider::Sqlite => false,
182+
#[cfg(feature = "mssql")]
183+
AdapterProvider::SqlServer => true,
176184
}
177185
}
178186
}

libs/driver-adapters/src/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ impl JsConnectionInfo {
5353
AdapterProvider::Postgres => quaint::connector::DEFAULT_POSTGRES_SCHEMA,
5454
#[cfg(feature = "sqlite")]
5555
AdapterProvider::Sqlite => quaint::connector::DEFAULT_SQLITE_DATABASE,
56+
#[cfg(feature = "mssql")]
57+
AdapterProvider::SqlServer => quaint::connector::DEFAULT_MSSQL_SCHEMA,
5658
}
5759
}
5860
}

0 commit comments

Comments
 (0)