Skip to content

Commit

Permalink
feat: migrate insert & bulkInsert to ts
Browse files Browse the repository at this point in the history
  • Loading branch information
lohart13 committed Feb 9, 2024
1 parent 906cbeb commit c663818
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 93 deletions.
89 changes: 89 additions & 0 deletions packages/core/src/dialects/abstract/query-interface-typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import isEmpty from 'lodash/isEmpty';
import { Deferrable } from '../../deferrable';
import type { ConstraintChecking } from '../../deferrable';
import { BaseError } from '../../errors';
import { Model } from '../../model';
import type { AttributeOptions, ModelStatic } from '../../model';
import { setTransactionFromCls } from '../../model-internals.js';
import { QueryTypes } from '../../query-types';
import type { QueryRawOptions, QueryRawOptionsWithType, Sequelize } from '../../sequelize';
Expand All @@ -13,6 +15,7 @@ import {
showAllToListSchemas,
showAllToListTables,
} from '../../utils/deprecations';
import { assertNoReservedBind, combineBinds } from '../../utils/sql';
import type { RequiredBy } from '../../utils/types';
import type { Connection } from './connection-manager.js';
import type { TableNameOrModel } from './query-generator-typescript.js';
Expand All @@ -31,9 +34,11 @@ import type {
DropSchemaOptions,
FetchDatabaseVersionOptions,
ListDatabasesOptions,
QiBulkInsertOptions,
QiDropAllSchemasOptions,
QiDropAllTablesOptions,
QiDropTableOptions,
QiInsertOptions,
QiListSchemasOptions,
QiListTablesOptions,
QiTruncateTableOptions,
Expand Down Expand Up @@ -741,4 +746,88 @@ export class AbstractQueryInterfaceTypeScript<Dialect extends AbstractDialect =

return this.sequelize.queryRaw(sql, { ...bulkDeleteOptions, raw: true, type: QueryTypes.DELETE });
}

/**
* Insert multiple records into a table
*
* @example
* queryInterface.bulkInsert('roles', [{
* label: 'user',
* createdAt: new Date(),
* updatedAt: new Date()
* }, {
* label: 'admin',
* createdAt: new Date(),
* updatedAt: new Date()
* }]);
*
* @param tableName Table name to insert record to
* @param values List of records to insert
* @param options Various options, please see Model.bulkCreate options
* @param attributeHash Various attributes mapped by field name
*/
async bulkInsert(
tableName: TableNameOrModel,
values: Array<Record<string, unknown>>,
options?: QiBulkInsertOptions,
attributeHash?: Record<string, AttributeOptions>,
): Promise<Array<Record<string, unknown>>> {
const bulkInsertOptions = { ...options };
const sql = this.queryGenerator.bulkInsertQuery(tableName, values, bulkInsertOptions, attributeHash);

// unlike bind, replacements are handled by QueryGenerator, not QueryRaw
delete bulkInsertOptions.replacements;

const results = await this.sequelize.queryRaw<Array<Record<string, unknown>>>(sql, {
...bulkInsertOptions,
type: QueryTypes.INSERT,
});

return results[0];
}

