Skip to content

Commit

Permalink
feat: re-add the ability to override the connector library (#17219)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The `dialectModulePath` has been fully removed to improve compatibility with bundlers.
BREAKING CHANGE: The `dialectModule` option has been split into multiple options. Each option is named after the npm library that is being replaced. For instance, `@sequelize/postgres` now accepts `pgModule`. `@sequelize/mssql` now accepts `tediousModule`
  • Loading branch information
ephys committed Mar 29, 2024
1 parent 0808ac1 commit b3c3362
Show file tree
Hide file tree
Showing 48 changed files with 499 additions and 254 deletions.
1 change: 0 additions & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
yarn lint-staged --concurrent false
yarn delete-changelog
git add .
23 changes: 23 additions & 0 deletions dev/delete-changelog.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { execFile } from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
Expand All @@ -15,6 +16,16 @@ await Promise.all(
changelogPaths.map(async changelogPath => {
if (await tryAccess(changelogPath)) {
await fs.unlink(changelogPath);
const { stderr, stdout } = await execFileAsync(`git`, ['add', changelogPath]);

if (stdout) {
console.info(`stdout: ${stdout}`);
}

if (stderr) {
console.error(`stderr: ${stderr}`);
}

console.info(`Deleted ${changelogPath}`);
}
}),
Expand All @@ -29,3 +40,15 @@ async function tryAccess(filename) {
return false;
}
}

function execFileAsync(file, args) {
return new Promise((resolve, reject) => {
execFile(file, args, (error, stdout, stderr) => {
if (error) {
reject(error);
}

resolve({ stdout, stderr });
});
});
}
35 changes: 1 addition & 34 deletions packages/core/src/abstract-dialect/connection-manager.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { isNodeError } from '@sequelize/utils/node';
import cloneDeep from 'lodash/cloneDeep';
import semver from 'semver';
import { TimeoutError } from 'sequelize-pool';
import { ConnectionAcquireTimeoutError } from '../errors';
import type { ConnectionOptions, Sequelize } from '../sequelize.js';
import * as deprecations from '../utils/deprecations';
import { logger } from '../utils/logger';
import type { AbstractDialect } from './index.js';
import type { AbstractDialect } from './dialect.js';
import { ReplicationPool } from './replication-pool.js';

const debug = logger.debugContext('connection-manager');
Expand Down Expand Up @@ -114,38 +113,6 @@ export class AbstractConnectionManager<
throw new Error(`disconnect not implemented in ${this.constructor.name}`);
}

/**
* Try to load dialect module from various configured options.
* Priority goes like dialectModulePath > dialectModule > require(default)
*
* @param moduleName Name of dialect module to lookup
*
* @private
*/
_loadDialectModule(moduleName: string): unknown {
try {
if (this.sequelize.config.dialectModulePath) {
return require(this.sequelize.config.dialectModulePath);
}

if (this.sequelize.config.dialectModule) {
return this.sequelize.config.dialectModule;
}

return require(moduleName);
} catch (error) {
if (isNodeError(error) && error.code === 'MODULE_NOT_FOUND') {
if (this.sequelize.config.dialectModulePath) {
throw new Error(`Unable to find dialect at ${this.sequelize.config.dialectModulePath}`);
}

throw new Error(`Please install ${moduleName} package manually`);
}

throw error;
}
}

/**
* Handler which executes on process exit or connection manager shutdown
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/abstract-dialect/data-types-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
DataTypeInstance,
} from './data-types.js';
import { AbstractDataType } from './data-types.js';
import type { AbstractDialect } from './index.js';
import type { AbstractDialect } from './dialect.js';

export function isDataType(value: any): value is DataType {
return isDataTypeClass(value) || value instanceof AbstractDataType;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/abstract-dialect/data-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
isDataTypeClass,
throwUnsupportedDataType,
} from './data-types-utils.js';
import type { AbstractDialect } from './index.js';
import type { AbstractDialect } from './dialect.js';
import type { TableNameWithSchema } from './query-interface.js';

// TODO: try merging "validate" & "sanitize" by making sanitize coerces the type, and if it cannot, throw a ValidationError.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { freezeDeep, isFunction } from '@sequelize/utils';
import { EMPTY_OBJECT, freezeDeep, getImmutablePojo, isFunction, isString } from '@sequelize/utils';
import cloneDeep from 'lodash/cloneDeep';
import merge from 'lodash/merge';
import type { Class } from 'type-fest';
Expand Down Expand Up @@ -274,6 +274,22 @@ declare const OptionType: unique symbol;

export type DialectOptions<Dialect extends AbstractDialect> = Dialect[typeof OptionType];

export type AbstractDialectParams<Options> = {
dataTypeOverrides: Record<string, Class<AbstractDataType<any>>>;
dataTypesDocumentationUrl: string;
/**
* The character used to delimit identifiers in SQL queries.
*
* This can be a string, in which case the character will be used for both the start & end of the identifier,
* or an object with `start` and `end` properties.
*/
identifierDelimiter: string | { start: string; end: string };
minimumDatabaseVersion: string;
name: DialectName;
options: Options | undefined;
sequelize: Sequelize;
};

