diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 876b23bf3a4a..98773e213584 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -925,7 +925,7 @@ class QueryGenerator { } /** - * Split a list of identifiers by "." and quote each part + * Adds quotes to identifier * * @param {string} identifier * @param {boolean} force @@ -936,6 +936,13 @@ class QueryGenerator { throw new Error(`quoteIdentifier for Dialect "${this.dialect}" is not implemented`); } + /** + * Split a list of identifiers by "." and quote each part. + * + * @param {string} identifiers + * + * @returns {string} + */ quoteIdentifiers(identifiers) { if (identifiers.includes('.')) { identifiers = identifiers.split('.'); diff --git a/lib/dialects/abstract/query-interface.js b/lib/dialects/abstract/query-interface.js index 3eff3ba18c58..6eebcd055398 100644 --- a/lib/dialects/abstract/query-interface.js +++ b/lib/dialects/abstract/query-interface.js @@ -430,6 +430,29 @@ class QueryInterface { return this.sequelize.normalizeAttribute(attribute); } + /** + * Split a list of identifiers by "." and quote each part + * + * @param {string} identifier + * @param {boolean} force + * + * @returns {string} + */ + quoteIdentifier(identifier, force) { + return this.queryGenerator.quoteIdentifier(identifier, force); + } + + /** + * Split a list of identifiers by "." and quote each part. + * + * @param {string} identifiers + * + * @returns {string} + */ + quoteIdentifiers(identifiers) { + return this.queryGenerator.quoteIdentifiers(identifiers); + } + /** * Change a column definition * diff --git a/lib/utils/class-to-invokable.ts b/lib/utils/class-to-invokable.ts index dedb567dfc0a..72da6ca38de1 100644 --- a/lib/utils/class-to-invokable.ts +++ b/lib/utils/class-to-invokable.ts @@ -15,7 +15,7 @@ interface Invokeable, Instance> { * @private */ export function classToInvokable, Instance extends object>( - Class: new (...args: Args) => Instance, + Class: new (...args: Args) => Instance ): Invokeable { return new Proxy>(Class as any, { apply(_target, _thisArg, args: Args) { @@ -23,6 +23,6 @@ export function classToInvokable, Instance extends objec }, construct(_target, args: Args) { return new Class(...args); - }, + } }); } diff --git a/test/unit/dialects/abstract/query-interface.test.ts b/test/unit/dialects/abstract/query-interface.test.ts new file mode 100644 index 000000000000..9cafa0441184 --- /dev/null +++ b/test/unit/dialects/abstract/query-interface.test.ts @@ -0,0 +1,40 @@ +import { expect } from 'chai'; +import Support from '../../support'; + +const { sequelize } = Support as any; + +describe('QueryInterface', () => { + describe('quoteIdentifier', () => { + // regression test which covers https://github.com/sequelize/sequelize/issues/12627 + it('should quote the identifier', () => { + const identifier = 'identifier'; + const quotedIdentifier = sequelize + .getQueryInterface() + .quoteIdentifier(identifier); + const expectedQuotedIdentifier = sequelize + .getQueryInterface() + .queryGenerator.quoteIdentifier(identifier); + + expect(quotedIdentifier).not.to.be.undefined; + expect(expectedQuotedIdentifier).not.to.be.undefined; + expect(quotedIdentifier).to.equal(expectedQuotedIdentifier); + }); + }); + + describe('quoteIdentifiers', () => { + // regression test which covers https://github.com/sequelize/sequelize/issues/12627 + it('should quote the identifiers', () => { + const identifier = 'table.identifier'; + const quotedIdentifiers = sequelize + .getQueryInterface() + .quoteIdentifiers(identifier); + const expectedQuotedIdentifiers = sequelize + .getQueryInterface() + .queryGenerator.quoteIdentifiers(identifier); + + expect(quotedIdentifiers).not.to.be.undefined; + expect(expectedQuotedIdentifiers).not.to.be.undefined; + expect(quotedIdentifiers).to.equal(expectedQuotedIdentifiers); + }); + }); +}); diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index ec4529af6d42..0f230c5df138 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -630,21 +630,20 @@ export class QueryInterface { ): Promise; /** - * Escape an identifier (e.g. a table or attribute name). If force is true, the identifier will be quoted - * even if the `quoteIdentifiers` option is false. + * Escape a table name */ - public quoteIdentifier(identifier: string, force: boolean): string; + public quoteTable(identifier: TableName): string; /** - * Escape a table name + * Escape an identifier (e.g. a table or attribute name). If force is true, the identifier will be quoted + * even if the `quoteIdentifiers` option is false. */ - public quoteTable(identifier: TableName): string; + public quoteIdentifier(identifier: string, force?: boolean): string; /** - * Split an identifier into .-separated tokens and quote each part. If force is true, the identifier will be - * quoted even if the `quoteIdentifiers` option is false. + * Split an identifier into .-separated tokens and quote each part. */ - public quoteIdentifiers(identifiers: string, force: boolean): string; + public quoteIdentifiers(identifiers: string): string; /** * Escape a value (e.g. a string, number or date) diff --git a/types/test/query-interface.ts b/types/test/query-interface.ts index 09c403b23810..25454db082ff 100644 --- a/types/test/query-interface.ts +++ b/types/test/query-interface.ts @@ -73,6 +73,10 @@ async function test() { await queryInterface.quoteTable({ tableName: 'foo', delimiter: 'bar' }); + queryInterface.quoteIdentifier("foo"); + queryInterface.quoteIdentifier("foo", true); + queryInterface.quoteIdentifiers("table.foo"); + await queryInterface.dropAllTables(); await queryInterface.renameTable('Person', 'User');