Initial commit - Event Planner application

This commit is contained in:
mberlin
2026-03-18 14:55:56 -03:00
commit 86d779eb4d
7548 changed files with 1006324 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
import type { Kysely } from '../../kysely.js';
import { DialectAdapterBase } from '../dialect-adapter-base.js';
export declare class MssqlAdapter extends DialectAdapterBase {
/**
* Whether or not this dialect supports `if not exists` in creation of tables/schemas/views/etc.
*
* 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.
*/
get supportsCreateIfNotExists(): boolean;
/**
* Whether or not this dialect supports transactional DDL.
*
* If this is true, migrations are executed inside a transaction.
*/
get supportsTransactionalDdl(): boolean;
get supportsOutput(): boolean;
/**
* This method is used to acquire a lock for the migrations so that
* it's not possible for two migration operations to run in parallel.
*
* Most dialects have explicit locks that can be used, like advisory locks
* in PostgreSQL and the get_lock function in MySQL.
*
* If the dialect doesn't have explicit locks the {@link MigrationLockOptions.lockTable}
* created by Kysely can be used instead. You can access it through the `options` object.
* The lock table has two columns `id` and `is_locked` and there's only one row in the table
* whose id is {@link MigrationLockOptions.lockRowId}. `is_locked` is an integer. Kysely
* takes care of creating the lock table and inserting the one single row to it before this
* method is executed. If the dialect supports schemas and the user has specified a custom
* schema in their migration settings, the options object also contains the schema name in
* {@link MigrationLockOptions.lockTableSchema}.
*
* Here's an example of how you might implement this method for a dialect that doesn't
* have explicit locks but supports `FOR UPDATE` row locks and transactional DDL:
*
* ```ts
* import { DialectAdapterBase, type MigrationLockOptions, Kysely } from 'kysely'
*
* export class MyAdapter extends DialectAdapterBase {
* override async acquireMigrationLock(
* db: Kysely<any>,
* options: MigrationLockOptions
* ): Promise<void> {
* const queryDb = options.lockTableSchema
* ? db.withSchema(options.lockTableSchema)
* : db
*
* // Since our imaginary dialect supports transactional DDL and has
* // row locks, we can simply take a row lock here and it will guarantee
* // all subsequent calls to this method from other transactions will
* // wait until this transaction finishes.
* await queryDb
* .selectFrom(options.lockTable)
* .selectAll()
* .where('id', '=', options.lockRowId)
* .forUpdate()
* .execute()
* }
*
* override async releaseMigrationLock() {
* // noop
* }
* }
* ```
*
* If `supportsTransactionalDdl` is `true` then the `db` passed to this method
* is a transaction inside which the migrations will be executed. Otherwise
* `db` is a single connection (session) that will be used to execute the
* migrations.
*/
acquireMigrationLock(db: Kysely<any>): Promise<void>;
/**
* Releases the migration lock. See {@link acquireMigrationLock}.
*
* If `supportsTransactionalDdl` is `true` then the `db` passed to this method
* is a transaction inside which the migrations were executed. Otherwise `db`
* is a single connection (session) that was used to execute the migrations
* and the `acquireMigrationLock` call.
*/
releaseMigrationLock(): Promise<void>;
}

View File

@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MssqlAdapter = void 0;
const migrator_js_1 = require("../../migration/migrator.js");
const sql_js_1 = require("../../raw-builder/sql.js");
const dialect_adapter_base_js_1 = require("../dialect-adapter-base.js");
class MssqlAdapter extends dialect_adapter_base_js_1.DialectAdapterBase {
get supportsCreateIfNotExists() {
return false;
}
get supportsTransactionalDdl() {
return true;
}
get supportsOutput() {
return true;
}
async acquireMigrationLock(db) {
// Acquire a transaction-level exclusive lock on the migrations table.
// https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-getapplock-transact-sql?view=sql-server-ver16
await (0, sql_js_1.sql) `exec sp_getapplock @DbPrincipal = ${sql_js_1.sql.lit('dbo')}, @Resource = ${sql_js_1.sql.lit(migrator_js_1.DEFAULT_MIGRATION_TABLE)}, @LockMode = ${sql_js_1.sql.lit('Exclusive')}`.execute(db);
}
async releaseMigrationLock() {
// Nothing to do here. `sp_getapplock` is automatically released at the
// end of the transaction and since `supportsTransactionalDdl` true, we know
// the `db` instance passed to acquireMigrationLock is actually a transaction.
}
}
exports.MssqlAdapter = MssqlAdapter;

View File

@@ -0,0 +1,181 @@
import type { KyselyTypeError } from '../../util/type-error.js';
export interface MssqlDialectConfig {
/**
* When `true`, connections are reset to their initial states when released
* back to the pool, resulting in additional requests to the database.
*
* Defaults to `false`.
*/
resetConnectionsOnRelease?: boolean;
/**
* This dialect uses the `tarn` package to manage the connection pool to your
* database. To use it as a peer dependency and not bundle it with Kysely's code,
* you need to pass the `tarn` package itself. You also need to pass some pool options
* (excluding `create`, `destroy` and `validate` functions which are controlled by this dialect),
* `min` & `max` connections at the very least.
*
* ### Examples
*
* ```ts
* import { MssqlDialect } from 'kysely'
* import * as Tarn from 'tarn'
* import * as Tedious from 'tedious'
*
* const dialect = new MssqlDialect({
* tarn: { ...Tarn, options: { max: 10, min: 0 } },
* tedious: {
* ...Tedious,
* connectionFactory: () => new Tedious.Connection({
* // ...
* server: 'localhost',
* // ...
* }),
* }
* })
* ```
*/
tarn: Tarn;
/**
* This dialect uses the `tedious` package to communicate with your MS SQL Server
* database. To use it as a peer dependency and not bundle it with Kysely's code,
* you need to pass the `tedious` package itself. You also need to pass a factory
* function that creates new `tedious` `Connection` instances on demand.
*
* ### Examples
*
* ```ts
* import { MssqlDialect } from 'kysely'
* import * as Tarn from 'tarn'
* import * as Tedious from 'tedious'
*
* const dialect = new MssqlDialect({
* tarn: { ...Tarn, options: { max: 10, min: 0 } },
* tedious: {
* ...Tedious,
* connectionFactory: () => new Tedious.Connection({
* // ...
* server: 'localhost',
* // ...
* }),
* }
* })
* ```
*/
tedious: Tedious;
/**
* When `true`, connections are validated before being acquired from the pool,
* resulting in additional requests to the database.
*
* Defaults to `true`.
*/
validateConnections?: boolean;
}
export interface Tedious {
connectionFactory: () => TediousConnection | Promise<TediousConnection>;
ISOLATION_LEVEL: TediousIsolationLevel;
Request: TediousRequestClass;
/**
* @deprecated use {@link MssqlDialectConfig.resetConnectionsOnRelease} instead.
*/
resetConnectionOnRelease?: KyselyTypeError<'deprecated: use `MssqlDialectConfig.resetConnectionsOnRelease` instead'>;
TYPES: TediousTypes;
}
export interface TediousConnection {
beginTransaction(callback: (err: Error | null | undefined, transactionDescriptor?: any) => void, name?: string | undefined, isolationLevel?: number | undefined): void;
cancel(): boolean;
close(): void;
commitTransaction(callback: (err: Error | null | undefined) => void, name?: string | undefined): void;
connect(connectListener: (err?: Error) => void): void;
execSql(request: TediousRequest): void;
off(event: 'error', listener: (error: unknown) => void): this;
off(event: string, listener: (...args: any[]) => void): this;
on(event: 'error', listener: (error: unknown) => void): this;
on(event: string, listener: (...args: any[]) => void): this;
once(event: 'end', listener: () => void): this;
once(event: string, listener: (...args: any[]) => void): this;
reset(callback: (err: Error | null | undefined) => void): void;
rollbackTransaction(callback: (err: Error | null | undefined) => void, name?: string | undefined): void;
saveTransaction(callback: (err: Error | null | undefined) => void, name: string): void;
}
export type TediousIsolationLevel = Record<string, number>;
export interface TediousRequestClass {
new (sqlTextOrProcedure: string | undefined, callback: (error?: Error | null, rowCount?: number, rows?: any) => void, options?: {
statementColumnEncryptionSetting?: any;
}): TediousRequest;
}
export declare class TediousRequest {
addParameter(name: string, dataType: TediousDataType, value?: unknown, options?: Readonly<{
output?: boolean;
length?: number;
precision?: number;
scale?: number;
}> | null): void;
off(event: 'row', listener: (columns: any) => void): this;
off(event: string, listener: (...args: any[]) => void): this;
on(event: 'row', listener: (columns: any) => void): this;
on(event: string, listener: (...args: any[]) => void): this;
once(event: 'requestCompleted', listener: () => void): this;
once(event: string, listener: (...args: any[]) => void): this;
pause(): void;
resume(): void;
}
export interface TediousTypes {
NVarChar: TediousDataType;
BigInt: TediousDataType;
Int: TediousDataType;
Float: TediousDataType;
Bit: TediousDataType;
DateTime: TediousDataType;
VarBinary: TediousDataType;
[x: string]: TediousDataType;
}
export interface TediousDataType {
}
export interface TediousColumnValue {
metadata: {
colName: string;
};
value: any;
}
export interface Tarn {
/**
* Tarn.js' pool options, excluding `create`, `destroy` and `validate` functions,
* which must be implemented by this dialect.
*/
options: Omit<TarnPoolOptions<any>, 'create' | 'destroy' | 'validate'> & {
/**
* @deprecated use {@link MssqlDialectConfig.validateConnections} instead.
*/
validateConnections?: KyselyTypeError<'deprecated: use `MssqlDialectConfig.validateConnections` instead'>;
};
/**
* Tarn.js' Pool class.
*/
Pool: typeof TarnPool;
}
export declare class TarnPool<R> {
constructor(opt: TarnPoolOptions<R>);
acquire(): TarnPendingRequest<R>;
destroy(): any;
release(resource: R): void;
}
export interface TarnPoolOptions<R> {
acquireTimeoutMillis?: number;
create(cb: (err: Error | null, resource: R) => void): any | (() => Promise<R>);
createRetryIntervalMillis?: number;
createTimeoutMillis?: number;
destroy(resource: R): any;
destroyTimeoutMillis?: number;
idleTimeoutMillis?: number;
log?(msg: string): any;
max: number;
min: number;
propagateCreateError?: boolean;
reapIntervalMillis?: number;
validate?(resource: R): boolean;
}
export interface TarnPendingRequest<R> {
promise: Promise<R>;
resolve: (resource: R) => void;
reject: (err: Error) => void;
}

