diff --git a/src/dialects/abstract/query-interface-typescript.ts b/src/dialects/abstract/query-interface-typescript.ts new file mode 100644 index 000000000000..1c0ad592a83c --- /dev/null +++ b/src/dialects/abstract/query-interface-typescript.ts @@ -0,0 +1,27 @@ +import type { Sequelize } from '../../sequelize'; +import type { AbstractQueryGenerator } from './query-generator'; +import type { CreateSchemaOptions, QueryInterfaceOptions } from './query-interface.types'; + +export class AbstractQueryInterfaceTypeScript { + readonly sequelize: Sequelize; + readonly queryGenerator: AbstractQueryGenerator; + + constructor(options: QueryInterfaceOptions) { + this.sequelize = options.sequelize; + this.queryGenerator = options.queryGenerator; + } + + /** + * Creates a new database schema. + * + * **Note:** this is a schema in the [postgres sense of the word](http://www.postgresql.org/docs/9.1/static/ddl-schemas.html), + * not a database table. In mysql and sqlite, this command will do nothing. + * + * @param schema + * @param options + */ + async createSchema(schema: string, options?: CreateSchemaOptions): Promise { + const sql = this.queryGenerator.createSchemaQuery(schema, options); + await this.sequelize.queryRaw(sql, options); + } +} diff --git a/src/dialects/abstract/query-interface.d.ts b/src/dialects/abstract/query-interface.d.ts index 8c4505bf7159..bc7cf65955ac 100644 --- a/src/dialects/abstract/query-interface.d.ts +++ b/src/dialects/abstract/query-interface.d.ts @@ -18,6 +18,7 @@ import type { Fn, Literal, Col } from '../../utils/sequelize-method.js'; import type { DataType } from './data-types.js'; import type { TableNameOrModel } from './query-generator-typescript'; import type { AbstractQueryGenerator, AddColumnQueryOptions, RemoveColumnQueryOptions } from './query-generator.js'; +import { AbstractQueryInterfaceTypeScript } from './query-interface-typescript'; interface Replaceable { /** @@ -280,7 +281,7 @@ export interface RemoveColumnOptions extends RemoveColumnQueryOptions, QueryRawO * This interface is available through sequelize.queryInterface. It should not be commonly used, but it's * referenced anyway, so it can be used. */ -export class QueryInterface { +export class AbstractQueryInterface extends AbstractQueryInterfaceTypeScript { /** * Returns the dialect-specific sql generator. * @@ -295,13 +296,6 @@ export class QueryInterface { constructor(sequelize: Sequelize, queryGenerator: AbstractQueryGenerator); - /** - * Queries the schema (table list). - * - * @param schema The schema to query. Applies only to Postgres. - */ - createSchema(schema?: string, options?: QueryRawOptions): Promise; - /** * Drops the specified schema (table). * @@ -319,7 +313,7 @@ export class QueryInterface { * * @param options */ - showAllSchemas(options?: QueryRawOptions): Promise; + showAllSchemas(options?: QueryRawOptions): Promise; /** * Return database version diff --git a/src/dialects/abstract/query-interface.js b/src/dialects/abstract/query-interface.js index 2ea5f254ba7d..d41f7fa003a3 100644 --- a/src/dialects/abstract/query-interface.js +++ b/src/dialects/abstract/query-interface.js @@ -4,6 +4,7 @@ import { cloneDeep } from '../../utils/object'; import { noSchemaParameter, noSchemaDelimiterParameter } from '../../utils/deprecations'; import { assertNoReservedBind, combineBinds } from '../../utils/sql'; import { AbstractDataType } from './data-types'; +import { AbstractQueryInterfaceTypeScript } from './query-interface-typescript'; const _ = require('lodash'); @@ -14,11 +15,9 @@ const { QueryTypes } = require('../../query-types'); /** * The interface that Sequelize uses to talk to all databases */ -// TODO: rename to AbstractQueryInterface -export class QueryInterface { +export class AbstractQueryInterface extends AbstractQueryInterfaceTypeScript { constructor(sequelize, queryGenerator) { - this.sequelize = sequelize; - this.queryGenerator = queryGenerator; + super({ sequelize, queryGenerator }); } /** @@ -61,20 +60,6 @@ export class QueryInterface { return await this.sequelize.queryRaw(sql, { ...options, type: QueryTypes.SELECT }); } - /** - * Create a schema - * - * @param {string} schema Schema name to create - * @param {object} [options] Query options - * - * @returns {Promise} - */ - async createSchema(schema, options) { - const sql = this.queryGenerator.createSchemaQuery(schema); - - return await this.sequelize.queryRaw(sql, options); - } - /** * Drop a schema * diff --git a/src/dialects/abstract/query-interface.types.ts b/src/dialects/abstract/query-interface.types.ts new file mode 100644 index 000000000000..02aa50676666 --- /dev/null +++ b/src/dialects/abstract/query-interface.types.ts @@ -0,0 +1,10 @@ +import type { QueryRawOptions, Sequelize } from '../../sequelize'; +import type { AbstractQueryGenerator, CreateSchemaQueryOptions } from './query-generator'; + +export interface QueryInterfaceOptions { + sequelize: Sequelize; + queryGenerator: AbstractQueryGenerator; +} + +/** Options accepted by {@link AbstractQueryInterfaceTypeScript#createSchema} */ +export interface CreateSchemaOptions extends CreateSchemaQueryOptions, QueryRawOptions {} diff --git a/src/dialects/db2/query-interface.d.ts b/src/dialects/db2/query-interface.d.ts index 913fbb961cbf..78e53fa2e401 100644 --- a/src/dialects/db2/query-interface.d.ts +++ b/src/dialects/db2/query-interface.d.ts @@ -1,8 +1,8 @@ import type { Sequelize } from '../../sequelize.js'; -import { QueryInterface } from '../abstract/query-interface.js'; +import { AbstractQueryInterface } from '../abstract/query-interface.js'; import type { Db2QueryGenerator } from './query-generator.js'; -export class Db2QueryInterface extends QueryInterface { +export class Db2QueryInterface extends AbstractQueryInterface { queryGenerator: Db2QueryGenerator; constructor(sequelize: Sequelize, queryGenerator: Db2QueryGenerator); diff --git a/src/dialects/db2/query-interface.js b/src/dialects/db2/query-interface.js index aa0c6fe0a2e9..a98d499b77f7 100644 --- a/src/dialects/db2/query-interface.js +++ b/src/dialects/db2/query-interface.js @@ -6,13 +6,13 @@ import { assertNoReservedBind } from '../../utils/sql'; const _ = require('lodash'); const { Op } = require('../../operators'); -const { QueryInterface } = require('../abstract/query-interface'); +const { AbstractQueryInterface } = require('../abstract/query-interface'); const { QueryTypes } = require('../../query-types'); /** * The interface that Sequelize uses to talk with Db2 database */ -export class Db2QueryInterface extends QueryInterface { +export class Db2QueryInterface extends AbstractQueryInterface { async getForeignKeyReferencesForTable(tableName, options) { const queryOptions = { ...options, diff --git a/src/dialects/ibmi/query-interface.d.ts b/src/dialects/ibmi/query-interface.d.ts index fd4f1d8b17b7..80dde4397051 100644 --- a/src/dialects/ibmi/query-interface.d.ts +++ b/src/dialects/ibmi/query-interface.d.ts @@ -1,8 +1,8 @@ import type { Sequelize } from '../../sequelize.js'; -import { QueryInterface } from '../abstract/query-interface.js'; +import { AbstractQueryInterface } from '../abstract/query-interface.js'; import type { IBMiQueryGenerator } from './query-generator.js'; -export class IBMiQueryInterface extends QueryInterface { +export class IBMiQueryInterface extends AbstractQueryInterface { queryGenerator: IBMiQueryGenerator; constructor(sequelize: Sequelize, queryGenerator: IBMiQueryGenerator); diff --git a/src/dialects/ibmi/query-interface.js b/src/dialects/ibmi/query-interface.js index 8df86d965d86..79d5a3f9b9a3 100644 --- a/src/dialects/ibmi/query-interface.js +++ b/src/dialects/ibmi/query-interface.js @@ -1,7 +1,7 @@ 'use strict'; const { Transaction } = require('../../transaction'); -const { QueryInterface } = require('../abstract/query-interface'); +const { AbstractQueryInterface } = require('../abstract/query-interface'); /** Returns an object that enables the `ibmi` dialect to call underlying odbc @@ -12,7 +12,7 @@ const { QueryInterface } = require('../abstract/query-interface'); @private */ -export class IBMiQueryInterface extends QueryInterface { +export class IBMiQueryInterface extends AbstractQueryInterface { startTransaction(transaction, options) { if (!(transaction instanceof Transaction)) { diff --git a/src/dialects/mssql/query-interface.d.ts b/src/dialects/mssql/query-interface.d.ts index a77bc9a862cc..a6a06d0ace91 100644 --- a/src/dialects/mssql/query-interface.d.ts +++ b/src/dialects/mssql/query-interface.d.ts @@ -1,8 +1,8 @@ import type { Sequelize } from '../../sequelize.js'; -import { QueryInterface } from '../abstract/query-interface.js'; +import { AbstractQueryInterface } from '../abstract/query-interface.js'; import type { MsSqlQueryGenerator } from './query-generator.js'; -export class MsSqlQueryInterface extends QueryInterface { +export class MsSqlQueryInterface extends AbstractQueryInterface { queryGenerator: MsSqlQueryGenerator; constructor(sequelize: Sequelize, queryGenerator: MsSqlQueryGenerator); diff --git a/src/dialects/mssql/query-interface.js b/src/dialects/mssql/query-interface.js index 5e8bf6238c86..0918a50b65dc 100644 --- a/src/dialects/mssql/query-interface.js +++ b/src/dialects/mssql/query-interface.js @@ -7,12 +7,12 @@ const _ = require('lodash'); const { QueryTypes } = require('../../query-types'); const { Op } = require('../../operators'); -const { QueryInterface } = require('../abstract/query-interface'); +const { AbstractQueryInterface } = require('../abstract/query-interface'); /** * The interface that Sequelize uses to talk with MSSQL database */ -export class MsSqlQueryInterface extends QueryInterface { +export class MsSqlQueryInterface extends AbstractQueryInterface { /** * A wrapper that fixes MSSQL's inability to cleanly remove columns from existing tables if they have a default constraint. * diff --git a/src/dialects/mysql/query-interface.d.ts b/src/dialects/mysql/query-interface.d.ts index 41fd7cdf726c..86cfb4505c09 100644 --- a/src/dialects/mysql/query-interface.d.ts +++ b/src/dialects/mysql/query-interface.d.ts @@ -1,8 +1,8 @@ import type { Sequelize } from '../../sequelize.js'; -import { QueryInterface } from '../abstract/query-interface.js'; +import { AbstractQueryInterface } from '../abstract/query-interface.js'; import type { MySqlQueryGenerator } from './query-generator.js'; -export class MySqlQueryInterface extends QueryInterface { +export class MySqlQueryInterface extends AbstractQueryInterface { queryGenerator: MySqlQueryGenerator; constructor(sequelize: Sequelize, queryGenerator: MySqlQueryGenerator); diff --git a/src/dialects/mysql/query-interface.js b/src/dialects/mysql/query-interface.js index a0093580ebc6..fec7eba149a8 100644 --- a/src/dialects/mysql/query-interface.js +++ b/src/dialects/mysql/query-interface.js @@ -3,13 +3,13 @@ import { assertNoReservedBind, combineBinds } from '../../utils/sql'; const sequelizeErrors = require('../../errors'); -const { QueryInterface } = require('../abstract/query-interface'); +const { AbstractQueryInterface } = require('../abstract/query-interface'); const { QueryTypes } = require('../../query-types'); /** * The interface that Sequelize uses to talk with MySQL/MariaDB database */ -export class MySqlQueryInterface extends QueryInterface { +export class MySqlQueryInterface extends AbstractQueryInterface { /** * A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint. * diff --git a/src/dialects/postgres/query-interface.d.ts b/src/dialects/postgres/query-interface.d.ts index dc96cd74b72b..471c9416f1a3 100644 --- a/src/dialects/postgres/query-interface.d.ts +++ b/src/dialects/postgres/query-interface.d.ts @@ -1,8 +1,8 @@ import type { Sequelize } from '../../sequelize.js'; -import { QueryInterface } from '../abstract/query-interface.js'; +import { AbstractQueryInterface } from '../abstract/query-interface.js'; import type { PostgresQueryGenerator } from './query-generator.js'; -export class PostgresQueryInterface extends QueryInterface { +export class PostgresQueryInterface extends AbstractQueryInterface { queryGenerator: PostgresQueryGenerator; constructor(sequelize: Sequelize, queryGenerator: PostgresQueryGenerator); diff --git a/src/dialects/postgres/query-interface.js b/src/dialects/postgres/query-interface.js index a0b8b9ccd7b4..8f5a6aa4faf2 100644 --- a/src/dialects/postgres/query-interface.js +++ b/src/dialects/postgres/query-interface.js @@ -5,12 +5,12 @@ import { camelizeObjectKeys } from '../../utils/object'; const DataTypes = require('../../data-types'); const { QueryTypes } = require('../../query-types'); -const { QueryInterface } = require('../abstract/query-interface'); +const { AbstractQueryInterface } = require('../abstract/query-interface'); /** * The interface that Sequelize uses to talk with Postgres database */ -export class PostgresQueryInterface extends QueryInterface { +export class PostgresQueryInterface extends AbstractQueryInterface { /** * Ensure enum and their values. * diff --git a/src/dialects/snowflake/query-interface.d.ts b/src/dialects/snowflake/query-interface.d.ts index a17e4f0b6a78..b7d82a1d95fd 100644 --- a/src/dialects/snowflake/query-interface.d.ts +++ b/src/dialects/snowflake/query-interface.d.ts @@ -1,8 +1,8 @@ import type { Sequelize } from '../../sequelize.js'; -import { QueryInterface } from '../abstract/query-interface.js'; +import { AbstractQueryInterface } from '../abstract/query-interface.js'; import type { SnowflakeQueryGenerator } from './query-generator.js'; -export class SnowflakeQueryInterface extends QueryInterface { +export class SnowflakeQueryInterface extends AbstractQueryInterface { queryGenerator: SnowflakeQueryGenerator; constructor(sequelize: Sequelize, queryGenerator: SnowflakeQueryGenerator); diff --git a/src/dialects/snowflake/query-interface.js b/src/dialects/snowflake/query-interface.js index 96792644fc3a..0010d54762e3 100644 --- a/src/dialects/snowflake/query-interface.js +++ b/src/dialects/snowflake/query-interface.js @@ -3,13 +3,13 @@ import { assertNoReservedBind, combineBinds } from '../../utils/sql'; const sequelizeErrors = require('../../errors'); -const { QueryInterface } = require('../abstract/query-interface'); +const { AbstractQueryInterface } = require('../abstract/query-interface'); const { QueryTypes } = require('../../query-types'); /** * The interface that Sequelize uses to talk with Snowflake database */ -export class SnowflakeQueryInterface extends QueryInterface { +export class SnowflakeQueryInterface extends AbstractQueryInterface { /** * A wrapper that fixes Snowflake's inability to cleanly remove columns from existing tables if they have a foreign key * constraint. diff --git a/src/dialects/sqlite/query-interface.d.ts b/src/dialects/sqlite/query-interface.d.ts index 3bc2cd0cf8f7..068ec9893f23 100644 --- a/src/dialects/sqlite/query-interface.d.ts +++ b/src/dialects/sqlite/query-interface.d.ts @@ -1,8 +1,8 @@ import type { Sequelize } from '../../sequelize.js'; -import { QueryInterface } from '../abstract/query-interface.js'; +import { AbstractQueryInterface } from '../abstract/query-interface.js'; import type { SqliteQueryGenerator } from './query-generator.js'; -export class SqliteQueryInterface extends QueryInterface { +export class SqliteQueryInterface extends AbstractQueryInterface { queryGenerator: SqliteQueryGenerator; constructor(sequelize: Sequelize, queryGenerator: SqliteQueryGenerator); diff --git a/src/dialects/sqlite/query-interface.js b/src/dialects/sqlite/query-interface.js index c46df1ea2335..36dc26a775a3 100644 --- a/src/dialects/sqlite/query-interface.js +++ b/src/dialects/sqlite/query-interface.js @@ -4,7 +4,7 @@ import { noSchemaParameter, noSchemaDelimiterParameter } from '../../utils/depre const sequelizeErrors = require('../../errors'); const { QueryTypes } = require('../../query-types'); -const { QueryInterface, QueryOptions, ColumnsDescription } = require('../abstract/query-interface'); +const { AbstractQueryInterface, QueryOptions, ColumnsDescription } = require('../abstract/query-interface'); const { cloneDeep } = require('../../utils/object.js'); const _ = require('lodash'); const crypto = require('node:crypto'); @@ -12,7 +12,7 @@ const crypto = require('node:crypto'); /** * The interface that Sequelize uses to talk with SQLite database */ -export class SqliteQueryInterface extends QueryInterface { +export class SqliteQueryInterface extends AbstractQueryInterface { /** * A wrapper that fixes SQLite's inability to remove columns from existing tables. * It will create a backup of the table, drop the table afterwards and create a diff --git a/src/model-typescript.ts b/src/model-typescript.ts index c5fa243bdb5f..93b4bf4a34c8 100644 --- a/src/model-typescript.ts +++ b/src/model-typescript.ts @@ -27,7 +27,7 @@ import type { UpsertOptions, Sequelize, AbstractQueryGenerator, - QueryInterface, + AbstractQueryInterface, } from '.'; export interface ModelHooks { @@ -145,7 +145,7 @@ const staticPrivateStates = new WeakMap; + createSchema(schema: string, options?: CreateSchemaOptions): Promise; /** * Show all defined schemas diff --git a/src/sequelize.js b/src/sequelize.js index 2c23b1c7ebe2..d7c108833b47 100644 --- a/src/sequelize.js +++ b/src/sequelize.js @@ -28,7 +28,7 @@ const { Association } = require('./associations/index'); const Validator = require('./utils/validator-extras').validator; const { Op } = require('./operators'); const deprecations = require('./utils/deprecations'); -const { QueryInterface } = require('./dialects/abstract/query-interface'); +const { AbstractQueryInterface } = require('./dialects/abstract/query-interface'); const { BelongsTo } = require('./associations/belongs-to'); const { HasOne } = require('./associations/has-one'); const { BelongsToMany } = require('./associations/belongs-to-many'); @@ -441,9 +441,9 @@ export class Sequelize extends SequelizeTypeScript { } /** - * Returns an instance of QueryInterface. + * Returns an instance of AbstractQueryInterface. * - * @returns {QueryInterface} An instance (singleton) of QueryInterface. + * @returns {AbstractQueryInterface} An instance (singleton) of AbstractQueryInterface. */ getQueryInterface() { return this.queryInterface; @@ -778,9 +778,10 @@ Use Sequelize#query if you wish to use replacements.`); * {@link Model.schema} * * @param {string} schema Name of the schema - * @param {object} [options={}] query options - * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging - * + * @param {object} [options={}] CreateSchemaQueryOptions + * @param {string} [options.collate=null] + * @param {string} [options.charset=null] + * * @returns {Promise} */ async createSchema(schema, options) { @@ -1279,7 +1280,7 @@ Sequelize.prototype.Validator = Sequelize.Validator = Validator; Sequelize.Model = Model; -Sequelize.QueryInterface = QueryInterface; +Sequelize.QueryInterface = AbstractQueryInterface; Sequelize.BelongsTo = BelongsTo; Sequelize.HasOne = HasOne; Sequelize.HasMany = HasMany; diff --git a/test/integration/query-interface/schemas.test.ts b/test/integration/query-interface/schemas.test.ts new file mode 100644 index 000000000000..a94e88fadaab --- /dev/null +++ b/test/integration/query-interface/schemas.test.ts @@ -0,0 +1,39 @@ +import { expect } from 'chai'; +import { spy } from 'sinon'; +import type { CreateSchemaQueryOptions } from '@sequelize/core/_non-semver-use-at-your-own-risk_/dialects/abstract/query-generator'; +import { sequelize } from '../support'; + +const queryInterface = sequelize.queryInterface; + +const testSchema = 'testSchema'; + +describe('QueryInterface#createSchema', async () => { + if (!sequelize.dialect.supports.schemas) { + return; + } + + it('creates a schema', async () => { + const preCreationSchemas = await queryInterface.showAllSchemas(); + expect(preCreationSchemas).to.not.include(testSchema, 'testSchema existed before tests ran'); + + await queryInterface.createSchema(testSchema); + const postCreationSchemas = await queryInterface.showAllSchemas(); + expect(postCreationSchemas).to.include(testSchema, 'createSchema did not create testSchema'); + }); + + it(`passes options through to the queryInterface's queryGenerator`, async () => { + const options: CreateSchemaQueryOptions = { + collate: 'en_US.UTF-8', + charset: 'utf8mb4', + }; + const queryGeneratorSpy = spy(queryInterface.queryGenerator, 'createSchemaQuery'); + + try { + await queryInterface.createSchema(testSchema, options); + } catch { + // Dialects which don't support collate/charset will throw + } + + expect(queryGeneratorSpy.args[0]).to.include(options); + }); +}); diff --git a/test/types/query-interface.ts b/test/types/query-interface.ts index 222dd6d3c959..3daa4cc9d8a8 100644 --- a/test/types/query-interface.ts +++ b/test/types/query-interface.ts @@ -1,7 +1,7 @@ -import type { QueryInterface } from '@sequelize/core'; +import type { AbstractQueryInterface } from '@sequelize/core'; import { DataTypes, Model, fn, literal, col } from '@sequelize/core'; -declare let queryInterface: QueryInterface; +declare let queryInterface: AbstractQueryInterface; async function test() { await queryInterface.createTable(