/**
* Inserts a new record
*
* @param tableName
* @param values
* @param options
* @param instanceOrAttributeHash
*/
async insert<M extends Model>(
tableName: TableNameOrModel,
values: Record<string, unknown>,
options?: QiInsertOptions,
instanceOrAttributeHash?: M | Record<string, AttributeOptions>,
): Promise<[M | Record<string, unknown>, number]> {
if (options?.bind) {
assertNoReservedBind(options.bind);
}

let attributeHash: Record<string, AttributeOptions> | undefined;
const insertOptions = { ...options };
if (instanceOrAttributeHash instanceof Model) {
const model = (instanceOrAttributeHash.constructor as ModelStatic<M>);
insertOptions.model = model;
insertOptions.instance = instanceOrAttributeHash;
insertOptions.hasTrigger = model.modelDefinition.options.hasTrigger ?? false;
} else {
attributeHash = instanceOrAttributeHash;
}

const { query, bind } = this.queryGenerator.insertQuery(tableName, values, insertOptions, attributeHash);

// unlike bind, replacements are handled by QueryGenerator, not QueryRaw
delete insertOptions.replacements;
insertOptions.bind = combineBinds(insertOptions.bind ?? {}, bind ?? {});

if (instanceOrAttributeHash instanceof Model) {
const results = await this.sequelize.queryRaw<M>(query, { ...insertOptions, type: QueryTypes.INSERT });
results[0].isNewRecord = false;

return results;
}

return this.sequelize.queryRaw(query, { ...insertOptions, type: QueryTypes.INSERT });
}
}
26 changes: 1 addition & 25 deletions packages/core/src/dialects/abstract/query-interface.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { AllowLowercase } from '../../utils/types.js';
import type { DataType } from './data-types.js';
import type { RemoveIndexQueryOptions, TableNameOrModel } from './query-generator-typescript';
import type { AddColumnQueryOptions } from './query-generator.js';
import type { AddLimitOffsetOptions, BulkInsertQueryOptions, InsertQueryOptions } from './query-generator.types.js';
import type { AddLimitOffsetOptions } from './query-generator.types.js';
import { AbstractQueryInterfaceTypeScript } from './query-interface-typescript';
import type { ColumnsDescription } from './query-interface.types.js';
import type { WhereOptions } from './where-sql-builder-types.js';
Expand All @@ -32,10 +32,6 @@ interface Replaceable {

interface QiOptionsWithReplacements extends QueryRawOptions, Replaceable { }

export interface QiInsertOptions extends InsertQueryOptions, QueryRawOptions { }

export interface QiBulkInsertOptions extends BulkInsertQueryOptions, QueryRawOptions { }

export interface QiSelectOptions extends QueryRawOptions, Filterable<any>, AddLimitOffsetOptions {
minifyAliases?: boolean;
}
Expand Down Expand Up @@ -321,16 +317,6 @@ export class AbstractQueryInterface<Dialect extends AbstractDialect = AbstractDi
*/
nameIndexes(indexes: string[], rawTablename: string): Promise<void>;

/**
* Inserts a new record
*/
insert(
instance: Model | null,
tableName: TableNameOrModel,
values: object,
options?: QiInsertOptions,
): Promise<object>;

/**
* Inserts or Updates a record in the database
*/
Expand All @@ -342,16 +328,6 @@ export class AbstractQueryInterface<Dialect extends AbstractDialect = AbstractDi
options?: QiUpsertOptions<M>,
): Promise<object>;

/**
* Inserts multiple records at once
*/
bulkInsert(
tableName: TableNameOrModel,
records: object[],
options?: QiBulkInsertOptions,
attributes?: Record<string, AttributeOptions>
): Promise<object | number>;

/**
* Updates a row
*/
Expand Down
58 changes: 0 additions & 58 deletions packages/core/src/dialects/abstract/query-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,30 +340,6 @@ export class AbstractQueryInterface extends AbstractQueryInterfaceTypeScript {
return await this.sequelize.queryRaw(sql, options);
}

async insert(instance, tableName, values, options) {
if (options?.bind) {
assertNoReservedBind(options.bind);
}

options = cloneDeep(options) ?? {};
options.hasTrigger = instance?.constructor?.options.hasTrigger;
const { query, bind } = this.queryGenerator.insertQuery(tableName, values, options);

options.type = QueryTypes.INSERT;
options.instance = instance;

// unlike bind, replacements are handled by QueryGenerator, not QueryRaw
delete options.replacements;
options.bind = combineBinds(options.bind, bind);

const results = await this.sequelize.queryRaw(query, options);
if (instance) {
results[0].isNewRecord = false;
}

return results;
}

/**
* Upsert
*
Expand Down Expand Up @@ -433,40 +409,6 @@ export class AbstractQueryInterface extends AbstractQueryInterfaceTypeScript {
return await this.sequelize.queryRaw(query, options);
}

/**
* Insert multiple records into a table
*
* @example
* queryInterface.bulkInsert('roles', [{
* label: 'user',
* createdAt: new Date(),
* updatedAt: new Date()
* }, {
* label: 'admin',
* createdAt: new Date(),
* updatedAt: new Date()
* }]);
*
* @param {string} tableName Table name to insert record to
* @param {Array} records List of records to insert
* @param {object} options Various options, please see Model.bulkCreate options
* @param {object} attributes Various attributes mapped by field name
*
* @returns {Promise}
*/
async bulkInsert(tableName, records, options, attributes) {
options = { ...options, type: QueryTypes.INSERT };

const sql = this.queryGenerator.bulkInsertQuery(tableName, records, options, attributes);

// unlike bind, replacements are handled by QueryGenerator, not QueryRaw
delete options.replacements;

const results = await this.sequelize.queryRaw(sql, options);

return results[0];
}

