Skip to content

Commit

Permalink
feat(mysql)!: move mysql to the @sequelize/mysql package (#17202)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Instead of installing `mysql2`, users need to install `@sequelize/mysql`.
  • Loading branch information
lohart13 committed Mar 25, 2024
1 parent 46ea159 commit 5c7830e
Show file tree
Hide file tree
Showing 26 changed files with 226 additions and 144 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -84,7 +84,7 @@ jobs:
run: yarn lerna run test-unit --scope=@sequelize/utils
- name: Unit tests (core - mariadb)
run: yarn lerna run test-unit-mariadb --scope=@sequelize/core
- name: Unit tests (mysql)
- name: Unit tests (core - mysql)
run: yarn lerna run test-unit-mysql --scope=@sequelize/core
- name: Unit tests (core - postgres)
run: yarn lerna run test-unit-postgres --scope=@sequelize/core
Expand Down
4 changes: 0 additions & 4 deletions packages/core/package.json
Expand Up @@ -95,7 +95,6 @@
"lcov-result-merger": "5.0.0",
"mocha": "10.3.0",
"moment": "2.30.1",
"mysql2": "3.9.2",
"nyc": "15.1.0",
"odbc": "2.4.8",
"p-map": "4.0.0",
Expand All @@ -113,9 +112,6 @@
"ibm_db": {
"optional": true
},
"mysql2": {
"optional": true
},
"odbc": {
"optional": true
},
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/dialects/mysql/query.d.ts

This file was deleted.

3 changes: 2 additions & 1 deletion packages/core/src/sequelize.internals.ts
Expand Up @@ -11,7 +11,8 @@ export function importDialect(dialect: DialectName): typeof AbstractDialect {
case 'mssql':
return require('./dialects/mssql').MsSqlDialect;
case 'mysql':
return require('./dialects/mysql').MySqlDialect;
// eslint-disable-next-line import/no-extraneous-dependencies -- legacy function, will be removed. User needs to install the dependency themselves
return require('@sequelize/mysql').MySqlDialect;
case 'postgres':
// eslint-disable-next-line import/no-extraneous-dependencies -- legacy function, will be removed. User needs to install the dependency themselves
return require('@sequelize/postgres').PostgresDialect;
Expand Down
38 changes: 18 additions & 20 deletions packages/core/test/unit/dialects/mysql/query-generator.test.js
Expand Up @@ -9,9 +9,7 @@ const Support = require('../../../support');

const dialect = Support.getTestDialect();
const { Op } = require('@sequelize/core');
const {
MySqlQueryGenerator: QueryGenerator,
} = require('@sequelize/core/_non-semver-use-at-your-own-risk_/dialects/mysql/query-generator.js');
const { MySqlQueryGenerator } = require('@sequelize/mysql');
const { createSequelizeInstance } = require('../../../support');

if (dialect === 'mysql') {
Expand Down Expand Up @@ -125,42 +123,42 @@ if (dialect === 'mysql') {
{
arguments: ['myTable'],
expectation: 'SELECT * FROM `myTable`;',
context: QueryGenerator,
context: MySqlQueryGenerator,
},
{
arguments: ['myTable', { attributes: ['id', 'name'] }],
expectation: 'SELECT `id`, `name` FROM `myTable`;',
context: QueryGenerator,
context: MySqlQueryGenerator,
},
{
arguments: ['myTable', { where: { id: 2 } }],
expectation: 'SELECT * FROM `myTable` WHERE `myTable`.`id` = 2;',
context: QueryGenerator,
context: MySqlQueryGenerator,
},
{
arguments: ['myTable', { where: { name: 'foo' } }],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name` = 'foo';",
context: QueryGenerator,
context: MySqlQueryGenerator,
},
{
arguments: ['myTable', { order: ['id'] }],
expectation: 'SELECT * FROM `myTable` ORDER BY `id`;',
context: QueryGenerator,
context: MySqlQueryGenerator,
},
{
arguments: ['myTable', { order: ['id', 'DESC'] }],
expectation: 'SELECT * FROM `myTable` ORDER BY `id`, `DESC`;',
context: QueryGenerator,
context: MySqlQueryGenerator,
},
{
arguments: ['myTable', { order: ['myTable.id'] }],
expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id`;',
context: QueryGenerator,
context: MySqlQueryGenerator,
},
{
arguments: ['myTable', { order: [['myTable.id', 'DESC']] }],
expectation: 'SELECT * FROM `myTable` ORDER BY `myTable`.`id` DESC;',
context: QueryGenerator,
context: MySqlQueryGenerator,
},
{
arguments: [
Expand All @@ -171,7 +169,7 @@ if (dialect === 'mysql') {
},
],
expectation: 'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC;',
context: QueryGenerator,
context: MySqlQueryGenerator,
needsSequelize: true,
},
{
Expand All @@ -184,19 +182,19 @@ if (dialect === 'mysql') {
],
expectation:
'SELECT * FROM `myTable` AS `myTable` ORDER BY `myTable`.`id` DESC, `myTable`.`name`;',
context: QueryGenerator,
context: MySqlQueryGenerator,
needsSequelize: true,
},
{
title: 'single string argument should be quoted',
arguments: ['myTable', { group: 'name' }],
expectation: 'SELECT * FROM `myTable` GROUP BY `name`;',
context: QueryGenerator,
context: MySqlQueryGenerator,
},
{
arguments: ['myTable', { group: ['name'] }],
expectation: 'SELECT * FROM `myTable` GROUP BY `name`;',
context: QueryGenerator,
context: MySqlQueryGenerator,
},
{
title: 'functions work for group by',
Expand All @@ -209,7 +207,7 @@ if (dialect === 'mysql') {
},
],
expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`);',
context: QueryGenerator,
context: MySqlQueryGenerator,
needsSequelize: true,
},
{
Expand All @@ -223,13 +221,13 @@ if (dialect === 'mysql') {
},
],
expectation: 'SELECT * FROM `myTable` GROUP BY YEAR(`createdAt`), `title`;',
context: QueryGenerator,
context: MySqlQueryGenerator,
needsSequelize: true,
},
{
arguments: ['myTable', { group: 'name', order: [['id', 'DESC']] }],
expectation: 'SELECT * FROM `myTable` GROUP BY `name` ORDER BY `id` DESC;',
context: QueryGenerator,
context: MySqlQueryGenerator,
},
{
title: 'Empty having',
Expand All @@ -242,7 +240,7 @@ if (dialect === 'mysql') {
},
],
expectation: 'SELECT * FROM `myTable`;',
context: QueryGenerator,
context: MySqlQueryGenerator,
needsSequelize: true,
},
{
Expand All @@ -259,7 +257,7 @@ if (dialect === 'mysql') {
],
expectation:
'SELECT `test`.* FROM (SELECT * FROM `myTable` AS `test` HAVING `test`.`creationYear` > 2002) AS `test`;',
context: QueryGenerator,
context: MySqlQueryGenerator,
needsSequelize: true,
},
],
Expand Down
6 changes: 2 additions & 4 deletions packages/core/test/unit/dialects/mysql/query.test.js
@@ -1,8 +1,6 @@
'use strict';

const {
MySqlQuery: Query,
} = require('@sequelize/core/_non-semver-use-at-your-own-risk_/dialects/mysql/query.js');
const { MySqlQuery } = require('@sequelize/mysql');

const Support = require('../../../support');
const chai = require('chai');
Expand All @@ -26,7 +24,7 @@ describe('[MYSQL/MARIADB Specific] Query', () => {
const invalidWarning = {};
const warnings = [validWarning, undefined, invalidWarning];

const query = new Query({}, current, {});
const query = new MySqlQuery({}, current, {});
const stub = sinon.stub(query, 'run');
stub.onFirstCall().resolves(warnings);

Expand Down
11 changes: 2 additions & 9 deletions packages/core/test/unit/query-interface/create-table.test.ts
Expand Up @@ -70,15 +70,8 @@ describe('QueryInterface#createTable', () => {
}

it('supports sql.uuidV1 default values', async () => {
const localSequelize =
dialect.name === 'mysql'
? createSequelizeInstance({
databaseVersion: '8.0.13',
})
: sequelize;
const stub = sinon.stub(localSequelize, 'queryRaw');

await localSequelize.queryInterface.createTable('table', {
const stub = sinon.stub(sequelize, 'queryRaw');
await sequelize.queryInterface.createTable('table', {
id: {
type: DataTypes.UUID,
primaryKey: true,
Expand Down
5 changes: 5 additions & 0 deletions packages/mysql/.eslintrc.js
@@ -0,0 +1,5 @@
module.exports = {
parserOptions: {
project: [`${__dirname}/tsconfig.json`],
},
};
49 changes: 49 additions & 0 deletions packages/mysql/package.json
@@ -0,0 +1,49 @@
{
"bugs": "https://github.com/sequelize/sequelize/issues",
"description": "MySQL Connector for Sequelize",
"exports": {
".": {
"import": {
"types": "./lib/index.d.mts",
"default": "./lib/index.mjs"
},
"require": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
}
}
},
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"sideEffects": false,
"homepage": "https://sequelize.org",
"license": "MIT",
"name": "@sequelize/mysql",
"repository": "https://github.com/sequelize/sequelize",
"scripts": {
"build": "../../build-packages.mjs mysql",
"test": "concurrently \"npm:test-*\"",
"test-typings": "tsc --noEmit --project tsconfig.json",
"test-exports": "../../dev/sync-exports.mjs ./src --check-outdated",
"sync-exports": "../../dev/sync-exports.mjs ./src"
},
"type": "commonjs",
"version": "0.0.0-development",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@sequelize/core": "workspace:*",
"@sequelize/utils": "workspace:*",
"dayjs": "^1.11.10",
"lodash": "4.17.21",
"mysql2": "3.9.2",
"wkx": "0.5.0"
},
"devDependencies": {
"@types/chai": "4.3.14",
"@types/mocha": "10.0.6",
"chai": "4.4.1",
"mocha": "10.3.0"
}
}
@@ -1,8 +1,8 @@
import { isValidTimeZone } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dayjs.js';
import dayjs from 'dayjs';
import type { TypeCastField } from 'mysql2';
import wkx from 'wkx';
import { isValidTimeZone } from '../../utils/dayjs.js';
import type { MySqlDialect } from './index.js';
import type { MySqlDialect } from '../dialect.js';

/**
* First pass of DB value parsing: Parses based on the MySQL Type ID.
Expand Down
@@ -1,10 +1,15 @@
import type { BindParamOptions, GeoJson } from '@sequelize/core';
import type { AcceptedDate } from '@sequelize/core/_non-semver-use-at-your-own-risk_/dialects/abstract/data-types.js';
import * as BaseTypes from '@sequelize/core/_non-semver-use-at-your-own-risk_/dialects/abstract/data-types.js';
import { isValidTimeZone } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dayjs.js';
import { isString } from '@sequelize/utils';
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import wkx from 'wkx';
import type { GeoJson } from '../../geo-json.js';
import { isValidTimeZone } from '../../utils/dayjs';
import type { AcceptedDate, BindParamOptions } from '../abstract/data-types.js';
import * as BaseTypes from '../abstract/data-types.js';

dayjs.extend(utc);
dayjs.extend(timezone);

export class FLOAT extends BaseTypes.FLOAT {
protected getNumberSqlTypeName(): string {
Expand Down
@@ -1,37 +1,25 @@
import { isError } from '@sequelize/utils';
import { isNodeError } from '@sequelize/utils/node';
import type {
Connection,
ConnectionOptions as MySqlConnectionOptions,
TypeCastField,
createConnection as mysqlCreateConnection,
} from 'mysql2';
import assert from 'node:assert';
import { promisify } from 'node:util';
import type { Connection as AbstractConnection, ConnectionOptions } from '@sequelize/core';
import {
AbstractConnectionManager,
AccessDeniedError,
ConnectionError,
ConnectionRefusedError,
HostNotFoundError,
HostNotReachableError,
InvalidConnectionError,
} from '../../errors';
import type { ConnectionOptions } from '../../sequelize.js';
import { timeZoneToOffsetString } from '../../utils/dayjs';
import { logger } from '../../utils/logger';
import type { Connection as AbstractConnection } from '../abstract/connection-manager';
import { AbstractConnectionManager } from '../abstract/connection-manager';
import type { MySqlDialect } from './index.js';
} from '@sequelize/core';
import { timeZoneToOffsetString } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/dayjs.js';
import { logger } from '@sequelize/core/_non-semver-use-at-your-own-risk_/utils/logger.js';
import { isError } from '@sequelize/utils';
import { isNodeError } from '@sequelize/utils/node';
import * as MySql from 'mysql2';
import assert from 'node:assert';
import { promisify } from 'node:util';
import type { MySqlDialect } from './dialect.js';

const debug = logger.debugContext('connection:mysql');

// TODO: once the code has been split into packages, we won't need to lazy load mysql2 anymore
type Lib = {
createConnection: typeof mysqlCreateConnection;
Connection: Connection;
};

export interface MySqlConnection extends Connection, AbstractConnection {}
export interface MySqlConnection extends MySql.Connection, AbstractConnection {}

/**
* MySQL Connection Manager
Expand All @@ -46,14 +34,14 @@ export class MySqlConnectionManager extends AbstractConnectionManager<
MySqlDialect,
MySqlConnection
> {
private readonly lib: Lib;
readonly #lib: typeof MySql;

constructor(dialect: MySqlDialect) {
super(dialect);
this.lib = this._loadDialectModule('mysql2') as Lib;
this.#lib = MySql;
}

#typecast(field: TypeCastField, next: () => void): unknown {
#typecast(field: MySql.TypeCastField, next: () => void): unknown {
const dataParser = this.dialect.getParserForDatabaseDataType(field.type);
if (dataParser) {
const value = dataParser(field);
Expand All @@ -78,7 +66,7 @@ export class MySqlConnectionManager extends AbstractConnectionManager<
async connect(config: ConnectionOptions): Promise<MySqlConnection> {
assert(typeof config.port === 'number', 'port has not been normalized');

const connectionConfig: MySqlConnectionOptions = {
const connectionConfig: MySql.ConnectionOptions = {
bigNumberStrings: false,
supportBigNumbers: true,
flags: ['-FOUND_ROWS'],
Expand All @@ -93,7 +81,7 @@ export class MySqlConnectionManager extends AbstractConnectionManager<
};

try {
const connection: MySqlConnection = await createConnection(this.lib, connectionConfig);
const connection: MySqlConnection = await createConnection(this.#lib, connectionConfig);

debug('connection acquired');

Expand Down Expand Up @@ -173,8 +161,8 @@ export class MySqlConnectionManager extends AbstractConnectionManager<
}

async function createConnection(
lib: Lib,
config: MySqlConnectionOptions,
lib: typeof MySql,
config: MySql.ConnectionOptions,
): Promise<MySqlConnection> {
return new Promise((resolve, reject) => {
const connection: MySqlConnection = lib.createConnection(config) as MySqlConnection;
Expand Down

0 comments on commit 5c7830e

Please sign in to comment.