feat: add capability for insert multiple rows in single query
Signed-off-by: Samarpan Bhattacharya <this.is.samy@gmail.com>
This commit is contained in:
parent
ca95adb16c
commit
7a02f12194
|
@ -13,3 +13,6 @@
|
||||||
node_modules
|
node_modules
|
||||||
checkstyle.xml
|
checkstyle.xml
|
||||||
loopback-connector-*.tgz
|
loopback-connector-*.tgz
|
||||||
|
.nyc_output
|
||||||
|
coverage
|
||||||
|
.vscode
|
101
lib/sql.js
101
lib/sql.js
|
@ -46,6 +46,14 @@ SQLConnector.Transaction = Transaction;
|
||||||
*/
|
*/
|
||||||
SQLConnector.prototype.relational = true;
|
SQLConnector.prototype.relational = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the multiInsertSupported property to indicate if multiple value insert SQL dialect is supported or not
|
||||||
|
* This can be overridden by derived connectors to allow `insert multiple values` dialect for createAll
|
||||||
|
* By default, it is set to false for backward compatibility
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
SQLConnector.prototype.multiInsertSupported = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoke a prototype method on the super class
|
* Invoke a prototype method on the super class
|
||||||
* @param {String} methodName Method name
|
* @param {String} methodName Method name
|
||||||
|
@ -540,6 +548,53 @@ SQLConnector.prototype.buildInsert = function(model, data, options) {
|
||||||
return this.parameterize(insertStmt);
|
return this.parameterize(insertStmt);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build INSERT SQL statement for multiple values
|
||||||
|
* @param {String} model The model name
|
||||||
|
* @param {Object} data The array of model data object
|
||||||
|
* @param {Object} options The options object
|
||||||
|
* @returns {Object} The ParameterizedSQL Object with INSERT SQL statement
|
||||||
|
*/
|
||||||
|
SQLConnector.prototype.buildInsertAll = function(model, data, options) {
|
||||||
|
if (!this.multiInsertSupported) {
|
||||||
|
debug('multiple value insert SQL dialect is not supported by this connector');
|
||||||
|
// return immediately if multiInsertSupported=false in connector
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const fieldsArray = this.buildFieldsFromArray(model, data);
|
||||||
|
if (fieldsArray.length === 0) {
|
||||||
|
debug('no fields found for insert query');
|
||||||
|
// return immediately if no fields found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const insertStmt = this.buildInsertInto(model, fieldsArray[0], options);
|
||||||
|
|
||||||
|
for (let i = 0; i < fieldsArray.length; i++) {
|
||||||
|
const columnValues = fieldsArray[i].columnValues;
|
||||||
|
const isLast = (i === (fieldsArray.length - 1));
|
||||||
|
const isFirst = (i === 0);
|
||||||
|
|
||||||
|
if (columnValues.length) {
|
||||||
|
const values = ParameterizedSQL.join(columnValues, ',');
|
||||||
|
// Multi value query.
|
||||||
|
// This lets multiple row insertion in single query
|
||||||
|
values.sql = (isFirst ? 'VALUES ' : '') +
|
||||||
|
'(' + values.sql + ')' +
|
||||||
|
(isLast ? '' : ',');
|
||||||
|
insertStmt.merge(values);
|
||||||
|
} else {
|
||||||
|
// Insert default values if no values provided
|
||||||
|
insertStmt.merge(this.buildInsertDefaultValues(model, data, options));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const returning = this.buildInsertReturning(model, data, options);
|
||||||
|
if (returning) {
|
||||||
|
insertStmt.merge(returning);
|
||||||
|
}
|
||||||
|
return this.parameterize(insertStmt);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a SQL statement with given parameters.
|
* Execute a SQL statement with given parameters.
|
||||||
*
|
*
|
||||||
|
@ -630,6 +685,34 @@ SQLConnector.prototype.create = function(model, data, options, callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create multiple data models in a single insert query
|
||||||
|
* Works only if `multiInsertSupported` is set to true for the connector
|
||||||
|
*
|
||||||
|
* @param {String} model The model name
|
||||||
|
* @param {Object} data The model instances data
|
||||||
|
* @param {Object} options Options object
|
||||||
|
* @param {Function} [callback] The callback function
|
||||||
|
*/
|
||||||
|
SQLConnector.prototype.createAll = function(model, data, options, callback) {
|
||||||
|
const self = this;
|
||||||
|
const stmt = this.buildInsertAll(model, data, options);
|
||||||
|
if (!stmt) {
|
||||||
|
debug('empty SQL statement returned for insert into multiple values');
|
||||||
|
callback(new Error(
|
||||||
|
g.f('empty SQL statement returned for insert into multiple values'),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
this.execute(stmt.sql, stmt.params, options, function(err, info) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
const insertedId = self.getInsertedId(model, info);
|
||||||
|
callback(err, insertedId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the model instance into the database
|
* Save the model instance into the database
|
||||||
* @param {String} model The model name
|
* @param {String} model The model name
|
||||||
|
@ -1177,6 +1260,24 @@ SQLConnector.prototype.buildFields = function(model, data, excludeIds) {
|
||||||
return this._buildFieldsForKeys(model, data, keys, excludeIds);
|
return this._buildFieldsForKeys(model, data, keys, excludeIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an array of fields for the database operation from data array
|
||||||
|
* @param {String} model Model name
|
||||||
|
* @param {Object} data Array of Model data object
|
||||||
|
* @param {Boolean} excludeIds Exclude id properties or not, default to false
|
||||||
|
* @returns {[{names: Array, values: Array, properties: Array}]}
|
||||||
|
*/
|
||||||
|
SQLConnector.prototype.buildFieldsFromArray = function(model, data, excludeIds) {
|
||||||
|
const fields = [];
|
||||||
|
if (data.length > 0) {
|
||||||
|
const keys = Object.keys(data[0]);
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
fields.push(this._buildFieldsForKeys(model, data[i], keys, excludeIds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build an array of fields for the replace database operation
|
* Build an array of fields for the replace database operation
|
||||||
* @param {String} model Model name
|
* @param {String} model Model name
|
||||||
|
|
|
@ -514,4 +514,38 @@ describe('sql connector', function() {
|
||||||
expect(function() { runExecute(); }).to.not.throw();
|
expect(function() { runExecute(); }).to.not.throw();
|
||||||
ds.connected = true;
|
ds.connected = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should build INSERT for multiple rows if multiInsertSupported is true', function() {
|
||||||
|
connector.multiInsertSupported = true;
|
||||||
|
const sql = connector.buildInsertAll('customer', [
|
||||||
|
{name: 'Adam', middleName: 'abc', vip: true},
|
||||||
|
{name: 'Test', middleName: null, vip: false},
|
||||||
|
]);
|
||||||
|
expect(sql.toJSON()).to.eql({
|
||||||
|
sql:
|
||||||
|
'INSERT INTO `CUSTOMER`(`NAME`,`middle_name`,`VIP`) VALUES ($1,$2,$3), ($4,$5,$6)',
|
||||||
|
params: ['Adam', 'abc', true, 'Test', null, false],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for INSERT multiple rows if multiInsertSupported is false',
|
||||||
|
function() {
|
||||||
|
connector.multiInsertSupported = false;
|
||||||
|
const sql = connector.buildInsertAll('customer', [
|
||||||
|
{name: 'Adam', middleName: 'abc', vip: true},
|
||||||
|
{name: 'Test', middleName: null, vip: false},
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
expect(sql).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for INSERT multiple rows if multiInsertSupported not set',
|
||||||
|
function() {
|
||||||
|
const sql = connector.buildInsertAll('customer', [
|
||||||
|
{name: 'Adam', middleName: 'abc', vip: true},
|
||||||
|
{name: 'Test', middleName: null, vip: false},
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
expect(sql).to.be.null;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue