UNPKG

24.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3var tslib_1 = require("tslib");
4var CockroachDriver_1 = require("../driver/cockroachdb/CockroachDriver");
5var QueryBuilder_1 = require("./QueryBuilder");
6var SqlServerDriver_1 = require("../driver/sqlserver/SqlServerDriver");
7var PostgresDriver_1 = require("../driver/postgres/PostgresDriver");
8var EntityMetadata_1 = require("../metadata/EntityMetadata");
9var UpdateResult_1 = require("./result/UpdateResult");
10var ReturningStatementNotSupportedError_1 = require("../error/ReturningStatementNotSupportedError");
11var ReturningResultsEntityUpdator_1 = require("./ReturningResultsEntityUpdator");
12var SqljsDriver_1 = require("../driver/sqljs/SqljsDriver");
13var MysqlDriver_1 = require("../driver/mysql/MysqlDriver");
14var BroadcasterResult_1 = require("../subscriber/BroadcasterResult");
15var AbstractSqliteDriver_1 = require("../driver/sqlite-abstract/AbstractSqliteDriver");
16var LimitOnUpdateNotSupportedError_1 = require("../error/LimitOnUpdateNotSupportedError");
17var OracleDriver_1 = require("../driver/oracle/OracleDriver");
18var UpdateValuesMissingError_1 = require("../error/UpdateValuesMissingError");
19var EntityColumnNotFound_1 = require("../error/EntityColumnNotFound");
20/**
21 * Allows to build complex sql queries in a fashion way and execute those queries.
22 */
23var UpdateQueryBuilder = /** @class */ (function (_super) {
24 tslib_1.__extends(UpdateQueryBuilder, _super);
25 // -------------------------------------------------------------------------
26 // Constructor
27 // -------------------------------------------------------------------------
28 function UpdateQueryBuilder(connectionOrQueryBuilder, queryRunner) {
29 var _this = _super.call(this, connectionOrQueryBuilder, queryRunner) || this;
30 _this.expressionMap.aliasNamePrefixingEnabled = false;
31 return _this;
32 }
33 // -------------------------------------------------------------------------
34 // Public Implemented Methods
35 // -------------------------------------------------------------------------
36 /**
37 * Gets generated sql query without parameters being replaced.
38 */
39 UpdateQueryBuilder.prototype.getQuery = function () {
40 var sql = this.createUpdateExpression();
41 sql += this.createOrderByExpression();
42 sql += this.createLimitExpression();
43 return sql.trim();
44 };
45 /**
46 * Executes sql generated by query builder and returns raw database results.
47 */
48 UpdateQueryBuilder.prototype.execute = function () {
49 return tslib_1.__awaiter(this, void 0, void 0, function () {
50 var queryRunner, transactionStartedByUs, broadcastResult, returningResultsEntityUpdator, _a, sql, parameters, updateResult, _b, broadcastResult, error_1, rollbackError_1;
51 return tslib_1.__generator(this, function (_c) {
52 switch (_c.label) {
53 case 0:
54 queryRunner = this.obtainQueryRunner();
55 transactionStartedByUs = false;
56 _c.label = 1;
57 case 1:
58 _c.trys.push([1, 13, 18, 23]);
59 if (!(this.expressionMap.useTransaction === true && queryRunner.isTransactionActive === false)) return [3 /*break*/, 3];
60 return [4 /*yield*/, queryRunner.startTransaction()];
61 case 2:
62 _c.sent();
63 transactionStartedByUs = true;
64 _c.label = 3;
65 case 3:
66 if (!(this.expressionMap.callListeners === true && this.expressionMap.mainAlias.hasMetadata)) return [3 /*break*/, 5];
67 broadcastResult = new BroadcasterResult_1.BroadcasterResult();
68 queryRunner.broadcaster.broadcastBeforeUpdateEvent(broadcastResult, this.expressionMap.mainAlias.metadata);
69 if (!(broadcastResult.promises.length > 0)) return [3 /*break*/, 5];
70 return [4 /*yield*/, Promise.all(broadcastResult.promises)];
71 case 4:
72 _c.sent();
73 _c.label = 5;
74 case 5:
75 returningResultsEntityUpdator = new ReturningResultsEntityUpdator_1.ReturningResultsEntityUpdator(queryRunner, this.expressionMap);
76 if (this.expressionMap.updateEntity === true &&
77 this.expressionMap.mainAlias.hasMetadata &&
78 this.expressionMap.whereEntities.length > 0) {
79 this.expressionMap.extraReturningColumns = returningResultsEntityUpdator.getUpdationReturningColumns();
80 }
81 _a = tslib_1.__read(this.getQueryAndParameters(), 2), sql = _a[0], parameters = _a[1];
82 updateResult = new UpdateResult_1.UpdateResult();
83 _b = updateResult;
84 return [4 /*yield*/, queryRunner.query(sql, parameters)];
85 case 6:
86 _b.raw = _c.sent();
87 if (!(this.expressionMap.updateEntity === true &&
88 this.expressionMap.mainAlias.hasMetadata &&
89 this.expressionMap.whereEntities.length > 0)) return [3 /*break*/, 8];
90 return [4 /*yield*/, returningResultsEntityUpdator.update(updateResult, this.expressionMap.whereEntities)];
91 case 7:
92 _c.sent();
93 _c.label = 8;
94 case 8:
95 if (!(this.expressionMap.callListeners === true && this.expressionMap.mainAlias.hasMetadata)) return [3 /*break*/, 10];
96 broadcastResult = new BroadcasterResult_1.BroadcasterResult();
97 queryRunner.broadcaster.broadcastAfterUpdateEvent(broadcastResult, this.expressionMap.mainAlias.metadata);
98 if (!(broadcastResult.promises.length > 0)) return [3 /*break*/, 10];
99 return [4 /*yield*/, Promise.all(broadcastResult.promises)];
100 case 9:
101 _c.sent();
102 _c.label = 10;
103 case 10:
104 if (!transactionStartedByUs) return [3 /*break*/, 12];
105 return [4 /*yield*/, queryRunner.commitTransaction()];
106 case 11:
107 _c.sent();
108 _c.label = 12;
109 case 12: return [2 /*return*/, updateResult];
110 case 13:
111 error_1 = _c.sent();
112 if (!transactionStartedByUs) return [3 /*break*/, 17];
113 _c.label = 14;
114 case 14:
115 _c.trys.push([14, 16, , 17]);
116 return [4 /*yield*/, queryRunner.rollbackTransaction()];
117 case 15:
118 _c.sent();
119 return [3 /*break*/, 17];
120 case 16:
121 rollbackError_1 = _c.sent();
122 return [3 /*break*/, 17];
123 case 17: throw error_1;
124 case 18:
125 if (!(queryRunner !== this.queryRunner)) return [3 /*break*/, 20];
126 return [4 /*yield*/, queryRunner.release()];
127 case 19:
128 _c.sent();
129 _c.label = 20;
130 case 20:
131 if (!(this.connection.driver instanceof SqljsDriver_1.SqljsDriver && !queryRunner.isTransactionActive)) return [3 /*break*/, 22];
132 return [4 /*yield*/, this.connection.driver.autoSave()];
133 case 21:
134 _c.sent();
135 _c.label = 22;
136 case 22: return [7 /*endfinally*/];
137 case 23: return [2 /*return*/];
138 }
139 });
140 });
141 };
142 // -------------------------------------------------------------------------
143 // Public Methods
144 // -------------------------------------------------------------------------
145 /**
146 * Values needs to be updated.
147 */
148 UpdateQueryBuilder.prototype.set = function (values) {
149 this.expressionMap.valuesSet = values;
150 return this;
151 };
152 /**
153 * Sets WHERE condition in the query builder.
154 * If you had previously WHERE expression defined,
155 * calling this function will override previously set WHERE conditions.
156 * Additionally you can add parameters used in where expression.
157 */
158 UpdateQueryBuilder.prototype.where = function (where, parameters) {
159 this.expressionMap.wheres = []; // don't move this block below since computeWhereParameter can add where expressions
160 var condition = this.computeWhereParameter(where);
161 if (condition)
162 this.expressionMap.wheres = [{ type: "simple", condition: condition }];
163 if (parameters)
164 this.setParameters(parameters);
165 return this;
166 };
167 /**
168 * Adds new AND WHERE condition in the query builder.
169 * Additionally you can add parameters used in where expression.
170 */
171 UpdateQueryBuilder.prototype.andWhere = function (where, parameters) {
172 this.expressionMap.wheres.push({ type: "and", condition: this.computeWhereParameter(where) });
173 if (parameters)
174 this.setParameters(parameters);
175 return this;
176 };
177 /**
178 * Adds new OR WHERE condition in the query builder.
179 * Additionally you can add parameters used in where expression.
180 */
181 UpdateQueryBuilder.prototype.orWhere = function (where, parameters) {
182 this.expressionMap.wheres.push({ type: "or", condition: this.computeWhereParameter(where) });
183 if (parameters)
184 this.setParameters(parameters);
185 return this;
186 };
187 /**
188 * Adds new AND WHERE with conditions for the given ids.
189 */
190 UpdateQueryBuilder.prototype.whereInIds = function (ids) {
191 return this.where(this.createWhereIdsExpression(ids));
192 };
193 /**
194 * Adds new AND WHERE with conditions for the given ids.
195 */
196 UpdateQueryBuilder.prototype.andWhereInIds = function (ids) {
197 return this.andWhere(this.createWhereIdsExpression(ids));
198 };
199 /**
200 * Adds new OR WHERE with conditions for the given ids.
201 */
202 UpdateQueryBuilder.prototype.orWhereInIds = function (ids) {
203 return this.orWhere(this.createWhereIdsExpression(ids));
204 };
205 /**
206 * Optional returning/output clause.
207 */
208 UpdateQueryBuilder.prototype.output = function (output) {
209 return this.returning(output);
210 };
211 /**
212 * Optional returning/output clause.
213 */
214 UpdateQueryBuilder.prototype.returning = function (returning) {
215 // not all databases support returning/output cause
216 if (!this.connection.driver.isReturningSqlSupported())
217 throw new ReturningStatementNotSupportedError_1.ReturningStatementNotSupportedError();
218 this.expressionMap.returning = returning;
219 return this;
220 };
221 /**
222 * Sets ORDER BY condition in the query builder.
223 * If you had previously ORDER BY expression defined,
224 * calling this function will override previously set ORDER BY conditions.
225 */
226 UpdateQueryBuilder.prototype.orderBy = function (sort, order, nulls) {
227 if (order === void 0) { order = "ASC"; }
228 var _a, _b;
229 if (sort) {
230 if (sort instanceof Object) {
231 this.expressionMap.orderBys = sort;
232 }
233 else {
234 if (nulls) {
235 this.expressionMap.orderBys = (_a = {}, _a[sort] = { order: order, nulls: nulls }, _a);
236 }
237 else {
238 this.expressionMap.orderBys = (_b = {}, _b[sort] = order, _b);
239 }
240 }
241 }
242 else {
243 this.expressionMap.orderBys = {};
244 }
245 return this;
246 };
247 /**
248 * Adds ORDER BY condition in the query builder.
249 */
250 UpdateQueryBuilder.prototype.addOrderBy = function (sort, order, nulls) {
251 if (order === void 0) { order = "ASC"; }
252 if (nulls) {
253 this.expressionMap.orderBys[sort] = { order: order, nulls: nulls };
254 }
255 else {
256 this.expressionMap.orderBys[sort] = order;
257 }
258 return this;
259 };
260 /**
261 * Sets LIMIT - maximum number of rows to be selected.
262 */
263 UpdateQueryBuilder.prototype.limit = function (limit) {
264 this.expressionMap.limit = limit;
265 return this;
266 };
267 /**
268 * Indicates if entity must be updated after update operation.
269 * This may produce extra query or use RETURNING / OUTPUT statement (depend on database).
270 * Enabled by default.
271 */
272 UpdateQueryBuilder.prototype.whereEntity = function (entity) {
273 var _this = this;
274 if (!this.expressionMap.mainAlias.hasMetadata)
275 throw new Error(".whereEntity method can only be used on queries which update real entity table.");
276 this.expressionMap.wheres = [];
277 var entities = entity instanceof Array ? entity : [entity];
278 entities.forEach(function (entity) {
279 var entityIdMap = _this.expressionMap.mainAlias.metadata.getEntityIdMap(entity);
280 if (!entityIdMap)
281 throw new Error("Provided entity does not have ids set, cannot perform operation.");
282 _this.orWhereInIds(entityIdMap);
283 });
284 this.expressionMap.whereEntities = entities;
285 return this;
286 };
287 /**
288 * Indicates if entity must be updated after update operation.
289 * This may produce extra query or use RETURNING / OUTPUT statement (depend on database).
290 * Enabled by default.
291 */
292 UpdateQueryBuilder.prototype.updateEntity = function (enabled) {
293 this.expressionMap.updateEntity = enabled;
294 return this;
295 };
296 // -------------------------------------------------------------------------
297 // Protected Methods
298 // -------------------------------------------------------------------------
299 /**
300 * Creates UPDATE express used to perform insert query.
301 */
302 UpdateQueryBuilder.prototype.createUpdateExpression = function () {
303 var _this = this;
304 var valuesSet = this.getValueSet();
305 var metadata = this.expressionMap.mainAlias.hasMetadata ? this.expressionMap.mainAlias.metadata : undefined;
306 // prepare columns and values to be updated
307 var updateColumnAndValues = [];
308 var newParameters = {};
309 var parametersCount = this.connection.driver instanceof MysqlDriver_1.MysqlDriver ||
310 this.connection.driver instanceof OracleDriver_1.OracleDriver ||
311 this.connection.driver instanceof AbstractSqliteDriver_1.AbstractSqliteDriver
312 ? 0 : Object.keys(this.expressionMap.nativeParameters).length;
313 if (metadata) {
314 EntityMetadata_1.EntityMetadata.createPropertyPath(metadata, valuesSet).forEach(function (propertyPath) {
315 // todo: make this and other query builder to work with properly with tables without metadata
316 var columns = metadata.findColumnsWithPropertyPath(propertyPath);
317 if (columns.length <= 0) {
318 throw new EntityColumnNotFound_1.EntityColumnNotFound(propertyPath);
319 }
320 columns.forEach(function (column) {
321 if (!column.isUpdate) {
322 return;
323 }
324 var paramName = "upd_" + column.databaseName;
325 //
326 var value = column.getEntityValue(valuesSet);
327 if (column.referencedColumn && value instanceof Object) {
328 value = column.referencedColumn.getEntityValue(value);
329 }
330 else if (!(value instanceof Function)) {
331 value = _this.connection.driver.preparePersistentValue(value, column);
332 }
333 // todo: duplication zone
334 if (value instanceof Function) { // support for SQL expressions in update query
335 updateColumnAndValues.push(_this.escape(column.databaseName) + " = " + value());
336 }
337 else {
338 if (_this.connection.driver instanceof SqlServerDriver_1.SqlServerDriver) {
339 value = _this.connection.driver.parametrizeValue(column, value);
340 // } else if (value instanceof Array) {
341 // value = new ArrayParameter(value);
342 }
343 if (_this.connection.driver instanceof MysqlDriver_1.MysqlDriver ||
344 _this.connection.driver instanceof OracleDriver_1.OracleDriver ||
345 _this.connection.driver instanceof AbstractSqliteDriver_1.AbstractSqliteDriver) {
346 newParameters[paramName] = value;
347 }
348 else {
349 _this.expressionMap.nativeParameters[paramName] = value;
350 }
351 var expression = null;
352 if (_this.connection.driver instanceof MysqlDriver_1.MysqlDriver && _this.connection.driver.spatialTypes.indexOf(column.type) !== -1) {
353 expression = "GeomFromText(" + _this.connection.driver.createParameter(paramName, parametersCount) + ")";
354 }
355 else if (_this.connection.driver instanceof PostgresDriver_1.PostgresDriver && _this.connection.driver.spatialTypes.indexOf(column.type) !== -1) {
356 if (column.srid != null) {
357 expression = "ST_SetSRID(ST_GeomFromGeoJSON(" + _this.connection.driver.createParameter(paramName, parametersCount) + "), " + column.srid + ")::" + column.type;
358 }
359 else {
360 expression = "ST_GeomFromGeoJSON(" + _this.connection.driver.createParameter(paramName, parametersCount) + ")::" + column.type;
361 }
362 }
363 else {
364 expression = _this.connection.driver.createParameter(paramName, parametersCount);
365 }
366 updateColumnAndValues.push(_this.escape(column.databaseName) + " = " + expression);
367 parametersCount++;
368 }
369 });
370 });
371 if (metadata.versionColumn)
372 updateColumnAndValues.push(this.escape(metadata.versionColumn.databaseName) + " = " + this.escape(metadata.versionColumn.databaseName) + " + 1");
373 if (metadata.updateDateColumn)
374 updateColumnAndValues.push(this.escape(metadata.updateDateColumn.databaseName) + " = CURRENT_TIMESTAMP"); // todo: fix issue with CURRENT_TIMESTAMP(6) being used, can "DEFAULT" be used?!
375 }
376 else {
377 Object.keys(valuesSet).map(function (key) {
378 var value = valuesSet[key];
379 // todo: duplication zone
380 if (value instanceof Function) { // support for SQL expressions in update query
381 updateColumnAndValues.push(_this.escape(key) + " = " + value());
382 }
383 else {
384 // we need to store array values in a special class to make sure parameter replacement will work correctly
385 // if (value instanceof Array)
386 // value = new ArrayParameter(value);
387 if (_this.connection.driver instanceof MysqlDriver_1.MysqlDriver ||
388 _this.connection.driver instanceof OracleDriver_1.OracleDriver ||
389 _this.connection.driver instanceof AbstractSqliteDriver_1.AbstractSqliteDriver) {
390 newParameters[key] = value;
391 }
392 else {
393 _this.expressionMap.nativeParameters[key] = value;
394 }
395 updateColumnAndValues.push(_this.escape(key) + " = " + _this.connection.driver.createParameter(key, parametersCount));
396 parametersCount++;
397 }
398 });
399 }
400 if (updateColumnAndValues.length <= 0) {
401 throw new UpdateValuesMissingError_1.UpdateValuesMissingError();
402 }
403 // we re-write parameters this way because we want our "UPDATE ... SET" parameters to be first in the list of "nativeParameters"
404 // because some drivers like mysql depend on order of parameters
405 if (this.connection.driver instanceof MysqlDriver_1.MysqlDriver ||
406 this.connection.driver instanceof OracleDriver_1.OracleDriver ||
407 this.connection.driver instanceof AbstractSqliteDriver_1.AbstractSqliteDriver) {
408 this.expressionMap.nativeParameters = Object.assign(newParameters, this.expressionMap.nativeParameters);
409 }
410 // get a table name and all column database names
411 var whereExpression = this.createWhereExpression();
412 var returningExpression = this.createReturningExpression();
413 // generate and return sql update query
414 if (returningExpression && (this.connection.driver instanceof PostgresDriver_1.PostgresDriver || this.connection.driver instanceof OracleDriver_1.OracleDriver || this.connection.driver instanceof CockroachDriver_1.CockroachDriver)) {
415 return "UPDATE " + this.getTableName(this.getMainTableName()) + " SET " + updateColumnAndValues.join(", ") + whereExpression + " RETURNING " + returningExpression;
416 }
417 else if (returningExpression && this.connection.driver instanceof SqlServerDriver_1.SqlServerDriver) {
418 return "UPDATE " + this.getTableName(this.getMainTableName()) + " SET " + updateColumnAndValues.join(", ") + " OUTPUT " + returningExpression + whereExpression;
419 }
420 else {
421 return "UPDATE " + this.getTableName(this.getMainTableName()) + " SET " + updateColumnAndValues.join(", ") + whereExpression; // todo: how do we replace aliases in where to nothing?
422 }
423 };
424 /**
425 * Creates "ORDER BY" part of SQL query.
426 */
427 UpdateQueryBuilder.prototype.createOrderByExpression = function () {
428 var _this = this;
429 var orderBys = this.expressionMap.orderBys;
430 if (Object.keys(orderBys).length > 0)
431 return " ORDER BY " + Object.keys(orderBys)
432 .map(function (columnName) {
433 if (typeof orderBys[columnName] === "string") {
434 return _this.replacePropertyNames(columnName) + " " + orderBys[columnName];
435 }
436 else {
437 return _this.replacePropertyNames(columnName) + " " + orderBys[columnName].order + " " + orderBys[columnName].nulls;
438 }
439 })
440 .join(", ");
441 return "";
442 };
443 /**
444 * Creates "LIMIT" parts of SQL query.
445 */
446 UpdateQueryBuilder.prototype.createLimitExpression = function () {
447 var limit = this.expressionMap.limit;
448 if (limit) {
449 if (this.connection.driver instanceof MysqlDriver_1.MysqlDriver) {
450 return " LIMIT " + limit;
451 }
452 else {
453 throw new LimitOnUpdateNotSupportedError_1.LimitOnUpdateNotSupportedError();
454 }
455 }
456 return "";
457 };
458 /**
459 * Gets array of values need to be inserted into the target table.
460 */
461 UpdateQueryBuilder.prototype.getValueSet = function () {
462 if (this.expressionMap.valuesSet instanceof Object)
463 return this.expressionMap.valuesSet;
464 throw new UpdateValuesMissingError_1.UpdateValuesMissingError();
465 };
466 return UpdateQueryBuilder;
467}(QueryBuilder_1.QueryBuilder));
468exports.UpdateQueryBuilder = UpdateQueryBuilder;
469
470//# sourceMappingURL=UpdateQueryBuilder.js.map