View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,70 @@
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 type { MssqlDialectConfig } from './mssql-dialect-config.js';
/**
* MS SQL Server dialect that uses the [tedious](https://tediousjs.github.io/tedious)
* library.
*
* The constructor takes an instance of {@link MssqlDialectConfig}.
*
* ```ts
* import * as Tedious from 'tedious'
* import * as Tarn from 'tarn'
*
* const dialect = new MssqlDialect({
* tarn: {
* ...Tarn,
* options: {
* min: 0,
* max: 10,
* },
* },
* tedious: {
* ...Tedious,
* connectionFactory: () => new Tedious.Connection({
* authentication: {
* options: {
* password: 'password',
* userName: 'username',
* },
* type: 'default',
* },
* options: {
* database: 'some_db',
* port: 1433,
* trustServerCertificate: true,
* },
* server: 'localhost',
* }),
* },
* })
* ```
*/
export declare class MssqlDialect implements Dialect {
#private;
constructor(config: MssqlDialectConfig);
/**
* Creates a driver for the dialect.
*/
createDriver(): Driver;
/**
* Creates a query compiler for the dialect.
*/
createQueryCompiler(): QueryCompiler;
/**
* Creates an adapter for the dialect.
*/
createAdapter(): DialectAdapter;
/**
* Creates a database introspector that can be used to get database metadata
* such as the table names and column names of those tables.
*
* `db` never has any plugins installed. It's created using
* {@link Kysely.withoutPlugins}.
*/
createIntrospector(db: Kysely<any>): DatabaseIntrospector;
}

View File

