Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use bind params in bulkInsertQuery #17131

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/core/src/dialects/abstract/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,3 +694,8 @@ export type BindCollector = {
*/
getBindParameterOrder(): string[] | null;
};

export enum ParameterStyle {
bind = 'bind',
replacement = 'replacement',
}
21 changes: 13 additions & 8 deletions packages/core/src/dialects/abstract/query-generator.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
SearchPathable,
} from '../../model.js';
import type { DataType } from './data-types.js';
import type { ParameterStyle } from './index.js';
import { AbstractQueryGeneratorTypeScript } from './query-generator-typescript.js';
import type { AttributeToSqlOptions } from './query-generator.internal-types.js';
import type { TableOrModel } from './query-generator.types.js';
Expand All @@ -19,6 +20,8 @@ import type { ColumnsDescription } from './query-interface.types.js';
import type { WhereOptions } from './where-sql-builder-types.js';

type ParameterOptions = {
parameterStyle?: ParameterStyle;
bindParam?: false | ((value: unknown) => string);
// only named replacements are allowed
replacements?: { [key: string]: unknown };
};
Expand All @@ -30,7 +33,6 @@ type SelectOptions<M extends Model> = FindOptions<M> & {
type InsertOptions = ParameterOptions &
SearchPathable & {
exception?: boolean;
bindParam?: false | ((value: unknown) => string);

updateOnDuplicate?: string[];
ignoreDuplicates?: boolean;
Expand All @@ -47,11 +49,12 @@ type BulkInsertOptions = ParameterOptions & {
returning?: boolean | Array<string | Literal | Col>;
};

type UpdateOptions = ParameterOptions & {
bindParam?: false | ((value: unknown) => string);
};
type UpdateOptions = ParameterOptions;

type ArithmeticQueryOptions = {
// only named replacements are allowed
replacements?: { [key: string]: unknown };

type ArithmeticQueryOptions = ParameterOptions & {
returning?: boolean | Array<string | Literal | Col>;
};

Expand All @@ -74,6 +77,8 @@ export interface AddColumnQueryOptions {
ifNotExists?: boolean;
}

type BoundQuery = { query: string; bind?: Record<string, unknown> };

/**
* The base class for all query generators, used to generate all SQL queries.
*
Expand All @@ -93,13 +98,13 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
valueHash: object,
columnDefinitions?: { [columnName: string]: NormalizedAttributeOptions },
options?: InsertOptions,
): { query: string; bind?: unknown[] };
): BoundQuery;
bulkInsertQuery(
tableName: TableName,
newEntries: object[],
options?: BulkInsertOptions,
columnDefinitions?: { [columnName: string]: NormalizedAttributeOptions },
): string;
): BoundQuery;

addColumnQuery(
table: TableName,
Expand All @@ -114,7 +119,7 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
where: WhereOptions,
options?: UpdateOptions,
columnDefinitions?: { [columnName: string]: NormalizedAttributeOptions },
): { query: string; bind?: unknown[] };
): BoundQuery;

arithmeticQuery(
operator: string,
Expand Down
51 changes: 40 additions & 11 deletions packages/core/src/dialects/abstract/query-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { joinSQLFragments } from '../../utils/join-sql-fragments';
import { isModelStatic } from '../../utils/model-utils';
import { nameIndex, spliceStr } from '../../utils/string';
import { attributeTypeToSql } from './data-types-utils';
import { ParameterStyle } from './index.js';
import { AbstractQueryGeneratorInternal } from './query-generator-internal.js';
import { AbstractQueryGeneratorTypeScript } from './query-generator-typescript';
import { joinWithLogicalOperator } from './where-sql-builder';
Expand Down Expand Up @@ -77,12 +78,13 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
defaults(options, this.options);

const modelAttributeMap = {};
const bind = Object.create(null);
const fields = [];
const returningModelAttributes = [];
const values = Object.create(null);
const quotedTable = this.quoteTable(table);
let bindParam = options.bindParam === undefined ? this.bindParam(bind) : options.bindParam;
let bind;
let bindParam;
let parameterStyle = options.parameterStyle ?? ParameterStyle.bind;
let query;
let valueQuery = '';
let emptyQuery = '';
Expand Down Expand Up @@ -120,12 +122,17 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
options.searchPath
) {
// Not currently supported with search path (requires output of multiple queries)
bindParam = undefined;
parameterStyle = ParameterStyle.replacement;
}

if (this.dialect.supports.EXCEPTION && options.exception) {
// Not currently supported with bind parameters (requires output of multiple queries)
bindParam = undefined;
parameterStyle = ParameterStyle.replacement;
}

if (parameterStyle === ParameterStyle.bind) {
bind = Object.create(null);
bindParam = options.bindParam || this.bindParam(bind);
}

valueHash = removeNullishValuesFromHash(valueHash, this.options.omitNull);
Expand Down Expand Up @@ -271,7 +278,7 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {

// Used by Postgres upsertQuery and calls to here with options.exception set to true
const result = { query };
if (options.bindParam !== false) {
if (parameterStyle === ParameterStyle.bind) {
result.bind = bind;
}

Expand All @@ -292,11 +299,19 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
options ||= {};
fieldMappedAttributes ||= {};

const parameterStyle = options.parameterStyle ?? ParameterStyle.bind;
const tuples = [];
const serials = {};
const allAttributes = [];
let bind;
let bindParam;
let onDuplicateKeyUpdate = '';

if (parameterStyle === ParameterStyle.bind) {
bind = Object.create(null);
bindParam = options.bindParam || this.bindParam(bind);
}

for (const fieldValueHash of fieldValueHashes) {
forOwn(fieldValueHash, (value, key) => {
if (!allAttributes.includes(key)) {
Expand All @@ -318,7 +333,7 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {

return this.escape(fieldValueHash[key] ?? null, {
// model // TODO: make bulkInsertQuery accept model instead of fieldValueHashes
// bindParam // TODO: support bind params
bindParam,
type: fieldMappedAttributes[key]?.type,
replacements: options.replacements,
});
Expand Down Expand Up @@ -386,7 +401,7 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
returning += returnValues.returningFragment;
}

return joinSQLFragments([
const query = joinSQLFragments([
'INSERT',
ignoreDuplicates,
'INTO',
Expand All @@ -399,6 +414,14 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
returning,
';',
]);

const result = { query };

if (parameterStyle === ParameterStyle.bind) {
result.bind = bind;
}

return result;
}

/**
Expand All @@ -419,8 +442,10 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
attrValueHash = removeNullishValuesFromHash(attrValueHash, options.omitNull, options);

const values = [];
const bind = Object.create(null);
const modelAttributeMap = {};
let parameterStyle = options.parameterStyle ?? ParameterStyle.bind;
let bind;
let bindParam;
let outputFragment = '';
let tmpTable = ''; // tmpTable declaration for trigger
let suffix = '';
Expand All @@ -430,10 +455,13 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {
options.searchPath
) {
// Not currently supported with search path (requires output of multiple queries)
options.bindParam = false;
parameterStyle = ParameterStyle.replacement;
}

const bindParam = options.bindParam === undefined ? this.bindParam(bind) : options.bindParam;
if (parameterStyle === ParameterStyle.bind) {
bind = Object.create(null);
bindParam = options.bindParam || this.bindParam(bind);
}

if (
this.dialect.supports['LIMIT ON UPDATE'] &&
Expand Down Expand Up @@ -501,7 +529,8 @@ export class AbstractQueryGenerator extends AbstractQueryGeneratorTypeScript {

// Used by Postgres upsertQuery and calls to here with options.exception set to true
const result = { query };
if (options.bindParam !== false) {

if (parameterStyle === ParameterStyle.bind) {
result.bind = bind;
}

Expand Down
14 changes: 12 additions & 2 deletions packages/core/src/dialects/abstract/query-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -477,14 +477,24 @@ export class AbstractQueryInterface extends AbstractQueryInterfaceTypeScript {
* @returns {Promise}
*/
async bulkInsert(tableName, records, options, attributes) {
if (options?.bind) {
assertNoReservedBind(options.bind);
}

options = { ...options, type: QueryTypes.INSERT };

const sql = this.queryGenerator.bulkInsertQuery(tableName, records, options, attributes);
const { bind, query } = this.queryGenerator.bulkInsertQuery(
tableName,
records,
options,
attributes,
);

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

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

return results[0];
}
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 @@ -34,7 +34,7 @@ export type {
TimeOptions,
VirtualOptions,
} from './dialects/abstract/data-types.js';
export { AbstractDialect } from './dialects/abstract/index.js';
export { AbstractDialect, ParameterStyle } from './dialects/abstract/index.js';
export { AbstractQueryGenerator } from './dialects/abstract/query-generator.js';
export * from './dialects/abstract/query-generator.types.js';
export * from './dialects/abstract/query-interface.js';
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ export const UniqueConstraintError = Pkg.UniqueConstraintError;
// export { useInflection } from './lib/utils';
export const useInflection = Pkg.useInflection;

// export { QueryTypes, Op, TableHints, IndexHints, DataTypes, Deferrable, ConstraintChecking };
// export { ParameterStyle, QueryTypes, Op, TableHints, IndexHints, DataTypes, GeoJsonType, Deferrable, ConstraintChecking };
export const ParameterStyle = Pkg.ParameterStyle;
export const QueryTypes = Pkg.QueryTypes;
export const Op = Pkg.Op;
export const TableHints = Pkg.TableHints;
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/sequelize.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Association } from './associations/index';
import * as DataTypes from './data-types';
import { ConstraintChecking, Deferrable } from './deferrable';
import { AbstractConnectionManager } from './dialects/abstract/connection-manager.js';
import { AbstractDialect } from './dialects/abstract/index.js';
import { AbstractDialect, ParameterStyle } from './dialects/abstract/index.js';
import { AbstractQueryGenerator } from './dialects/abstract/query-generator.js';
import { AbstractQueryInterface } from './dialects/abstract/query-interface';
import { AbstractQuery } from './dialects/abstract/query.js';
Expand Down Expand Up @@ -1022,6 +1022,7 @@ Use Sequelize#query if you wish to use replacements.`);
static TransactionType = TransactionType;
static Lock = Lock;
static IsolationLevel = IsolationLevel;
static ParameterStyle = ParameterStyle;

log(...args) {
let options;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/integration/model/bulk-create.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ describe('Model', () => {
default: {
// mysql, sqlite
expect(sql).to.include(
'INSERT INTO `Beers` (`id`,`style`,`createdAt`,`updatedAt`) VALUES (NULL',
'INSERT INTO `Beers` (`id`,`style`,`createdAt`,`updatedAt`) VALUES (',
);
}
}
Expand Down