- There are 4 built-in dialects for PostgreSQL, MySQL, Microsoft SQL
- Server (MSSQL), and SQLite. Additionally, the community has implemented
- several dialects to choose from. Find out more at{' '}
- "Dialects".
+ There are {builtInDialects.length} built-in dialects for PostgreSQL,
+ MySQL, Microsoft SQL Server (MSSQL), SQLite, and PGlite. Additionally,
+ the community has implemented several dialects to choose from. Find out
+ more at "Dialects".
Driver installation
diff --git a/site/docs/getting-started/Instantiation.tsx b/site/docs/getting-started/Instantiation.tsx
index 87ee52e71..0a4a3bc9e 100644
--- a/site/docs/getting-started/Instantiation.tsx
+++ b/site/docs/getting-started/Instantiation.tsx
@@ -116,6 +116,17 @@ const dialect = new ${dialectClassName}({
})`
}
+ if (dialect === 'pglite') {
+ const driverImportName = 'PGlite'
+
+ return `import { ${driverImportName} } from '${driverNPMPackageName}'
+import { Kysely, ${dialectClassName} } from 'kysely'
+
+const dialect = new ${dialectClassName}({
+ pglite: new ${driverImportName}(),
+})`
+ }
+
throw new Error(`Unsupported dialect: ${dialect}`)
}
diff --git a/site/docs/getting-started/Querying.tsx b/site/docs/getting-started/Querying.tsx
index 380d340e4..1e200f8e2 100644
--- a/site/docs/getting-started/Querying.tsx
+++ b/site/docs/getting-started/Querying.tsx
@@ -36,6 +36,7 @@ export async function deletePerson(id: number) {
return person
}`,
+ // TODO: Update to use output clause once #687 is completed
mssql: `// As of v0.27.0, Kysely doesn't support the \`OUTPUT\` clause. This will change
// in the future. For now, the following implementations achieve the same results
// as other dialects' examples, but with extra steps.
@@ -63,7 +64,7 @@ export async function deletePerson(id: number) {
return person
}`,
sqlite: postgresqlCodeSnippet,
- // TODO: Update to use output clause once #687 is completed
+ pglite: postgresqlCodeSnippet,
}
export function Querying(props: PropsWithDialect) {
diff --git a/site/docs/getting-started/Summary.tsx b/site/docs/getting-started/Summary.tsx
index 6fed7f893..4da7b240b 100644
--- a/site/docs/getting-started/Summary.tsx
+++ b/site/docs/getting-started/Summary.tsx
@@ -2,14 +2,9 @@ import Admonition from '@theme/Admonition'
import CodeBlock from '@theme/CodeBlock'
import Link from '@docusaurus/Link'
import { IUseADifferentDatabase } from './IUseADifferentDatabase'
-import {
- PRETTY_DIALECT_NAMES,
- type Dialect,
- type PropsWithDialect,
-} from './shared'
+import type { Dialect, PropsWithDialect } from './shared'
-const dialectSpecificCodeSnippets: Record = {
- postgresql: ` await db.schema.createTable('person')
+const postgresqlCodeSnippet = ` await db.schema.createTable('person')
.addColumn('id', 'serial', (cb) => cb.primaryKey())
.addColumn('first_name', 'varchar', (cb) => cb.notNull())
.addColumn('last_name', 'varchar')
@@ -17,7 +12,10 @@ const dialectSpecificCodeSnippets: Record = {
.addColumn('created_at', 'timestamp', (cb) =>
cb.notNull().defaultTo(sql\`now()\`)
)
- .execute()`,
+ .execute()`
+
+const dialectSpecificCodeSnippets: Record = {
+ postgresql: postgresqlCodeSnippet,
mysql: ` await db.schema.createTable('person')
.addColumn('id', 'integer', (cb) => cb.primaryKey().autoIncrement())
.addColumn('first_name', 'varchar(255)', (cb) => cb.notNull())
@@ -46,13 +44,17 @@ const dialectSpecificCodeSnippets: Record = {
cb.notNull().defaultTo(sql\`current_timestamp\`)
)
.execute()`,
+ pglite: postgresqlCodeSnippet,
}
+const truncateTableSnippet = `await sql\`truncate table \${sql.table('person')}\`.execute(db)`
+
const dialectSpecificTruncateSnippets: Record = {
- postgresql: `await sql\`truncate table \${sql.table('person')}\`.execute(db)`,
- mysql: `await sql\`truncate table \${sql.table('person')}\`.execute(db)`,
- mssql: `await sql\`truncate table \${sql.table('person')}\`.execute(db)`,
+ postgresql: truncateTableSnippet,
+ mysql: truncateTableSnippet,
+ mssql: truncateTableSnippet,
sqlite: `await sql\`delete from \${sql.table('person')}\`.execute(db)`,
+ pglite: truncateTableSnippet,
}
export function Summary(props: PropsWithDialect) {
diff --git a/site/docs/getting-started/shared.tsx b/site/docs/getting-started/shared.tsx
index f07efb512..82ea39960 100644
--- a/site/docs/getting-started/shared.tsx
+++ b/site/docs/getting-started/shared.tsx
@@ -1,7 +1,7 @@
import type { ReactNode } from 'react'
import packageJson from '../../package.json'
-export type Dialect = 'postgresql' | 'mysql' | 'sqlite' | 'mssql'
+export type Dialect = 'postgresql' | 'mysql' | 'sqlite' | 'mssql' | 'pglite'
export type PropsWithDialect
= P & {
dialect: Dialect | undefined
@@ -31,6 +31,7 @@ export const DIALECT_CLASS_NAMES = {
mysql: 'MysqlDialect',
mssql: 'MssqlDialect',
sqlite: 'SqliteDialect',
+ pglite: 'PGliteDialect',
} as const satisfies Record
export const getDriverNPMPackageNames = (
@@ -41,6 +42,7 @@ export const getDriverNPMPackageNames = (
mysql: 'mysql2',
mssql: 'tedious',
sqlite: 'better-sqlite3',
+ pglite: '@electric-sql/pglite',
}) as const satisfies Record
export const POOL_NPM_PACKAGE_NAMES = {
@@ -52,6 +54,7 @@ export const PRETTY_DIALECT_NAMES = {
mysql: 'MySQL',
mssql: 'Microsoft SQL Server (MSSQL)',
sqlite: 'SQLite',
+ pglite: 'PGlite',
} as const satisfies Record
export const PRETTY_PACKAGE_MANAGER_NAMES = {
diff --git a/site/src/components/SectionFeatures/index.tsx b/site/src/components/SectionFeatures/index.tsx
index 292224604..8207ebe5b 100644
--- a/site/src/components/SectionFeatures/index.tsx
+++ b/site/src/components/SectionFeatures/index.tsx
@@ -58,8 +58,8 @@ const FeatureList: FeatureItem[] = [
<>
Kysely's community-driven dialect system makes it easy to implement
support for any SQL database without waiting for the core team. It ships
- with official dialects for PostgreSQL, MySQL, MS SQL Server, and SQLite
- right out of the box.
+ with official dialects for PostgreSQL, MySQL, MS SQL Server, SQLite, and
+ PGlite right out of the box.
>
),
},
diff --git a/src/dialect/database-introspector.ts b/src/dialect/database-introspector.ts
index 75093b811..8974828d7 100644
--- a/src/dialect/database-introspector.ts
+++ b/src/dialect/database-introspector.ts
@@ -43,6 +43,7 @@ export interface DatabaseMetadata {
export interface TableMetadata {
readonly name: string
readonly isView: boolean
+ readonly isForeign: boolean
readonly columns: ColumnMetadata[]
readonly schema?: string
}
diff --git a/src/dialect/dialect-adapter-base.ts b/src/dialect/dialect-adapter-base.ts
index fab6512aa..3abe60252 100644
--- a/src/dialect/dialect-adapter-base.ts
+++ b/src/dialect/dialect-adapter-base.ts
@@ -12,6 +12,10 @@ export abstract class DialectAdapterBase implements DialectAdapter {
return true
}
+ get supportsMultipleConnections(): boolean {
+ return true
+ }
+
get supportsTransactionalDdl(): boolean {
return false
}
diff --git a/src/dialect/dialect-adapter.ts b/src/dialect/dialect-adapter.ts
index f8bf61fe1..b868592cb 100644
--- a/src/dialect/dialect-adapter.ts
+++ b/src/dialect/dialect-adapter.ts
@@ -12,28 +12,45 @@ export interface DialectAdapter {
/**
* Whether or not this dialect supports `if not exists` in creation of tables/schemas/views/etc.
*
+ * Default is `false`.
+ *
* If this is false, Kysely's internal migrations tables and schemas are created
* without `if not exists` in migrations. This is not a problem if the dialect
* supports transactional DDL.
*/
- readonly supportsCreateIfNotExists: boolean
+ readonly supportsCreateIfNotExists?: boolean
+
+ /**
+ * Whether or not this dialect supports multiple connections at the same time.
+ *
+ * Default is `true`.
+ *
+ * If this is false, Kysely will use a single connection for all database operations.
+ */
+ readonly supportsMultipleConnections?: boolean
/**
* Whether or not this dialect supports transactional DDL.
*
+ * Default is `false`.
+ *
* If this is true, migrations are executed inside a transaction.
*/
- readonly supportsTransactionalDdl: boolean
+ readonly supportsTransactionalDdl?: boolean
/**
* Whether or not this dialect supports the `returning` in inserts
* updates and deletes.
+ *
+ * Default is `false`.
*/
- readonly supportsReturning: boolean
+ readonly supportsReturning?: boolean
/**
* Whether or not this dialect supports the `output` clause in inserts
* updates and deletes.
+ *
+ * Default is `false`.
*/
readonly supportsOutput?: boolean
diff --git a/src/dialect/mssql/mssql-introspector.ts b/src/dialect/mssql/mssql-introspector.ts
index 9b2a9baf0..ef8cad93f 100644
--- a/src/dialect/mssql/mssql-introspector.ts
+++ b/src/dialect/mssql/mssql-introspector.ts
@@ -142,6 +142,7 @@ export class MssqlIntrospector implements DatabaseIntrospector {
tableDictionary[key] ||
freeze({
columns: [],
+ isForeign: false,
isView: rawColumn.table_type === 'V ',
name: rawColumn.table_name,
schema: rawColumn.table_schema_name ?? undefined,
diff --git a/src/dialect/mysql/mysql-dialect-config.ts b/src/dialect/mysql/mysql-dialect-config.ts
index 364869c9b..45344afe4 100644
--- a/src/dialect/mysql/mysql-dialect-config.ts
+++ b/src/dialect/mysql/mysql-dialect-config.ts
@@ -6,6 +6,13 @@ import { DatabaseConnection } from '../../driver/database-connection.js'
* https://github.com/sidorares/node-mysql2#using-connection-pools
*/
export interface MysqlDialectConfig {
+ /**
+ * The `mysql2` `createConnection` function or similar.
+ *
+ * TODO: ...
+ */
+ createConnection?: (opts: any) => MysqlConnection | Promise
+
/**
* A mysql2 Pool instance or a function that returns one.
*
@@ -41,7 +48,10 @@ export interface MysqlPool {
end(callback: (error: unknown) => void): void
}
-export interface MysqlPoolConnection {
+export interface MysqlConnection {
+ config: unknown
+ connect(callback?: (error: unknown) => void): void
+ destroy(): void
query(
sql: string,
parameters: ReadonlyArray,
@@ -51,6 +61,12 @@ export interface MysqlPoolConnection {
parameters: ReadonlyArray,
callback: (error: unknown, result: MysqlQueryResult) => void,
): void
+ threadId: number
+}
+
+export type MysqlConectionConstructor = new (opts?: object) => MysqlConnection
+
+export interface MysqlPoolConnection extends MysqlConnection {
release(): void
}
diff --git a/src/dialect/mysql/mysql-driver.ts b/src/dialect/mysql/mysql-driver.ts
index 83c865a73..b147d5350 100644
--- a/src/dialect/mysql/mysql-driver.ts
+++ b/src/dialect/mysql/mysql-driver.ts
@@ -1,4 +1,5 @@
import {
+ ControlConnectionProvider,
DatabaseConnection,
QueryResult,
} from '../../driver/database-connection.js'
@@ -39,7 +40,10 @@ export class MysqlDriver implements Driver {
let connection = this.#connections.get(rawConnection)
if (!connection) {
- connection = new MysqlConnection(rawConnection)
+ connection = new MysqlConnection(
+ rawConnection,
+ this.#config.createConnection,
+ )
this.#connections.set(rawConnection, connection)
// The driver must take care of calling `onCreateConnection` when a new
@@ -57,18 +61,6 @@ export class MysqlDriver implements Driver {
return connection
}
- async #acquireConnection(): Promise {
- return new Promise((resolve, reject) => {
- this.#pool!.getConnection(async (err, rawConnection) => {
- if (err) {
- reject(err)
- } else {
- resolve(rawConnection)
- }
- })
- })
- }
-
async beginTransaction(
connection: DatabaseConnection,
settings: TransactionSettings,
@@ -155,6 +147,18 @@ export class MysqlDriver implements Driver {
})
})
}
+
+ async #acquireConnection(): Promise {
+ return new Promise((resolve, reject) => {
+ this.#pool!.getConnection(async (err, rawConnection) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve(rawConnection)
+ }
+ })
+ })
+ }
}
function isOkPacket(obj: unknown): obj is MysqlOkPacket {
@@ -162,43 +166,91 @@ function isOkPacket(obj: unknown): obj is MysqlOkPacket {
}
class MysqlConnection implements DatabaseConnection {
+ readonly #createConnection: MysqlDialectConfig['createConnection']
readonly #rawConnection: MysqlPoolConnection
- constructor(rawConnection: MysqlPoolConnection) {
+ constructor(
+ rawConnection: MysqlPoolConnection,
+ createConnection: MysqlDialectConfig['createConnection'],
+ ) {
+ this.#createConnection = createConnection
this.#rawConnection = rawConnection
}
+ async cancelQuery(
+ controlConnectionProvider: ControlConnectionProvider,
+ ): Promise {
+ try {
+ // this removes the connection from the pool.
+ // this is done to avoid picking it up after the `kill` command next, which
+ // would cause an error when attempting to query.
+ this.#rawConnection.destroy()
+ } catch {
+ // noop
+ }
+
+ const { config, threadId } = this.#rawConnection
+
+ // this kills the query and the connection database-side.
+ // we're not using `kill query ` here because it doesn't
+ // guarantee that the query is killed immediately. we saw that in tests,
+ // the query can still run for a while after - including registering writes.
+ const cancelQuery = `kill connection ${threadId}`
+
+ if (this.#createConnection && config) {
+ const controlConnection = await this.#createConnection({ ...config })
+
+ return await new Promise((resolve, reject) => {
+ controlConnection.connect((connectError) => {
+ if (connectError) {
+ return reject(connectError)
+ }
+
+ controlConnection.query(cancelQuery, [], (error) => {
+ controlConnection.destroy()
+
+ if (error) {
+ return reject(error)
+ }
+
+ resolve()
+ })
+ })
+ })
+ }
+
+ await controlConnectionProvider(async (controlConnection) => {
+ await controlConnection.executeQuery(CompiledQuery.raw(cancelQuery, []))
+ })
+ }
+
async executeQuery(compiledQuery: CompiledQuery): Promise> {
try {
const result = await this.#executeQuery(compiledQuery)
- if (isOkPacket(result)) {
- const { insertId, affectedRows, changedRows } = result
-
- return {
- insertId:
- insertId !== undefined &&
- insertId !== null &&
- insertId.toString() !== '0'
- ? BigInt(insertId)
- : undefined,
- numAffectedRows:
- affectedRows !== undefined && affectedRows !== null
- ? BigInt(affectedRows)
- : undefined,
- numChangedRows:
- changedRows !== undefined && changedRows !== null
- ? BigInt(changedRows)
- : undefined,
- rows: [],
- }
- } else if (Array.isArray(result)) {
+ if (!isOkPacket(result)) {
return {
- rows: result as O[],
+ rows: (Array.isArray(result) ? result : []) as never,
}
}
+ const { insertId, affectedRows, changedRows } = result
+
return {
+ insertId:
+ insertId !== undefined &&
+ insertId !== null &&
+ insertId.toString() !== '0'
+ ? BigInt(insertId)
+ : undefined,
+ numAffectedRows:
+ affectedRows !== undefined && affectedRows !== null
+ ? BigInt(affectedRows)
+ : undefined,
+ numChangedRows:
+ changedRows !== undefined && changedRows !== null
+ ? BigInt(changedRows)
+ : undefined,
rows: [],
}
} catch (err) {
@@ -206,31 +258,13 @@ class MysqlConnection implements DatabaseConnection {
}
}
- #executeQuery(compiledQuery: CompiledQuery): Promise {
- return new Promise((resolve, reject) => {
- this.#rawConnection.query(
- compiledQuery.sql,
- compiledQuery.parameters,
- (err, result) => {
- if (err) {
- reject(err)
- } else {
- resolve(result)
- }
- },
- )
- })
- }
-
async *streamQuery(
compiledQuery: CompiledQuery,
_chunkSize: number,
): AsyncIterableIterator> {
const stream = this.#rawConnection
.query(compiledQuery.sql, compiledQuery.parameters)
- .stream({
- objectMode: true,
- })
+ .stream({ objectMode: true })
try {
for await (const row of stream) {
@@ -238,23 +272,38 @@ class MysqlConnection implements DatabaseConnection {
rows: [row],
}
}
- } catch (ex) {
+ } catch (error) {
if (
- ex &&
- typeof ex === 'object' &&
- 'code' in ex &&
- // @ts-ignore
- ex.code === 'ERR_STREAM_PREMATURE_CLOSE'
+ error &&
+ typeof error === 'object' &&
+ 'code' in error &&
+ error.code === 'ERR_STREAM_PREMATURE_CLOSE'
) {
// Most likely because of https://github.com/mysqljs/mysql/blob/master/lib/protocol/sequences/Query.js#L220
return
}
- throw ex
+ throw error
}
}
[PRIVATE_RELEASE_METHOD](): void {
this.#rawConnection.release()
}
+
+ #executeQuery(compiledQuery: CompiledQuery): Promise {
+ return new Promise((resolve, reject) => {
+ this.#rawConnection.query(
+ compiledQuery.sql,
+ compiledQuery.parameters,
+ (err, result) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve(result)
+ }
+ },
+ )
+ })
+ }
}
diff --git a/src/dialect/mysql/mysql-introspector.ts b/src/dialect/mysql/mysql-introspector.ts
index d407361d0..6cc59c47a 100644
--- a/src/dialect/mysql/mysql-introspector.ts
+++ b/src/dialect/mysql/mysql-introspector.ts
@@ -47,6 +47,7 @@ export class MysqlIntrospector implements DatabaseIntrospector {
'columns.TABLE_NAME',
'columns.TABLE_SCHEMA',
'tables.TABLE_TYPE',
+ 'tables.ENGINE',
'columns.IS_NULLABLE',
'columns.DATA_TYPE',
'columns.EXTRA',
@@ -83,6 +84,7 @@ export class MysqlIntrospector implements DatabaseIntrospector {
table = freeze({
name: it.TABLE_NAME,
isView: it.TABLE_TYPE === 'VIEW',
+ isForeign: it.ENGINE === 'FEDERATED',
schema: it.TABLE_SCHEMA,
columns: [],
})
@@ -116,6 +118,7 @@ interface RawColumnMetadata {
TABLE_NAME: string
TABLE_SCHEMA: string
TABLE_TYPE: string
+ ENGINE: string
IS_NULLABLE: 'YES' | 'NO'
DATA_TYPE: string
EXTRA: string
diff --git a/src/dialect/pglite/pglite-adapter.ts b/src/dialect/pglite/pglite-adapter.ts
new file mode 100644
index 000000000..8ad993061
--- /dev/null
+++ b/src/dialect/pglite/pglite-adapter.ts
@@ -0,0 +1,19 @@
+import { PostgresAdapter } from '../postgres/postgres-adapter.js'
+
+export class PGliteAdapter extends PostgresAdapter {
+ override get supportsMultipleConnections(): boolean {
+ return false
+ }
+
+ async acquireMigrationLock(): Promise {
+ // PGlite only has one connection that's reserved by the migration system
+ // for the whole time between acquireMigrationLock and releaseMigrationLock.
+ // We don't need to do anything here.
+ }
+
+ async releaseMigrationLock(): Promise {
+ // PGlite only has one connection that's reserved by the migration system
+ // for the whole time between acquireMigrationLock and releaseMigrationLock.
+ // We don't need to do anything here.
+ }
+}
diff --git a/src/dialect/pglite/pglite-dialect-config.ts b/src/dialect/pglite/pglite-dialect-config.ts
new file mode 100644
index 000000000..c63180811
--- /dev/null
+++ b/src/dialect/pglite/pglite-dialect-config.ts
@@ -0,0 +1,67 @@
+import type { DatabaseConnection } from '../../driver/database-connection.js'
+
+/**
+ * Config for the PGlite dialect.
+ */
+export interface PGliteDialectConfig {
+ /**
+ * Called once when the first query is executed.
+ *
+ * This is a Kysely specific feature and does not come from the `@electric-sql/pglite`
+ * module.
+ */
+ onCreateConnection?: (connection: DatabaseConnection) => Promise
+
+ /**
+ * A PGlite instance or a function that returns one.
+ *
+ * If a function is provided, it's called once when the first query is executed.
+ *
+ * https://pglite.dev/docs/api#main-constructor
+ */
+ pglite: PGlite | (() => Promise)
+}
+
+/**
+ * This interface is the subset of the PGlite instance that kysely needs.
+ *
+ * We don't use the type from `@electric-sql/pglite` here to not have a dependency
+ * to it.
+ *
+ * https://pglite.dev/docs/api
+ */
+export interface PGlite {
+ close(): Promise
+ closed: boolean
+ query(
+ query: string,
+ params?: any[],
+ options?: PGliteQueryOptions,
+ ): Promise>
+ ready: boolean
+ transaction(callback: (tx: PGliteTransaction) => Promise): Promise
+ waitReady: Promise
+}
+
+export interface PGliteQueryOptions {
+ blob?: Blob | File
+ onNotice?: (notice: any) => void
+ paramTypes?: number[]
+ parsers?: Record any>
+ rowMode?: 'array' | 'object'
+ serializers?: Record string>
+}
+
+export interface PGliteQueryResults {
+ affectedRows?: number
+ blob?: Blob
+ fields: {
+ dataTypeID: number
+ name: string
+ }[]
+ rows: T[]
+}
+
+export interface PGliteTransaction extends Pick {
+ rollback(): Promise
+}
diff --git a/src/dialect/pglite/pglite-dialect.ts b/src/dialect/pglite/pglite-dialect.ts
new file mode 100644
index 000000000..fd868a324
--- /dev/null
+++ b/src/dialect/pglite/pglite-dialect.ts
@@ -0,0 +1,59 @@
+import type { Driver } from '../../driver/driver.js'
+import type { Kysely } from '../../kysely.js'
+import type { QueryCompiler } from '../../query-compiler/query-compiler.js'
+import type { DatabaseIntrospector } from '../database-introspector.js'
+import type { DialectAdapter } from '../dialect-adapter.js'
+import type { Dialect } from '../dialect.js'
+import { PostgresIntrospector } from '../postgres/postgres-introspector.js'
+import { PostgresQueryCompiler } from '../postgres/postgres-query-compiler.js'
+import { PGliteAdapter } from './pglite-adapter.js'
+import type { PGliteDialectConfig } from './pglite-dialect-config.js'
+import { PGliteDriver } from './pglite-driver.js'
+
+/**
+ * PGlite dialect.
+ *
+ * The constructor takes an instance of {@link PGliteDialectConfig}.
+ *
+ * ```ts
+ * import {Â PGlite } from '@electric-sql/pglite'
+ *
+ * new PGliteDialect({
+ * pglite: new PGlite()
+ * })
+ * ```
+ *
+ * If you want the client to only be created once it's first used, `pglite`
+ * can be a function:
+ *
+ * ```ts
+ * import {Â PGlite } from '@electric-sql/pglite'
+ *
+ * new PGliteDialect({
+ * pglite: () => new PGlite()
+ * })
+ * ```
+ */
+export class PGliteDialect implements Dialect {
+ readonly #config: PGliteDialectConfig
+
+ constructor(config: PGliteDialectConfig) {
+ this.#config = config
+ }
+
+ createAdapter(): DialectAdapter {
+ return new PGliteAdapter()
+ }
+
+ createDriver(): Driver {
+ return new PGliteDriver(this.#config)
+ }
+
+ createIntrospector(db: Kysely): DatabaseIntrospector {
+ return new PostgresIntrospector(db)
+ }
+
+ createQueryCompiler(): QueryCompiler {
+ return new PostgresQueryCompiler()
+ }
+}
diff --git a/src/dialect/pglite/pglite-driver.ts b/src/dialect/pglite/pglite-driver.ts
new file mode 100644
index 000000000..c87d3702f
--- /dev/null
+++ b/src/dialect/pglite/pglite-driver.ts
@@ -0,0 +1,192 @@
+import type {
+ DatabaseConnection,
+ QueryResult,
+} from '../../driver/database-connection.js'
+import type { Driver } from '../../driver/driver.js'
+import { parseSavepointCommand } from '../../parser/savepoint-parser.js'
+import type { CompiledQuery } from '../../query-compiler/compiled-query.js'
+import type { QueryCompiler } from '../../query-compiler/query-compiler.js'
+import { Deferred } from '../../util/deferred.js'
+import { freeze, isFunction } from '../../util/object-utils.js'
+import { createQueryId } from '../../util/query-id.js'
+import { extendStackTrace } from '../../util/stack-trace-utils.js'
+import type {
+ PGlite,
+ PGliteDialectConfig,
+ PGliteTransaction,
+} from './pglite-dialect-config.js'
+
+const PRIVATE_BEGIN_TRANSACTION_METHOD = Symbol()
+const PRIVATE_COMMIT_TRANSACTION_METHOD = Symbol()
+const PRIVATE_ROLLBACK_TRANSACTION_METHOD = Symbol()
+
+export class PGliteDriver implements Driver {
+ readonly #config: PGliteDialectConfig
+ #connection?: PGliteConnection
+ #pglite?: PGlite
+
+ constructor(config: PGliteDialectConfig) {
+ this.#config = freeze({ ...config })
+ }
+
+ async acquireConnection(): Promise {
+ return this.#connection!
+ }
+
+ async beginTransaction(connection: PGliteConnection): Promise {
+ await connection[PRIVATE_BEGIN_TRANSACTION_METHOD]()
+ }
+
+ async commitTransaction(connection: PGliteConnection): Promise {
+ await connection[PRIVATE_COMMIT_TRANSACTION_METHOD]()
+ }
+
+ async destroy(): Promise {
+ if (!this.#pglite?.closed) {
+ await this.#pglite?.close()
+ }
+ }
+
+ async init(): Promise {
+ this.#pglite = isFunction(this.#config.pglite)
+ ? await this.#config.pglite()
+ : this.#config.pglite
+
+ if (this.#pglite.closed) {
+ throw new Error('PGlite instance is already closed.')
+ }
+
+ if (!this.#pglite.ready) {
+ await this.#pglite.waitReady
+ }
+
+ this.#connection = new PGliteConnection(this.#pglite!)
+
+ if (this.#config.onCreateConnection) {
+ await this.#config.onCreateConnection(this.#connection)
+ }
+ }
+
+ async releaseConnection(): Promise {
+ // noop
+ }
+
+ async releaseSavepoint(
+ connection: DatabaseConnection,
+ savepointName: string,
+ compileQuery: QueryCompiler['compileQuery'],
+ ): Promise {
+ await connection.executeQuery(
+ compileQuery(
+ parseSavepointCommand('release', savepointName),
+ createQueryId(),
+ ),
+ )
+ }
+
+ async rollbackToSavepoint(
+ connection: DatabaseConnection,
+ savepointName: string,
+ compileQuery: QueryCompiler['compileQuery'],
+ ): Promise {
+ await connection.executeQuery(
+ compileQuery(
+ parseSavepointCommand('rollback to', savepointName),
+ createQueryId(),
+ ),
+ )
+ }
+
+ async rollbackTransaction(connection: PGliteConnection): Promise {
+ await connection[PRIVATE_ROLLBACK_TRANSACTION_METHOD]()
+ }
+
+ async savepoint(
+ connection: DatabaseConnection,
+ savepointName: string,
+ compileQuery: QueryCompiler['compileQuery'],
+ ): Promise {
+ await connection.executeQuery(
+ compileQuery(
+ parseSavepointCommand('savepoint', savepointName),
+ createQueryId(),
+ ),
+ )
+ }
+}
+
+class PGliteConnection implements DatabaseConnection {
+ readonly #pglite: PGlite
+ #commitTransaction?: () => void
+ #rollbackTransaction?: () => void
+ #transaction?: PGliteTransaction
+ #transactionClosedPromise?: Promise
+
+ constructor(pglite: PGlite) {
+ this.#pglite = pglite
+ }
+
+ async executeQuery(compiledQuery: CompiledQuery): Promise> {
+ try {
+ const { affectedRows, rows } = await (
+ this.#transaction || this.#pglite
+ ).query(compiledQuery.sql, compiledQuery.parameters as never, {
+ rowMode: 'object',
+ })
+
+ return {
+ numAffectedRows:
+ affectedRows != null ? BigInt(affectedRows) : undefined,
+ rows: rows || [],
+ }
+ } catch (error) {
+ throw extendStackTrace(error, new Error())
+ }
+ }
+
+ async *streamQuery(): AsyncIterableIterator> {
+ throw new Error('Streaming is not supported by PGlite.')
+ }
+
+ async [PRIVATE_BEGIN_TRANSACTION_METHOD](): Promise {
+ const {
+ promise: waitForCommit,
+ reject: rollback,
+ resolve: commit,
+ } = new Deferred()
+ const { promise: waitForBegin, resolve: hasBegun } = new Deferred()
+
+ this.#commitTransaction = commit
+ this.#rollbackTransaction = rollback
+
+ // we want to use PGlite's exclusive transaction mode, to lock the instance,
+ // in case this dialect is not the only one using it.
+ this.#transactionClosedPromise = this.#pglite.transaction(async (tx) => {
+ this.#transaction = tx
+
+ hasBegun()
+
+ await waitForCommit
+ })
+
+ await waitForBegin
+ }
+
+ async [PRIVATE_COMMIT_TRANSACTION_METHOD](): Promise {
+ this.#commitTransaction?.()
+ await this.#transactionClosedPromise
+ this.#commitTransaction = undefined
+ this.#rollbackTransaction = undefined
+ this.#transaction = undefined
+ this.#transactionClosedPromise = undefined
+ }
+
+ async [PRIVATE_ROLLBACK_TRANSACTION_METHOD](): Promise {
+ this.#rollbackTransaction?.()
+ await this.#transactionClosedPromise?.catch(() => {})
+ this.#commitTransaction = undefined
+ this.#rollbackTransaction = undefined
+ this.#transaction = undefined
+ this.#transactionClosedPromise = undefined
+ }
+}
diff --git a/src/dialect/postgres/postgres-dialect-config.ts b/src/dialect/postgres/postgres-dialect-config.ts
index 88facba7c..58e9b910d 100644
--- a/src/dialect/postgres/postgres-dialect-config.ts
+++ b/src/dialect/postgres/postgres-dialect-config.ts
@@ -5,13 +5,14 @@ import { DatabaseConnection } from '../../driver/database-connection.js'
*/
export interface PostgresDialectConfig {
/**
- * A postgres Pool instance or a function that returns one.
+ * A postgres `Client` constructor, to be used for connecting to the database
+ * outside of the `pool` to avoid waiting for an idle connection.
*
- * If a function is provided, it's called once when the first query is executed.
+ * This is useful for cancelling queries.
*
- * https://node-postgres.com/apis/pool
+ * Defaults to the pool's undocumented `Client` member, if it exists.
*/
- pool: PostgresPool | (() => Promise)
+ controlClient?: PostgresClientConstructor
/**
* https://github.com/brianc/node-postgres/tree/master/packages/pg-cursor
@@ -39,6 +40,15 @@ export interface PostgresDialectConfig {
* Called every time a connection is acquired from the pool.
*/
onReserveConnection?: (connection: DatabaseConnection) => Promise
+
+ /**
+ * A postgres `Pool` instance or a function that returns one.
+ *
+ * If a function is provided, it's called once when the first query is executed.
+ *
+ * https://node-postgres.com/apis/pool
+ */
+ pool: PostgresPool | (() => Promise)
}
/**
@@ -50,29 +60,79 @@ export interface PostgresDialectConfig {
* https://node-postgres.com/apis/pool
*/
export interface PostgresPool {
+ // internal
+ Client?: PostgresClientConstructor
connect(): Promise
end(): Promise
+ // we don't care about the type of options here, for now.
+ options: object
}
-export interface PostgresPoolClient {
+/**
+ * This interface is the subset of pg driver's `Client` class that
+ * kysely needs.
+ *
+ * We don't use the type from `pg` here to not have a dependency to it.
+ *
+ * https://node-postgres.com/apis/client
+ */
+export interface PostgresClient {
+ connect(): Promise
+ end(): void
+ // internal
+ processID?: number
query(
sql: string,
parameters: ReadonlyArray,
): Promise>
query(cursor: PostgresCursor): PostgresCursor
+}
+
+export type PostgresClientConstructor = new (options: any) => PostgresClient
+
+/**
+ * This interface is the subset of pg driver's `Client` class that
+ * is returned by the `Pool` class, and that kysely needs.
+ *
+ * We don't use the type from `pg` here to not have a dependency to it.
+ *
+ * https://node-postgres.com/apis/pool#releasing-clients
+ */
+export interface PostgresPoolClient extends Omit {
release(): void
}
+/**
+ * This interface is pg driver's `Cursor` class that kysely needs.
+ *
+ * We don't use the type from `pg-cursor` here to not have a dependency to it.
+ *
+ * https://node-postgres.com/apis/cursor
+ */
export interface PostgresCursor {
read(rowsCount: number): Promise
close(): Promise
}
+/**
+ * This interface is pg driver's `Cursor` class constructor that kysely needs.
+ *
+ * We don't use the type from `pg-cursor` here to not have a dependency to it.
+ *
+ * https://node-postgres.com/apis/cursor#constructor
+ */
export type PostgresCursorConstructor = new (
sql: string,
parameters: unknown[],
) => PostgresCursor
+/**
+ * This interface is the subset of pg driver's `Result` shape that kysely needs.
+ *
+ * We don't use the type from `pg` here to not have a dependency to it.
+ *
+ * https://node-postgres.com/apis/result
+ */
export interface PostgresQueryResult {
command: 'UPDATE' | 'DELETE' | 'INSERT' | 'SELECT' | 'MERGE'
rowCount: number
diff --git a/src/dialect/postgres/postgres-driver.ts b/src/dialect/postgres/postgres-driver.ts
index 5fe16648f..e207cfd16 100644
--- a/src/dialect/postgres/postgres-driver.ts
+++ b/src/dialect/postgres/postgres-driver.ts
@@ -1,5 +1,7 @@
import {
+ ControlConnectionProvider,
DatabaseConnection,
+ QueryOptions,
QueryResult,
} from '../../driver/database-connection.js'
import { Driver, TransactionSettings } from '../../driver/driver.js'
@@ -7,9 +9,10 @@ import { parseSavepointCommand } from '../../parser/savepoint-parser.js'
import { CompiledQuery } from '../../query-compiler/compiled-query.js'
import { QueryCompiler } from '../../query-compiler/query-compiler.js'
import { isFunction, freeze } from '../../util/object-utils.js'
-import { createQueryId } from '../../util/query-id.js'
+import { createQueryId, QueryId } from '../../util/query-id.js'
import { extendStackTrace } from '../../util/stack-trace-utils.js'
import {
+ PostgresClientConstructor,
PostgresCursorConstructor,
PostgresDialectConfig,
PostgresPool,
@@ -39,7 +42,9 @@ export class PostgresDriver implements Driver {
if (!connection) {
connection = new PostgresConnection(client, {
+ Client: this.#config.controlClient || this.#pool!.Client,
cursor: this.#config.cursor ?? null,
+ poolOptions: this.#pool!.options,
})
this.#connections.set(client, connection)
@@ -140,24 +145,81 @@ export class PostgresDriver implements Driver {
}
interface PostgresConnectionOptions {
+ Client?: PostgresClientConstructor
cursor: PostgresCursorConstructor | null
+ poolOptions: object
}
class PostgresConnection implements DatabaseConnection {
- #client: PostgresPoolClient
- #options: PostgresConnectionOptions
+ readonly #client: PostgresPoolClient
+ readonly #options: PostgresConnectionOptions
+ #queryId?: QueryId
+ #pid?: number
constructor(client: PostgresPoolClient, options: PostgresConnectionOptions) {
this.#client = client
this.#options = options
}
- async executeQuery(compiledQuery: CompiledQuery): Promise> {
+ async cancelQuery(
+ controlConnectionProvider: ControlConnectionProvider,
+ ): Promise {
+ if (!this.#queryId) {
+ return
+ }
+
+ const { Client, poolOptions } = this.#options
+
+ const queryIdToCancel = this.#queryId
+ const cancelQuery = `select pg_cancel_backend(${this.#pid})`
+
+ // we fallback to a pool connection, and execute a SQL query to cancel the
+ // query. this is not ideal, as we might have to wait for an idle connection.
+ if (!Client) {
+ return await controlConnectionProvider(async (controlConnection) => {
+ // by the time we get the connection, another query might have been executed.
+ // we need to ensure we're not canceling the wrong query.
+ if (queryIdToCancel === this.#queryId) {
+ await controlConnection.executeQuery(
+ CompiledQuery.raw(cancelQuery, []),
+ )
+ }
+ })
+ }
+
+ const controlClient = new Client({ ...poolOptions })
+
try {
- const { command, rowCount, rows } = await this.#client.query(
- compiledQuery.sql,
- [...compiledQuery.parameters],
- )
+ await controlClient.connect()
+
+ // by the time we get the connection, another query might have been executed.
+ // we need to ensure we're not canceling the wrong query.
+ if (queryIdToCancel !== this.#queryId) {
+ return
+ }
+
+ await controlClient.query(cancelQuery, [])
+ } finally {
+ controlClient.end()
+ }
+ }
+
+ async executeQuery(
+ compiledQuery: CompiledQuery,
+ options?: QueryOptions,
+ ): Promise> {
+ try {
+ if (options?.cancelable) {
+ await this.#setupCancelability()
+ }
+
+ this.#queryId = compiledQuery.queryId
+
+ const result = await this.#client.query(compiledQuery.sql, [
+ ...compiledQuery.parameters,
+ ])
+
+ const { command, rowCount, rows } = result
return {
numAffectedRows:
@@ -171,12 +233,15 @@ class PostgresConnection implements DatabaseConnection {
}
} catch (err) {
throw extendStackTrace(err, new Error())
+ } finally {
+ this.#queryId = undefined
}
}
async *streamQuery(
compiledQuery: CompiledQuery,
chunkSize: number,
+ options?: QueryOptions,
): AsyncIterableIterator> {
if (!this.#options.cursor) {
throw new Error(
@@ -188,6 +253,12 @@ class PostgresConnection implements DatabaseConnection {
throw new Error('chunkSize must be a positive integer')
}
+ if (options?.cancelable) {
+ await this.#setupCancelability()
+ }
+
+ this.#queryId = compiledQuery.queryId
+
const cursor = this.#client.query(
new this.#options.cursor(
compiledQuery.sql,
@@ -208,6 +279,7 @@ class PostgresConnection implements DatabaseConnection {
}
}
} finally {
+ this.#queryId = undefined
await cursor.close()
}
}
@@ -215,4 +287,29 @@ class PostgresConnection implements DatabaseConnection {
[PRIVATE_RELEASE_METHOD](): void {
this.#client.release()
}
+
+ async #setupCancelability(): Promise {
+ if (this.#pid) {
+ return
+ }
+
+ const { processID } = this.#client
+
+ // `processID` is an undocumented member of the `Client` class.
+ // it might not exist in old or future versions of the `pg` driver.
+ // if it does, use it.
+ if (processID) {
+ this.#pid = processID
+ return
+ }
+
+ const {
+ rows: [{ pid }],
+ } = await this.#client.query<{ pid: string }>(
+ 'select pg_backend_pid() as pid',
+ [],
+ )
+
+ this.#pid = Number(pid)
+ }
}
diff --git a/src/dialect/postgres/postgres-introspector.ts b/src/dialect/postgres/postgres-introspector.ts
index 1c5855232..f9d840245 100644
--- a/src/dialect/postgres/postgres-introspector.ts
+++ b/src/dialect/postgres/postgres-introspector.ts
@@ -70,6 +70,7 @@ export class PostgresIntrospector implements DatabaseIntrospector {
'r' /*regular table*/,
'v' /*view*/,
'p' /*partitioned table*/,
+ 'f' /*foreign table*/,
])
.where('ns.nspname', '!~', '^pg_')
.where('ns.nspname', '!=', 'information_schema')
@@ -112,6 +113,7 @@ export class PostgresIntrospector implements DatabaseIntrospector {
table = freeze({
name: it.table,
isView: it.table_type === 'v',
+ isForeign: it.table_type === 'f',
schema: it.schema,
columns: [],
})
diff --git a/src/dialect/sqlite/sqlite-adapter.ts b/src/dialect/sqlite/sqlite-adapter.ts
index 28822d664..c6206b0f7 100644
--- a/src/dialect/sqlite/sqlite-adapter.ts
+++ b/src/dialect/sqlite/sqlite-adapter.ts
@@ -3,6 +3,10 @@ import { DialectAdapterBase } from '../dialect-adapter-base.js'
import { MigrationLockOptions } from '../dialect-adapter.js'
export class SqliteAdapter extends DialectAdapterBase {
+ override get supportsMultipleConnections(): boolean {
+ return false
+ }
+
override get supportsTransactionalDdl(): boolean {
return false
}
diff --git a/src/dialect/sqlite/sqlite-driver.ts b/src/dialect/sqlite/sqlite-driver.ts
index 53a64369e..a831e1b67 100644
--- a/src/dialect/sqlite/sqlite-driver.ts
+++ b/src/dialect/sqlite/sqlite-driver.ts
@@ -13,7 +13,6 @@ import { SqliteDatabase, SqliteDialectConfig } from './sqlite-dialect-config.js'
export class SqliteDriver implements Driver {
readonly #config: SqliteDialectConfig
- readonly #connectionMutex = new ConnectionMutex()
#db?: SqliteDatabase
#connection?: DatabaseConnection
@@ -35,9 +34,6 @@ export class SqliteDriver implements Driver {
}
async acquireConnection(): Promise {
- // SQLite only has one single connection. We use a mutex here to wait
- // until the single connection has been released.
- await this.#connectionMutex.lock()
return this.#connection!
}
@@ -93,7 +89,7 @@ export class SqliteDriver implements Driver {
}
async releaseConnection(): Promise {
- this.#connectionMutex.unlock()
+ // noop
}
async destroy(): Promise {
@@ -149,27 +145,3 @@ class SqliteConnection implements DatabaseConnection {
}
}
}
-
-class ConnectionMutex {
- #promise?: Promise
- #resolve?: () => void
-
- async lock(): Promise {
- while (this.#promise) {
- await this.#promise
- }
-
- this.#promise = new Promise((resolve) => {
- this.#resolve = resolve
- })
- }
-
- unlock(): void {
- const resolve = this.#resolve
-
- this.#promise = undefined
- this.#resolve = undefined
-
- resolve?.()
- }
-}
diff --git a/src/dialect/sqlite/sqlite-introspector.ts b/src/dialect/sqlite/sqlite-introspector.ts
index 487b2fa62..a4d20e256 100644
--- a/src/dialect/sqlite/sqlite-introspector.ts
+++ b/src/dialect/sqlite/sqlite-introspector.ts
@@ -135,6 +135,7 @@ export class SqliteIntrospector implements DatabaseIntrospector {
return {
name: name,
isView: type === 'view',
+ isForeign: false,
columns: columns.map((col) => ({
name: col.name,
dataType: col.type,
diff --git a/src/driver/connection-mutex.ts b/src/driver/connection-mutex.ts
new file mode 100644
index 000000000..52c28ae2d
--- /dev/null
+++ b/src/driver/connection-mutex.ts
@@ -0,0 +1,30 @@
+/**
+ * This mutex is used to ensure that only one operation at a time can
+ * acquire a connection from the driver. This is necessary when the
+ * driver only has a single connection, like SQLite and PGlite.
+ *
+ * @internal
+ */
+export class ConnectionMutex {
+ #promise?: Promise
+ #resolve?: () => void
+
+ async lock(): Promise {
+ while (this.#promise) {
+ await this.#promise
+ }
+
+ this.#promise = new Promise((resolve) => {
+ this.#resolve = resolve
+ })
+ }
+
+ unlock(): void {
+ const resolve = this.#resolve
+
+ this.#promise = undefined
+ this.#resolve = undefined
+
+ resolve?.()
+ }
+}
diff --git a/src/driver/database-connection.ts b/src/driver/database-connection.ts
index 6d9db8d33..ae9305209 100644
--- a/src/driver/database-connection.ts
+++ b/src/driver/database-connection.ts
@@ -6,13 +6,30 @@ import { CompiledQuery } from '../query-compiler/compiled-query.js'
* These are created by an instance of {@link Driver}.
*/
export interface DatabaseConnection {
- executeQuery(compiledQuery: CompiledQuery): Promise>
+ cancelQuery?(
+ controlConnectionProvider: ControlConnectionProvider,
+ ): Promise
+
+ executeQuery(
+ compiledQuery: CompiledQuery,
+ options?: QueryOptions,
+ ): Promise>
+
streamQuery(
compiledQuery: CompiledQuery,
- chunkSize?: number,
+ chunkSize: number,
+ options?: QueryOptions,
): AsyncIterableIterator>
}
+export interface QueryOptions {
+ cancelable?: boolean
+}
+
+export type ControlConnectionProvider = (
+ consumer: (connection: DatabaseConnection) => Promise,
+) => Promise
+
export interface QueryResult {
/**
* This is defined for insert, update, delete and merge queries and contains
diff --git a/src/driver/runtime-driver.ts b/src/driver/runtime-driver.ts
index eec01f345..425382d37 100644
--- a/src/driver/runtime-driver.ts
+++ b/src/driver/runtime-driver.ts
@@ -1,7 +1,9 @@
+import { DialectAdapter } from '../dialect/dialect-adapter.js'
import { CompiledQuery } from '../query-compiler/compiled-query.js'
import { QueryCompiler } from '../query-compiler/query-compiler.js'
import { Log } from '../util/log.js'
import { performanceNow } from '../util/performance-now.js'
+import { ConnectionMutex } from './connection-mutex.js'
import { DatabaseConnection, QueryResult } from './database-connection.js'
import { Driver, TransactionSettings } from './driver.js'
@@ -18,11 +20,16 @@ export class RuntimeDriver implements Driver {
#initDone: boolean
#destroyPromise?: Promise
#connections = new WeakSet()
+ #connectionMutex?: ConnectionMutex
- constructor(driver: Driver, log: Log) {
- this.#initDone = false
+ constructor(driver: Driver, adapter: DialectAdapter, log: Log) {
this.#driver = driver
+ this.#initDone = false
this.#log = log
+
+ if (adapter.supportsMultipleConnections === false) {
+ this.#connectionMutex = new ConnectionMutex()
+ }
}
async init(): Promise {
@@ -54,6 +61,10 @@ export class RuntimeDriver implements Driver {
await this.init()
}
+ if (this.#connectionMutex) {
+ await this.#connectionMutex.lock()
+ }
+
const connection = await this.#driver.acquireConnection()
if (!this.#connections.has(connection)) {
@@ -69,6 +80,8 @@ export class RuntimeDriver implements Driver {
async releaseConnection(connection: DatabaseConnection): Promise {
await this.#driver.releaseConnection(connection)
+
+ this.#connectionMutex?.unlock()
}
beginTransaction(
@@ -167,12 +180,13 @@ export class RuntimeDriver implements Driver {
connection.executeQuery = async (
compiledQuery,
+ options,
): Promise> => {
let caughtError: unknown
const startTime = performanceNow()
try {
- return await executeQuery.call(connection, compiledQuery)
+ return await executeQuery.call(connection, compiledQuery, options)
} catch (error) {
caughtError = error
await dis.#logError(error, compiledQuery, startTime)
@@ -187,6 +201,7 @@ export class RuntimeDriver implements Driver {
connection.streamQuery = async function* (
compiledQuery,
chunkSize,
+ options,
): AsyncIterableIterator> {
let caughtError: unknown
const startTime = performanceNow()
@@ -196,6 +211,7 @@ export class RuntimeDriver implements Driver {
connection,
compiledQuery,
chunkSize,
+ options,
)) {
yield result
}
diff --git a/src/index.ts b/src/index.ts
index e4c0a8c33..ba2708b9c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -76,9 +76,9 @@ export * from './dialect/database-introspector.js'
export * from './dialect/sqlite/sqlite-dialect.js'
export * from './dialect/sqlite/sqlite-dialect-config.js'
export * from './dialect/sqlite/sqlite-driver.js'
-export * from './dialect/postgres/postgres-query-compiler.js'
-export * from './dialect/postgres/postgres-introspector.js'
-export * from './dialect/postgres/postgres-adapter.js'
+export * from './dialect/sqlite/sqlite-query-compiler.js'
+export * from './dialect/sqlite/sqlite-introspector.js'
+export * from './dialect/sqlite/sqlite-adapter.js'
export * from './dialect/mysql/mysql-dialect.js'
export * from './dialect/mysql/mysql-dialect-config.js'
@@ -90,9 +90,9 @@ export * from './dialect/mysql/mysql-adapter.js'
export * from './dialect/postgres/postgres-driver.js'
export * from './dialect/postgres/postgres-dialect-config.js'
export * from './dialect/postgres/postgres-dialect.js'
-export * from './dialect/sqlite/sqlite-query-compiler.js'
-export * from './dialect/sqlite/sqlite-introspector.js'
-export * from './dialect/sqlite/sqlite-adapter.js'
+export * from './dialect/postgres/postgres-query-compiler.js'
+export * from './dialect/postgres/postgres-introspector.js'
+export * from './dialect/postgres/postgres-adapter.js'
export * from './dialect/mssql/mssql-adapter.js'
export * from './dialect/mssql/mssql-dialect-config.js'
@@ -101,6 +101,11 @@ export * from './dialect/mssql/mssql-driver.js'
export * from './dialect/mssql/mssql-introspector.js'
export * from './dialect/mssql/mssql-query-compiler.js'
+export * from './dialect/pglite/pglite-adapter.js'
+export * from './dialect/pglite/pglite-driver.js'
+export * from './dialect/pglite/pglite-dialect.js'
+export * from './dialect/pglite/pglite-dialect-config.js'
+
export * from './query-compiler/default-query-compiler.js'
export * from './query-compiler/query-compiler.js'
@@ -224,6 +229,7 @@ export * from './util/column-type.js'
export * from './util/compilable.js'
export * from './util/explainable.js'
export * from './util/streamable.js'
+export * from './util/executable.js'
export * from './util/log.js'
export {
AnyAliasedColumn,
@@ -243,6 +249,7 @@ export {
export * from './util/infer-result.js'
export { logOnce } from './util/log-once.js'
export { createQueryId, QueryId } from './util/query-id.js'
+export * from './util/abort.js'
export {
SelectExpression,
diff --git a/src/kysely.ts b/src/kysely.ts
index 7703126b9..79539ff04 100644
--- a/src/kysely.ts
+++ b/src/kysely.ts
@@ -2,7 +2,10 @@ import { Dialect } from './dialect/dialect.js'
import { SchemaModule } from './schema/schema.js'
import { DynamicModule } from './dynamic/dynamic.js'
import { DefaultConnectionProvider } from './driver/default-connection-provider.js'
-import { QueryExecutor } from './query-executor/query-executor.js'
+import {
+ ExecuteQueryOptions,
+ QueryExecutor,
+} from './query-executor/query-executor.js'
import { QueryCreator, QueryCreatorProps } from './query-creator.js'
import { KyselyPlugin } from './plugin/kysely-plugin.js'
import { DefaultQueryExecutor } from './query-executor/default-query-executor.js'
@@ -48,7 +51,7 @@ import {
provideControlledConnection,
} from './util/provide-controlled-connection.js'
import { ConnectionProvider } from './driver/connection-provider.js'
-import { logOnce } from './util/log-once.js'
+import { ExecuteOptions } from './util/executable.js'
// @ts-ignore
Symbol.asyncDispose ??= Symbol('Symbol.asyncDispose')
@@ -113,7 +116,7 @@ export class Kysely
const adapter = dialect.createAdapter()
const log = new Log(args.log ?? [])
- const runtimeDriver = new RuntimeDriver(driver, log)
+ const runtimeDriver = new RuntimeDriver(driver, adapter, log)
const connectionProvider = new DefaultConnectionProvider(runtimeDriver)
const executor = new DefaultQueryExecutor(
@@ -542,20 +545,13 @@ export class Kysely
*
* See {@link https://github.com/kysely-org/kysely/blob/master/site/docs/recipes/0004-splitting-query-building-and-execution.md#execute-compiled-queries splitting build, compile and execute code recipe} for more information.
*/
- executeQuery(
+ async executeQuery(
query: CompiledQuery | Compilable,
- // TODO: remove this in the future. deprecated in 0.28.x
- queryId?: QueryId,
+ options?: ExecuteOptions,
): Promise> {
- if (queryId !== undefined) {
- logOnce(
- 'Passing `queryId` in `db.executeQuery` is deprecated and will result in a compile-time error in the future.',
- )
- }
-
const compiledQuery = isCompilable(query) ? query.compile() : query
- return this.getExecutor().executeQuery(compiledQuery)
+ return await this.getExecutor().executeQuery(compiledQuery, options)
}
async [Symbol.asyncDispose]() {
@@ -1181,17 +1177,21 @@ class NotCommittedOrRolledBackAssertingExecutor implements QueryExecutor {
return this.#executor.provideConnection(consumer)
}
- executeQuery(compiledQuery: CompiledQuery): Promise> {
+ executeQuery(
+ compiledQuery: CompiledQuery,
+ options?: ExecuteQueryOptions,
+ ): Promise> {
assertNotCommittedOrRolledBack(this.#state)
- return this.#executor.executeQuery(compiledQuery)
+ return this.#executor.executeQuery(compiledQuery, options)
}
stream(
compiledQuery: CompiledQuery,
chunkSize: number,
+ options?: ExecuteQueryOptions,
): AsyncIterableIterator> {
assertNotCommittedOrRolledBack(this.#state)
- return this.#executor.stream(compiledQuery, chunkSize)
+ return this.#executor.stream(compiledQuery, chunkSize, options)
}
withConnectionProvider(
diff --git a/src/migration/migrator.ts b/src/migration/migrator.ts
index 68b20aacc..8be2c5807 100644
--- a/src/migration/migrator.ts
+++ b/src/migration/migrator.ts
@@ -146,8 +146,8 @@ export class Migrator {
* }
* ```
*/
- async migrateToLatest(): Promise {
- return this.#migrate(() => ({ direction: 'Up', step: Infinity }))
+ async migrateToLatest(options?: MigrateOptions): Promise {
+ return this.#migrate(() => ({ direction: 'Up', step: Infinity }), options)
}
/**
@@ -203,6 +203,7 @@ export class Migrator {
*/
async migrateTo(
targetMigrationName: string | NoMigrations,
+ options?: MigrateOptions,
): Promise {
return this.#migrate(
({
@@ -226,6 +227,7 @@ export class Migrator {
const executedIndex = executedMigrations.indexOf(
targetMigrationName as string,
)
+
const pendingIndex = pendingMigrations.findIndex(
(m) => m.name === (targetMigrationName as string),
)
@@ -235,14 +237,17 @@ export class Migrator {
direction: 'Down',
step: executedMigrations.length - executedIndex - 1,
}
- } else if (pendingIndex !== -1) {
+ }
+
+ if (pendingIndex !== -1) {
return { direction: 'Up', step: pendingIndex + 1 }
- } else {
- throw new Error(
- `migration "${targetMigrationName}" isn't executed or pending`,
- )
}
+
+ throw new Error(
+ `migration "${targetMigrationName}" isn't executed or pending`,
+ )
},
+ options,
)
}
@@ -274,8 +279,8 @@ export class Migrator {
* await migrator.migrateUp()
* ```
*/
- async migrateUp(): Promise {
- return this.#migrate(() => ({ direction: 'Up', step: 1 }))
+ async migrateUp(options?: MigrateOptions): Promise {
+ return this.#migrate(() => ({ direction: 'Up', step: 1 }), options)
}
/**
@@ -306,8 +311,8 @@ export class Migrator {
* await migrator.migrateDown()
* ```
*/
- async migrateDown(): Promise {
- return this.#migrate(() => ({ direction: 'Down', step: 1 }))
+ async migrateDown(options?: MigrateOptions): Promise {
+ return this.#migrate(() => ({ direction: 'Down', step: 1 }), options)
}
async #migrate(
@@ -315,10 +320,11 @@ export class Migrator {
direction: MigrationDirection
step: number
},
+ options: MigrateOptions | undefined,
): Promise {
try {
await this.#ensureMigrationTablesExists()
- return await this.#runMigrations(getMigrationDirectionAndStep)
+ return await this.#runMigrations(getMigrationDirectionAndStep, options)
} catch (error) {
if (error instanceof MigrationResultSetError) {
return error.resultSet
@@ -489,6 +495,7 @@ export class Migrator {
direction: MigrationDirection
step: number
},
+ options: MigrateOptions | undefined,
): Promise {
const adapter = this.#props.db.getExecutor().adapter
@@ -526,11 +533,14 @@ export class Migrator {
}
}
- if (adapter.supportsTransactionalDdl && !this.#props.disableTransactions) {
- return this.#props.db.transaction().execute(run)
- } else {
+ const disableTransaction =
+ options?.disableTransactions ?? this.#props.disableTransactions
+
+ if (!adapter.supportsTransactionalDdl || disableTransaction) {
return this.#props.db.connection().execute(run)
}
+
+ return this.#props.db.transaction().execute(run)
}
async #getState(db: Kysely): Promise {
@@ -752,7 +762,18 @@ export class Migrator {
}
}
-export interface MigratorProps {
+export interface MigrateOptions {
+ /**
+ * When `true`, don't run migrations in transactions even if the dialect supports transactional DDL.
+ *
+ * Default is `false`.
+ *
+ * This is useful when some migrations include queries that would fail otherwise.
+ */
+ readonly disableTransactions?: boolean
+}
+
+export interface MigratorProps extends MigrateOptions {
readonly db: Kysely
readonly provider: MigrationProvider
@@ -825,15 +846,6 @@ export interface MigratorProps {
* Default is `name0.localeCompare(name1)`.
*/
readonly nameComparator?: (name0: string, name1: string) => number
-
- /**
- * When `true`, don't run migrations in transactions even if the dialect supports transactional DDL.
- *
- * Default is `false`.
- *
- * This is useful when some migrations include queries that would fail otherwise.
- */
- readonly disableTransactions?: boolean
}
/**
diff --git a/src/operation-node/unique-constraint-node.ts b/src/operation-node/unique-constraint-node.ts
index c25e5d816..d35e74d5b 100644
--- a/src/operation-node/unique-constraint-node.ts
+++ b/src/operation-node/unique-constraint-node.ts
@@ -1,11 +1,12 @@
-import { freeze } from '../util/object-utils.js'
+import { logOnce } from '../util/log-once.js'
+import { freeze, isString } from '../util/object-utils.js'
import { ColumnNode } from './column-node.js'
import { IdentifierNode } from './identifier-node.js'
import { OperationNode } from './operation-node.js'
export interface UniqueConstraintNode extends OperationNode {
readonly kind: 'UniqueConstraintNode'
- readonly columns: ReadonlyArray
+ readonly columns: ReadonlyArray
readonly name?: IdentifierNode
readonly nullsNotDistinct?: boolean
readonly deferrable?: boolean
@@ -18,21 +19,56 @@ export type UniqueConstraintNodeProps = Omit<
>
/**
+ * TODO: remove this interface once support for `string[]` is removed.
+ *
* @internal
*/
-export const UniqueConstraintNode = freeze({
+interface UniqueConstraintNodeFactory {
+ is(node: OperationNode): node is UniqueConstraintNode
+ create(
+ columns: OperationNode[],
+ constraintName?: string,
+ nullsNotDistinct?: boolean,
+ ): UniqueConstraintNode
+ /**
+ * @deprecated pass `ColumnNode[]` instead of strings.
+ */
+ create(
+ columns: string[],
+ constraintName?: string,
+ nullsNotDistinct?: boolean,
+ ): UniqueConstraintNode
+ cloneWith(
+ node: UniqueConstraintNode,
+ props: UniqueConstraintNodeProps,
+ ): UniqueConstraintNode
+}
+
+/**
+ * @internal
+ */
+export const UniqueConstraintNode: UniqueConstraintNodeFactory = freeze({
is(node: OperationNode): node is UniqueConstraintNode {
return node.kind === 'UniqueConstraintNode'
},
create(
- columns: string[],
+ columns: string[] | OperationNode[],
constraintName?: string,
nullsNotDistinct?: boolean,
): UniqueConstraintNode {
+ // TODO: remove this block when support for `string[]` is removed.
+ if (isString(columns.at(0))) {
+ logOnce(
+ '`UniqueConstraintNode.create(columns: string[], ...)` is deprecated - pass `ColumnNode[]` instead.',
+ )
+
+ columns = (columns as string[]).map(ColumnNode.create)
+ }
+
return freeze({
kind: 'UniqueConstraintNode',
- columns: freeze(columns.map(ColumnNode.create)),
+ columns: freeze(columns) as OperationNode[],
name: constraintName ? IdentifierNode.create(constraintName) : undefined,
nullsNotDistinct,
})
@@ -42,9 +78,6 @@ export const UniqueConstraintNode = freeze({
node: UniqueConstraintNode,
props: UniqueConstraintNodeProps,
): UniqueConstraintNode {
- return freeze({
- ...node,
- ...props,
- })
+ return freeze({ ...node, ...props })
},
})
diff --git a/src/query-builder/delete-query-builder.ts b/src/query-builder/delete-query-builder.ts
index 62fc2b637..bda8b01f5 100644
--- a/src/query-builder/delete-query-builder.ts
+++ b/src/query-builder/delete-query-builder.ts
@@ -42,11 +42,7 @@ import { freeze } from '../util/object-utils.js'
import { KyselyPlugin } from '../plugin/kysely-plugin.js'
import { WhereInterface } from './where-interface.js'
import { MultiTableReturningInterface } from './returning-interface.js'
-import {
- isNoResultErrorConstructor,
- NoResultError,
- NoResultErrorConstructor,
-} from './no-result-error.js'
+import { isNoResultErrorConstructor, NoResultError } from './no-result-error.js'
import { DeleteResult } from './delete-result.js'
import { DeleteQueryNode } from '../operation-node/delete-query-node.js'
import { LimitNode } from '../operation-node/limit-node.js'
@@ -65,7 +61,7 @@ import {
parseReferentialBinaryOperation,
} from '../parser/binary-operation-parser.js'
import { KyselyTypeError } from '../util/type-error.js'
-import { Streamable } from '../util/streamable.js'
+import { Streamable, StreamOptions } from '../util/streamable.js'
import { ExpressionOrFactory } from '../parser/expression-parser.js'
import {
ValueExpression,
@@ -81,6 +77,11 @@ import {
} from './output-interface.js'
import { JoinType } from '../operation-node/join-node.js'
import { OrderByInterface } from './order-by-interface.js'
+import {
+ Executable,
+ ExecuteOptions,
+ ExecuteOrThrowOptions,
+} from '../util/executable.js'
export class DeleteQueryBuilder
implements
@@ -90,6 +91,7 @@ export class DeleteQueryBuilder
OrderByInterface,
OperationNodeSource,
Compilable,
+ Executable,
Explainable,
Streamable
{
@@ -1051,15 +1053,13 @@ export class DeleteQueryBuilder
)
}
- /**
- * Executes the query and returns an array of rows.
- *
- * Also see the {@link executeTakeFirst} and {@link executeTakeFirstOrThrow} methods.
- */
- async execute(): Promise[]> {
+ async execute(options?: ExecuteOptions): Promise[]> {
const compiledQuery = this.compile()
- const result = await this.#props.executor.executeQuery(compiledQuery)
+ const result = await this.#props.executor.executeQuery(
+ compiledQuery,
+ options,
+ )
const { adapter } = this.#props.executor
const query = compiledQuery.query as DeleteQueryNode
@@ -1068,37 +1068,37 @@ export class DeleteQueryBuilder
(query.returning && adapter.supportsReturning) ||
(query.output && adapter.supportsOutput)
) {
- return result.rows as any
+ return result.rows as never
}
- return [new DeleteResult(result.numAffectedRows ?? BigInt(0)) as any]
+ return [new DeleteResult(result.numAffectedRows ?? BigInt(0)) as never]
}
- /**
- * Executes the query and returns the first result or undefined if
- * the query returned no result.
- */
- async executeTakeFirst(): Promise> {
- const [result] = await this.execute()
- return result as SimplifySingleResult
+ async executeTakeFirst(
+ options?: ExecuteOptions,
+ ): Promise> {
+ const [result] = await this.execute(options)
+
+ return result
}
- /**
- * Executes the query and returns the first result or throws if
- * the query returned no result.
- *
- * By default an instance of {@link NoResultError} is thrown, but you can
- * provide a custom error class, or callback as the only argument to throw a different
- * error.
- */
async executeTakeFirstOrThrow(
- errorConstructor:
- | NoResultErrorConstructor
- | ((node: QueryNode) => Error) = NoResultError,
+ errorConstructorOrOptions?:
+ | ExecuteOrThrowOptions
+ | ExecuteOrThrowOptions['errorConstructor'],
): Promise> {
- const result = await this.executeTakeFirst()
+ if (typeof errorConstructorOrOptions === 'function') {
+ errorConstructorOrOptions = {
+ errorConstructor: errorConstructorOrOptions,
+ }
+ }
+
+ const result = await this.executeTakeFirst(errorConstructorOrOptions)
if (result === undefined) {
+ const errorConstructor =
+ errorConstructorOrOptions?.errorConstructor ?? NoResultError
+
const error = isNoResultErrorConstructor(errorConstructor)
? new errorConstructor(this.toOperationNode())
: errorConstructor(this.toOperationNode())
@@ -1106,13 +1106,25 @@ export class DeleteQueryBuilder
throw error
}
- return result as SimplifyResult
+ return result as never
}
- async *stream(chunkSize: number = 100): AsyncIterableIterator {
+ async *stream(
+ chunkSizeOrOptions?: StreamOptions | StreamOptions['chunkSize'],
+ ): AsyncIterableIterator {
+ if (typeof chunkSizeOrOptions !== 'object') {
+ chunkSizeOrOptions = {
+ chunkSize: chunkSizeOrOptions,
+ }
+ }
+
const compiledQuery = this.compile()
- const stream = this.#props.executor.stream(compiledQuery, chunkSize)
+ const stream = this.#props.executor.stream(
+ compiledQuery,
+ chunkSizeOrOptions.chunkSize ?? 100,
+ chunkSizeOrOptions,
+ )
for await (const item of stream) {
yield* item.rows
diff --git a/src/query-builder/insert-query-builder.ts b/src/query-builder/insert-query-builder.ts
index 681799091..a12e67587 100644
--- a/src/query-builder/insert-query-builder.ts
+++ b/src/query-builder/insert-query-builder.ts
@@ -34,11 +34,7 @@ import {
ReturningCallbackRow,
ReturningRow,
} from '../parser/returning-parser.js'
-import {
- isNoResultErrorConstructor,
- NoResultError,
- NoResultErrorConstructor,
-} from './no-result-error.js'
+import { isNoResultErrorConstructor, NoResultError } from './no-result-error.js'
import {
ExpressionOrFactory,
parseExpression,
@@ -57,7 +53,7 @@ import { Selectable } from '../util/column-type.js'
import { Explainable, ExplainFormat } from '../util/explainable.js'
import { Expression } from '../expression/expression.js'
import { KyselyTypeError } from '../util/type-error.js'
-import { Streamable } from '../util/streamable.js'
+import { Streamable, StreamOptions } from '../util/streamable.js'
import { parseTop } from '../parser/top-parser.js'
import {
OutputCallback,
@@ -67,6 +63,11 @@ import {
SelectExpressionFromOutputExpression,
} from './output-interface.js'
import { OrActionNode } from '../operation-node/or-action-node.js'
+import {
+ Executable,
+ ExecuteOptions,
+ ExecuteOrThrowOptions,
+} from '../util/executable.js'
export class InsertQueryBuilder
implements
@@ -74,6 +75,7 @@ export class InsertQueryBuilder
OutputInterface,
OperationNodeSource,
Compilable,
+ Executable,
Explainable,
Streamable
{
@@ -1284,15 +1286,13 @@ export class InsertQueryBuilder
)
}
- /**
- * Executes the query and returns an array of rows.
- *
- * Also see the {@link executeTakeFirst} and {@link executeTakeFirstOrThrow} methods.
- */
- async execute(): Promise[]> {
+ async execute(options?: ExecuteOptions): Promise[]> {
const compiledQuery = this.compile()
- const result = await this.#props.executor.executeQuery(compiledQuery)
+ const result = await this.#props.executor.executeQuery(
+ compiledQuery,
+ options,
+ )
const { adapter } = this.#props.executor
const query = compiledQuery.query as InsertQueryNode
@@ -1301,42 +1301,42 @@ export class InsertQueryBuilder
(query.returning && adapter.supportsReturning) ||
(query.output && adapter.supportsOutput)
) {
- return result.rows as any
+ return result.rows as never
}
return [
new InsertResult(
result.insertId,
result.numAffectedRows ?? BigInt(0),
- ) as any,
+ ) as never,
]
}
- /**
- * Executes the query and returns the first result or undefined if
- * the query returned no result.
- */
- async executeTakeFirst(): Promise> {
- const [result] = await this.execute()
- return result as SimplifySingleResult
+ async executeTakeFirst(
+ options?: ExecuteOptions,
+ ): Promise> {
+ const [result] = await this.execute(options)
+
+ return result
}
- /**
- * Executes the query and returns the first result or throws if
- * the query returned no result.
- *
- * By default an instance of {@link NoResultError} is thrown, but you can
- * provide a custom error class, or callback as the only argument to throw a different
- * error.
- */
async executeTakeFirstOrThrow(
- errorConstructor:
- | NoResultErrorConstructor
- | ((node: QueryNode) => Error) = NoResultError,
+ errorConstructorOrOptions?:
+ | ExecuteOrThrowOptions
+ | ExecuteOrThrowOptions['errorConstructor'],
): Promise> {
- const result = await this.executeTakeFirst()
+ if (typeof errorConstructorOrOptions === 'function') {
+ errorConstructorOrOptions = {
+ errorConstructor: errorConstructorOrOptions,
+ }
+ }
+
+ const result = await this.executeTakeFirst(errorConstructorOrOptions)
if (result === undefined) {
+ const errorConstructor =
+ errorConstructorOrOptions?.errorConstructor ?? NoResultError
+
const error = isNoResultErrorConstructor(errorConstructor)
? new errorConstructor(this.toOperationNode())
: errorConstructor(this.toOperationNode())
@@ -1344,13 +1344,25 @@ export class InsertQueryBuilder
throw error
}
- return result as SimplifyResult
+ return result as never
}
- async *stream(chunkSize: number = 100): AsyncIterableIterator {
+ async *stream(
+ chunkSizeOrOptions?: StreamOptions | StreamOptions['chunkSize'],
+ ): AsyncIterableIterator {
+ if (typeof chunkSizeOrOptions !== 'object') {
+ chunkSizeOrOptions = {
+ chunkSize: chunkSizeOrOptions,
+ }
+ }
+
const compiledQuery = this.compile()
- const stream = this.#props.executor.stream(compiledQuery, chunkSize)
+ const stream = this.#props.executor.stream(
+ compiledQuery,
+ chunkSizeOrOptions.chunkSize ?? 100,
+ chunkSizeOrOptions,
+ )
for await (const item of stream) {
yield* item.rows
diff --git a/src/query-builder/merge-query-builder.ts b/src/query-builder/merge-query-builder.ts
index 58238294e..3e6429528 100644
--- a/src/query-builder/merge-query-builder.ts
+++ b/src/query-builder/merge-query-builder.ts
@@ -45,6 +45,11 @@ import { CompiledQuery } from '../query-compiler/compiled-query.js'
import { NOOP_QUERY_EXECUTOR } from '../query-executor/noop-query-executor.js'
import { QueryExecutor } from '../query-executor/query-executor.js'
import { Compilable } from '../util/compilable.js'
+import {
+ Executable,
+ ExecuteOptions,
+ ExecuteOrThrowOptions,
+} from '../util/executable.js'
import { freeze } from '../util/object-utils.js'
import { QueryId } from '../util/query-id.js'
import {
@@ -54,11 +59,7 @@ import {
SqlBool,
} from '../util/type-utils.js'
import { MergeResult } from './merge-result.js'
-import {
- NoResultError,
- NoResultErrorConstructor,
- isNoResultErrorConstructor,
-} from './no-result-error.js'
+import { NoResultError, isNoResultErrorConstructor } from './no-result-error.js'
import {
OutputCallback,
OutputExpression,
@@ -323,10 +324,11 @@ export class WheneableMergeQueryBuilder<
O,
>
implements
- Compilable,
MultiTableReturningInterface,
OutputInterface,
- OperationNodeSource
+ OperationNodeSource,
+ Compilable,
+ Executable
{
readonly #props: MergeQueryBuilderProps
@@ -875,15 +877,13 @@ export class WheneableMergeQueryBuilder<
)
}
- /**
- * Executes the query and returns an array of rows.
- *
- * Also see the {@link executeTakeFirst} and {@link executeTakeFirstOrThrow} methods.
- */
- async execute(): Promise[]> {
+ async execute(options?: ExecuteOptions): Promise[]> {
const compiledQuery = this.compile()
- const result = await this.#props.executor.executeQuery(compiledQuery)
+ const result = await this.#props.executor.executeQuery(
+ compiledQuery,
+ options,
+ )
const { adapter } = this.#props.executor
const query = compiledQuery.query as MergeQueryNode
@@ -892,37 +892,37 @@ export class WheneableMergeQueryBuilder<
(query.returning && adapter.supportsReturning) ||
(query.output && adapter.supportsOutput)
) {
- return result.rows as any
+ return result.rows as never
}
- return [new MergeResult(result.numAffectedRows) as any]
+ return [new MergeResult(result.numAffectedRows) as never]
}
- /**
- * Executes the query and returns the first result or undefined if
- * the query returned no result.
- */
- async executeTakeFirst(): Promise> {
- const [result] = await this.execute()
- return result as SimplifySingleResult
+ async executeTakeFirst(
+ options?: ExecuteOptions,
+ ): Promise> {
+ const [result] = await this.execute(options)
+
+ return result
}
- /**
- * Executes the query and returns the first result or throws if
- * the query returned no result.
- *
- * By default an instance of {@link NoResultError} is thrown, but you can
- * provide a custom error class, or callback as the only argument to throw a different
- * error.
- */
async executeTakeFirstOrThrow(
- errorConstructor:
- | NoResultErrorConstructor
- | ((node: QueryNode) => Error) = NoResultError,
+ errorConstructorOrOptions?:
+ | ExecuteOrThrowOptions
+ | ExecuteOrThrowOptions['errorConstructor'],
): Promise> {
- const result = await this.executeTakeFirst()
+ if (typeof errorConstructorOrOptions === 'function') {
+ errorConstructorOrOptions = {
+ errorConstructor: errorConstructorOrOptions,
+ }
+ }
+
+ const result = await this.executeTakeFirst(errorConstructorOrOptions)
if (result === undefined) {
+ const errorConstructor =
+ errorConstructorOrOptions?.errorConstructor ?? NoResultError
+
const error = isNoResultErrorConstructor(errorConstructor)
? new errorConstructor(this.toOperationNode())
: errorConstructor(this.toOperationNode())
@@ -930,7 +930,7 @@ export class WheneableMergeQueryBuilder<
throw error
}
- return result as SimplifyResult
+ return result as never
}
}
diff --git a/src/query-builder/select-query-builder.ts b/src/query-builder/select-query-builder.ts
index de5ed6bd7..cdc7c92cc 100644
--- a/src/query-builder/select-query-builder.ts
+++ b/src/query-builder/select-query-builder.ts
@@ -30,6 +30,7 @@ import {
Nullable,
ShallowRecord,
Simplify,
+ SimplifyResult,
SimplifySingleResult,
SqlBool,
} from '../util/type-utils.js'
@@ -48,11 +49,7 @@ import { asArray, freeze } from '../util/object-utils.js'
import { GroupByArg, parseGroupBy } from '../parser/group-by-parser.js'
import { KyselyPlugin } from '../plugin/kysely-plugin.js'
import { WhereInterface } from './where-interface.js'
-import {
- isNoResultErrorConstructor,
- NoResultError,
- NoResultErrorConstructor,
-} from './no-result-error.js'
+import { isNoResultErrorConstructor, NoResultError } from './no-result-error.js'
import { HavingInterface } from './having-interface.js'
import { IdentifierNode } from '../operation-node/identifier-node.js'
import { Explainable, ExplainFormat } from '../util/explainable.js'
@@ -69,7 +66,7 @@ import {
} from '../parser/binary-operation-parser.js'
import { KyselyTypeError } from '../util/type-error.js'
import { Selectable } from '../util/column-type.js'
-import { Streamable } from '../util/streamable.js'
+import { Streamable, StreamOptions } from '../util/streamable.js'
import { ExpressionOrFactory } from '../parser/expression-parser.js'
import { ExpressionWrapper } from '../expression/expression-wrapper.js'
import { SelectQueryBuilderExpression } from './select-query-builder-expression.js'
@@ -83,6 +80,11 @@ import { TopModifier } from '../operation-node/top-node.js'
import { parseTop } from '../parser/top-parser.js'
import { JoinType } from '../operation-node/join-node.js'
import { OrderByInterface } from './order-by-interface.js'
+import {
+ Executable,
+ ExecuteOptions,
+ ExecuteOrThrowOptions,
+} from '../util/executable.js'
export interface SelectQueryBuilder
extends WhereInterface,
@@ -90,6 +92,7 @@ export interface SelectQueryBuilder
OrderByInterface,
SelectQueryBuilderExpression,
Compilable,
+ Executable,
Explainable,
Streamable {
where<
@@ -2117,32 +2120,17 @@ export interface SelectQueryBuilder
compile(): CompiledQuery>
- /**
- * Executes the query and returns an array of rows.
- *
- * Also see the {@link executeTakeFirst} and {@link executeTakeFirstOrThrow} methods.
- */
- execute(): Promise[]>
+ execute(options?: ExecuteOptions): Promise>[]>
- /**
- * Executes the query and returns the first result or undefined if
- * the query returned no result.
- */
- executeTakeFirst(): Promise>
+ executeTakeFirst(options?: ExecuteOptions): Promise>
- /**
- * Executes the query and returns the first result or throws if
- * the query returned no result.
- *
- * By default an instance of {@link NoResultError} is thrown, but you can
- * provide a custom error class, or callback to throw a different
- * error.
- */
executeTakeFirstOrThrow(
- errorConstructor?: NoResultErrorConstructor | ((node: QueryNode) => Error),
- ): Promise>
+ options?: ExecuteOrThrowOptions | ExecuteOrThrowOptions['errorConstructor'],
+ ): Promise>
- stream(chunkSize?: number): AsyncIterableIterator
+ stream(
+ chunkSizeOrOptions?: StreamOptions | StreamOptions['chunkSize'],
+ ): AsyncIterableIterator
explain = Record>(
format?: ExplainFormat,
@@ -2647,27 +2635,42 @@ class SelectQueryBuilderImpl
)
}
- async execute(): Promise[]> {
+ async execute(options?: ExecuteOptions): Promise[]> {
const compiledQuery = this.compile()
- const result = await this.#props.executor.executeQuery(compiledQuery)
+ const result = await this.#props.executor.executeQuery(
+ compiledQuery,
+ options,
+ )
- return result.rows
+ return result.rows as never
}
- async executeTakeFirst(): Promise> {
- const [result] = await this.execute()
- return result as SimplifySingleResult
+ async executeTakeFirst(
+ options?: ExecuteOptions,
+ ): Promise> {
+ const [result] = await this.execute(options)
+
+ return result
}
async executeTakeFirstOrThrow(
- errorConstructor:
- | NoResultErrorConstructor
- | ((node: QueryNode) => Error) = NoResultError,
- ): Promise> {
- const result = await this.executeTakeFirst()
+ errorConstructorOrOptions?:
+ | ExecuteOrThrowOptions
+ | ExecuteOrThrowOptions['errorConstructor'],
+ ): Promise> {
+ if (typeof errorConstructorOrOptions === 'function') {
+ errorConstructorOrOptions = {
+ errorConstructor: errorConstructorOrOptions,
+ }
+ }
+
+ const result = await this.executeTakeFirst(errorConstructorOrOptions)
if (result === undefined) {
+ const errorConstructor =
+ errorConstructorOrOptions?.errorConstructor ?? NoResultError
+
const error = isNoResultErrorConstructor(errorConstructor)
? new errorConstructor(this.toOperationNode())
: errorConstructor(this.toOperationNode())
@@ -2675,13 +2678,25 @@ class SelectQueryBuilderImpl
throw error
}
- return result as O
+ return result as never
}
- async *stream(chunkSize: number = 100): AsyncIterableIterator {
+ async *stream(
+ chunkSizeOrOptions?: StreamOptions | StreamOptions['chunkSize'],
+ ): AsyncIterableIterator {
+ if (typeof chunkSizeOrOptions !== 'object') {
+ chunkSizeOrOptions = {
+ chunkSize: chunkSizeOrOptions,
+ }
+ }
+
const compiledQuery = this.compile()
- const stream = this.#props.executor.stream(compiledQuery, chunkSize)
+ const stream = this.#props.executor.stream(
+ compiledQuery,
+ chunkSizeOrOptions.chunkSize ?? 100,
+ chunkSizeOrOptions,
+ )
for await (const item of stream) {
yield* item.rows
diff --git a/src/query-builder/update-query-builder.ts b/src/query-builder/update-query-builder.ts
index be1f8f172..d13827066 100644
--- a/src/query-builder/update-query-builder.ts
+++ b/src/query-builder/update-query-builder.ts
@@ -49,11 +49,7 @@ import { UpdateResult } from './update-result.js'
import { KyselyPlugin } from '../plugin/kysely-plugin.js'
import { WhereInterface } from './where-interface.js'
import { MultiTableReturningInterface } from './returning-interface.js'
-import {
- isNoResultErrorConstructor,
- NoResultError,
- NoResultErrorConstructor,
-} from './no-result-error.js'
+import { isNoResultErrorConstructor, NoResultError } from './no-result-error.js'
import { Explainable, ExplainFormat } from '../util/explainable.js'
import { AliasedExpression, Expression } from '../expression/expression.js'
import {
@@ -63,7 +59,7 @@ import {
parseValueBinaryOperationOrExpression,
} from '../parser/binary-operation-parser.js'
import { KyselyTypeError } from '../util/type-error.js'
-import { Streamable } from '../util/streamable.js'
+import { Streamable, StreamOptions } from '../util/streamable.js'
import { ExpressionOrFactory } from '../parser/expression-parser.js'
import {
ValueExpression,
@@ -87,6 +83,11 @@ import {
OrderByModifiers,
parseOrderBy,
} from '../parser/order-by-parser.js'
+import {
+ Executable,
+ ExecuteOptions,
+ ExecuteOrThrowOptions,
+} from '../util/executable.js'
export class UpdateQueryBuilder
implements
@@ -96,6 +97,7 @@ export class UpdateQueryBuilder
OrderByInterface,
OperationNodeSource,
Compilable,
+ Executable,
Explainable,
Streamable
{
@@ -1140,15 +1142,13 @@ export class UpdateQueryBuilder
)
}
- /**
- * Executes the query and returns an array of rows.
- *
- * Also see the {@link executeTakeFirst} and {@link executeTakeFirstOrThrow} methods.
- */
- async execute(): Promise[]> {
+ async execute(options?: ExecuteOptions): Promise[]> {
const compiledQuery = this.compile()
- const result = await this.#props.executor.executeQuery(compiledQuery)
+ const result = await this.#props.executor.executeQuery(
+ compiledQuery,
+ options,
+ )
const { adapter } = this.#props.executor
const query = compiledQuery.query as UpdateQueryNode
@@ -1157,42 +1157,42 @@ export class UpdateQueryBuilder
(query.returning && adapter.supportsReturning) ||
(query.output && adapter.supportsOutput)
) {
- return result.rows as any
+ return result.rows as never
}
return [
new UpdateResult(
result.numAffectedRows ?? BigInt(0),
result.numChangedRows,
- ) as any,
+ ) as never,
]
}
- /**
- * Executes the query and returns the first result or undefined if
- * the query returned no result.
- */
- async executeTakeFirst(): Promise> {
- const [result] = await this.execute()
- return result as SimplifySingleResult
+ async executeTakeFirst(
+ options?: ExecuteOptions,
+ ): Promise