@@ -0,0 +1,65 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MssqlDialect = void 0;
const mssql_adapter_js_1 = require("./mssql-adapter.js");
const mssql_driver_js_1 = require("./mssql-driver.js");
const mssql_introspector_js_1 = require("./mssql-introspector.js");
const mssql_query_compiler_js_1 = require("./mssql-query-compiler.js");
/**
* MS SQL Server dialect that uses the [tedious](https://tediousjs.github.io/tedious)
* library.
*
* The constructor takes an instance of {@link MssqlDialectConfig}.
*
* ```ts
* import * as Tedious from 'tedious'
* import * as Tarn from 'tarn'
*
* const dialect = new MssqlDialect({
* tarn: {
* ...Tarn,
* options: {
* min: 0,
* max: 10,
* },
* },
* tedious: {
* ...Tedious,
* connectionFactory: () => new Tedious.Connection({
* authentication: {
* options: {
* password: 'password',
* userName: 'username',
* },
* type: 'default',
* },
* options: {
* database: 'some_db',
* port: 1433,
* trustServerCertificate: true,
* },
* server: 'localhost',
* }),
* },
* })
* ```
*/
class MssqlDialect {
#config;
constructor(config) {
this.#config = config;
}
createDriver() {
return new mssql_driver_js_1.MssqlDriver(this.#config);
}
createQueryCompiler() {
return new mssql_query_compiler_js_1.MssqlQueryCompiler();
}
createAdapter() {
return new mssql_adapter_js_1.MssqlAdapter();
}
createIntrospector(db) {
return new mssql_introspector_js_1.MssqlIntrospector(db);
}
}
exports.MssqlDialect = MssqlDialect;

View File

@@ -0,0 +1,59 @@
import type { DatabaseConnection, QueryResult } from '../../driver/database-connection.js';
import type { Driver, TransactionSettings } from '../../driver/driver.js';
import type { MssqlDialectConfig, Tedious, TediousConnection } from './mssql-dialect-config.js';
import { CompiledQuery } from '../../query-compiler/compiled-query.js';
declare const PRIVATE_RESET_METHOD: unique symbol;
declare const PRIVATE_DESTROY_METHOD: unique symbol;
declare const PRIVATE_VALIDATE_METHOD: unique symbol;
export declare class MssqlDriver implements Driver {
#private;
constructor(config: MssqlDialectConfig);
/**
* Initializes the driver.
*
* After calling this method the driver should be usable and `acquireConnection` etc.
* methods should be callable.
*/
init(): Promise<void>;
/**
* Acquires a new connection from the pool.
*/
acquireConnection(): Promise<DatabaseConnection>;
/**
* Begins a transaction.
*/
beginTransaction(connection: MssqlConnection, settings: TransactionSettings): Promise<void>;
/**
* Commits a transaction.
*/
commitTransaction(connection: MssqlConnection): Promise<void>;
/**
* Rolls back a transaction.
*/
rollbackTransaction(connection: MssqlConnection): Promise<void>;
savepoint(connection: MssqlConnection, savepointName: string): Promise<void>;
rollbackToSavepoint(connection: MssqlConnection, savepointName: string): Promise<void>;
/**
* Releases a connection back to the pool.
*/
releaseConnection(connection: MssqlConnection): Promise<void>;
/**
* Destroys the driver and releases all resources.
*/
destroy(): Promise<void>;
}
declare class MssqlConnection implements DatabaseConnection {
#private;
constructor(connection: TediousConnection, tedious: Tedious);
beginTransaction(settings: TransactionSettings): Promise<void>;
commitTransaction(): Promise<void>;
connect(): Promise<this>;
executeQuery<O>(compiledQuery: CompiledQuery): Promise<QueryResult<O>>;
rollbackTransaction(savepointName?: string): Promise<void>;
savepoint(savepointName: string): Promise<void>;
streamQuery<O>(compiledQuery: CompiledQuery, chunkSize: number): AsyncIterableIterator<QueryResult<O>>;
[PRIVATE_DESTROY_METHOD](): Promise<void>;
[PRIVATE_RESET_METHOD](): Promise<void>;
[PRIVATE_VALIDATE_METHOD](): Promise<boolean>;
}
export {};

View File

@@ -0,0 +1,359 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MssqlDriver = void 0;
const object_utils_js_1 = require("../../util/object-utils.js");
const compiled_query_js_1 = require("../../query-compiler/compiled-query.js");
const stack_trace_utils_js_1 = require("../../util/stack-trace-utils.js");
const random_string_js_1 = require("../../util/random-string.js");
const deferred_js_1 = require("../../util/deferred.js");
const PRIVATE_RESET_METHOD = Symbol();
const PRIVATE_DESTROY_METHOD = Symbol();
const PRIVATE_VALIDATE_METHOD = Symbol();
class MssqlDriver {
#config;
#pool;
constructor(config) {
this.#config = (0, object_utils_js_1.freeze)({ ...config });
const { tarn, tedious, validateConnections } = this.#config;
const { validateConnections: deprecatedValidateConnections, ...poolOptions } = tarn.options;
this.#pool = new tarn.Pool({
...poolOptions,
create: async () => {
const connection = await tedious.connectionFactory();
return await new MssqlConnection(connection, tedious).connect();
},
destroy: async (connection) => {
await connection[PRIVATE_DESTROY_METHOD]();
},
// @ts-ignore `tarn` accepts a function that returns a promise here, but
// the types are not aligned and it type errors.
validate: validateConnections === false ||
deprecatedValidateConnections === false
? undefined
: (connection) => connection[PRIVATE_VALIDATE_METHOD](),
});
}
async init() {
// noop
}
async acquireConnection() {
return await this.#pool.acquire().promise;
}
async beginTransaction(connection, settings) {
await connection.beginTransaction(settings);
}
async commitTransaction(connection) {
await connection.commitTransaction();
}
async rollbackTransaction(connection) {
await connection.rollbackTransaction();
}
async savepoint(connection, savepointName) {
await connection.savepoint(savepointName);
}
async rollbackToSavepoint(connection, savepointName) {
await connection.rollbackTransaction(savepointName);
}
async releaseConnection(connection) {
if (this.#config.resetConnectionsOnRelease ||
this.#config.tedious.resetConnectionOnRelease) {
await connection[PRIVATE_RESET_METHOD]();
}
this.#pool.release(connection);
}
async destroy() {
await this.#pool.destroy();
}
}
exports.MssqlDriver = MssqlDriver;
class MssqlConnection {
#connection;
#hasSocketError;
#tedious;
constructor(connection, tedious) {
this.#connection = connection;
this.#hasSocketError = false;
this.#tedious = tedious;
}
async beginTransaction(settings) {
const { isolationLevel } = settings;
await new Promise((resolve, reject) => this.#connection.beginTransaction((error) => {
if (error)
reject(error);
else
resolve(undefined);
}, isolationLevel ? (0, random_string_js_1.randomString)(8) : undefined, isolationLevel
? this.#getTediousIsolationLevel(isolationLevel)
: undefined));
}
async commitTransaction() {
await new Promise((resolve, reject) => this.#connection.commitTransaction((error) => {
if (error)
reject(error);
else
resolve(undefined);
}));
}
async connect() {
const { promise: waitForConnected, reject, resolve } = new deferred_js_1.Deferred();
this.#connection.connect((error) => {
if (error) {
return reject(error);
}
resolve();
});
this.#connection.on('error', (error) => {
if (error instanceof Error &&
'code' in error &&
error.code === 'ESOCKET') {
this.#hasSocketError = true;
}
console.error(error);
reject(error);
});
function endListener() {
reject(new Error('The connection ended without ever completing the connection'));
}
this.#connection.once('end', endListener);
await waitForConnected;
this.#connection.off('end', endListener);
return this;
}
async executeQuery(compiledQuery) {
try {
const deferred = new deferred_js_1.Deferred();
const request = new MssqlRequest({
compiledQuery,
tedious: this.#tedious,
onDone: deferred,
});
this.#connection.execSql(request.request);
const { rowCount, rows } = await deferred.promise;
return {
numAffectedRows: rowCount !== undefined ? BigInt(rowCount) : undefined,
rows,
};
}
catch (err) {
throw (0, stack_trace_utils_js_1.extendStackTrace)(err, new Error());
}
}
async rollbackTransaction(savepointName) {
await new Promise((resolve, reject) => this.#connection.rollbackTransaction((error) => {
if (error)
reject(error);
else
resolve(undefined);
}, savepointName));
}
async savepoint(savepointName) {
await new Promise((resolve, reject) => this.#connection.saveTransaction((error) => {
if (error)
reject(error);
else
resolve(undefined);
}, savepointName));
}
async *streamQuery(compiledQuery, chunkSize) {
if (!Number.isInteger(chunkSize) || chunkSize <= 0) {
throw new Error('chunkSize must be a positive integer');
}
const request = new MssqlRequest({
compiledQuery,
streamChunkSize: chunkSize,
tedious: this.#tedious,
});
this.#connection.execSql(request.request);
try {
while (true) {
const rows = await request.readChunk();
if (rows.length === 0) {
break;
}
yield { rows };
if (rows.length < chunkSize) {
break;
}
}
}
finally {
await this.#cancelRequest(request);
}
}
#getTediousIsolationLevel(isolationLevel) {
const { ISOLATION_LEVEL } = this.#tedious;
const mapper = {
'read committed': ISOLATION_LEVEL.READ_COMMITTED,
'read uncommitted': ISOLATION_LEVEL.READ_UNCOMMITTED,
'repeatable read': ISOLATION_LEVEL.REPEATABLE_READ,
serializable: ISOLATION_LEVEL.SERIALIZABLE,
snapshot: ISOLATION_LEVEL.SNAPSHOT,
};
const tediousIsolationLevel = mapper[isolationLevel];
if (tediousIsolationLevel === undefined) {
throw new Error(`Unknown isolation level: ${isolationLevel}`);
}
return tediousIsolationLevel;
}
#cancelRequest(request) {
return new Promise((resolve) => {
request.request.once('requestCompleted', resolve);
const wasCanceled = this.#connection.cancel();
if (!wasCanceled) {
request.request.off('requestCompleted', resolve);
resolve();
}
});
}
[PRIVATE_DESTROY_METHOD]() {
if ('closed' in this.#connection && this.#connection.closed) {
return Promise.resolve();
}
return new Promise((resolve) => {
this.#connection.once('end', resolve);
this.#connection.close();
});
}
async [PRIVATE_RESET_METHOD]() {
await new Promise((resolve, reject) => {
this.#connection.reset((error) => {
if (error) {
return reject(error);
}
resolve();
});
});
}
async [PRIVATE_VALIDATE_METHOD]() {
if (this.#hasSocketError || this.#isConnectionClosed()) {
return false;
}
try {
const deferred = new deferred_js_1.Deferred();
const request = new MssqlRequest({
compiledQuery: compiled_query_js_1.CompiledQuery.raw('select 1'),
onDone: deferred,
tedious: this.#tedious,
});
this.#connection.execSql(request.request);
await deferred.promise;
return true;
}
catch {
return false;
}
}
#isConnectionClosed() {
return 'closed' in this.#connection && Boolean(this.#connection.closed);
}
}
class MssqlRequest {
#request;
#rows;
#streamChunkSize;
#subscribers;
#tedious;
#rowCount;
constructor(props) {
const { compiledQuery, onDone, streamChunkSize, tedious } = props;
this.#rows = [];
this.#streamChunkSize = streamChunkSize;
this.#subscribers = {};
this.#tedious = tedious;
if (onDone) {
const subscriptionKey = 'onDone';
this.#subscribers[subscriptionKey] = (event, error) => {
if (event === 'chunkReady') {
return;
}
delete this.#subscribers[subscriptionKey];
if (event === 'error') {
return onDone.reject(error);
}
onDone.resolve({
rowCount: this.#rowCount,
rows: this.#rows,
});
};
}
this.#request = new this.#tedious.Request(compiledQuery.sql, (err, rowCount) => {
if (err) {
return Object.values(this.#subscribers).forEach((subscriber) => subscriber('error', err instanceof AggregateError ? err.errors : err));
}
this.#rowCount = rowCount;
});
this.#addParametersToRequest(compiledQuery.parameters);
this.#attachListeners();
}
get request() {
return this.#request;
}
readChunk() {
const subscriptionKey = this.readChunk.name;
return new Promise((resolve, reject) => {
this.#subscribers[subscriptionKey] = (event, error) => {
delete this.#subscribers[subscriptionKey];
if (event === 'error') {
return reject(error);
}
resolve(this.#rows.splice(0, this.#streamChunkSize));
};
this.#request.resume();
});
}
#addParametersToRequest(parameters) {
for (let i = 0; i < parameters.length; i++) {
const parameter = parameters[i];
this.#request.addParameter(String(i + 1), this.#getTediousDataType(parameter), parameter);
}
}
#attachListeners() {
const pauseAndEmitChunkReady = this.#streamChunkSize
? () => {
if (this.#streamChunkSize <= this.#rows.length) {
this.#request.pause();
Object.values(this.#subscribers).forEach((subscriber) => subscriber('chunkReady'));
}
}
: () => { };
const rowListener = (columns) => {
const row = {};
for (const column of columns) {
row[column.metadata.colName] = column.value;
}
this.#rows.push(row);
pauseAndEmitChunkReady();
};
this.#request.on('row', rowListener);
this.#request.once('requestCompleted', () => {
Object.values(this.#subscribers).forEach((subscriber) => subscriber('completed'));
this.#request.off('row', rowListener);
});
}
#getTediousDataType(value) {
if ((0, object_utils_js_1.isNull)(value) || (0, object_utils_js_1.isUndefined)(value) || (0, object_utils_js_1.isString)(value)) {
return this.#tedious.TYPES.NVarChar;
}
if ((0, object_utils_js_1.isBigInt)(value) || ((0, object_utils_js_1.isNumber)(value) && value % 1 === 0)) {
if (value < -2147483648 || value > 2147483647) {
return this.#tedious.TYPES.BigInt;
}
else {
return this.#tedious.TYPES.Int;
}
}
if ((0, object_utils_js_1.isNumber)(value)) {
return this.#tedious.TYPES.Float;
}
if ((0, object_utils_js_1.isBoolean)(value)) {
return this.#tedious.TYPES.Bit;
}
if ((0, object_utils_js_1.isDate)(value)) {
return this.#tedious.TYPES.DateTime;
}
if ((0, object_utils_js_1.isBuffer)(value)) {
return this.#tedious.TYPES.VarBinary;
}
return this.#tedious.TYPES.NVarChar;
}
}

View File

@@ -0,0 +1,20 @@
import type { Kysely } from '../../kysely.js';
import type { DatabaseIntrospector, DatabaseMetadata, DatabaseMetadataOptions, SchemaMetadata, TableMetadata } from '../database-introspector.js';
export declare class MssqlIntrospector implements DatabaseIntrospector {
#private;
constructor(db: Kysely<any>);
/**
* Get schema metadata.
*/
getSchemas(): Promise<SchemaMetadata[]>;
/**
* Get tables and views metadata.
*/
getTables(options?: DatabaseMetadataOptions): Promise<TableMetadata[]>;
/**
* Get the database metadata such as table and column names.
*
* @deprecated Use getTables() instead.
*/
getMetadata(options?: DatabaseMetadataOptions): Promise<DatabaseMetadata>;
}

View File

@@ -0,0 +1,110 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MssqlIntrospector = void 0;
const migrator_js_1 = require("../../migration/migrator.js");
const object_utils_js_1 = require("../../util/object-utils.js");
class MssqlIntrospector {
#db;
constructor(db) {
this.#db = db;
}
async getSchemas() {
return await this.#db.selectFrom('sys.schemas').select('name').execute();
}
async getTables(options = { withInternalKyselyTables: false }) {
const rawColumns = await this.#db
.selectFrom('sys.tables as tables')
.leftJoin('sys.schemas as table_schemas', 'table_schemas.schema_id', 'tables.schema_id')
.innerJoin('sys.columns as columns', 'columns.object_id', 'tables.object_id')
.innerJoin('sys.types as types', 'types.user_type_id', 'columns.user_type_id')
.leftJoin('sys.schemas as type_schemas', 'type_schemas.schema_id', 'types.schema_id')
.leftJoin('sys.extended_properties as comments', (join) => join
.onRef('comments.major_id', '=', 'tables.object_id')
.onRef('comments.minor_id', '=', 'columns.column_id')
.on('comments.name', '=', 'MS_Description'))
.$if(!options.withInternalKyselyTables, (qb) => qb
.where('tables.name', '!=', migrator_js_1.DEFAULT_MIGRATION_TABLE)
.where('tables.name', '!=', migrator_js_1.DEFAULT_MIGRATION_LOCK_TABLE))
.select([
'tables.name as table_name',
(eb) => eb
.ref('tables.type')
.$castTo()
.as('table_type'),
'table_schemas.name as table_schema_name',
'columns.default_object_id as column_default_object_id',
'columns.generated_always_type_desc as column_generated_always_type',
'columns.is_computed as column_is_computed',
'columns.is_identity as column_is_identity',
'columns.is_nullable as column_is_nullable',
'columns.is_rowguidcol as column_is_rowguidcol',
'columns.name as column_name',
'types.is_nullable as type_is_nullable',
'types.name as type_name',
'type_schemas.name as type_schema_name',
'comments.value as column_comment',
])
.unionAll(this.#db
.selectFrom('sys.views as views')
.leftJoin('sys.schemas as view_schemas', 'view_schemas.schema_id', 'views.schema_id')
.innerJoin('sys.columns as columns', 'columns.object_id', 'views.object_id')
.innerJoin('sys.types as types', 'types.user_type_id', 'columns.user_type_id')
.leftJoin('sys.schemas as type_schemas', 'type_schemas.schema_id', 'types.schema_id')
.leftJoin('sys.extended_properties as comments', (join) => join
.onRef('comments.major_id', '=', 'views.object_id')
.onRef('comments.minor_id', '=', 'columns.column_id')
.on('comments.name', '=', 'MS_Description'))
.select([
'views.name as table_name',
'views.type as table_type',
'view_schemas.name as table_schema_name',
'columns.default_object_id as column_default_object_id',
'columns.generated_always_type_desc as column_generated_always_type',
'columns.is_computed as column_is_computed',
'columns.is_identity as column_is_identity',
'columns.is_nullable as column_is_nullable',
'columns.is_rowguidcol as column_is_rowguidcol',
'columns.name as column_name',
'types.is_nullable as type_is_nullable',
'types.name as type_name',
'type_schemas.name as type_schema_name',
'comments.value as column_comment',
]))
.orderBy('table_schema_name')
.orderBy('table_name')
.orderBy('column_name')
.execute();
const tableDictionary = {};
for (const rawColumn of rawColumns) {
const key = `${rawColumn.table_schema_name}.${rawColumn.table_name}`;
const table = (tableDictionary[key] =
tableDictionary[key] ||
(0, object_utils_js_1.freeze)({
columns: [],
isView: rawColumn.table_type === 'V ',
name: rawColumn.table_name,
schema: rawColumn.table_schema_name ?? undefined,
}));
table.columns.push((0, object_utils_js_1.freeze)({
dataType: rawColumn.type_name,
dataTypeSchema: rawColumn.type_schema_name ?? undefined,
hasDefaultValue: rawColumn.column_default_object_id > 0 ||
rawColumn.column_generated_always_type !== 'NOT_APPLICABLE' ||
rawColumn.column_is_identity ||
rawColumn.column_is_computed ||
rawColumn.column_is_rowguidcol,
isAutoIncrementing: rawColumn.column_is_identity,
isNullable: rawColumn.column_is_nullable && rawColumn.type_is_nullable,
name: rawColumn.column_name,
comment: rawColumn.column_comment ?? undefined,
}));
}
return Object.values(tableDictionary);
}
async getMetadata(options) {
return {
tables: await this.getTables(options),
};
}
}
exports.MssqlIntrospector = MssqlIntrospector;

View File

@@ -0,0 +1,17 @@
import type { AddColumnNode } from '../../operation-node/add-column-node.js';
import type { AlterTableColumnAlterationNode } from '../../operation-node/alter-table-node.js';
import type { DropColumnNode } from '../../operation-node/drop-column-node.js';
import type { OffsetNode } from '../../operation-node/offset-node.js';
import type { MergeQueryNode } from '../../operation-node/merge-query-node.js';
import { DefaultQueryCompiler } from '../../query-compiler/default-query-compiler.js';
import type { CollateNode } from '../../operation-node/collate-node.js';
export declare class MssqlQueryCompiler extends DefaultQueryCompiler {
protected getCurrentParameterPlaceholder(): string;
protected visitOffset(node: OffsetNode): void;
protected compileColumnAlterations(columnAlterations: readonly AlterTableColumnAlterationNode[]): void;
protected visitAddColumn(node: AddColumnNode): void;
protected visitDropColumn(node: DropColumnNode): void;
protected visitMergeQuery(node: MergeQueryNode): void;
protected visitCollate(node: CollateNode): void;
protected announcesNewColumnDataType(): boolean;
}

View File

@@ -0,0 +1,83 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MssqlQueryCompiler = void 0;
const default_query_compiler_js_1 = require("../../query-compiler/default-query-compiler.js");
const COLLATION_CHAR_REGEX = /^[a-z0-9_]$/i;
class MssqlQueryCompiler extends default_query_compiler_js_1.DefaultQueryCompiler {
getCurrentParameterPlaceholder() {
return `@${this.numParameters}`;
}
visitOffset(node) {
super.visitOffset(node);
this.append(' rows');
}
// mssql allows multi-column alterations in a single statement,
// but you can only use the command keyword/s once.
// it also doesn't support multiple kinds of commands in the same
// alter table statement, but we compile that anyway for the sake
// of WYSIWYG.
compileColumnAlterations(columnAlterations) {
const nodesByKind = {};
for (const columnAlteration of columnAlterations) {
if (!nodesByKind[columnAlteration.kind]) {
nodesByKind[columnAlteration.kind] = [];
}
nodesByKind[columnAlteration.kind].push(columnAlteration);
}
let first = true;
if (nodesByKind.AddColumnNode) {
this.append('add ');
this.compileList(nodesByKind.AddColumnNode);
first = false;
}
// multiple of these are not really supported by mssql,
// but for the sake of WYSIWYG.
if (nodesByKind.AlterColumnNode) {
if (!first)
this.append(', ');
this.compileList(nodesByKind.AlterColumnNode);
}
if (nodesByKind.DropColumnNode) {
if (!first)
this.append(', ');
this.append('drop column ');
this.compileList(nodesByKind.DropColumnNode);
}
// not really supported by mssql, but for the sake of WYSIWYG.
if (nodesByKind.ModifyColumnNode) {
if (!first)
this.append(', ');
this.compileList(nodesByKind.ModifyColumnNode);
}
// not really supported by mssql, but for the sake of WYSIWYG.
if (nodesByKind.RenameColumnNode) {
if (!first)
this.append(', ');
this.compileList(nodesByKind.RenameColumnNode);
}
}
visitAddColumn(node) {
this.visitNode(node.column);
}
visitDropColumn(node) {
this.visitNode(node.column);
}
visitMergeQuery(node) {
super.visitMergeQuery(node);
this.append(';');
}
visitCollate(node) {
this.append('collate ');
const { name } = node.collation;
for (const char of name) {
if (!COLLATION_CHAR_REGEX.test(char)) {
throw new Error(`Invalid collation: ${name}`);
}
}
this.append(name);
}
announcesNewColumnDataType() {
return false;
}
}
exports.MssqlQueryCompiler = MssqlQueryCompiler;