export abstract class AbstractDialect<Options extends object = {}> {
declare [OptionType]: Options;

Expand Down Expand Up @@ -475,17 +491,37 @@ export abstract class AbstractDialect<Options extends object = {}> {

readonly sequelize: Sequelize;

abstract readonly defaultVersion: string;
abstract readonly Query: typeof AbstractQuery;
abstract readonly TICK_CHAR_LEFT: string;
abstract readonly TICK_CHAR_RIGHT: string;
abstract readonly queryGenerator: AbstractQueryGenerator;
abstract readonly queryInterface: AbstractQueryInterface;
abstract readonly connectionManager: AbstractConnectionManager<any, any>;
abstract readonly dataTypesDocumentationUrl: string;

/**
* @deprecated use {@link minimumDatabaseVersion}
*/
get defaultVersion(): string {
return this.minimumDatabaseVersion;
}

/**
* @deprecated use {@link identifierDelimiter}.start
*/
get TICK_CHAR_LEFT(): string {
return this.identifierDelimiter.start;
}

/**
* @deprecated use {@link identifierDelimiter}.end
*/
get TICK_CHAR_RIGHT(): string {
return this.identifierDelimiter.end;
}

readonly identifierDelimiter: { readonly start: string; readonly end: string };
readonly minimumDatabaseVersion: string;
readonly dataTypesDocumentationUrl: string;
readonly options: Options;
readonly name: DialectName;
readonly DataTypes: Record<string, Class<AbstractDataType<any>>>;

/** dialect-specific implementation of shared data types */
readonly #dataTypeOverrides: Map<string, Class<AbstractDataType<any>>>;
Expand All @@ -499,14 +535,20 @@ export abstract class AbstractDialect<Options extends object = {}> {
return Dialect.supports;
}

constructor(
sequelize: Sequelize,
dialectDataTypes: Record<string, Class<AbstractDataType<any>>>,
dialectName: DialectName,
) {
this.sequelize = sequelize;
this.DataTypes = dialectDataTypes;
this.name = dialectName;
constructor(params: AbstractDialectParams<Options>) {
this.sequelize = params.sequelize;
this.name = params.name;
this.dataTypesDocumentationUrl = params.dataTypesDocumentationUrl;
this.options = params.options ? getImmutablePojo(params.options) : EMPTY_OBJECT;

this.identifierDelimiter = isString(params.identifierDelimiter)
? Object.freeze({
start: params.identifierDelimiter,
end: params.identifierDelimiter,
})
: getImmutablePojo(params.identifierDelimiter);

this.minimumDatabaseVersion = params.minimumDatabaseVersion;

const baseDataTypes = new Map<string, Class<AbstractDataType<any>>>();
for (const dataType of Object.values(BaseDataTypes) as Array<Class<AbstractDataType<any>>>) {
Expand All @@ -532,7 +574,7 @@ export abstract class AbstractDialect<Options extends object = {}> {
}

const dataTypeOverrides = new Map<string, Class<AbstractDataType<any>>>();
for (const dataType of Object.values(this.DataTypes)) {
for (const dataType of Object.values(params.dataTypeOverrides)) {
const replacedDataTypeId: string = (
dataType as unknown as typeof AbstractDataType
).getDataTypeId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { Sequelize } from '../sequelize.js';
import { extractModelDefinition } from '../utils/model-utils.js';
import { injectReplacements } from '../utils/sql.js';
import { attributeTypeToSql } from './data-types-utils.js';
import type { AbstractDialect } from './index.js';
import type { AbstractDialect } from './dialect.js';
import type { EscapeOptions } from './query-generator-typescript.js';
import type { AddLimitOffsetOptions } from './query-generator.internal-types.js';
import type { GetConstraintSnippetQueryOptions, TableOrModel } from './query-generator.types.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
} from '../utils/model-utils.js';
import type { BindParamOptions, DataType } from './data-types.js';
import { AbstractDataType } from './data-types.js';
import type { AbstractDialect } from './index.js';
import type { AbstractDialect } from './dialect.js';
import { AbstractQueryGeneratorInternal } from './query-generator-internal.js';
import type {
AddConstraintQueryOptions,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'node:assert';
import { QueryTypes } from '../query-types.js';
import type { QueryRawOptions, Sequelize } from '../sequelize.js';
import type { AbstractDialect } from './index.js';
import type { AbstractDialect } from './dialect.js';
import type { AbstractQueryGenerator } from './query-generator.js';
import type { FetchDatabaseVersionOptions } from './query-interface.types.js';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
showAllToListTables,
} from '../utils/deprecations';
import type { Connection } from './connection-manager.js';
import type { AbstractDialect } from './index.js';
import type { AbstractDialect } from './dialect.js';
import type { TableOrModel } from './query-generator.types.js';
import { AbstractQueryInterfaceInternal } from './query-interface-internal.js';
import type { TableNameWithSchema } from './query-interface.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/abstract-dialect/query-interface.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type {
import type { QueryRawOptions, QueryRawOptionsWithModel } from '../sequelize';
import type { AllowLowercase } from '../utils/types.js';
import type { DataType } from './data-types.js';
import type { AbstractDialect } from './index.js';
import type { AbstractDialect } from './dialect.js';
import type { AddLimitOffsetOptions } from './query-generator.internal-types.js';
import type { AddColumnQueryOptions } from './query-generator.js';
import type { RemoveIndexQueryOptions, TableOrModel } from './query-generator.types.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/expression-builders/dialect-aware-fn.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Class } from 'type-fest';
import type { AbstractDialect } from '../abstract-dialect/index.js';
import type { AbstractDialect } from '../abstract-dialect/dialect.js';
import type { EscapeOptions } from '../abstract-dialect/query-generator-typescript.js';
import type { Expression } from '../sequelize.js';
import { BaseSqlExpression } from './base-sql-expression.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/expression-builders/json-sql-null.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AbstractDialect } from '../abstract-dialect/index.js';
import type { AbstractDialect } from '../abstract-dialect/dialect.js';
import { DialectAwareFn } from './dialect-aware-fn.js';
import { literal } from './literal.js';

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/expression-builders/uuid.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import crypto from 'node:crypto';
import { v1 as generateUuidV1 } from 'uuid';
import type { AbstractDialect } from '../abstract-dialect/index.js';
import type { AbstractDialect } from '../abstract-dialect/dialect.js';
import { DialectAwareFn } from './dialect-aware-fn.js';

export class SqlUuidV4 extends DialectAwareFn {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type {
TimeOptions,
VirtualOptions,
} from './abstract-dialect/data-types.js';
export { AbstractDialect } from './abstract-dialect/index.js';
export { AbstractDialect } from './abstract-dialect/dialect.js';
export { AbstractQueryGenerator } from './abstract-dialect/query-generator.js';
export * from './abstract-dialect/query-generator.types.js';
export * from './abstract-dialect/query-interface.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/model-set-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SetView } from '@sequelize/utils';
import { inspect } from 'node:util';
// @ts-expect-error -- toposort-class definition will be added to sequelize/toposort later
import Toposort from 'toposort-class';
import type { AbstractDialect } from './abstract-dialect/index.js';
import type { AbstractDialect } from './abstract-dialect/dialect.js';
import type { Model, ModelStatic } from './model';
import type { SequelizeTypeScript } from './sequelize-typescript.js';

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/sequelize-typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {
import type { Connection, GetConnectionOptions } from './abstract-dialect/connection-manager.js';
import { normalizeDataType, validateDataType } from './abstract-dialect/data-types-utils.js';
import type { AbstractDataType } from './abstract-dialect/data-types.js';
import type { AbstractDialect } from './abstract-dialect/index.js';
import type { AbstractDialect } from './abstract-dialect/dialect.js';
import type { EscapeOptions } from './abstract-dialect/query-generator-typescript.js';
import type { QiDropAllSchemasOptions } from './abstract-dialect/query-interface.types.js';
import type { AbstractQuery } from './abstract-dialect/query.js';
Expand Down
18 changes: 1 addition & 17 deletions packages/core/src/sequelize.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import type {
TransactionNestMode,
TransactionType,
} from '.';
import type { AbstractDialect, DialectOptions } from './abstract-dialect';
import type { DataType } from './abstract-dialect/data-types.js';
import type { AbstractDialect, DialectOptions } from './abstract-dialect/dialect.js';
import type {
ColumnsDescription,
RawConstraintDescription,
Expand Down Expand Up @@ -164,7 +164,6 @@ export interface NormalizedReplicationOptions {
*/
export interface Config {
readonly database: string;
readonly dialectModule?: object;
readonly host?: string;
readonly port: number;
readonly username: string;
Expand All @@ -173,7 +172,6 @@ export interface Config {
readonly protocol: 'tcp';
readonly ssl: boolean;
readonly replication: NormalizedReplicationOptions;
readonly dialectModulePath: null | string;
readonly keepDefaultTimezone?: boolean;
readonly dialectOptions: Readonly<LegacyDialectOptions>;
}
Expand All @@ -190,20 +188,6 @@ interface SequelizeCoreOptions<Dialect extends AbstractDialect> extends Logging
*/
dialect?: DialectName | Class<Dialect>;

/**
* If specified, will use the provided module as the dialect.
*
* @example
* `dialectModule: require('@myorg/tedious'),`
*/
dialectModule?: object;

/**
* If specified, load the dialect library from this path. For example, if you want to use pg.js instead of
* pg when connecting to a pg database, you should specify 'pg.js' here
*/
dialectModulePath?: string;

/**
* An object of additional options, which are passed directly to the connection library
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/sequelize.internals.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AbstractDialect } from './abstract-dialect/index.js';
import type { AbstractDialect } from './abstract-dialect/dialect.js';
import type { DialectName } from './sequelize.js';

export function importDialect(dialect: DialectName): typeof AbstractDialect {
Expand Down
Loading

0 comments on commit b3c3362

Please sign in to comment.