diff --git a/query-engine/js-connectors/smoke-test-js/README.md b/query-engine/js-connectors/smoke-test-js/README.md index 081d293d3c2..ff9a1cdc4a1 100644 --- a/query-engine/js-connectors/smoke-test-js/README.md +++ b/query-engine/js-connectors/smoke-test-js/README.md @@ -10,7 +10,7 @@ We assume Node.js `v18.16.1`+ is installed. - Create a `.envrc` starting from `.envrc.example`, and fill in the missing values following the given template - Install Node.js dependencies via ```bash - npm i + pnpm i ``` ### PlanetScale @@ -33,11 +33,11 @@ In the current directory: ## How to test -There is no automatic test. However, [./src/index.ts](./src/index.ts) includes a pipeline you can use to interactively experiment with the new Query Engine. +There is no automatic test. However, [./src/planetscale.ts](./src/planetscale.ts) includes a pipeline you can use to interactively experiment with the new Query Engine. In particular, the pipeline steps are currently the following: -- Define `db`, a class instance wrapper around the `@planetscale/database` JS driver for PlanetScale +- Define `db`, a class instance wrapper around the `@planetscale/database` serverless driver for PlanetScale - Define `nodejsFnCtx`, an object exposing (a)sync "Queryable" functions that can be safely passed to Rust, so that it can interact with `db`'s class methods - Load the *debug* version of `libquery`, i.e., the compilation artifact of the `query-engine-node-api` crate - Define `engine` via the `QueryEngine` constructor exposed by Rust diff --git a/query-engine/js-connectors/smoke-test-js/prisma/schema.prisma b/query-engine/js-connectors/smoke-test-js/prisma/schema.prisma index 3782fdf3c94..1234a64ce53 100644 --- a/query-engine/js-connectors/smoke-test-js/prisma/schema.prisma +++ b/query-engine/js-connectors/smoke-test-js/prisma/schema.prisma @@ -4,6 +4,7 @@ generator client { } datasource db { + // Once ran `prisma migrate reset` as per instructions in README.md replace `mysql` with `@prisma/planetscale` in the line below provider = "@prisma/planetscale" url = env("JS_PLANETSCALE_DATABASE_URL") shadowDatabaseUrl = env("JS_PLANETSCALE_SHADOW_DATABASE_URL") diff --git a/query-engine/js-connectors/smoke-test-js/src/driver/planetscale.ts b/query-engine/js-connectors/smoke-test-js/src/connector/planetscale.ts similarity index 90% rename from query-engine/js-connectors/smoke-test-js/src/driver/planetscale.ts rename to query-engine/js-connectors/smoke-test-js/src/connector/planetscale.ts index f893f82d8ef..7adc9a01523 100644 --- a/query-engine/js-connectors/smoke-test-js/src/driver/planetscale.ts +++ b/query-engine/js-connectors/smoke-test-js/src/connector/planetscale.ts @@ -112,16 +112,17 @@ type PlanetScaleConfig = class PrismaPlanetScale implements Connector, Closeable { private client: planetScale.Connection - private maybeVersion?: string + private versionPromise: Promise private isRunning: boolean = true constructor(config: PlanetScaleConfig) { this.client = planetScale.connect(config) - // lazily retrieve the version and store it into `maybeVersion` - this.client.execute('SELECT @@version, @@GLOBAL.version').then((results) => { - this.maybeVersion = results.rows[0]['@@version'] - }) + this.versionPromise = new Promise((resolve, reject) => { + this.client.execute('SELECT @@version') + .then((results) => resolve(results.rows[0]['@@version'])) + .catch((error) => reject(error)); + }); } async close(): Promise { @@ -133,10 +134,12 @@ class PrismaPlanetScale implements Connector, Closeable { /** * Returns false, if connection is considered to not be in a working state. */ - isHealthy(): boolean { - const result = this.maybeVersion !== undefined - && this.isRunning - return result + async isHealthy(): Promise { + try { + return await this.versionPromise !== undefined && this.isRunning + } catch { + return false + } } /** @@ -174,7 +177,7 @@ class PrismaPlanetScale implements Connector, Closeable { * parsing or normalization. */ version(): Promise { - return Promise.resolve(this.maybeVersion) + return this.versionPromise } } diff --git a/query-engine/js-connectors/smoke-test-js/src/connector/util.ts b/query-engine/js-connectors/smoke-test-js/src/connector/util.ts new file mode 100644 index 00000000000..c88fc201111 --- /dev/null +++ b/query-engine/js-connectors/smoke-test-js/src/connector/util.ts @@ -0,0 +1,13 @@ + +import { Closeable, Connector } from '../engines/types/Library.js'; + +// *.bind(db) is required to preserve the `this` context. +// There are surely other ways than this to use class methods defined in JS within a +// connector context, but this is the most straightforward. +export const binder = (connector: Connector & Closeable): Connector & Closeable => ({ + queryRaw: connector.queryRaw.bind(connector), + executeRaw: connector.executeRaw.bind(connector), + version: connector.version.bind(connector), + isHealthy: connector.isHealthy.bind(connector), + close: connector.close.bind(connector), +}) diff --git a/query-engine/js-connectors/smoke-test-js/src/driver/mock.ts b/query-engine/js-connectors/smoke-test-js/src/driver/mock.ts deleted file mode 100644 index ee56179a2a7..00000000000 --- a/query-engine/js-connectors/smoke-test-js/src/driver/mock.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { setTimeout } from 'node:timers/promises' - -import { Closeable, ColumnType, Query, Connector, ResultSet } from '../engines/types/Library.js' - -class MockSQL implements Connector, Closeable { - private maybeVersion?: string - private isRunning: boolean = true - - constructor(connectionString: string) { - // lazily retrieve the version and store it into `maybeVersion` - setTimeout(50) - .then(() => { - this.maybeVersion = 'x.y.z' - }) - } - - async close(): Promise { - console.log('[nodejs] calling close() on connection pool') - if (this.isRunning) { - this.isRunning = false - await setTimeout(150) - console.log('[nodejs] closed connection pool') - } - } - - /** - * Returns false, if connection is considered to not be in a working state. - */ - isHealthy(): boolean { - const result = this.maybeVersion !== undefined - && this.isRunning - console.log(`[nodejs] isHealthy: ${result}`) - return result - } - - /** - * Execute a query given as SQL, interpolating the given parameters. - */ - async queryRaw(params: Query): Promise { - console.log('[nodejs] calling queryRaw', params) - await setTimeout(100) - - const resultSet: ResultSet = { - columnNames: ['id', 'firstname', 'company_id'], - columnTypes: [ColumnType.Int64, ColumnType.Text, ColumnType.Int64], - rows: [ - [1, 'Alberto', 1], - [2, 'Tom', 1], - ], - } - console.log('[nodejs] resultSet', resultSet) - - return resultSet - } - - /** - * Execute a query given as SQL, interpolating the given parameters and - * returning the number of affected rows. - * Note: Queryable expects a u64, but napi.rs only supports u32. - */ - async executeRaw(params: Query): Promise { - console.log('[nodejs] calling executeRaw', params) - await setTimeout(100) - - const affectedRows = 32 - return affectedRows - } - - /** - * Return the version of the underlying database, queried directly from the - * source. This corresponds to the `version()` function on PostgreSQL for - * example. The version string is returned directly without any form of - * parsing or normalization. - */ - version(): Promise { - return Promise.resolve(this.maybeVersion) - } -} - -export const createMockConnector = (connectionString: string): Connector & Closeable => { - const db = new MockSQL(connectionString) - return db -} diff --git a/query-engine/js-connectors/smoke-test-js/src/driver/util.ts b/query-engine/js-connectors/smoke-test-js/src/driver/util.ts deleted file mode 100644 index 66a7be6072b..00000000000 --- a/query-engine/js-connectors/smoke-test-js/src/driver/util.ts +++ /dev/null @@ -1,13 +0,0 @@ - -import { Closeable, Connector } from '../engines/types/Library.js'; - -// *.bind(db) is required to preserve the `this` context. -// There are surely other ways than this to use class methods defined in JS within a -// driver context, but this is the most straightforward. -export const binder = (queryable: Connector & Closeable): Connector & Closeable => ({ - queryRaw: queryable.queryRaw.bind(queryable), - executeRaw: queryable.executeRaw.bind(queryable), - version: queryable.version.bind(queryable), - isHealthy: queryable.isHealthy.bind(queryable), - close: queryable.close.bind(queryable), -}) diff --git a/query-engine/js-connectors/smoke-test-js/src/engines/types/Library.ts b/query-engine/js-connectors/smoke-test-js/src/engines/types/Library.ts index 8a819d73930..8e0624d96e5 100644 --- a/query-engine/js-connectors/smoke-test-js/src/engines/types/Library.ts +++ b/query-engine/js-connectors/smoke-test-js/src/engines/types/Library.ts @@ -52,7 +52,7 @@ export type Connector = { queryRaw: (params: Query) => Promise executeRaw: (params: Query) => Promise version: () => Promise - isHealthy: () => boolean + isHealthy: () => Promise } export type Closeable = { diff --git a/query-engine/js-connectors/smoke-test-js/src/planetscale.ts b/query-engine/js-connectors/smoke-test-js/src/planetscale.ts index 429c22d4a8c..63b55b53cd6 100644 --- a/query-engine/js-connectors/smoke-test-js/src/planetscale.ts +++ b/query-engine/js-connectors/smoke-test-js/src/planetscale.ts @@ -1,8 +1,8 @@ import { setImmediate, setTimeout } from 'node:timers/promises' -import { binder } from './driver/util.js' -import { createPlanetScaleConnector } from './driver/planetscale.js' +import { binder } from './connector/util.js' +import { createPlanetScaleConnector } from './connector/planetscale.js' import { initQueryEngine } from './util.js' async function main() { @@ -14,18 +14,19 @@ async function main() { }) // `binder` is required to preserve the `this` context to the group of functions passed to libquery. - const driver = binder(db) + const conn = binder(db) // wait for the database pool to be initialized await setImmediate(0) - const engine = initQueryEngine(driver) + const engine = initQueryEngine(conn) console.log('[nodejs] connecting...') await engine.connect('trace') console.log('[nodejs] connected') - console.log('[nodejs] isHealthy', await driver.isHealthy()) + console.log('[nodejs] version', await conn.version()) + console.log('[nodejs] isHealthy', await conn.isHealthy()) // Smoke test for PlanetScale that ensures we're able to decode every common data type. const resultSet = await engine.query(`{ @@ -77,7 +78,7 @@ async function main() { // Close the database connection. This is required to prevent the process from hanging. console.log('[nodejs] closing database connection...') - await driver.close() + await conn.close() console.log('[nodejs] closed database connection') process.exit(0) diff --git a/query-engine/js-connectors/smoke-test-js/src/util.ts b/query-engine/js-connectors/smoke-test-js/src/util.ts index c5f5275d3fc..1d754ac868f 100644 --- a/query-engine/js-connectors/smoke-test-js/src/util.ts +++ b/query-engine/js-connectors/smoke-test-js/src/util.ts @@ -4,7 +4,7 @@ import fs from 'node:fs' import { Connector, Library, QueryEngineInstance } from './engines/types/Library.js' -export function initQueryEngine(driver: Connector): QueryEngineInstance { +export function initQueryEngine(conn: Connector): QueryEngineInstance { // I assume nobody will run this on Windows ¯\_(ツ)_/¯ const libExt = os.platform() === 'darwin' ? 'dylib' : 'so' const dirname = path.dirname(new URL(import.meta.url).pathname) @@ -29,7 +29,7 @@ export function initQueryEngine(driver: Connector): QueryEngineInstance { } const logCallback = (...args) => console.log(args) - const engine = new QueryEngine(queryEngineOptions, logCallback, driver) + const engine = new QueryEngine(queryEngineOptions, logCallback, conn) return engine } diff --git a/query-engine/js-connectors/src/proxy.rs b/query-engine/js-connectors/src/proxy.rs index 375e1a9de82..a3ebb250803 100644 --- a/query-engine/js-connectors/src/proxy.rs +++ b/query-engine/js-connectors/src/proxy.rs @@ -38,12 +38,12 @@ pub struct Proxy { } /// Reify creates a Rust proxy to access the JS driver passed in as a parameter. -pub fn reify(js_driver: JsObject) -> napi::Result { - let query_raw = js_driver.get_named_property("queryRaw")?; - let execute_raw = js_driver.get_named_property("executeRaw")?; - let version = js_driver.get_named_property("version")?; - let close = js_driver.get_named_property("close")?; - let is_healthy = js_driver.get_named_property("isHealthy")?; +pub fn reify(js_connector: JsObject) -> napi::Result { + let query_raw = js_connector.get_named_property("queryRaw")?; + let execute_raw = js_connector.get_named_property("executeRaw")?; + let version = js_connector.get_named_property("version")?; + let close = js_connector.get_named_property("close")?; + let is_healthy = js_connector.get_named_property("isHealthy")?; let driver = Proxy { query_raw, diff --git a/query-engine/js-connectors/src/queryable.rs b/query-engine/js-connectors/src/queryable.rs index 09b737fd351..3c20e05d024 100644 --- a/query-engine/js-connectors/src/queryable.rs +++ b/query-engine/js-connectors/src/queryable.rs @@ -113,7 +113,7 @@ impl QuaintQueryable for JsQueryable { /// Returns false, if connection is considered to not be in a working state. fn is_healthy(&self) -> bool { - // TODO: use self.driver.is_healthy() + // TODO: use self.proxy.is_healthy() true }