async update(instance, tableName, values, where, options) {
if (options?.bind) {
assertNoReservedBind(options.bind);
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/dialects/abstract/query-interface.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import type { QueryRawOptions } from '../../sequelize';
import type {
AddConstraintQueryOptions,
BulkDeleteQueryOptions,
BulkInsertQueryOptions,
CreateDatabaseQueryOptions,
CreateSchemaQueryOptions,
DropSchemaQueryOptions,
DropTableQueryOptions,
InsertQueryOptions,
ListDatabasesQueryOptions,
ListSchemasQueryOptions,
ListTablesQueryOptions,
Expand Down Expand Up @@ -142,3 +144,9 @@ export interface ShowConstraintsOptions extends ShowConstraintsQueryOptions, Que

/** Options accepted by {@link AbstractQueryInterface#bulkDelete} */
export interface BulkDeleteOptions extends BulkDeleteQueryOptions, QueryRawOptions { }

/** Options accepted by {@link AbstractQueryInterface#bulkInsert} */
export interface QiBulkInsertOptions extends BulkInsertQueryOptions, QueryRawOptions { }

/** Options accepted by {@link AbstractQueryInterface#insert} */
export interface QiInsertOptions extends InsertQueryOptions, QueryRawOptions { }
2 changes: 1 addition & 1 deletion packages/core/src/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -3748,7 +3748,7 @@ Instead of specifying a Model, either:

if (this.isNewRecord) {
query = 'insert';
args = [this, this.constructor, values, options];
args = [this.constructor, values, options, this];
}

const [result, rowsUpdated] = await this.constructor.queryInterface[query](...args);
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/sequelize.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,7 @@ export class Sequelize extends SequelizeTypeScript {
/* eslint-disable max-len -- these signatures are more readable if they are all aligned */
query(sql: string | BaseSqlExpression, options: QueryOptionsWithType<QueryTypes.UPDATE>): Promise<[undefined, number]>;
query(sql: string | BaseSqlExpression, options: QueryOptionsWithType<QueryTypes.BULKUPDATE>): Promise<number>;
query(sql: string | BaseSqlExpression, options: QueryOptionsWithType<QueryTypes.INSERT>): Promise<[number, number]>;
query(sql: string | BaseSqlExpression, options: QueryOptionsWithType<QueryTypes.INSERT>): Promise<[Record<string, unknown>, number]>;
query(sql: string | BaseSqlExpression, options: QueryOptionsWithType<QueryTypes.UPSERT>): Promise<number>;
query(sql: string | BaseSqlExpression, options: QueryOptionsWithType<QueryTypes.DELETE>): Promise<number>;
query(sql: string | BaseSqlExpression, options: QueryOptionsWithType<QueryTypes.DESCRIBE>): Promise<ColumnsDescription>;
Expand All @@ -960,6 +960,7 @@ export class Sequelize extends SequelizeTypeScript {
query<M extends Model>(sql: string | BaseSqlExpression, options: QueryOptionsWithModel<M>): Promise<M[]>;
query<T extends object>(sql: string | BaseSqlExpression, options: QueryOptionsWithType<QueryTypes.SELECT> & { plain: true }): Promise<T | null>;
query<T extends object>(sql: string | BaseSqlExpression, options: QueryOptionsWithType<QueryTypes.SELECT>): Promise<T[]>;
query<T extends object>(sql: string | BaseSqlExpression, options: QueryOptionsWithType<QueryTypes.INSERT>): Promise<[T, number]>;
query(sql: string | BaseSqlExpression, options: (QueryOptions | QueryOptionsWithType<QueryTypes.RAW>) & { plain: true }): Promise<{ [key: string]: unknown } | null>;
query(sql: string | BaseSqlExpression, options?: QueryOptions | QueryOptionsWithType<QueryTypes.RAW>): Promise<[unknown[], unknown]>;

Expand All @@ -971,7 +972,7 @@ export class Sequelize extends SequelizeTypeScript {
*/
queryRaw(sql: string, options: QueryRawOptionsWithType<QueryTypes.UPDATE>): Promise<[undefined, number]>;
queryRaw(sql: string, options: QueryRawOptionsWithType<QueryTypes.BULKUPDATE>): Promise<number>;
queryRaw(sql: string, options: QueryRawOptionsWithType<QueryTypes.INSERT>): Promise<[number, number]>;
queryRaw(sql: string, options: QueryRawOptionsWithType<QueryTypes.INSERT>): Promise<[Record<string, unknown>, number]>;
queryRaw(sql: string, options: QueryRawOptionsWithType<QueryTypes.UPSERT>): Promise<number>;
queryRaw(sql: string, options: QueryRawOptionsWithType<QueryTypes.DELETE>): Promise<number>;
queryRaw(sql: string, options: QueryRawOptionsWithType<QueryTypes.DESCRIBE>): Promise<ColumnsDescription>;
Expand All @@ -980,6 +981,7 @@ export class Sequelize extends SequelizeTypeScript {
queryRaw<M extends Model>(sql: string, options: QueryRawOptionsWithModel<M>): Promise<M[]>;
queryRaw<T extends object>(sql: string, options: QueryRawOptionsWithType<QueryTypes.SELECT> & { plain: true }): Promise<T | null>;
queryRaw<T extends object>(sql: string, options: QueryRawOptionsWithType<QueryTypes.SELECT>): Promise<T[]>;
queryRaw<T extends object>(sql: string, options: QueryRawOptionsWithType<QueryTypes.INSERT>): Promise<[T, number]>;
queryRaw(sql: string, options: (QueryRawOptions | QueryRawOptionsWithType<QueryTypes.RAW>) & { plain: true }): Promise<{ [key: string]: unknown } | null>;
queryRaw(sql: string, options?: QueryRawOptions | QueryRawOptionsWithType<QueryTypes.RAW>): Promise<[unknown[], unknown]>;
/* eslint-enable max-len */
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/types/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async function test() {

expectTypeOf(
await sequelize.query('INSERT into test set test=1', { type: QueryTypes.INSERT }),
).toEqualTypeOf<[number, number]>();
).toEqualTypeOf<[Record<string, unknown>, number]>();
}

// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- false positive :/
Expand Down
3 changes: 2 additions & 1 deletion packages/core/test/types/query-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,5 +249,6 @@ async function test() {

await queryInterface.upsert('test', { a: 1 }, { b: 2 }, { c: 3 }, { model: TestModel });

await queryInterface.insert(null, 'test', {});
await queryInterface.insert('test', {});
await queryInterface.insert(TestModel, {});
}
2 changes: 1 addition & 1 deletion packages/core/test/unit/instance/set.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => {

describe('custom setter', () => {
before(function () {
this.stubCreate = sinon.stub(current.queryInterface, 'insert').callsFake(async instance => [instance, 1]);
this.stubCreate = sinon.stub(current.queryInterface, 'insert').callsFake(async (_table, _values, _options, instance) => [instance, 1]);
});

after(function () {
Expand Down
8 changes: 4 additions & 4 deletions packages/core/test/unit/query-interface/insert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('QueryInterface#insert', () => {
it('does not parse replacements outside of raw sql', async () => {
const stub = sinon.stub(sequelize, 'queryRaw');

await sequelize.queryInterface.insert(null, User, {
await sequelize.queryInterface.insert(User, {
firstName: 'Zoe',
}, {
returning: sequelize.dialect.supports.insert.returning ? [':data'] : undefined,
Expand All @@ -42,7 +42,7 @@ describe('QueryInterface#insert', () => {
it('throws if a bind parameter name starts with the reserved "sequelize_" prefix', async () => {
sinon.stub(sequelize, 'queryRaw');

await expect(sequelize.queryInterface.insert(null, User, {
await expect(sequelize.queryInterface.insert(User, {
firstName: literal('$sequelize_test'),
}, {
bind: {
Expand All @@ -54,7 +54,7 @@ describe('QueryInterface#insert', () => {
it('merges user-provided bind parameters with sequelize-generated bind parameters (object bind)', async () => {
const stub = sinon.stub(sequelize, 'queryRaw');

await sequelize.queryInterface.insert(null, User, {
await sequelize.queryInterface.insert(User, {
firstName: literal('$firstName'),
lastName: 'Doe',
}, {
Expand All @@ -78,7 +78,7 @@ describe('QueryInterface#insert', () => {
it('merges user-provided bind parameters with sequelize-generated bind parameters (array bind)', async () => {
const stub = sinon.stub(sequelize, 'queryRaw');

await sequelize.queryInterface.insert(null, User, {
await sequelize.queryInterface.insert(User, {
firstName: literal('$1'),
lastName: 'Doe',
}, {
Expand Down

0 comments on commit c663818

Please sign in to comment.