diff --git a/entity/SlaveAdministration_entity/recordcontainers/db/orderClauseProcess.js b/entity/SlaveAdministration_entity/recordcontainers/db/orderClauseProcess.js index 15573a0041204ad95d34527e83c5eea5fd65893f..ebce5894dd3bdab6da45463445e5722003d8afc4 100644 --- a/entity/SlaveAdministration_entity/recordcontainers/db/orderClauseProcess.js +++ b/entity/SlaveAdministration_entity/recordcontainers/db/orderClauseProcess.js @@ -1,3 +1,3 @@ import { db, result } from "@aditosoftware/jdito-types"; -result.object({"ASYS_SYNCSLAVES.LASTSYNC": db.ASCENDING}); \ No newline at end of file +result.object({ "ASYS_SYNCSLAVES.LASTSYNC": db.ASCENDING }); \ No newline at end of file diff --git a/process/PreparedSqlStatement_lib/PreparedSqlStatement_lib.aod b/process/PreparedSqlStatement_lib/PreparedSqlStatement_lib.aod new file mode 100644 index 0000000000000000000000000000000000000000..45fc5569e2e79988c8a7cf2557d794e6b19f73e8 --- /dev/null +++ b/process/PreparedSqlStatement_lib/PreparedSqlStatement_lib.aod @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.3.0" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.3.0"> + <name>PreparedSqlStatement_lib</name> + <majorModelMode>DISTRIBUTED</majorModelMode> + <process>%aditoprj%/process/PreparedSqlStatement_lib/process.js</process> + <variants> + <element>LIBRARY</element> + </variants> +</process> diff --git a/process/PreparedSqlStatement_lib/process.js b/process/PreparedSqlStatement_lib/process.js new file mode 100644 index 0000000000000000000000000000000000000000..421142594bff6806ae05e52f6ead6bfebc80497b --- /dev/null +++ b/process/PreparedSqlStatement_lib/process.js @@ -0,0 +1,138 @@ + +/** + * Object containing a prepared sql statement + * + * @param {string} [pSqlString] - The sql statement that contains already the placeholders for values: `?`. + * + * **Never** include values from external sources such as user input, web services, entity-parameters, etc. + * directly as pSqlString. Doing so will expose the system to SQL injection attacks because only the + * `pPreparedValues` are set as bind parameters and/or are escaped automatically. + * + * @param {[string, number][]} [pPreparedValues] - The values for the query + * @class + */ +export function PreparedSqlStatement(pSqlString, pPreparedValues) +{ + /** + * @type {string} + */ + this.sqlString = ""; + /** + * @type {[string, number][]} + */ + this.preparedValues = []; + + this.append(pSqlString, pPreparedValues); +} + +/** + * Creates a new PreparedSqlStatement object from a prepared statement array + * + * @param {[string, [string, number][]]} preparedArray - The sql statement and its prepared values and types. + * + * **Never** include values from external sources such as web services, entity-parameters, etc. + * directly and unverified as parameter here. Doing so will expose the system to SQL injection attacks because the + * first element of the array-param is the sql-expression part. + * This means, that a sql expression could be passed from external sources into the execution. + * + * Example A, **not okay**: + * ``` + * var conditionParam = JSON.parse(vars.get("$param.condition_param")); + * var prepared = PreparedSqlStatement.fromArray(conditionParam);// sql injection possible here + * entityCondition.and(prepared); + * ``` + * The parameter could be overwritten by the user with a value like `["); drop table users;", []]` + * + * Example B, **okay**: + * ``` + * var idList = JSON.parse(vars.get("$param.idList_param")); + * var prepared = PreparedSqlStatement.fromArray(["MYTABLE.MYID in (?, ?, ?)", idList]);// sql injection not possible + * entityCondition.and(prepared); + * ``` + * Because the values are passed as bind parameters, the user may pass `["''); drop table users;"]` as parameter but + * that is never parsed and executed as sql-expression. + * + * For this particular example here the best solution however is to use the `SqlBuilder` without using + * `PreparedSqlStatement` at all. + * + * + * @return {PreparedSqlStatement} A new PreparedSqlStatement object + */ +PreparedSqlStatement.fromArray = function([pSqlString, pPreparedValues]) +{ + return new PreparedSqlStatement(pSqlString, pPreparedValues); +}; + +/** + * Creates a prepared statement array that can be used by some db.* methods + * + * @return {[string, [string, number][]]} The statement as an array + */ +PreparedSqlStatement.prototype.toArray = function() +{ + return [this.sqlString, this.preparedValues]; +}; + +/** + * Adds the given sql to the statement + * + * @param {string} pSqlString - The sql statement that contains already the placeholders for values: `?`. + * + * **Never** include values from external sources such as user input, web services, entity-parameters, etc. + * directly as pSqlString. Doing so will expose the system to SQL injection attacks because only the + * `pPreparedValues` are set as bind parameters and/or are escaped automatically. + * + * @param {[string, number][]} [pPreparedValues] - The values for the query + * @return {PreparedSqlStatement} The current object + */ +PreparedSqlStatement.prototype.append = function(pSqlString, pPreparedValues) +{ + if (pSqlString) + { + this.sqlString += pSqlString; + } + if (pPreparedValues) + { + pPreparedValues.forEach(([value, type]) => this.addPreparedValue(value, type)); + } + return this; +}; + +/** + * Adds the sql from a PreparedSqlStatement to the statement + * + * @param {PreparedSqlStatement} pPreparedStatement - The sql statement and its prepared values and types + * @param {string} [pSeparator] - A string that is added between the existing and new sql + * @return {PreparedSqlStatement} The current object + */ +PreparedSqlStatement.prototype.appendStatement = function(pPreparedStatement, pSeparator) +{ + if (pSeparator == undefined || !this.sqlString) + { + pSeparator = ""; + } + return this.append(pSeparator + pPreparedStatement.sqlString, pPreparedStatement.preparedValues); +}; + +/** + * Adds a prepared value + * + * @param {string} pValue - The value for the query + * @param {number} pType - The type of the value (SQLTYPES.*) + * @return {PreparedSqlStatement} The current object + */ +PreparedSqlStatement.prototype.addPreparedValue = function(pValue, pType) +{ + this.preparedValues.push([pValue, pType]); + return this; +}; + +/** + * Checks if the sql stamtement has no content + * + * @returns {boolean} If the statement is empty + */ +PreparedSqlStatement.prototype.isEmpty = function() +{ + return this.sqlString == ""; +}; \ No newline at end of file diff --git a/process/Sql_lib/process.js b/process/Sql_lib/process.js index b5fe4b8975ce7859e1943b046c16cad8cdeb9b82..cac1aed0a9f8bba8c65735848c3abaa59b54131c 100644 --- a/process/Sql_lib/process.js +++ b/process/Sql_lib/process.js @@ -1,64 +1,96 @@ -import { db, logging, SQLTYPES, text, translate, util, vars } from "@aditosoftware/jdito-types"; +import { db, logging, project, SQLTYPES, text, translate, util, vars } from "@aditosoftware/jdito-types"; import { ArrayUtils, Utils } from "Util_lib"; +import { PreparedSqlStatement } from "PreparedSqlStatement_lib"; // see Documentation property of this lib for further explanation +/** + * @typedef {[string, number][]} PreparedValueArray + * @typedef {[string, PreparedValueArray]} PreparedSqlArray + */ + /** * Creates a new SqlBuilder object and sets the select clause of the sql. * - * @param {string|Array|SqlBuilder} pFields You can pass:<br/> - * - A String is just used AS IT IS. (e.g. "FIRSTNAME, LASTNAME")<br/> - * - SqlBuilder is used as Subquery<br/> - * - The array can also contain Strings, SqlBuilder which are just concatenated (e.g. ["FIRSTNAME", "LASTNAME", someSqlBuilderContainingFullSelect])<br/> + * @param {string|Array|SqlBuilder} pFields - You can pass: + * - A String is just used AS IT IS. (e.g. "FIRSTNAME, LASTNAME") + * - SqlBuilder is used as Subquery + * - The array can also contain Strings, SqlBuilder which are just concatenated (e.g. ["FIRSTNAME", "LASTNAME", someSqlBuilderContainingFullSelect]) * Please see .select() for more information and examples. - * @param {string} [pAlias=currentAlias] This alias is used for fetching the ColumnTypes and also for the .table, .cell, .updateData, ... -functions + * @param {string} [pAlias=currentAlias] - This alias is used for fetching the ColumnTypes and also for the .table, .cell, .updateData, ... -functions * @return {SqlBuilder} A new SqlBuilder object already containing the provided fields * * @example - * var lastname = "Huber"; + * let lastname = "Huber"; * - * var persons = newSelect("FIRSTNAME") + * let persons = newSelect("FIRSTNAME") * .from("PERSON") * .where("PERSON.LASTNAME", lastname) * .arrayColumn(); */ export function newSelect(pFields, pAlias) { - return new SqlBuilder(pAlias).select(pFields); + return new SqlBuilder(pAlias) + .select(pFields); } /** - * Creates a new SqlBuilder object and calls .where on it.<br/> - * This is very useful if you just need a condition as you can pass the first condition directly.<br/> - * Note: Even if you ommit all parameters, you do not have to call .where again. You can directly write .and / .or after newWhere().<br/> + * Creates a new SqlBuilder object and calls .where on it. + * This is very useful if you just need a condition as you can pass the first condition directly. + * Note: Even if you ommit all parameters, you do not have to call .where again. You can directly write .and / .or after newWhere(). * - * @param {string|Array|SqlBuilder} [pFieldOrCond] If this is the only parameter, it is used as Subselect <br/> - * else it is used as Field. <br/> - * Please see .where() for more information and examples. - * @param {string|SqlBuilder|Array} [pValue] This is the value whitch is used for the condition.<br/> - * Basically it can be nearly everything you need.<br/> - * Please see .where() for more information and examples. - * @param {string} [pCondition="# = ?"] This is the condition which should be used.<br/> - * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/> - * ? will be replaced by pValue<br/> - * <strong>IMPORTANT: the # has to be before the ?</strong><br/> - * Please see .where() for more information and examples. - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] You can specify which datatype should be used for the prepared statement<br/> - * In most cases you don't need this.<br/> - * Please see .where() for more information and examples. - * @param {string} [pAlias=currentAlias] This alias is used for fetching the ColumnTypes and also for the .table, .cell, .updateData, ... -functions + * @param {string|PreparedSqlStatement|SqlBuilder|null} [pFieldOrCond] - This can be either a full condition (then it has to be the only parameter provided) or a field. + * + * If you pass a condition (the only param), these types are allowed: + * - string: just added as it is + * - PreparedSqlStatement: a PreparedSqlStatement object is used as it is + * - SqlBuilder: ONLY THE WHERE-CONDITION is used from it + * + * If you pass a field (at least pValue has to be filled), this param provides the field information to + * load the SQLTYPE for this condition. + * It can be provided in the following ways: + * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" + * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case + * Note2: if you need a table alias use the next variant (as array) + * - an array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"] + * - a SqlBuilder: the full select is used as subselect and compared with pValue. + * (e.g. select * from PERSON where (select "NAME" from ORGANISATION where ... ) = ?) + * Note: for this you have to provide pFieldType as the type cannot be calculated from the subselect! + * Note: this can also be null if you don't need the field and use a pCondition without a # + * + * @param {*} [pValue] - This is the value which is used for the condition. The type can be anything, but some types are handled in different ways: + * - string: Is just used as value for the prepared statement. Of course it has to fit the type of the db-column. + * If the string starts with a '$' it is recognized as a jdito variable and will be resolved automatically, + * Use 2 '$' to escape the $ if you don't want it to be treated as jdito variable (see {@link SqlUtils.escapeVars}) + * - Array: If the condition allows multiple values (for example 'in'), the values can be passed in an array. + * - SqlBuilder: Can be used for sub-queries + * - PreparedSqlStatement: Can be used for sub-queries + * - all other types: Will be converted to a string + * - NOT ALLOWED: Prepared sql arrays - these will raise an error, because they could provoke sql injection + * + * + * @param {string} [pCondition="# = ?"] - This is the condition which should be used to compare the field with the value. + * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit # + * ? will be replaced by pValue + * <strong>IMPORTANT: the # has to be before the ?</strong> + * + * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] - You can specify which datatype should be used for the prepared statement + * In most cases you don't need this, but it's required if pFieldOrCond is a sub-select or null. + * This is helpful if you for example have a pCondition "year(#) = ?" + * then the db-field is DATETIME, but the value is INTEGER. In this case you can overwrite the type. + * @param {string} [pAlias=currentAlias] - This alias is used for fetching the ColumnTypes and also for the .table, .cell, .updateData, ... -functions * @return {SqlBuilder} A new SqlBuilder object which already called .where * * @example * // examples, how where / whereIfSet could be used in a conditionProcess. * //////Example 1///// - * var cond = newWhereIfSet("CONTACT.PERSON_ID", "$param.PersonId_param") + * let cond = newWhereIfSet("CONTACT.PERSON_ID", "$param.PersonId_param") * .andIfSet("CONTACT.PERSON_ID", JSON.parse(vars.getString("$param.BlacklistPersons_param")), SqlBuilder.NOT_IN()) * * result.string(cond.toString()); * * //////Example 2///// - * var cond = newWhere(); + * let cond = newWhere(); * * // note: we can use .and* now without an extra .where * if (SOMECHECKS) @@ -71,41 +103,67 @@ export function newSelect(pFields, pAlias) */ export function newWhere(pFieldOrCond, pValue, pCondition, pFieldType, pAlias) { - return new SqlBuilder(pAlias).where(pFieldOrCond, pValue, pCondition, pFieldType); + return new SqlBuilder(pAlias) + .where(pFieldOrCond, pValue, pCondition, pFieldType); } /** - * Creates a new SqlBuilder object and calls .whereIfSet on it.<br/> - * This is very useful if you just need a condition as you can pass the first condition directly.<br/> + * Creates a new SqlBuilder object and calls .whereIfSet on it. + * This is very useful if you just need a condition as you can pass the first condition directly. * Note: Even if you ommit all parameters, you do not have to call .where again. You can directly write .and / .or after newWhere(). * - * @param {string|Array|SqlBuilder} [pFieldOrCond] If this is the only parameter, it is used as Subselect <br/> - * else it is used as Field. <br/> - * Please see .whereIfSet() for more information and examples. - * @param {string|SqlBuilder|Array} [pValue] This is the value whitch is used for the condition.<br/> - * Basically it can be nearly everything you need.<br/> - * Please see .whereIfSet() for more information and examples. - * @param {string} [pCondition="# = ?"] This is the condition which should be used.<br/> - * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/> - * ? will be replaced by pValue<br/> - * <strong>IMPORTANT: the # has to be before the ?</strong><br/> - * Please see .whereIfSet() for more information and examples. - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] You can specify which datatype should be used for the prepared statement<br/> - * In most cases you don't need this.<br/> - * Please see .whereIfSet() for more information and examples. - * @param {string} [pAlias=currentAlias] This alias is used for fetching the ColumnTypes and also for the .table, .cell, .updateData, ... -functions + * @param {string|PreparedSqlStatement|SqlBuilder|null} [pFieldOrCond] - This can be either a full condition (then it has to be the only parameter provided) or a field. + * + * If you pass a condition (the only param), these types are allowed: + * - string: just added as it is + * - PreparedSqlStatement: a PreparedSqlStatement object is used as it is + * - SqlBuilder: ONLY THE WHERE-CONDITION is used from it + * + * If you pass a field (at least pValue has to be filled), this param provides the field information to + * load the SQLTYPE for this condition. + * It can be provided in the following ways: + * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" + * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case + * Note2: if you need a table alias use the next variant (as array) + * - an array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"] + * - a SqlBuilder: the full select is used as subselect and compared with pValue. + * (e.g. select * from PERSON where (select "NAME" from ORGANISATION where ... ) = ?) + * Note: for this you have to provide pFieldType as the type cannot be calculated from the subselect! + * Note: this can also be null if you don't need the field and use a pCondition without a # + * + * @param {*} [pValue] - This is the value which is used for the condition. The type can be anything, but some types are handled in different ways: + * - string: Is just used as value for the prepared statement. Of course it has to fit the type of the db-column. + * If the string starts with a '$' it is recognized as a jdito variable and will be resolved automatically, + * Use 2 '$' to escape the $ if you don't want it to be treated as jdito variable (see {@link SqlUtils.escapeVars}) + * - Array: If the condition allows multiple values (for example 'in'), the values can be passed in an array. + * - SqlBuilder: Can be used for sub-queries + * - PreparedSqlStatement: Can be used for sub-queries + * - all other types: Will be converted to a string + * - NOT ALLOWED: Prepared sql arrays - these will raise an error, because they could provoke sql injection + * + * + * @param {string} [pCondition="# = ?"] - This is the condition which should be used to compare the field with the value. + * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit # + * ? will be replaced by pValue + * <strong>IMPORTANT: the # has to be before the ?</strong> + * + * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] - You can specify which datatype should be used for the prepared statement + * In most cases you don't need this, but it's required if pFieldOrCond is a sub-select or null. + * This is helpful if you for example have a pCondition "year(#) = ?" + * then the db-field is DATETIME, but the value is INTEGER. In this case you can overwrite the type. + * @param {string} [pAlias=currentAlias] - This alias is used for fetching the ColumnTypes and also for the .table, .cell, .updateData, ... -functions * @return {SqlBuilder} A new SqlBuilder object which already called .whereIfSet * * @example * // examples, how where / whereIfSet could be used in a conditionProcess. * //////Example 1///// - * var cond = newWhereIfSet("CONTACT.PERSON_ID", "$param.PersonId_param") + * let cond = newWhereIfSet("CONTACT.PERSON_ID", "$param.PersonId_param") * .andIfSet("CONTACT.PERSON_ID", JSON.parse(vars.getString("$param.BlacklistPersons_param")), SqlBuilder.NOT_IN()) * * result.string(cond.toString()); * * //////Example 2///// - * var cond = newWhere(); + * let cond = newWhere(); * * // note: we can use .and* now without an extra .where * if (SOMECHECKS) @@ -118,31 +176,48 @@ export function newWhere(pFieldOrCond, pValue, pCondition, pFieldType, pAlias) */ export function newWhereIfSet(pFieldOrCond, pValue, pCondition, pFieldType, pAlias) { - return new SqlBuilder(pAlias).whereIfSet(pFieldOrCond, pValue, pCondition, pFieldType); + return new SqlBuilder(pAlias) + .whereIfSet(pFieldOrCond, pValue, pCondition, pFieldType); } /** - * Object for building sqls. The main purpose of this is to make it easy to use prepared statements.<br/> - * You should ALWAYS use prepared statemnts for Security and maybe also for performance reasons.<br/> - * If you are not used to use prepared statements all the time you WILL forget to use it, when it's really needed. (eg. direct user input, not just ids)<br/> - * <br/> - * This can also be useful to build complex sqls where parts should be added<br/> - * dynamically while keeping the code clean.<br/> - * <br/> - * There exist some shortcut funtions<br/> - * - if you need a full select use newSelect(...)<br/> + * Object for building sqls. The main purpose of this is to make it easy to use prepared statements. + * You should ALWAYS use prepared statemnts for Security and maybe also for performance reasons. + * If you are not used to use prepared statements all the time you WILL forget to use it, when it's really needed. (eg. direct user input, not just ids) + * + * This can also be useful to build complex sqls where parts should be added + * dynamically while keeping the code clean. + * + * There exist some shortcut funtions + * - if you need a full select use newSelect(...) * - if you need only a condition use newWhere(...) or newWhereIfSet(...) * - * @param {string} [pAlias=currentAlias] This alias is used for fetching the ColumnTypes and also for the .table, .cell, .updateData, ... -functions + * @param {string} [pAlias=currentAlias] - This alias is used for fetching the ColumnTypes and also for the .table, .cell, .updateData, ... -functions + * @property {PreparedSqlStatement} _select + * @property {PreparedSqlStatement} _from + * @property {string} _tableName - Caches the table name of a condition, used for insert/update/delete + * @property {PreparedSqlStatement[]} _joins + * @property {PreparedSqlStatement} _groupBy + * @property {PreparedSqlStatement} _having + * @property {PreparedSqlStatement} _orderBy + * @property {PreparedSqlStatement[]} _unions + * @property {number} _startRow + * @property {number} _pageSize + * @property {boolean} _hasMoreRows + * @property {string} _subselectAlias + * @property {PreparedSqlStatement} _where + * @property {SqlBuilder._WhereStatus} _whereStatus * @class */ -export function SqlBuilder (pAlias) +export function SqlBuilder(pAlias) { if(!(this instanceof SqlBuilder)) + { throw SqlBuilder._ERROR_INSTANCIATE_WITH_NEW(); + } this._select = null; this._from = null; - this._tableName = null; //for insert/update/delete + this._tableName = null; this._joins = []; this._groupBy = null; this._having = null; @@ -157,203 +232,215 @@ export function SqlBuilder (pAlias) this._subselectAlias = null; - this._where = {}; + this._where = null; + /** + * @typedef {object} SqlBuilder._WhereStatus + * @property {boolean} _lastWasOr - If the last condition was an OR. For better bracket-placement + * @property {boolean} _previouslyOnlyOr - Also for better bracket-placement + * @property {boolean} _whereWasCalled - .where has always to be called first for a better semantic + */ + this._whereStatus = null; this._initWhere(); } /** - * @return {Symbol} + * @return {symbol} */ -SqlBuilder.getCanBuildSqlSymbol = function () +SqlBuilder.getCanBuildSqlSymbol = function() { - return Symbol["for"]("canBuildSql"); -} + return Symbol.for("canBuildSql"); +}; -SqlBuilder.defineCanBuildSql = function (pObject) +/** + * @param {object} pObject + */ +SqlBuilder.defineCanBuildSql = function(pObject) { pObject[SqlBuilder.getCanBuildSqlSymbol()] = true; -} +}; -SqlBuilder.checkCanBuildSql = function (pObject) +/** + * @param {object} pObject + */ +SqlBuilder.checkCanBuildSql = function(pObject) { return pObject[SqlBuilder.getCanBuildSqlSymbol()]; -} +}; SqlBuilder.defineCanBuildSql(SqlBuilder.prototype); /** - * Deep copies the SqlBuilder object and returns a new one.<br/> + * Deep copies the SqlBuilder object and returns a new one. * Use this if you want to add for example add additional parameters without modifying the current builder. + * * @return a full copy of the current SqlBuilder */ SqlBuilder.prototype.copy = function() { return Utils.clone(this); -} +}; // errors which are thrown by the SqlBuilder SqlBuilder._ERROR_INSTANCIATE_WITH_NEW = function() { - return new Error(translate.text("SqlBuilder must be instanciated with 'new' or one of the factory methods (newSelect, newWhere, newWhereIfSet)")); -} + return new Error("SqlBuilder must be instanciated with 'new' or one of the factory methods (newSelect, newWhere, newWhereIfSet)"); +}; SqlBuilder._ERROR_INVALID_CONDITION_VALUE_TYPE = function() { - return new Error(translate.text("SqlBuilder: invalid value-type for pCondition")); -} + return new Error("SqlBuilder: invalid value-type for pCondition"); +}; SqlBuilder._ERROR_NO_CONDITION = function() { - return new Error(translate.text("SqlBuilder: if you use a subQuery (e.g. SqlBuilder) you have to provide pCondition (e.g. \"exists ?\")")); -} + return new Error("SqlBuilder: if you use a subQuery (e.g. SqlBuilder) you have to provide pCondition (e.g. \"exists ?\")"); +}; SqlBuilder._ERROR_INVALID_SUBQUERY_TYPE = function() { - return new Error(translate.text("SqlBuilder: invalid value-type for pFieldOrCond. It can be a fully qualified SqlBuilder (e.g. select, from, ... have to be set) or an jdito-prepared-statement array")); -} + return new Error("SqlBuilder: invalid value-type for pFieldOrCond. It can be a fully qualified SqlBuilder (e.g. select, from, ... have to be set) or an jdito-prepared-statement array"); +}; SqlBuilder._ERROR_VALUE_IS_MANDATORY = function() { - return new Error(translate.text("SqlBuilder: pValue (or pFieldOrCond if only one param) is not allowed to be null, undefined or []. (use *IfSet functions if you need optional conditions which are just ignored if value is null or undefined)")); -} + return new Error("SqlBuilder: pValue (or pFieldOrCond if only one param) is not allowed to be null, undefined or []. (use *IfSet functions if you need optional conditions which are just ignored if value is null or undefined)"); +}; SqlBuilder._ERROR_VALUE_IS_MANDATORY_JDITO_VAR = function() { - return new Error(translate.text("SqlBuilder: pValue has to be a jdito variable which returns something different than null. (use *IfSet functions if you need optional conditions which are just ignored if value is null or undefined)")); -} + return new Error("SqlBuilder: pValue has to be a jdito variable which returns something different than null. (use *IfSet functions if you need optional conditions which are just ignored if value is null or undefined)"); +}; SqlBuilder._ERROR_UNSUPPORTED_PARAMETER_COMBINATION = function() { - return new Error(translate.text("SqlBuilder: unsupportet parameter combination")); -} + return new Error("SqlBuilder: unsupportet parameter combination"); +}; SqlBuilder._ERROR_NO_TABLE = function() { - return new Error(translate.text("SqlBuilder.deleteDat/updateData: You have to specify a tablename")); -} + return new Error("SqlBuilder.deleteDat/updateData: You have to specify a tablename"); +}; SqlBuilder._ERROR_NO_PARAMETER_PROVIDED = function() { - return new Error(translate.text("SqlBuilder: You have to specify at least one parameter")); -} + return new Error("SqlBuilder: You have to specify at least one parameter"); +}; SqlBuilder._ERROR_WHERE_NOT_FIRST = function() { - return new Error(translate.text("SqlBuilder: .where has to be called before following and/or.")); -} + return new Error("SqlBuilder: .where has to be called before following and/or."); +}; SqlBuilder._ERROR_ONLY_ONE_WHERE = function() { - return new Error(translate.text("SqlBuilder: .where has to be called only one time. Use and/or for further conditions.")); -} + return new Error("SqlBuilder: .where has to be called only one time. Use and/or for further conditions."); +}; -SqlBuilder._ERROR_INCOMPLETE_SELECT = function () +SqlBuilder._ERROR_INCOMPLETE_SELECT = function() { - return new Error(translate.text("SqlBuilder: select and from were expected, but not provided.")); -} + return new Error("SqlBuilder: select and from were expected, but not provided."); +}; -SqlBuilder._ERROR_CONDITION_IS_MANDATORY = function () +SqlBuilder._ERROR_CONDITION_IS_MANDATORY = function() { - return new Error(translate.text("SqlBuilder: You have to provide a subquery as SqlBuilder, prepared-array or string")); -} + return new Error("SqlBuilder: You have to provide a subquery as SqlBuilder, prepared-array or string"); +}; -SqlBuilder._ERROR_SUBSELECT_AS_FIELD_NOT_COMPLETE = function () +SqlBuilder._ERROR_SUBSELECT_AS_FIELD_NOT_COMPLETE = function() { - return new Error(translate.text("SqlBuilder: If pFieldOrCond is a SqlBuilder & pValue is provided, pFieldOrCond has to be a full SqlBuilder which will be used as subselect")); -} + return new Error("SqlBuilder: If pFieldOrCond is a SqlBuilder & pValue is provided, pFieldOrCond has to be a full SqlBuilder which will be used as subselect"); +}; -SqlBuilder._ERROR_SUBSELECT_AS_FIELD_NO_FIELD_TYPE = function () +SqlBuilder._ERROR_SUBSELECT_AS_FIELD_NO_FIELD_TYPE = function() { - return new Error(translate.text("SqlBuilder: If pFieldOrCond is a SqlBuilder & pValue is provided, you have to provide also pFieldType, as the type cannot be calculated from pFieldOrCond because it is a subselect")); -} + return new Error("SqlBuilder: If pFieldOrCond is a SqlBuilder & pValue is provided, you have to provide also pFieldType, as the type cannot be calculated from pFieldOrCond because it is a subselect"); +}; -SqlBuilder._ERROR_CONDITION_WRONG_FORMAT = function () +SqlBuilder._ERROR_CONDITION_WRONG_FORMAT = function() { - return new Error(translate.text("SqlBuilder: The '#' in pCondition has to occur before the '?' and '?' has to occur 1 time, '#' has to occur 1 or 0 times.")); -} + return new Error("SqlBuilder: The '#' in pCondition has to occur before the '?' and '?' has to occur 1 time, '#' has to occur 1 or 0 times."); +}; -SqlBuilder._ERROR_NOT_BOOLEAN = function () +SqlBuilder._ERROR_NOT_BOOLEAN = function() { - return new Error(translate.text("pExecuteOnlyIfConditionExists has to be of type boolean. This parameter controls what happens if the condition is empty (select / delete all or nothing)")); -} + return new Error("pExecuteOnlyIfConditionExists has to be of type boolean. This parameter controls what happens if the condition is empty (select / delete all or nothing)"); +}; -SqlBuilder._ERROR_UPDATE_VALUES_INVALID = function () +SqlBuilder._ERROR_UPDATE_VALUES_INVALID = function() { - return new Error(translate.text("SqlBuilder: The provided values object for updateFields is invalid or is not an object.")); -} + return new Error("SqlBuilder: The provided values object for updateFields is invalid or is not an object."); +}; -SqlBuilder._ERROR_PAGESIZE_INVALID = function () +SqlBuilder._ERROR_PAGESIZE_INVALID = function() { - return new Error(translate.text("SqlBuilder: The pagesize is not set or is not a number.")); -} + return new Error("SqlBuilder: The pagesize is not set or is not a number."); +}; -SqlBuilder._ERROR_NOT_A_FUNCTION = function () +SqlBuilder._ERROR_NOT_A_FUNCTION = function() { - return new Error(translate.text("SqlBuilder: The provided callback function is not a function.")); -} + return new Error("SqlBuilder: The provided callback function is not a function."); +}; + +SqlBuilder._ERROR_SQL_ARRAY_AS_VALUE_NOT_ALLOWED = function() +{ + return new Error("SqlBuilder: Prepared SQL arrays are not allowed as values for conditions. You can use a SqlBuilder or PreparedSqlStatement object to pass a subquery instead."); +}; + +SqlBuilder._ERROR_SQL_ARRAY_AS_FIELD_OR_COND_NOT_ALLOWED = function() +{ + return new Error("SqlBuilder: Prepared SQL arrays are not allowed as fields or conditions. You can use a SqlBuilder or PreparedSqlStatement object to pass a condition instead."); +}; + +SqlBuilder._ERROR_PREPARED_SQL_AS_FIELD_NOT_ALLOWED = function() +{ + return new Error("SqlBuilder: PreparedSqlStatement objects are not allowed as fields. To use a PreparedSqlStatement, it has to be a full condition and the only argument."); +}; + /** * Alternative way of creating a new SqlBuilder object that allows to use * methods on it directly without having to put brackets around it * * @return {SqlBuilder} a new SqlBuilder object * - * @example - * var query = SqlBuilder.begin() - * .select("ORGANISATION.NAME, FIRSTNAME, LASTNAME") - * .from("PERSON") - * .join("CONTACT", "CONTACT.PERSON_ID = PERSON.PERSONID") - * .leftJoin("ORGANISATION", SqlCondition.begin() - * .and("CONTACT.ORGANISATION_ID = ORGANISATION.ORGANISATIONID") - * .andPrepare("ORGANISATION.NAME", "S%", "# like ?") - * .build("1=2")) - * .where(SqlCondition.begin() - * .andPrepare("CONTACT.STATUS", $KeywordRegistry.contactStatus$active()) - * .build("1=2")); - * - * if (getCountry) //changing and adding parts - * { - * query.select("ORGANISATION.NAME, FIRSTNAME, LASTNAME, COUNTRY"); - * query.leftJoin("ADDRESS", "CONTACT.ADDRESS_ID = ADDRESS.ADDRESSID"); - * } - * - * var data = db.table(query.build()); - * * @deprecated using .begin is deprecated as it's now possible to write "new SqlBuilder().select(...).from(...).... - You can now use "newSelect(...)", "newWhere(...)", "newWhereIfSet(...)" or "new SqlBuilder()" to create a new SqlBuilder instance. - For further SqlBuilder usage see the documentation-property of the Sql_lib. + * You can now use "newSelect(...)", "newWhere(...)", "newWhereIfSet(...)" or "new SqlBuilder()" to create a new SqlBuilder instance. + * For further SqlBuilder usage see the documentation-property of the Sql_lib. */ -SqlBuilder.begin = function () +SqlBuilder.begin = function() { return new SqlBuilder(); -} - +}; /** * Builds the sql and uses SqlUtils.translateXXXWithQuotes to make a string out of it. - * @param {string} [pDefaultConditionIfNone=""] the default condition string to use if the SqlBuilder contains no condition. In most cases you won't need this - * @param {boolean} [pForceAsStatement=false] forces the use of SqlUtils.translateStatementWithQuotes even if it's no full statement. This is needed for example if you do not want brakets around the generated statement + * + * @param {string} [pDefaultConditionIfNone=""] - the default condition string to use if the SqlBuilder contains no condition. In most cases you won't need this + * @param {boolean} [pForceAsStatement=false] - forces the use of SqlUtils.translateStatementWithQuotes even if it's no full statement. This is needed for example if you do not want brakets around the generated statement * @return {string} the sql as string */ SqlBuilder.prototype.toString = function(pDefaultConditionIfNone, pForceAsStatement) { - var built = this.build(pDefaultConditionIfNone) - + let built = this.build(pDefaultConditionIfNone); if (built[0] !== "") { if (!pForceAsStatement && !this.isFullSelect() && (this.hasCondition() || pDefaultConditionIfNone)) + { return SqlUtils.translateConditionWithQuotes(built, this.alias); + } else + { return SqlUtils.translateStatementWithQuotes(built, this.alias); + } } - return ""; -} +}; /** * Sets the select clause of the sql. - * @param {string|Array|SqlBuilder} pFields You can pass:<br/> - * - A String is just used AS IT IS. (e.g. "FIRSTNAME, LASTNAME")<br/> - * - SqlBuilder is used as Subquery<br/> + * + * @param {string|Array|SqlBuilder} pFields - You can pass: + * - A String is just used AS IT IS. (e.g. "FIRSTNAME, LASTNAME") + * - SqlBuilder is used as Subquery * - The array can also contain Strings, SqlBuilder which are just concatenated (e.g. ["FIRSTNAME", "LASTNAME", someSqlBuilderContainingFullSelect]) * * @return {SqlBuilder} current SqlBuilder object @@ -362,25 +449,27 @@ SqlBuilder.prototype.select = function(pFields) { this._select = SqlBuilder._getStatement(pFields, "select", undefined, true, true); return this; -} +}; /** * Sets the select clause of the sql with distinct. + * * @param {string|string[]} pFields * @return {SqlBuilder} current SqlBuilder object */ -SqlBuilder.prototype.selectDistinct = function (pFields) +SqlBuilder.prototype.selectDistinct = function(pFields) { this._select = SqlBuilder._getStatement(pFields, "select distinct", undefined, true, true); return this; -} +}; /** * Sets the select clause to "select count(...)" - * @param {string} [pField=*] sql column to count, if omitted "count(*)" will be used + * + * @param {string} [pField="*"] - sql column to count, if omitted "count(*)" will be used * @return {SqlBuilder} current SqlBuilder object */ -SqlBuilder.prototype.selectCount = function (pField) +SqlBuilder.prototype.selectCount = function(pField) { if (pField == undefined) { @@ -388,11 +477,11 @@ SqlBuilder.prototype.selectCount = function (pField) } this._select = SqlBuilder._getStatement(pField, "select count(", ")", true, true); return this; -} - +}; /** * sets an alias-name which is added at some places if this SqlBuilder is used as subselect (e.g. in .select(), .join(), .from(), ...) + * * @param {string} pSubselectAlias * * @return {SqlBuilder} current SqlBuilder object @@ -401,7 +490,7 @@ SqlBuilder.prototype.subselectAlias = function(pSubselectAlias) { this._subselectAlias = pSubselectAlias; return this; -} +}; /** * Sets the table that is used for insert/update/delete functions. @@ -409,442 +498,448 @@ SqlBuilder.prototype.subselectAlias = function(pSubselectAlias) * @param {string} pTable * @return {SqlBuilder} current SqlBuilder object */ -SqlBuilder.prototype.tableName = function (pTable) +SqlBuilder.prototype.tableName = function(pTable) { this._tableName = pTable; return this; -} +}; /** - * Sets the from clause of the sql.<br/> - * <br/> - * Note: It is recommended to add joins via the .join functions.<br/> - * But in some cases you may already get a full from clause including the joins. In this case it is also possible to include them in the from-string.<br/> + * Sets the from clause of the sql. + * + * Note: It is recommended to add joins via the .join functions. + * But in some cases you may already get a full from clause including the joins. In this case it is also possible to include them in the from-string. * - * @param {string|SqlBuilder} pTable if it is a String, it is used as it is as table<br/> + * @param {string|SqlBuilder} pTable - if it is a String, it is used as it is as table * if it is a SqlBuilder, it is used as subselect: e.g. select * from (select FIRSTNAME from PERSON) - * @param {string} [pTableAlias] table alias + * @param {string} [pTableAlias] - table alias * @return {SqlBuilder} current SqlBuilder object */ SqlBuilder.prototype.from = function(pTable, pTableAlias) { this._from = SqlBuilder._getStatement(pTable, "", pTableAlias, false, (pTableAlias ? false : true)); if (typeof(pTable) === "string") + { this._tableName = pTable; + } return this; -} +}; /** * Adds a join clause to the sql. - * @param {string|SqlBuilder} pTable if it is a String, it is used as it is as table<br/> + * + * @param {string|SqlBuilder} pTable - if it is a String, it is used as it is as table * if it is a SqlBuilder, it is used as subselect: e.g. select * from Table1 join (select FIRSTNAME from PERSON) on ... - * @param {string|SqlBuilder} [pCondition] The where condition. This can be<br/> - * - a string (without the where keyword)<br/> + * @param {string|SqlBuilder|Array} [pCondition] - The where condition. This can be + * - a string (without the where keyword) * - a SqlBuilder NOTE: only the condition is used from it * - * @param {string} [pTableAlias] This alias is used to add an alias to the tablename - * @param {string} [pPrefix] string before the join, for example "left", "right" - * @param {string} [pReplacementForWordJoin] if this is set, this is used instead of the word "join". Needed for e.g. OUTER APPLY in MSSQL + * @param {string} [pTableAlias] - This alias is used to add an alias to the tablename + * @param {string} [pPrefix] - string before the join, for example "left", "right" + * @param {string} [pReplacementForWordJoin] - if this is set, this is used instead of the word "join". Needed for e.g. OUTER APPLY in MSSQL * @return {SqlBuilder} current SqlBuilder object */ SqlBuilder.prototype.join = function(pTable, pCondition, pTableAlias, pPrefix, pReplacementForWordJoin) { - // support for deprecated SqlCondition - if (pCondition instanceof SqlCondition) - { - pCondition = newWhere(pCondition); - logging.log("Warning: using .where with a SqlCondition as pFieldOrCond is deprecated. The SqlCondition will be removed in version >= 2020.x\n" - + "For SqlBuilder usage see the documentation-property of the Sql_lib."); - } - - var prefix = (pReplacementForWordJoin ? pReplacementForWordJoin : "join"); + let prefix = (pReplacementForWordJoin ? pReplacementForWordJoin : "join"); if (pPrefix) + { prefix = pPrefix + " " + prefix; + } - var postfix = pCondition ? "on" : ""; + let postfix = pCondition ? "on" : ""; if (pTableAlias) + { postfix = pTableAlias + " " + postfix; + } else if (pTable instanceof SqlBuilder && pTable._subselectAlias) + { postfix = pTable._subselectAlias + " " + postfix; + } - var joinPart = SqlBuilder._getStatement(pTable, prefix, postfix.trim()); + let joinPart = SqlBuilder._getStatement(pTable, prefix, postfix.trim()); if (pCondition) { if (pCondition instanceof SqlBuilder) - pCondition = [pCondition._where.sqlStorage, pCondition._where.preparedValues] + { + pCondition = pCondition._where.toArray(); + } - var conditionPart = SqlBuilder._getStatement(pCondition); + let conditionPart = SqlBuilder._getStatement(pCondition); - joinPart.sqlStorage += " " + conditionPart.sqlStorage; - joinPart.preparedValues = joinPart.preparedValues.concat(conditionPart.preparedValues); + joinPart.appendStatement(conditionPart, " "); } - - this._joins.push(joinPart) + this._joins.push(joinPart); return this; -} +}; /** * Adds a left join clause to the sql. * - * @param {string|SqlBuilder} pTable if it is a String, it is used as it is as table<br/> + * @param {string|SqlBuilder} pTable - if it is a String, it is used as it is as table * if it is a SqlBuilder, it is used as subselect: e.g. select * from Table1 join (select FIRSTNAME from PERSON) on ... - * @param {string|SqlBuilder} [pCondition] The where condition. This can be<br/> - * - a string (without the where keyword)<br/> + * @param {string|SqlBuilder} [pCondition] - The where condition. This can be + * - a string (without the where keyword) * - a SqlBuilder NOTE: only the condition is used from it * - * @param {string} [pTableAlias] This alias is used to add an alias to the tablename + * @param {string} [pTableAlias] - This alias is used to add an alias to the tablename * @return {SqlBuilder} current SqlBuilder object */ SqlBuilder.prototype.leftJoin = function(pTable, pCondition, pTableAlias) { return this.join(pTable, pCondition, pTableAlias, "left"); -} +}; /** * Adds a right join clause to the sql. * - * @param {string|SqlBuilder} pTable if it is a String, it is used as it is as table<br/> + * @param {string|SqlBuilder} pTable - if it is a String, it is used as it is as table * if it is a SqlBuilder, it is used as subselect: e.g. select * from Table1 join (select FIRSTNAME from PERSON) on ... - * @param {string|SqlBuilder} [pCondition] The where condition. This can be<br/> - * - a string (without the where keyword)<br/> + * @param {string|SqlBuilder} [pCondition] - The where condition. This can be + * - a string (without the where keyword) * - a SqlBuilder NOTE: only the condition is used from it * - * @param {string} [pTableAlias] This alias is used to add an alias to the tablename + * @param {string} [pTableAlias] - This alias is used to add an alias to the tablename * @return {SqlBuilder} current SqlBuilder object */ SqlBuilder.prototype.rightJoin = function(pTable, pCondition, pTableAlias) { return this.join(pTable, pCondition, pTableAlias, "right"); -} +}; /** - * Throws an error if pValue is null, undefined or a SqlBuilder without condition (or if pValue is a $-variable: error if the result of it is null or undefined)<br/> - * Also throws an error if pFieldOrCond is the only parameter and it is null<br/> - * <br/> - * Starts the where clause of the SQL. You may pass the first condition with it.<br/> - * But you can also call this function without any parameter and add the conditions with subsequent .and / .or<br/> - * <br/> - * This method exists mainly for semantic reasons and can only be callled once.<br/> - * As shourtcut you could use the newWhere(...) function.<br/> - * - * @param {string|Array|SqlBuilder|null} [pFieldOrCond] If this is the only parameter, it is used as Condition <br/> - * else it is used as Field.<br/> - * <br/> - * If you use it as Subselect (the only param), it can be:<br/> - * - a string: just added as it is<br/> - * - a PreparedSqlArray: an Array in this form: [sqlStr, [[value1, type1], [valueN, typeN]]]<br/> - * the sql is just used as it is.<br/> - * - a SqlBuilder: ONLY THE CONDITION is used from it<br/> - * <br/> - * If you use it as a Field (at least pValue has to be filled), this param provides the field information to<br/> - * load the SQLTYPE for this condition. <br/> - * It can be provided in the following ways:<br/> - * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" <br/> - * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case<br/> - * Note2: if you need a table alias use the next variant (as array)<br/> - * - a array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"]<br/> - * - a SqlBuilder: the full select is used as subselect and compared with pValue. <br/> - * (e.g. select * from PERSON where (select "NAME" from ORGANISATION where ... ) = ?)<br/> - * Note: for this you have to provide pFieldType as the type cannot be calculated from the subselect!<br/> + * Throws an error if pValue is null, undefined or a SqlBuilder without condition (or if pValue is a $-variable: error if the result of it is null or undefined) + * Also throws an error if pFieldOrCond is the only parameter and it is null + * + * Starts the where clause of the SQL. You may pass the first condition with it. + * But you can also call this function without any parameter and add the conditions with subsequent .and / .or + * + * This method exists mainly for semantic reasons and can only be callled once. + * As shourtcut you could use the newWhere(...) function. + * + * @param {string|PreparedSqlStatement|SqlBuilder|null} [pFieldOrCond] - This can be either a full condition (then it has to be the only parameter provided) or a field. + * + * If you pass a condition (the only param), these types are allowed: + * - string: just added as it is + * - PreparedSqlStatement: a PreparedSqlStatement object is used as it is + * - SqlBuilder: ONLY THE WHERE-CONDITION is used from it + * + * If you pass a field (at least pValue has to be filled), this param provides the field information to + * load the SQLTYPE for this condition. + * It can be provided in the following ways: + * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" + * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case + * Note2: if you need a table alias use the next variant (as array) + * - an array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"] + * - a SqlBuilder: the full select is used as subselect and compared with pValue. + * (e.g. select * from PERSON where (select "NAME" from ORGANISATION where ... ) = ?) + * Note: for this you have to provide pFieldType as the type cannot be calculated from the subselect! * Note: this can also be null if you don't need the field and use a pCondition without a # * - * @param {string|SqlBuilder|Array} [pValue] This is the value which is used for the condition.<br/> - * Basically it can be nearly everything you need.<br/> - * - String, etc: is just used as value for the prepared statement. Of course it has to fit the type of the db-column<br/> - * - String starting with '$' is treated as jdito-variable: is loaded with vars.getString("$..."). <br/> - * Note: Use 2 '$' to escape the $ if you don't want it to be treated as JditoVar + * @param {*} [pValue] - This is the value which is used for the condition. The type can be anything, but some types are handled in different ways: + * - string: Is just used as value for the prepared statement. Of course it has to fit the type of the db-column. + * If the string starts with a '$' it is recognized as a jdito variable and will be resolved automatically, + * Use 2 '$' to escape the $ if you don't want it to be treated as jdito variable (see {@link SqlUtils.escapeVars}) + * - Array: If the condition allows multiple values (for example 'in'), the values can be passed in an array. + * - SqlBuilder: Can be used for sub-queries + * - PreparedSqlStatement: Can be used for sub-queries + * - all other types: Will be converted to a string + * - NOT ALLOWED: Prepared sql arrays - these will raise an error, because they could provoke sql injection + * * - * @param {string} [pCondition="# = ?"] This is the condition which should be used to compare the field with the value.<br/> - * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/> - * ? will be replaced by pValue<br/> + * @param {string} [pCondition="# = ?"] - This is the condition which should be used to compare the field with the value. + * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit # + * ? will be replaced by pValue * <strong>IMPORTANT: the # has to be before the ?</strong> - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] You can specify which datatype should be used for the prepared statement <br/> - * In most cases you don't need this.<br/> - * This is helpful if you for example have a pCondition "year(#) = ?"<br/> + * + * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] - You can specify which datatype should be used for the prepared statement + * In most cases you don't need this, but it's required if pFieldOrCond is a sub-select or null. + * This is helpful if you for example have a pCondition "year(#) = ?" * then the db-field is DATETIME, but the value is INTEGER. In this case you can overwrite the type. - * + * * @return {SqlBuilder} current SqlBuilder object */ SqlBuilder.prototype.where = function(pFieldOrCond, pValue, pCondition, pFieldType) -{ - // support for deprecated SqlCondition - if (pFieldOrCond instanceof SqlCondition && pValue === undefined && pCondition === undefined && pFieldType === undefined) - { - let copiedCondition = newWhere(); - - copiedCondition._where.preparedValues = pFieldOrCond.preparedValues; - copiedCondition._where._lastWasOr = pFieldOrCond._lastWasOr; - copiedCondition._where.sqlStorage = pFieldOrCond._sqlStorage; - - pFieldOrCond = copiedCondition; - - logging.log("Warning: using .where with a SqlCondition as pFieldOrCond is deprecated. The SqlCondition will be removed in version >= 2020.x\n" - + "For SqlBuilder usage see the documentation-property of the Sql_lib."); - } - - return this._setWhere(pFieldOrCond, pValue, pCondition, pFieldType, this.or); -} +{ + return this._setWhere(this.or, pFieldOrCond, pValue, pCondition, pFieldType); +}; /** - * Difference to where(): where throws errors on invalid values, whereIfSet just ignores the condition and does nothing (usefull e.g. for the parameter variables ("$param.ddd") in conditionProcesses.)<br/> - * <br/> - * Starts the whereIfSet clause of the SQL. You may pass the first condition with it.<br/> - * But you can also call this function without any parameter and add the conditions with subsequent .and / .or<br/> - * <br/> - * This method exists mainly for semantic reasons and can only be callled once.<br/> + * Difference to where(): where throws errors on invalid values, whereIfSet just ignores the condition and does nothing (usefull e.g. for the parameter variables ("$param.ddd") in conditionProcesses.) + * + * Starts the whereIfSet clause of the SQL. You may pass the first condition with it. + * But you can also call this function without any parameter and add the conditions with subsequent .and / .or + * + * This method exists mainly for semantic reasons and can only be callled once. * As shourtcut you could use the newWhereIfSet(...) function. * - * @param {string|Array|SqlBuilder|null} [pFieldOrCond] If this is the only parameter, it is used as Condition <br/> - * else it is used as Field.<br/> - * <br/> - * If you use it as Subselect (the only param), it can be:<br/> - * - a string: just added as it is<br/> - * - a PreparedSqlArray: an Array in this form: [sqlStr, [[value1, type1], [valueN, typeN]]]<br/> - * the sql is just used as it is.<br/> - * - a SqlBuilder: ONLY THE CONDITION is used from it<br/> - * <br/> - * If you use it as a Field (at least pValue has to be filled), this param provides the field information to<br/> - * load the SQLTYPE for this condition. <br/> - * It can be provided in the following ways:<br/> - * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" <br/> - * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case<br/> - * Note2: if you need a table alias use the next variant (as array)<br/> - * - a array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"]<br/> - * - a SqlBuilder: the full select is used as subselect and compared with pValue. <br/> - * (e.g. select * from PERSON where (select "NAME" from ORGANISATION where ... ) = ?)<br/> - * Note: for this you have to provide pFieldType as the type cannot be calculated from the subselect!<br/> + * @param {string|PreparedSqlStatement|SqlBuilder|null} [pFieldOrCond] - This can be either a full condition (then it has to be the only parameter provided) or a field. + * + * If you pass a condition (the only param), these types are allowed: + * - string: just added as it is + * - PreparedSqlStatement: a PreparedSqlStatement object is used as it is + * - SqlBuilder: ONLY THE WHERE-CONDITION is used from it + * + * If you pass a field (at least pValue has to be filled), this param provides the field information to + * load the SQLTYPE for this condition. + * It can be provided in the following ways: + * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" + * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case + * Note2: if you need a table alias use the next variant (as array) + * - an array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"] + * - a SqlBuilder: the full select is used as subselect and compared with pValue. + * (e.g. select * from PERSON where (select "NAME" from ORGANISATION where ... ) = ?) + * Note: for this you have to provide pFieldType as the type cannot be calculated from the subselect! * Note: this can also be null if you don't need the field and use a pCondition without a # * - * @param {string|SqlBuilder|Array} [pValue] This is the value which is used for the condition.<br/> - * Basically it can be nearly everything you need.<br/> - * - String, etc: is just used as value for the prepared statement. Of course it has to fit the type of the db-column<br/> - * - String starting with '$' is treated as jdito-variable: is loaded with vars.getString("$..."). <br/> - * Note: Use 2 '$' to escape the $ if you don't want it to be treated as JditoVar + * @param {*} [pValue] - This is the value which is used for the condition. The type can be anything, but some types are handled in different ways: + * - string: Is just used as value for the prepared statement. Of course it has to fit the type of the db-column. + * If the string starts with a '$' it is recognized as a jdito variable and will be resolved automatically, + * Use 2 '$' to escape the $ if you don't want it to be treated as jdito variable (see {@link SqlUtils.escapeVars}) + * - Array: If the condition allows multiple values (for example 'in'), the values can be passed in an array. + * - SqlBuilder: Can be used for sub-queries + * - PreparedSqlStatement: Can be used for sub-queries + * - all other types: Will be converted to a string + * - NOT ALLOWED: Prepared sql arrays - these will raise an error, because they could provoke sql injection + * * - * @param {string} [pCondition="# = ?"] This is the condition which should be used to compare the field with the value.<br/> - * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/> - * ? will be replaced by pValue<br/> + * @param {string} [pCondition="# = ?"] - This is the condition which should be used to compare the field with the value. + * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit # + * ? will be replaced by pValue * <strong>IMPORTANT: the # has to be before the ?</strong> * - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] You can specify which datatype should be used for the prepared statement <br/> - * In most cases you don't need this.<br/> - * This is helpful if you for example have a pCondition "year(#) = ?"<br/> + * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] - You can specify which datatype should be used for the prepared statement + * In most cases you don't need this, but it's required if pFieldOrCond is a sub-select or null. + * This is helpful if you for example have a pCondition "year(#) = ?" * then the db-field is DATETIME, but the value is INTEGER. In this case you can overwrite the type. * * @return {SqlBuilder} current SqlBuilder object */ SqlBuilder.prototype.whereIfSet = function(pFieldOrCond, pValue, pCondition, pFieldType) { - return this._setWhere(pFieldOrCond, pValue, pCondition, pFieldType, this.orIfSet); -} + return this._setWhere(this.orIfSet, pFieldOrCond, pValue, pCondition, pFieldType); +}; /** - * helper function for .where and .whereIfSet because they do almost the same<br/> + * helper function for .where and .whereIfSet because they do almost the same * See .where() for further explanations * - * @param {string|Array|SqlBuilder|null} [pFieldOrCond] - * @param {string|SqlBuilder|Array} [pValue] - * @param {string} [pCondition="# = ?"] <strong>IMPORTANT: the # has to be before the ?</strong><br/> + * @param {Function} pAddCondFn - This is a callback which is called if a condition should be added (needs to have same parameters as .or() + * @param {string|PreparedSqlStatement|SqlBuilder|null} [pFieldOrCond] + * @param {*} [pValue] + * @param {string} [pCondition="# = ?"] - <strong>IMPORTANT: the # has to be before the ?</strong> * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] - * @param {Function} pAddCondFn This is a callback which is called if a condition should be added (needs to have same parameters as .or() * * @return {SqlBuilder} current SqlBuilder object * @ignore */ -SqlBuilder.prototype._setWhere = function (pFieldOrCond, pValue, pCondition, pFieldType, pAddCondFn) +SqlBuilder.prototype._setWhere = function(pAddCondFn, pFieldOrCond, pValue, pCondition, pFieldType) { // allow where-call without parameter to just enable where mode if (pFieldOrCond === undefined && pValue === undefined && pCondition === undefined && pFieldType === undefined) { - this._where._whereWasCalled = true; + this._whereStatus._whereWasCalled = true; return this; } - // where has to be called before all other and / or if (this.hasCondition()) + { throw SqlBuilder._ERROR_WHERE_NOT_FIRST(); + } // only one where call is allowed - if (this._where._whereWasCalled) + if (this._whereStatus._whereWasCalled) + { throw SqlBuilder._ERROR_ONLY_ONE_WHERE(); + } - this._where._whereWasCalled = true; + this._whereStatus._whereWasCalled = true; return pAddCondFn.call(this, pFieldOrCond, pValue, pCondition, pFieldType); -} +}; /** * helper function which adds a condition * - * @param {string|SqlBuilder|Array} pCondition the condition to add - * @param {boolean} [pMandatory=true] if true: throws error on SqlBuilder without conditon or PreparedSqlArray with empty string. Else: just does nothing - * @param {Function} pAddPreparedConditionCallback A Callback funtion which receives a PreparedSqlArray as parameter - * @param {boolean} pBrackets if true, Brackets are added in some cases + * @param {string|Array|SqlBuilder|PreparedSqlStatement} pCondition - the condition to add + * @param {Function} pAddPreparedConditionCallback - A Callback funtion which receives a PreparedSqlArray as parameter + * @param {boolean} [pMandatory=true] - if true: throws error on SqlBuilder without conditon or PreparedSqlArray with empty string. Else: just does nothing + * @param {boolean} [pBrackets] - if true, Brackets are added in some cases * * @return {SqlBuilder} current SqlBuilder object * @ignore */ -SqlBuilder.prototype._addWhereCondition = function(pCondition, pMandatory, pAddPreparedConditionCallback, pBrackets) +SqlBuilder.prototype._addWhereCondition = function(pCondition, pAddPreparedConditionCallback, pMandatory, pBrackets) { if (pCondition === undefined) + { return this; + } if (pMandatory === undefined) + { pMandatory = true; - - var sql = pCondition; + } // the field is a simple string -> just add the string, no prepared statement - if (Utils.isString(sql)) + if (Utils.isString(pCondition)) { - pAddPreparedConditionCallback.call(this, [sql, []]); + pAddPreparedConditionCallback.call(this, new PreparedSqlStatement(pCondition)); return this; } - // the field is an array -> it is a prepared condition - if (Array.isArray(sql)) - { - if (sql[0]) - { - this._where.preparedValues = this._where.preparedValues.concat(sql[1]); + let sqlString = ""; + let preparedValues = []; - // add only brackets if needed - if (pBrackets) - sql[0] = " ( " + sql[0] + " ) "; + if (pCondition instanceof SqlBuilder) + { + //use only the where condition of the SqlBuilder + pCondition = pCondition._where; + } - pAddPreparedConditionCallback.call(this, [sql[0], []], pBrackets) - return this; - } - else if (pMandatory) - throw SqlBuilder._ERROR_CONDITION_IS_MANDATORY(); - - return this; + if (pCondition instanceof PreparedSqlStatement) + { + sqlString = pCondition.sqlString; + preparedValues = pCondition.preparedValues; + } + else if (Array.isArray(pCondition)) + { + sqlString = pCondition[0]; + preparedValues = pCondition[1]; + } + else + { + throw SqlBuilder._ERROR_INVALID_CONDITION_VALUE_TYPE(); } - // the field is a SqlBuilder -> it is a SqlBuilder which contains a condition -> the condition of the SqlBuilder is added. - if (sql instanceof SqlBuilder) + if (sqlString.trim()) { - // add only brackets if needed - var sqlString = sql._where.sqlStorage; - - - var condString = sqlString; - if (condString.trim() != "") + if (pBrackets) { - if (pBrackets) - condString = " ( " + condString + " ) "; - - pAddPreparedConditionCallback.call(this, [condString, sql._where.preparedValues], pBrackets); - return this; + sqlString = " ( " + sqlString + " ) "; } - else if (pMandatory) - throw SqlBuilder._ERROR_CONDITION_IS_MANDATORY(); - - return this; + pAddPreparedConditionCallback.call(this, new PreparedSqlStatement(sqlString, preparedValues), pBrackets); + } + else if (pMandatory) + { + throw SqlBuilder._ERROR_CONDITION_IS_MANDATORY(); } - throw SqlBuilder._ERROR_INVALID_CONDITION_VALUE_TYPE(); -} + return this; +}; /** * helper function which adds a Subquery-condition * - * @param {SqlBuilder|Array} pSubquery the subquery to add - * @param {boolean} [pMandatory=true] if true: throws error on SqlBuilder without conditon or PreparedSqlArray with empty string. Else: just does nothing - * @param {boolean} pCondition the condition to be used: e.g. "exists(?)" the ? is replaced by the subquery - * @param {Function} pAddPreparedConditionCallback A Callback funtion which receives a PreparedSqlArray as parameter + * @param {SqlBuilder|PreparedSqlStatement} pSubquery - the subquery to add + * @param {string} pCondition - the condition to be used: e.g. "exists(?)" the ? is replaced by the subquery + * @param {Function} pAddPreparedConditionCallback - A Callback funtion which receives a PreparedSqlArray as parameter + * @param {boolean} [pMandatory=true] - if true: throws error on SqlBuilder without conditon or PreparedSqlArray with empty string. Else: just does nothing * * @return {SqlBuilder} current SqlBuilder object * @ignore */ -SqlBuilder.prototype._addWhereSubquery = function(pSubquery, pMandatory, pCondition, pAddPreparedConditionCallback) +SqlBuilder.prototype._addWhereSubquery = function(pSubquery, pCondition, pAddPreparedConditionCallback, pMandatory) { if (pSubquery === undefined) + { return this; + } if (pMandatory === undefined) + { pMandatory = true; - - var sql = pSubquery; + } - // the field is an array -> it is a prepared statement which already SHOULD contain exists or another condition - // Both can be handled by _prepare - if (Array.isArray(sql)) + if (pSubquery instanceof PreparedSqlStatement) { - if (sql[0]) - pAddPreparedConditionCallback.call(this, this._prepare(undefined, sql, pCondition)); + if (pSubquery.sqlString) + { + pAddPreparedConditionCallback.call(this, this._prepare(undefined, pSubquery.toArray(), pCondition)); + } else if (pMandatory) + { throw SqlBuilder._ERROR_VALUE_IS_MANDATORY(); + } return this; } - // the field is a SqlBuilder -> it is a SqlBuilder which contains a condition -> the condition of the SqlBuilder is added. - if (sql instanceof SqlBuilder) + if (pSubquery instanceof SqlBuilder) { - var subQuery = pSubquery; - // Without condition this function cannot be used with SqlBuilder object as it cannot contain a condition if (!pCondition) + { throw SqlBuilder._ERROR_NO_CONDITION(); + } - if (subQuery.isFullSelect() || subQuery.hasCondition()) //can also be only an condition if SqlBuilder.NOT() is used + if (pSubquery.isFullSelect() || pSubquery.hasCondition()) //can also be only an condition if SqlBuilder.NOT() is used { - var preparedObj = subQuery.build(); + let preparedObj = pSubquery.build(); pAddPreparedConditionCallback.call(this, this._prepare(undefined, preparedObj, pCondition)); } else if (pMandatory) + { throw SqlBuilder._ERROR_VALUE_IS_MANDATORY(); + } return this; } - throw SqlBuilder._ERROR_INVALID_SUBQUERY_TYPE(); -} +}; /** * helper function which adds a condition to the where * - * @param {string|Array|SqlBuilder|null} pFieldOrCond see .where() - * @param {string|SqlBuilder|Array} pValue see .where() - * @param {boolean} pMandatory if true: throw error if pValue is null, undefined, SqlBuilder without condition, etc... else just ignore the condition - * @param {string} [pCondition="# = ?"] see .where() - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] see .where() - * @param {Function} pAddPreparedConditionCallback A Callback funtion which receives a PreparedSqlArray as parameter + * @param {string|Array|PreparedSqlStatement|SqlBuilder|null} pFieldOrCond - see .where() + * @param {*} pValue - see .where() + * @param {Function} pAddPreparedConditionCallback - A Callback funtion which receives a PreparedSqlArray as parameter + * @param {boolean} [pMandatory] - if true: throw error if pValue is null, undefined, SqlBuilder without condition, etc... else just ignore the condition + * @param {string|((pAlias: string)=>(string|[string,number]))} [pCondition="# = ?"] - see .where() + * @param {number} [pFieldType=AutomaticallyLoadedType] - see .where() * * @return {SqlBuilder} current SqlBuilder object * @ignore */ -SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pMandatory, pCondition, pFieldType, pAddPreparedConditionCallback) +SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pAddPreparedConditionCallback, pMandatory, pCondition, pFieldType) { - if (!this._where._whereWasCalled) + if (!this._whereStatus._whereWasCalled) + { throw SqlBuilder._ERROR_WHERE_NOT_FIRST(); + } //skip if no values are provided and mandatory is false if (!pMandatory && pFieldOrCond === undefined && pValue === undefined && pCondition === undefined && pFieldType === undefined) + { return this; + } if (pFieldOrCond === undefined && pValue === undefined && pCondition === undefined && pFieldType === undefined) + { throw SqlBuilder._ERROR_NO_PARAMETER_PROVIDED(); + } //In a special case, pCondition can be a function. It will be called with the alias as argument and //must return an array of the condition string and (optionally) the required sql field type. //alternatively the function may return a string only to make the usage more bulletproof and convenient, so both SqlBuilder.EQUAL() //and SqlBuilder.EQUAL work equally - if (typeof pCondition === "function") + if (Utils.isFunction(pCondition)) { - var resCond = pCondition(this.alias); - if (Array.isArray(resCond)) + let resCondition = pCondition(this.alias); + let resConditionString; + if (Array.isArray(resCondition)) { - pCondition = resCond[0]; - pFieldType = pFieldType || resCond[1]; + resConditionString = resCondition[0]; + pFieldType = pFieldType || resCondition[1]; } - else if(Utils.isString(pCondition)) + else if(Utils.isString(resCondition)) { - pCondition = resCond; + resConditionString = resCondition; } + pCondition = resConditionString; } + this._verifyConditionFormat(pCondition); // Special case: if only pFieldOrCond is set and we can identify it as a valid field-string (e.g. "Table.Field") we assume that it is not just a condition string. @@ -855,15 +950,32 @@ SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pMandatory, pCon if (pValue === undefined && pCondition === undefined && pFieldType === undefined && typeof pFieldOrCond == "string" && SqlUtils.isFullFieldQualifier(pFieldOrCond)) { if (pMandatory) + { throw SqlBuilder._ERROR_VALUE_IS_MANDATORY(); + } return this; } - + + if (SqlUtils.isPreparedSqlArray(pValue)) + { + throw SqlBuilder._ERROR_SQL_ARRAY_AS_VALUE_NOT_ALLOWED(); + } + + if (SqlUtils.isPreparedSqlArray(pFieldOrCond)) + { + throw SqlBuilder._ERROR_SQL_ARRAY_AS_FIELD_OR_COND_NOT_ALLOWED(); + } + // just call the andCondition function if it is only a Condition if (pFieldOrCond !== undefined && pValue === undefined && pCondition === undefined && pFieldType === undefined) { - return this._addWhereCondition(pFieldOrCond, pMandatory, pAddPreparedConditionCallback, true); + return this._addWhereCondition(pFieldOrCond, pAddPreparedConditionCallback, pMandatory, true); + } + + if (pFieldOrCond instanceof PreparedSqlStatement) + { + throw SqlBuilder._ERROR_PREPARED_SQL_AS_FIELD_NOT_ALLOWED(); } // Subselects containing full select can be used as field, if pValue and pFieldType are provided. @@ -877,133 +989,145 @@ SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pMandatory, pCon { throw SqlBuilder._ERROR_SUBSELECT_AS_FIELD_NO_FIELD_TYPE(); } - - var tmpCond = newWhere(this.alias) - ._addWhere("SQL_LIB_DUMMY_TABLE.SQL_LIB_DUMMY_COLUMN", pValue, pMandatory, pCondition, pFieldType, pAddPreparedConditionCallback); + let tmpCond = newWhere(this.alias) + ._addWhere("SQL_LIB_DUMMY_TABLE.SQL_LIB_DUMMY_COLUMN", pValue, pAddPreparedConditionCallback, pMandatory, pCondition, pFieldType); - var subSqlPrepared = pFieldOrCond.build(); + let subSqlPrepared = pFieldOrCond.build(); - tmpCond._where.sqlStorage = SqlUtils.replaceConditionTemplate(tmpCond._where.sqlStorage, 'SQL_LIB_DUMMY_TABLE.SQL_LIB_DUMMY_COLUMN', "( " + subSqlPrepared[0] + " )"); - tmpCond._where.preparedValues = subSqlPrepared[1].concat(tmpCond._where.preparedValues) + tmpCond._where.sqlString = SqlUtils.replaceConditionTemplate(tmpCond._where.sqlString, "SQL_LIB_DUMMY_TABLE.SQL_LIB_DUMMY_COLUMN", "( " + subSqlPrepared[0] + " )"); + tmpCond._where.preparedValues = subSqlPrepared[1].concat(tmpCond._where.preparedValues); - this._addWhereCondition(tmpCond, pMandatory, pAddPreparedConditionCallback, true) + this._addWhereCondition(tmpCond, pAddPreparedConditionCallback, pMandatory, true); return this; } // first check the default-mandatory-cases: null or undefined. everything else such as checking $-variables is done later if (pMandatory && (pValue === null || pValue === undefined)) + { throw SqlBuilder._ERROR_VALUE_IS_MANDATORY(); + } // a field is string or array -> normal case // OR !pFieldOrCond and pValue and pCondition is given -> preparedSQL/SqlBuilder can be used without field if pCondition is set (e.g. with "exists ?") - if ((typeof pFieldOrCond == "string" || Array.isArray(pFieldOrCond) || (!pFieldOrCond && pFieldType)) - || (!pFieldOrCond && (pCondition && pValue instanceof SqlBuilder || !(pValue instanceof SqlBuilder)))) + if (!(typeof pFieldOrCond == "string" || Array.isArray(pFieldOrCond) || (!pFieldOrCond && pFieldType)) + && !(!pFieldOrCond && (pCondition && pValue instanceof SqlBuilder || !(pValue instanceof SqlBuilder)))) { - var field = pFieldOrCond; - var typeofValue = typeof pValue; + throw SqlBuilder._ERROR_UNSUPPORTED_PARAMETER_COMBINATION(); + } + + let field = pFieldOrCond; + let typeofValue = typeof pValue; - // ... a string starting with $ -> jdito varable which has to be resolved - if (typeofValue == "string" && pValue.length >= 2 && pValue[0] == "$" && pValue[1] != "$") // escape $ if using two $ + // ... a string starting with $ -> jdito varable which has to be resolved + if (typeofValue == "string" && pValue.length >= 2 && pValue[0] == "$") + { + if (pValue[1] != "$") // escape $ if using two $ { //important: just overwrite the value because some $local variables may contain an array and then the default handling of arrays (which //is generating an IN-statement) should apply pValue = vars.get(pValue); if (pMandatory && pValue === null) + { throw SqlBuilder._ERROR_VALUE_IS_MANDATORY_JDITO_VAR(); + } + if (SqlUtils.isPreparedSqlArray(pValue)) + { + throw SqlBuilder._ERROR_SQL_ARRAY_AS_VALUE_NOT_ALLOWED(); + } typeofValue = typeof pValue; } - - // remove the first $ if there are two $ - if (typeofValue == "string" && pValue.length >= 2 && pValue[0] == "$" && pValue[1] == "$") + else // remove the first $ if there are two $ + { pValue = pValue.slice(1); - - //support for Set by converting to Array - if (pValue instanceof Set) - pValue = Array.from(pValue); - - // pValue can be... - // ... a SqlBuilder / Prepared statement array -> it is a SqlBuilder containing a complete subquery or an simple array (in statement) - if (pValue instanceof SqlBuilder || Array.isArray(pValue) || (typeofValue == "string" && (pFieldOrCond == undefined || pFieldOrCond == null))) - { - // check if the array is really a value-array for an in and not a prepared statement - if (Array.isArray(pValue) && !SqlUtils.isPreparedSqlArray(pValue)) + } + } + + //support for Set by converting to Array + if (pValue instanceof Set) + { + pValue = Array.from(pValue); + } + + if (Array.isArray(pValue)) + { + if (pValue.length == 0) + { + if (pMandatory) { - if (pValue.length == 0) - { - if (pMandatory) - throw SqlBuilder._ERROR_VALUE_IS_MANDATORY(); - - return this; - } - - // if it is null -> ignore it. -> the pCondition should not contain a # in this case - if (field != null) - { - var [alias, parsedField] = SqlUtils.parseField(field); - if (pFieldType === undefined || pFieldType === null) - pFieldType = SqlUtils.getSingleColumnType(parsedField, undefined, this.alias); - } - //overwrite condition to set a default behaviour - if (pCondition == undefined) - pCondition = SqlBuilder.IN(); - // value-array -> convert it to a prepared statement ["(?, ?, ?)", [[val1, type1], [val2, type2], [val3, type3]]] - - var mergeOp = pCondition == SqlBuilder.NOT_IN() ? " and " : " or "; - var inCondition = ArrayUtils.chunk(pValue, 1000) - .map(function (values) - { - return this._prepare(field, SqlUtils.getSqlInStatement(undefined, values, undefined, true, pFieldType), pCondition, pFieldType, false); - }, this) - .reduce(function (fullCondition, nextCondition) - { - return [ - fullCondition[0] + mergeOp + nextCondition[0], - fullCondition[1].concat(nextCondition[1]) - ]; - }); - - this._addWhereCondition(inCondition, undefined, pAddPreparedConditionCallback, true); - return this; + throw SqlBuilder._ERROR_VALUE_IS_MANDATORY(); } - if (pFieldOrCond !== null && pFieldOrCond !== undefined) - { - if (!pCondition) - pCondition = SqlBuilder.EQUAL(); - - pCondition = SqlUtils.replaceConditionTemplate(pCondition, '#', SqlUtils.parseField(pFieldOrCond)[0]); - } - else if (!pCondition) + return this; + } + + // if it is null -> ignore it. -> the pCondition should not contain a # in this case + if (field != null) + { + let parsedField = SqlUtils.parseField(field)[1]; + if (pFieldType === undefined || pFieldType === null) { - pCondition = "?"; + pFieldType = SqlUtils.getSingleColumnType(parsedField, undefined, this.alias); } - - // _addWhereSubquery can handle SqlBuilder and prepared statements as value - return this._addWhereSubquery(pValue, pMandatory, pCondition, pAddPreparedConditionCallback); } - - if (!pCondition) - pCondition = SqlBuilder.EQUAL(); - - // ... everything else -> just pass it - if (pValue === false || pValue === 0 || pValue === "" || pValue) + //overwrite condition to set a default behaviour + if (pCondition == undefined) { - let prep = this._prepare(field, pValue, pCondition, pFieldType) - this._addWhereCondition(prep, undefined, pAddPreparedConditionCallback); + pCondition = SqlBuilder.IN(); } + // value-array -> convert it to a prepared statement ["(?, ?, ?)", [[val1, type1], [val2, type2], [val3, type3]]] + + let mergeOp = pCondition == SqlBuilder.NOT_IN() ? " and " : " or "; + let inCondition = ArrayUtils.chunk(pValue, 1000) + .map(values => + { + return this._prepare(field, SqlUtils.getSqlInStatement(undefined, values, undefined, true, pFieldType), + pCondition, pFieldType, false); + }) + .reduce((fullCondition, nextCondition) => fullCondition.appendStatement(nextCondition, mergeOp)); + + this._addWhereCondition(inCondition, pAddPreparedConditionCallback, undefined, true); return this; } + + // pValue can be... + // ... a SqlBuilder / Prepared statement array -> it is a SqlBuilder containing a complete subquery or an simple array (in statement) + if (pValue instanceof SqlBuilder || (typeofValue == "string" && (pFieldOrCond == undefined || pFieldOrCond == null)) + || pValue instanceof PreparedSqlStatement) + { + if (pFieldOrCond !== null && pFieldOrCond !== undefined) + { + pCondition = SqlUtils.replaceConditionTemplate(pCondition || SqlBuilder.EQUAL(), "#", SqlUtils.parseField(pFieldOrCond)[0]); + } + else if (!pCondition) + { + pCondition = "?"; + } + + // _addWhereSubquery can handle SqlBuilder and prepared statements as value + return this._addWhereSubquery(pValue, pCondition, pAddPreparedConditionCallback, pMandatory); + } - throw SqlBuilder._ERROR_UNSUPPORTED_PARAMETER_COMBINATION(); -} + if (!pCondition) + { + pCondition = SqlBuilder.EQUAL(); + } + + // ... everything else -> just pass it + if (pValue === false || pValue === 0 || pValue === "" || pValue) + { + let prep = this._prepare(field, pValue, pCondition, pFieldType); + this._addWhereCondition(prep, pAddPreparedConditionCallback); + } + return this; +}; /** * helper function that checks if the format of a condition is valid * - * @param {string} pCondition condition + * @param {*} pCondition - condition * @throws when the format is invalid */ -SqlBuilder.prototype._verifyConditionFormat = function (pCondition) +SqlBuilder.prototype._verifyConditionFormat = function(pCondition) { if (!pCondition) { @@ -1012,13 +1136,13 @@ SqlBuilder.prototype._verifyConditionFormat = function (pCondition) // replace by {@NUMBERSIGN@} / {@QUESTIONSIGN@} as the js-regexp cannot do lookbehind (needed by the regexp used in replaceConditionTemplate to check escapes) // so we just use replaceConditionTemplate to replace by something which never should occur anywhere (it uses text.replaceAll which supports lookbehind because it uses java) - var conditionFormat = SqlUtils.replaceConditionTemplate(pCondition, "#", "{@NUMBERSIGN@}") - conditionFormat = SqlUtils.replaceConditionTemplate(conditionFormat, "\\?", "{@QUESTIONSIGN@}") + let conditionFormat = SqlUtils.replaceConditionTemplate(pCondition, "#", "{@NUMBERSIGN@}"); + conditionFormat = SqlUtils.replaceConditionTemplate(conditionFormat, "\\?", "{@QUESTIONSIGN@}"); - var indexOfNumberSign = conditionFormat.indexOf("{@NUMBERSIGN@}"); - var indexOfQuestionSign = conditionFormat.indexOf("{@QUESTIONSIGN@}"); + let indexOfNumberSign = conditionFormat.indexOf("{@NUMBERSIGN@}"); + let indexOfQuestionSign = conditionFormat.indexOf("{@QUESTIONSIGN@}"); - var isFormatValid = !(indexOfQuestionSign == -1 + let isFormatValid = !(indexOfQuestionSign == -1 || indexOfNumberSign > indexOfQuestionSign || indexOfNumberSign != conditionFormat.lastIndexOf("{@NUMBERSIGN@}") || indexOfQuestionSign != conditionFormat.lastIndexOf("{@QUESTIONSIGN@}")); @@ -1027,83 +1151,100 @@ SqlBuilder.prototype._verifyConditionFormat = function (pCondition) { throw SqlBuilder._ERROR_CONDITION_WRONG_FORMAT(); } -} +}; /** * helper function to add a condition via "and" * - * @param {string|Array|SqlBuilder|null} pFieldOrCond see .where() - * @param {string|SqlBuilder|Array} pValue see .where() - * @param {boolean} [pMandatory=true] if true: throw error if pValue is null, undefined, SqlBuilder without condition, etc... else just ignore the condition - * @param {string} [pCondition="# = ?"] see .where() - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] see .where() + * @param {string|Array|PreparedSqlStatement|SqlBuilder|null} pFieldOrCond - see .where() + * @param {*} pValue - see .where() + * @param {boolean} [pMandatory=true] - if true: throw error if pValue is null, undefined, SqlBuilder without condition, etc... else just ignore the condition + * @param {string} [pCondition="# = ?"] - see .where() + * @param {number} [pFieldType=AutomaticallyLoadedType] - see .where() * * @ignore */ SqlBuilder.prototype._and = function(pFieldOrCond, pValue, pMandatory, pCondition, pFieldType) { - return this._addWhere(pFieldOrCond, pValue, pMandatory, pCondition, pFieldType, function(pPreparedCondition) - { - this._where._previouslyOnlyOr = false; - if (SqlUtils.isNonEmptyPreparedSqlArray(pPreparedCondition)) + return this._addWhere(pFieldOrCond, pValue, + /** + * @this {SqlBuilder} + * @param {PreparedSqlStatement} pPreparedCondition + */ + function(pPreparedCondition) { - if (this.hasCondition()) - this._where.sqlStorage += " and "; - - this._where.sqlStorage += pPreparedCondition[0]; - this._where.preparedValues = this._where.preparedValues.concat(pPreparedCondition[1]); - } - }); -} + this._whereStatus._previouslyOnlyOr = false; + if (!(pPreparedCondition instanceof PreparedSqlStatement) || pPreparedCondition.isEmpty()) + { + return; + } + this._where.appendStatement(pPreparedCondition, " and "); + }, pMandatory, pCondition, pFieldType); +}; /** * helper function to add a condition via "or" * The callback inside of this function adds brackets where needed. * - * @param {string|Array|SqlBuilder|null} pFieldOrCond see .where() - * @param {string|SqlBuilder|Array} pValue see .where() - * @param {boolean} [pMandatory=true] if true: throw error if pValue is null, undefined, SqlBuilder without condition, etc... else just ignore the condition - * @param {string} [pCondition="# = ?"] see .where() - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] see .where() + * @param {string|Array|PreparedSqlStatement|SqlBuilder|null} pFieldOrCond - see .where() + * @param {*} pValue - see .where() + * @param {boolean} [pMandatory=true] - if true: throw error if pValue is null, undefined, SqlBuilder without condition, etc... else just ignore the condition + * @param {string} [pCondition="# = ?"] - see .where() + * @param {number} [pFieldType=AutomaticallyLoadedType] - see .where() * * @ignore */ SqlBuilder.prototype._or = function(pFieldOrCond, pValue, pMandatory, pCondition, pFieldType) { - return this._addWhere(pFieldOrCond, pValue, pMandatory, pCondition, pFieldType, function(pPreparedCondition, pAlreadySurroundedByBrackets) - { - if (SqlUtils.isNonEmptyPreparedSqlArray(pPreparedCondition)) - { - if (this._where._previouslyOnlyOr) + return this._addWhere(pFieldOrCond, pValue, + /** + * @this {SqlBuilder} + * @param {PreparedSqlStatement} pPreparedCondition + * @param {boolean} pAlreadySurroundedByBrackets + */ + function(pPreparedCondition, pAlreadySurroundedByBrackets) + { + if (!(pPreparedCondition instanceof PreparedSqlStatement) || pPreparedCondition.isEmpty()) { - this._where.sqlStorage = this._where.sqlStorage + " or " + pPreparedCondition[0]; - this._where._lastWasOr = true; + return; + } + if (this._whereStatus._previouslyOnlyOr) + { + this._where.append(" or " + pPreparedCondition.sqlString); + this._whereStatus._lastWasOr = true; } else if (this.hasCondition()) { - let cond = pPreparedCondition[0]; - + let cond = pPreparedCondition.sqlString; + if (!pAlreadySurroundedByBrackets) + { cond = "(" + cond + ")"; - - if (this._where._lastWasOr) - this._where.sqlStorage = this._where.sqlStorage + " or " + cond; + } + + if (this._whereStatus._lastWasOr) + { + this._where.append(" or " + cond); + } else - this._where.sqlStorage = "(" + this._where.sqlStorage + ") or " + cond; - - this._where._lastWasOr = true; + { + this._where.sqlString = "(" + this._where.sqlString + ") or " + cond; + } + + this._whereStatus._lastWasOr = true; } else { if (!this.hasCondition()) - this._where._previouslyOnlyOr = true; - - this._where.sqlStorage = pPreparedCondition[0]; + { + this._whereStatus._previouslyOnlyOr = true; + } + + this._where.sqlString = pPreparedCondition.sqlString; } - this._where.preparedValues = this._where.preparedValues.concat(pPreparedCondition[1]); - } - }); -} + this._where.append(null, pPreparedCondition.preparedValues); + }, pMandatory, pCondition, pFieldType); +}; /** * Constant-like function which provides a value for pCondition if you need a "not" statement. @@ -1111,12 +1252,12 @@ SqlBuilder.prototype._or = function(pFieldOrCond, pValue, pMandatory, pCondition * @return {string} * * @example - * var cond = newWhere(null, someCondition, SqlBuilder.NOT()) + * let cond = newWhere(null, someCondition, SqlBuilder.NOT()) */ SqlBuilder.NOT = function() { return "not ?"; -} +}; /** * Constant-like function which provides a value for pCondition if you need a "not in" statement. @@ -1124,12 +1265,12 @@ SqlBuilder.NOT = function() * @return {string} * * @example - * var cond = newWhere("PERSON.FIRSTNAME", ["Fritz"], SqlBuilder.NOT_IN()) + * let cond = newWhere("PERSON.FIRSTNAME", ["Fritz"], SqlBuilder.NOT_IN()) */ SqlBuilder.NOT_IN = function() { return "# not in ?"; -} +}; /** * Constant-like function which provides a value for pCondition if you need a "in" statement. @@ -1137,12 +1278,12 @@ SqlBuilder.NOT_IN = function() * @return {string} * * @example - * var cond = newWhere("PERSON.FIRSTNAME", ["Fritz"], SqlBuilder.IN()) + * let cond = newWhere("PERSON.FIRSTNAME", ["Fritz"], SqlBuilder.IN()) */ SqlBuilder.IN = function() { return "# in ?"; -} +}; /** * Constant-like function which provides a value for pCondition if you need a "exists" statement. @@ -1150,12 +1291,12 @@ SqlBuilder.IN = function() * @return {string} * * @example - * var cond = newWhere(null, mySubSqlBuilder, SqlBuilder.EXISTS()) + * let cond = newWhere(null, mySubSqlBuilder, SqlBuilder.EXISTS()) */ SqlBuilder.EXISTS = function() { return "exists ?"; -} +}; /** * Constant-like function which provides a value for pCondition if you need a "not exists" statement. @@ -1163,12 +1304,12 @@ SqlBuilder.EXISTS = function() * @return {string} * * @example - * var cond = newWhere(null, mySubSqlBuilder, SqlBuilder.NOT_EXISTS()) + * let cond = newWhere(null, mySubSqlBuilder, SqlBuilder.NOT_EXISTS()) */ SqlBuilder.NOT_EXISTS = function() { return "not exists ?"; -} +}; /** * Constant-like function which provides a value for pCondition if you need a "year(#) = ?" statement. @@ -1177,13 +1318,18 @@ SqlBuilder.NOT_EXISTS = function() * @return {Function} * * @example - * var cond = newWhere("FORECAST.DATE_START", DateUtils.getCurrentYear(), SqlBuilder.YEAR_EQUALS()); + * let cond = newWhere("FORECAST.DATE_START", DateUtils.getCurrentYear(), SqlBuilder.YEAR_EQUALS()); */ -SqlBuilder.YEAR_EQUALS = function () +SqlBuilder.YEAR_EQUALS = function() { //function will be called later so it can use the alias of the SqlBuilder - return function (pAlias) {return [(new SqlMaskingUtils(pAlias).yearFromDate("#")) + " = ?", SQLTYPES.INTEGER];}; -} + return function(pAlias) + + { + return [(new SqlMaskingUtils(pAlias) + .yearFromDate("#")) + " = ?", SQLTYPES.INTEGER]; + }; +}; /** * Constant-like function which provides a value for pCondition if you need a "# = ?" statement. @@ -1192,12 +1338,12 @@ SqlBuilder.YEAR_EQUALS = function () * @return {string} * * @example - * var cond = newWhere("PERSON.FIRSTNAME", "Fritz", SqlBuilder.EQUAL()) + * let cond = newWhere("PERSON.FIRSTNAME", "Fritz", SqlBuilder.EQUAL()) */ -SqlBuilder.EQUAL = function () +SqlBuilder.EQUAL = function() { return "# = ?"; -} +}; /** * Constant-like function which provides a value for pCondition if you need a "# <> ?" statement. @@ -1205,12 +1351,12 @@ SqlBuilder.EQUAL = function () * @return {string} * * @example - * var cond = newWhere("PERSON.FIRSTNAME", "Fritz", SqlBuilder.NOT_EQUALS()) + * let cond = newWhere("PERSON.FIRSTNAME", "Fritz", SqlBuilder.NOT_EQUALS()) */ -SqlBuilder.NOT_EQUAL = function () +SqlBuilder.NOT_EQUAL = function() { return "# <> ?"; -} +}; /** * Constant-like function which provides a value for pCondition if you need a "# like ?" statement. @@ -1218,12 +1364,12 @@ SqlBuilder.NOT_EQUAL = function () * @return {string} * * @example - * var cond = newWhere("PERSON.FIRSTNAME", "F%", SqlBuilder.LIKE()) + * let cond = newWhere("PERSON.FIRSTNAME", "F%", SqlBuilder.LIKE()) */ -SqlBuilder.LIKE = function () +SqlBuilder.LIKE = function() { return "# like ?"; -} +}; /** * Constant-like function which provides a value for pCondition if you need a "# like ?" statement. @@ -1231,62 +1377,62 @@ SqlBuilder.LIKE = function () * @return {string} * * @example - * var cond = newWhere("PERSON.FIRSTNAME", "F%", SqlBuilder.NOT_LIKE()) + * let cond = newWhere("PERSON.FIRSTNAME", "F%", SqlBuilder.NOT_LIKE()) */ -SqlBuilder.NOT_LIKE = function () +SqlBuilder.NOT_LIKE = function() { return "# not like ?"; -} +}; /** * Constant-like function which provides a value for pCondition if you need a "# > ?" statement. * * @return {string} */ -SqlBuilder.GREATER = function () +SqlBuilder.GREATER = function() { return "# > ?"; -} +}; /** * Constant-like function which provides a value for pCondition if you need a "# < ?" statement. * * @return {string} */ -SqlBuilder.LESS = function () +SqlBuilder.LESS = function() { return "# < ?"; -} +}; /** * Constant-like function which provides a value for pCondition if you need a "# >= ?" statement. * * @return {string} */ -SqlBuilder.GREATER_OR_EQUAL = function () +SqlBuilder.GREATER_OR_EQUAL = function() { return "# >= ?"; -} +}; /** * Constant-like function which provides a value for pCondition if you need a "# <= ?" statement. * * @return {string} */ -SqlBuilder.LESS_OR_EQUAL = function () +SqlBuilder.LESS_OR_EQUAL = function() { return "# <= ?"; -} +}; /** * Constant-like function which returns an impossible condition ("1 = 2"). * * @return {string} */ -SqlBuilder.NORESULT_CONDITION = function () +SqlBuilder.NORESULT_CONDITION = function() { return "1 = 2"; -} +}; /** * Object providing constant-like functions for sql-any-conditions. @@ -1295,28 +1441,52 @@ SqlBuilder.ANY = { /** * Constant-like function that returns a "# = any ?" statement. */ - EQUAL : function () {return "# = any ?";}, + EQUAL: function() + + { + return "# = any ?"; + }, /** * Constant-like function that returns a "# <> any ?" statement. */ - NOT_EQUAL : function () {return "# <> any ?";}, + NOT_EQUAL: function() + + { + return "# <> any ?"; + }, /** * Constant-like function that returns a "# > any ?" statement. */ - GREATER : function () {return "# > any ?";}, + GREATER: function() + + { + return "# > any ?"; + }, /** * Constant-like function that returns a "# >= any ?" statement. */ - GREATER_OR_EQUAL : function () {return "# >= any ?";}, + GREATER_OR_EQUAL: function() + + { + return "# >= any ?"; + }, /** * Constant-like function that returns a "# < any ?" statement. */ - LESS : function () {return "# < any ?";}, + LESS: function() + + { + return "# < any ?"; + }, /** * Constant-like function that returns a "# <= any ?" statement. */ - LESS_OR_EQUAL : function () {return "# <= any ?";} -} + LESS_OR_EQUAL: function() + + { + return "# <= any ?"; + } +}; /** * Object providing constant-like functions for sql-all-conditions. @@ -1325,67 +1495,98 @@ SqlBuilder.ALL = { /** * Constant-like function that returns a "# = all ?" statement. */ - EQUAL : function () {return "# = all ?";}, + EQUAL: function() + + { + return "# = all ?"; + }, /** * Constant-like function that returns a "# <> all ?" statement. */ - NOT_EQUAL : function () {return "# <> all ?";}, + NOT_EQUAL: function() + + { + return "# <> all ?"; + }, /** * Constant-like function that returns a "# > all ?" statement. */ - GREATER : function () {return "# > all ?";}, + GREATER: function() + + { + return "# > all ?"; + }, /** * Constant-like function that returns a "# >= all ?" statement. */ - GREATER_OR_EQUAL : function () {return "# >= all ?";}, + GREATER_OR_EQUAL: function() + + { + return "# >= all ?"; + }, /** * Constant-like function that returns a "# < all ?" statement. */ - LESS : function () {return "# < all ?";}, + LESS: function() + + { + return "# < all ?"; + }, /** * Constant-like function that returns a "# <= all ?" statement. */ - LESS_OR_EQUAL : function () {return "# <= all ?";} -} + LESS_OR_EQUAL: function() + + { + return "# <= all ?"; + } +}; /** - * Throws an error if pValue is null, undefined or a SqlBuilder without condition (or if pValue is a $-variable: error if the result of it is null or undefined)<br/> - * Also throws an error if pFieldOrCond is the only parameter and it is null<br/> - * <br/> - * Adds a condition by using "or" to the Sql.<br/> + * Throws an error if pValue is null, undefined or a SqlBuilder without condition (or if pValue is a $-variable: error if the result of it is null or undefined) + * Also throws an error if pFieldOrCond is the only parameter and it is null + * + * Adds a condition by using "or" to the Sql. * Note: You have to call .where before using .and / .or (this is mainly for semantic reasons) * - * @param {string|Array|SqlBuilder|null} [pFieldOrCond] If this is the only parameter, it is used as Condition <br/> - * else it is used as Field.<br/> - * <br/> - * If you use it as Subselect (the only param), it can be:<br/> - * - a string: just added as it is<br/> - * - a PreparedSqlArray: an Array in this form: [sqlStr, [[value1, type1], [valueN, typeN]]]<br/> - * the sql is just used as it is.<br/> - * - a SqlBuilder: ONLY THE CONDITION is used from it<br/> - * <br/> - * If you use it as a Field (at least pValue has to be filled), this param provides the field information to<br/> - * load the SQLTYPE for this condition. <br/> - * It can be provided in the following ways:<br/> - * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" <br/> - * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case<br/> - * Note2: if you need a table alias use the next variant (as array)<br/> - * - a array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"]<br/> + * @param {string|PreparedSqlStatement|SqlBuilder|null} [pFieldOrCond] - This can be either a full condition (then it has to be the only parameter provided) or a field. + * + * If you pass a condition (the only param), these types are allowed: + * - string: just added as it is + * - PreparedSqlStatement: a PreparedSqlStatement object is used as it is + * - SqlBuilder: ONLY THE WHERE-CONDITION is used from it + * + * If you pass a field (at least pValue has to be filled), this param provides the field information to + * load the SQLTYPE for this condition. + * It can be provided in the following ways: + * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" + * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case + * Note2: if you need a table alias use the next variant (as array) + * - an array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"] + * - a SqlBuilder: the full select is used as subselect and compared with pValue. + * (e.g. select * from PERSON where (select "NAME" from ORGANISATION where ... ) = ?) + * Note: for this you have to provide pFieldType as the type cannot be calculated from the subselect! * Note: this can also be null if you don't need the field and use a pCondition without a # * - * @param {string|SqlBuilder|Array} [pValue] This is the value which is used for the condition.<br/> - * Basically it can be nearly everything you need.<br/> - * - String, etc: is just used as value for the prepared statement. Of course it has to fit the type of the db-column<br/> - * - String starting with '$' is treated as jdito-variable: is loaded with vars.getString("$..."). <br/> - * Note: Use 2 '$' to escape the $ if you don't want it to be treated as JditoVar + * @param {*} [pValue] - This is the value which is used for the condition. The type can be anything, but some types are handled in different ways: + * - string: Is just used as value for the prepared statement. Of course it has to fit the type of the db-column. + * If the string starts with a '$' it is recognized as a jdito variable and will be resolved automatically, + * Use 2 '$' to escape the $ if you don't want it to be treated as jdito variable (see {@link SqlUtils.escapeVars}) + * - Array: If the condition allows multiple values (for example 'in'), the values can be passed in an array. + * - SqlBuilder: Can be used for sub-queries + * - PreparedSqlStatement: Can be used for sub-queries + * - all other types: Will be converted to a string + * - NOT ALLOWED: Prepared sql arrays - these will raise an error, because they could provoke sql injection + * * - * @param {string} [pCondition="# = ?"] This is the condition which should be used to compare the field with the value.<br/> - * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/> + * @param {string} [pCondition="# = ?"] - This is the condition which should be used to compare the field with the value. + * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit # * ? will be replaced by pValue + * <strong>IMPORTANT: the # has to be before the ?</strong> * - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] You can specify which datatype should be used for the prepared statement <br/> - * In most cases you don't need this.<br/> - * This is helpful if you for example have a pCondition "year(#) = ?"<br/> + * @param {number} [pFieldType=AutomaticallyLoadedType] - You can specify which datatype should be used for the prepared statement + * In most cases you don't need this, but it's required if pFieldOrCond is a sub-select or null. + * This is helpful if you for example have a pCondition "year(#) = ?" * then the db-field is DATETIME, but the value is INTEGER. In this case you can overwrite the type. * * @return {SqlBuilder} current SqlBuilder object @@ -1393,45 +1594,52 @@ SqlBuilder.ALL = { SqlBuilder.prototype.or = function(pFieldOrCond, pValue, pCondition, pFieldType) { return this._or(pFieldOrCond, pValue, true, pCondition, pFieldType); -} +}; /** - * Difference to or(): or() throws errors on invalid values, orIfSet just ignores the condition and does nothing (usefull e.g. for the parameter variables ("$param.ddd") in conditionProcesses.)<br/> - * <br/> - * Adds a condition by using "or" to the Sql.<br/> + * Difference to or(): or() throws errors on invalid values, orIfSet just ignores the condition and does nothing (usefull e.g. for the parameter variables ("$param.ddd") in conditionProcesses.) + * + * Adds a condition by using "or" to the Sql. * Note: You have to call .where before using .and / .or (this is mainly for semantic reasons) * - * @param {string|Array|SqlBuilder|null} [pFieldOrCond] If this is the only parameter, it is used as Condition <br/> - * else it is used as Field.<br/> - * <br/> - * If you use it as Subselect (the only param), it can be:<br/> - * - a string: just added as it is<br/> - * - a PreparedSqlArray: an Array in this form: [sqlStr, [[value1, type1], [valueN, typeN]]]<br/> - * the sql is just used as it is.<br/> - * - a SqlBuilder: ONLY THE CONDITION is used from it<br/> - * <br/> - * If you use it as a Field (at least pValue has to be filled), this param provides the field information to<br/> - * load the SQLTYPE for this condition. <br/> - * It can be provided in the following ways:<br/> - * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" <br/> - * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case<br/> - * Note2: if you need a table alias use the next variant (as array)<br/> - * - a array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"]<br/> + * @param {string|PreparedSqlStatement|SqlBuilder|null} [pFieldOrCond] - This can be either a full condition (then it has to be the only parameter provided) or a field. + * + * If you pass a condition (the only param), these types are allowed: + * - string: just added as it is + * - PreparedSqlStatement: a PreparedSqlStatement object is used as it is + * - SqlBuilder: ONLY THE WHERE-CONDITION is used from it + * + * If you pass a field (at least pValue has to be filled), this param provides the field information to + * load the SQLTYPE for this condition. + * It can be provided in the following ways: + * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" + * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case + * Note2: if you need a table alias use the next variant (as array) + * - an array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"] + * - a SqlBuilder: the full select is used as subselect and compared with pValue. + * (e.g. select * from PERSON where (select "NAME" from ORGANISATION where ... ) = ?) + * Note: for this you have to provide pFieldType as the type cannot be calculated from the subselect! * Note: this can also be null if you don't need the field and use a pCondition without a # * - * @param {string|SqlBuilder|Array} [pValue] This is the value which is used for the condition.<br/> - * Basically it can be nearly everything you need.<br/> - * - String, etc: is just used as value for the prepared statement. Of course it has to fit the type of the db-column<br/> - * - String starting with '$' is treated as jdito-variable: is loaded with vars.getString("$..."). <br/> - * Note: Use 2 '$' to escape the $ if you don't want it to be treated as JditoVar + * @param {*} [pValue] - This is the value which is used for the condition. The type can be anything, but some types are handled in different ways: + * - string: Is just used as value for the prepared statement. Of course it has to fit the type of the db-column. + * If the string starts with a '$' it is recognized as a jdito variable and will be resolved automatically, + * Use 2 '$' to escape the $ if you don't want it to be treated as jdito variable (see {@link SqlUtils.escapeVars}) + * - Array: If the condition allows multiple values (for example 'in'), the values can be passed in an array. + * - SqlBuilder: Can be used for sub-queries + * - PreparedSqlStatement: Can be used for sub-queries + * - all other types: Will be converted to a string + * - NOT ALLOWED: Prepared sql arrays - these will raise an error, because they could provoke sql injection + * * - * @param {string} [pCondition="# = ?"] This is the condition which should be used to compare the field with the value.<br/> - * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/> + * @param {string} [pCondition="# = ?"] - This is the condition which should be used to compare the field with the value. + * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit # * ? will be replaced by pValue + * <strong>IMPORTANT: the # has to be before the ?</strong> * - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] You can specify which datatype should be used for the prepared statement <br/> - * In most cases you don't need this.<br/> - * This is helpful if you for example have a pCondition "year(#) = ?"<br/> + * @param {number} [pFieldType=AutomaticallyLoadedType] - You can specify which datatype should be used for the prepared statement + * In most cases you don't need this, but it's required if pFieldOrCond is a sub-select or null. + * This is helpful if you for example have a pCondition "year(#) = ?" * then the db-field is DATETIME, but the value is INTEGER. In this case you can overwrite the type. * * @return {SqlBuilder} current SqlBuilder object @@ -1439,46 +1647,53 @@ SqlBuilder.prototype.or = function(pFieldOrCond, pValue, pCondition, pFieldType) SqlBuilder.prototype.orIfSet = function(pFieldOrCond, pValue, pCondition, pFieldType) { return this._or(pFieldOrCond, pValue, false, pCondition, pFieldType); -} +}; /** - * Throws an error if pValue is null, undefined or a SqlBuilder without condition (or if pValue is a $-variable: error if the result of it is null or undefined)<br/> - * Also throws an error if pFieldOrCond is the only parameter and it is null<br/> - * <br/> - * Adds a condition by using "and" to the Sql.<br/> + * Throws an error if pValue is null, undefined or a SqlBuilder without condition (or if pValue is a $-variable: error if the result of it is null or undefined) + * Also throws an error if pFieldOrCond is the only parameter and it is null + * + * Adds a condition by using "and" to the Sql. * Note: You have to call .where before using .and / .or (this is mainly for semantic reasons) * - * @param {string|Array|SqlBuilder|null} [pFieldOrCond] If this is the only parameter, it is used as Condition <br/> - * else it is used as Field.<br/> - * <br/> - * If you use it as Subselect (the only param), it can be:<br/> - * - a string: just added as it is<br/> - * - a PreparedSqlArray: an Array in this form: [sqlStr, [[value1, type1], [valueN, typeN]]]<br/> - * the sql is just used as it is.<br/> - * - a SqlBuilder: ONLY THE CONDITION is used from it<br/> - * <br/> - * If you use it as a Field (at least pValue has to be filled), this param provides the field information to<br/> - * load the SQLTYPE for this condition. <br/> - * It can be provided in the following ways:<br/> - * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" <br/> - * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case<br/> - * Note2: if you need a table alias use the next variant (as array)<br/> - * - a array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"]<br/> + * @param {string|PreparedSqlStatement|SqlBuilder|null} [pFieldOrCond] - This can be either a full condition (then it has to be the only parameter provided) or a field. + * + * If you pass a condition (the only param), these types are allowed: + * - string: just added as it is + * - PreparedSqlStatement: a PreparedSqlStatement object is used as it is + * - SqlBuilder: ONLY THE WHERE-CONDITION is used from it + * + * If you pass a field (at least pValue has to be filled), this param provides the field information to + * load the SQLTYPE for this condition. + * It can be provided in the following ways: + * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" + * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case + * Note2: if you need a table alias use the next variant (as array) + * - an array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"] + * - a SqlBuilder: the full select is used as subselect and compared with pValue. + * (e.g. select * from PERSON where (select "NAME" from ORGANISATION where ... ) = ?) + * Note: for this you have to provide pFieldType as the type cannot be calculated from the subselect! * Note: this can also be null if you don't need the field and use a pCondition without a # * - * @param {string|SqlBuilder|Array} [pValue] This is the value which is used for the condition.<br/> - * Basically it can be nearly everything you need.<br/> - * - String, etc: is just used as value for the prepared statement. Of course it has to fit the type of the db-column<br/> - * - String starting with '$' is treated as jdito-variable: is loaded with vars.getString("$..."). <br/> - * Note: Use 2 '$' to escape the $ if you don't want it to be treated as JditoVar + * @param {*} [pValue] - This is the value which is used for the condition. The type can be anything, but some types are handled in different ways: + * - string: Is just used as value for the prepared statement. Of course it has to fit the type of the db-column. + * If the string starts with a '$' it is recognized as a jdito variable and will be resolved automatically, + * Use 2 '$' to escape the $ if you don't want it to be treated as jdito variable (see {@link SqlUtils.escapeVars}) + * - Array: If the condition allows multiple values (for example 'in'), the values can be passed in an array. + * - SqlBuilder: Can be used for sub-queries + * - PreparedSqlStatement: Can be used for sub-queries + * - all other types: Will be converted to a string + * - NOT ALLOWED: Prepared sql arrays - these will raise an error, because they could provoke sql injection + * * - * @param {string} [pCondition="# = ?"] This is the condition which should be used to compare the field with the value.<br/> - * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/> + * @param {string} [pCondition="# = ?"] - This is the condition which should be used to compare the field with the value. + * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit # * ? will be replaced by pValue + * <strong>IMPORTANT: the # has to be before the ?</strong> * - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] You can specify which datatype should be used for the prepared statement <br/> - * In most cases you don't need this.<br/> - * This is helpful if you for example have a pCondition "year(#) = ?"<br/> + * @param {number} [pFieldType=AutomaticallyLoadedType] - You can specify which datatype should be used for the prepared statement + * In most cases you don't need this, but it's required if pFieldOrCond is a sub-select or null. + * This is helpful if you for example have a pCondition "year(#) = ?" * then the db-field is DATETIME, but the value is INTEGER. In this case you can overwrite the type. * * @return {SqlBuilder} current SqlBuilder object @@ -1486,45 +1701,52 @@ SqlBuilder.prototype.orIfSet = function(pFieldOrCond, pValue, pCondition, pField SqlBuilder.prototype.and = function(pFieldOrCond, pValue, pCondition, pFieldType) { return this._and(pFieldOrCond, pValue, true, pCondition, pFieldType); -} +}; /** - * Difference to and(): and() throws errors on invalid values, andIfSet just ignores the condition and does nothing (usefull e.g. for the parameter variables ("$param.ddd") in conditionProcesses.)<br/> - * <br/> - * Adds a condition by using "and" to the Sql.<br/> + * Difference to and(): and() throws errors on invalid values, andIfSet just ignores the condition and does nothing (usefull e.g. for the parameter variables ("$param.ddd") in conditionProcesses.) + * + * Adds a condition by using "and" to the Sql. * Note: You have to call .where before using .and / .or (this is mainly for semantic reasons) * - * @param {string|Array|SqlBuilder|null} [pFieldOrCond] If this is the only parameter, it is used as Condition <br/> - * else it is used as Field.<br/> - * <br/> - * If you use it as Subselect (the only param), it can be:<br/> - * - a string: just added as it is<br/> - * - a PreparedSqlArray: an Array in this form: [sqlStr, [[value1, type1], [valueN, typeN]]]<br/> - * the sql is just used as it is.<br/> - * - a SqlBuilder: ONLY THE CONDITION is used from it<br/> - * <br/> - * If you use it as a Field (at least pValue has to be filled), this param provides the field information to<br/> - * load the SQLTYPE for this condition. <br/> - * It can be provided in the following ways:<br/> - * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" <br/> - * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case<br/> - * Note2: if you need a table alias use the next variant (as array)<br/> - * - a array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"]<br/> + * @param {string|PreparedSqlStatement|SqlBuilder|null} [pFieldOrCond] - This can be either a full condition (then it has to be the only parameter provided) or a field. + * + * If you pass a condition (the only param), these types are allowed: + * - string: just added as it is + * - PreparedSqlStatement: a PreparedSqlStatement object is used as it is + * - SqlBuilder: ONLY THE WHERE-CONDITION is used from it + * + * If you pass a field (at least pValue has to be filled), this param provides the field information to + * load the SQLTYPE for this condition. + * It can be provided in the following ways: + * - a string: ONLY in this form: "TABLENAME.COLUMNNAME" + * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case + * Note2: if you need a table alias use the next variant (as array) + * - an array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"] + * - a SqlBuilder: the full select is used as subselect and compared with pValue. + * (e.g. select * from PERSON where (select "NAME" from ORGANISATION where ... ) = ?) + * Note: for this you have to provide pFieldType as the type cannot be calculated from the subselect! * Note: this can also be null if you don't need the field and use a pCondition without a # * - * @param {string|SqlBuilder|Array} [pValue] This is the value which is used for the condition.<br/> - * Basically it can be nearly everything you need.<br/> - * - String, etc: is just used as value for the prepared statement. Of course it has to fit the type of the db-column<br/> - * - String starting with '$' is treated as jdito-variable: is loaded with vars.getString("$..."). <br/> - * Note: Use 2 '$' to escape the $ if you don't want it to be treated as JditoVar + * @param {*} [pValue] - This is the value which is used for the condition. The type can be anything, but some types are handled in different ways: + * - string: Is just used as value for the prepared statement. Of course it has to fit the type of the db-column. + * If the string starts with a '$' it is recognized as a jdito variable and will be resolved automatically, + * Use 2 '$' to escape the $ if you don't want it to be treated as jdito variable (see {@link SqlUtils.escapeVars}) + * - Array: If the condition allows multiple values (for example 'in'), the values can be passed in an array. + * - SqlBuilder: Can be used for sub-queries + * - PreparedSqlStatement: Can be used for sub-queries + * - all other types: Will be converted to a string + * - NOT ALLOWED: Prepared sql arrays - these will raise an error, because they could provoke sql injection + * * - * @param {string} [pCondition="# = ?"] This is the condition which should be used to compare the field with the value.<br/> - * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/> + * @param {string} [pCondition="# = ?"] - This is the condition which should be used to compare the field with the value. + * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit # * ? will be replaced by pValue + * <strong>IMPORTANT: the # has to be before the ?</strong> * - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] You can specify which datatype should be used for the prepared statement <br/> - * In most cases you don't need this.<br/> - * This is helpful if you for example have a pCondition "year(#) = ?"<br/> + * @param {number} [pFieldType=AutomaticallyLoadedType] - You can specify which datatype should be used for the prepared statement + * In most cases you don't need this, but it's required if pFieldOrCond is a sub-select or null. + * This is helpful if you for example have a pCondition "year(#) = ?" * then the db-field is DATETIME, but the value is INTEGER. In this case you can overwrite the type. * * @return {SqlBuilder} current SqlBuilder object @@ -1532,31 +1754,31 @@ SqlBuilder.prototype.and = function(pFieldOrCond, pValue, pCondition, pFieldType SqlBuilder.prototype.andIfSet = function(pFieldOrCond, pValue, pCondition, pFieldType) { return this._and(pFieldOrCond, pValue, false, pCondition, pFieldType); -} +}; /** * Sets the order by clause of the sql. * - * @param {string|string[]} pOrderBy a string is added as it is, a array is concatenated by ', ' + * @param {string|string[]} pOrderBy - a string is added as it is, a array is concatenated by ', ' * @return {SqlBuilder} current SqlBuilder object */ SqlBuilder.prototype.orderBy = function(pOrderBy) { this._orderBy = SqlBuilder._getStatement(pOrderBy, "order by", undefined, true); return this; -} +}; /** * Sets the group by clause of the sql. * - * @param {string|string[]} pFields a string is added as it is, a array is concatenated by ', ' + * @param {string|string[]} pFields - a string is added as it is, a array is concatenated by ', ' * @return {SqlBuilder} current SqlBuilder object */ SqlBuilder.prototype.groupBy = function(pFields) { this._groupBy = SqlBuilder._getStatement(pFields, "group by", undefined, true); return this; -} +}; /** * Adds another SqlBuilder object or select string with union. @@ -1568,7 +1790,7 @@ SqlBuilder.prototype.union = function(pSelect) { this._unions.push(SqlBuilder._getStatement(pSelect, "union")); return this; -} +}; /** * Adds another SqlBuilder object or select string with union all. @@ -1580,12 +1802,12 @@ SqlBuilder.prototype.unionAll = function(pSelect) { this._unions.push(SqlBuilder._getStatement(pSelect, "union all")); return this; -} +}; /** * Adds a having clause to the sql. * - * @param {string|SqlBuilder} [pCondition] The having condition. This can be + * @param {string|SqlBuilder} [pCondition] - The having condition. This can be * - a string (without the where keyword) * - a SqlBuilder NOTE: only the condition is used from it * @@ -1595,36 +1817,39 @@ SqlBuilder.prototype.having = function(pCondition) { this._having = SqlBuilder._getStatement(pCondition, "having"); return this; -} +}; /** * checks if conditions have been added to the object + * * @return {boolean} true if conditions have been added, false when not */ -SqlBuilder.prototype.hasCondition = function() { - if (this._where.sqlStorage) - return true; - return false; -} +SqlBuilder.prototype.hasCondition = function() +{ + return !this._where.isEmpty(); +}; /** * checks if conditions have been added to the object * Note: this does not nessecarily mean that hasCondition() is true + * * @return {boolean} true if .where was already called */ -SqlBuilder.prototype.whereWasCalled = function() { - return this._where._whereWasCalled; -} +SqlBuilder.prototype.whereWasCalled = function() +{ + return this._whereStatus._whereWasCalled; +}; /** * checks if all mandatory parts to execute the select have been added to the object * ("select" and "from" parts) + * * @return {boolean} true if select and from have been added, false if not */ SqlBuilder.prototype.isFullSelect = function() { return !(!this._select || !this._from); -} +}; /** * Function that resets the current where-condition as if no conditions would have been added @@ -1637,68 +1862,79 @@ SqlBuilder.prototype.clearWhere = function() { this._initWhere(); return this; -} +}; /** * function that initializes the properties of the ._where object, this is used in the * constructor and .clearWhere */ -SqlBuilder.prototype._initWhere = function () +SqlBuilder.prototype._initWhere = function() { - //TODO: maybe put conditions in an object/array for better internal object structure - this._where.sqlStorage = ""; - this._where.preparedValues = []; - this._where._lastWasOr = false; // save, if the last condition was an OR. For better bracket-placement - this._where._previouslyOnlyOr = false; // also for better bracket-placement - this._where._whereWasCalled = false; // where has always to be called first for a better semantic -} + this._where = new PreparedSqlStatement(); + this._whereStatus = { + _lastWasOr: false, + _previouslyOnlyOr: false, + _whereWasCalled: false + }; +}; /** - * helper function for composing preparedStatements <br/> - * <br/> + * helper function for composing preparedStatements + * * see .where for more information about the parameters * - * @param {string | String[]} pField the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" or as array with column-alias: ["ORGANISATION", "NAME", "myorgAlias"] - * @param {string} pValue the value that shall be set into the prepared statement - * @param {string} pCondition the strucutre of the SQL condition as preparedString, you can use a number sign "#" as placeholder for you fieldname; <br/> - * e.g. "# > ?"; escaping the number sign is possible with a backslash "\" <br/> + * @param {string | string[]} pField - the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" or as array with column-alias: ["ORGANISATION", "NAME", "myorgAlias"] + * @param {*} pValue - the value that shall be set into the prepared statement + * @param {string} pCondition - the strucutre of the SQL condition as preparedString, you can use a number sign "#" as placeholder for you fieldname; + * e.g. "# > ?"; escaping the number sign is possible with a backslash "\" * Default is "# = ?" - * @param {number|boolean} [pFieldType] SQL-column-type; if the fieldType is not given it's loaded automatically; <br/> - * The loaded type is cached if no type is given. So it is also safe to use this in a loop. <br/> - * e.g. <br/> - * for (...) { <br/> - * cond.andPrepare("SALESPROJECT_CLASSIFICATION.TYPE", entry, "# <> ?") <br/> + * @param {number} [pFieldType] - SQL-column-type; if the fieldType is not given it's loaded automatically; + * The loaded type is cached if no type is given. So it is also safe to use this in a loop. + * e.g. + * for (...) { + * cond.andPrepare("SALESPROJECT_CLASSIFICATION.TYPE", entry, "# <> ?") * } - * @param {boolean} pSubselectBrackets if true, brackets are added to subselects - * @return {Array} a preparedSqlArray built out of the given parameters + * @param {boolean} [pSubselectBrackets] - if true, brackets are added to subselects + * @return {PreparedSqlStatement} a PreparedSqlStatement built out of the given parameters * * @ignore */ SqlBuilder.prototype._prepare = function(pField, pValue, pCondition, pFieldType, pSubselectBrackets) { if (pSubselectBrackets == undefined) + { pSubselectBrackets = true; + } if (pValue == undefined) - throw new Error(translate.withArguments("${SQL_LIB_UNDEFINED_VALUE} field: %0", [pField])); + { + throw new Error(translate.withArguments("${SQL_LIB_UNDEFINED_VALUE} field: %0", [pField.toString()])); + } if (pCondition == undefined) + { pCondition = SqlBuilder.EQUAL(); + } - var alias, field; + let alias; if (pField != null) { - [alias, field] = SqlUtils.parseField(pField) + [alias] = SqlUtils.parseField(pField); if (pFieldType == undefined) + { pFieldType = SqlUtils.getSingleColumnType(pField, undefined, this.alias); + } - var table = SqlUtils._parseFieldQualifier(pField).table; + let table = SqlUtils._parseFieldQualifier(pField).table; //Set the table for update/delete if it isn't already set, so you don't need to specify the table if the where-condition contains it if (table && !this._tableName) + { this._tableName = table; + } } - var values = []; + /** @type {[string, number][]} */ + let values = []; // If subselect: replace '?' with the subselect if (Array.isArray(pValue)) @@ -1708,42 +1944,52 @@ SqlBuilder.prototype._prepare = function(pField, pValue, pCondition, pFieldType, } else { - var type = pFieldType; + let type = pFieldType; //booleans are normally stored with TINYINT, this is to support true/false directly if (_isIntegerType(type) && Utils.isBoolean(pValue)) + { pValue = Number(pValue); + } values = [[pValue.toString(), type]]; } if (pField != null) + { pCondition = SqlUtils.replaceConditionTemplate(pCondition, "#", alias); + } - return [pCondition, values]; + return new PreparedSqlStatement(pCondition, values); - function _isIntegerType (pType) + /** + * Checks if the given sql type is a form of integer + * + * @param {number} pType - The sql type + * @returns If it's an integer type + */ + function _isIntegerType(pType) { return pType == SQLTYPES.TINYINT || pType == SQLTYPES.SMALLINT || pType == SQLTYPES.INTEGER || pType == SQLTYPES.BIGINT; } -} +}; /** * generates a part of the sql * - * @param {string|string[]|SqlBuilder} pElement the element to append - * @param {string} [pPrefix] string to be added before pElement - * @param {string} [pPostfix] string to be added after pElement - * @param {boolean} [pAutoJoin=false] if this is true and pElement is an array, it will be automatically <br/> + * @param {string|string[]|SqlBuilder} pElement - the element to append + * @param {string} [pPrefix] - string to be added before pElement + * @param {string} [pPostfix] - string to be added after pElement + * @param {boolean} [pAutoJoin=false] - if this is true and pElement is an array, it will be automatically * joined together to a string - * @param {boolean} [pUseSubselectAlias=false] if true the subselectAlias is added if the element is a subquery - * + * @param {boolean} [pUseSubselectAlias=false] - if true the subselectAlias is added if the element is a subquery + * @return {PreparedSqlStatement} * @ignore */ -SqlBuilder._getStatement = function (pElement, pPrefix, pPostfix, pAutoJoin, pUseSubselectAlias) +SqlBuilder._getStatement = function(pElement, pPrefix, pPostfix, pAutoJoin, pUseSubselectAlias) { - var preparedValues = []; + let preparedValues = []; if (typeof pElement !== "string") { if (Array.isArray(pElement) && pElement.length !== undefined && pAutoJoin) //array of fields @@ -1751,7 +1997,9 @@ SqlBuilder._getStatement = function (pElement, pPrefix, pPostfix, pAutoJoin, pUs for (let i = 0; i < pElement.length; i++) { if (typeof pElement[i] !== "string") + { pElement[i] = _getElement(pElement[i]); + } } pElement = ArrayUtils.joinNonEmptyFields(pElement, ", "); @@ -1763,19 +2011,26 @@ SqlBuilder._getStatement = function (pElement, pPrefix, pPostfix, pAutoJoin, pUs } if (pPrefix && pElement) + { pElement = pPrefix + " " + pElement; + } if (pPostfix && pElement) + { pElement += " " + pPostfix; + } - return { - preparedValues: preparedValues, - sqlStorage: pElement.toString() - }; + return new PreparedSqlStatement(pElement.toString(), preparedValues); - function _getElement (element) + /** + * Builds an sql element + * + * @param {*} element - Part of a sql + * @returns The sql element + */ + function _getElement(element) { - var isSubQuery = false; - var subselectAlias = ""; + let isSubQuery = false; + let subselectAlias = ""; if (SqlBuilder.checkCanBuildSql(element)) { if (element instanceof SqlBuilder && element.isFullSelect()) @@ -1783,98 +2038,96 @@ SqlBuilder._getStatement = function (pElement, pPrefix, pPostfix, pAutoJoin, pUs isSubQuery = true; if (pUseSubselectAlias && element._subselectAlias) + { subselectAlias = " " + element._subselectAlias; + } } element = element.build(); } preparedValues = preparedValues.concat(element[1]); if (isSubQuery || pAutoJoin) + { return "(" + element[0] + ")" + subselectAlias; + } return element[0]; } -} +}; /** * builds a prepared condition out of the object. Only the condition is used. Select, from, ... are ignored. * - * @return {Array} prepared condition + * @return {PreparedSqlArray} prepared condition */ SqlBuilder.prototype.buildCondition = function() { - return [this._where.sqlStorage, this._where.preparedValues]; -} + return this._where.toArray(); +}; /** * builds a prepared statement out of the object. If a part doesn't exit, it's just ignored. * - * @param {string} [pDefaultConditionIfNone=""] a default condition string which should be used if the SqlBuilder doesn't have any condition - * @return {Array} prepared statement + * @param {string} [pDefaultConditionIfNone=""] - a default condition string which should be used if the SqlBuilder doesn't have any condition + * @return {PreparedSqlArray} prepared statement */ SqlBuilder.prototype.build = function(pDefaultConditionIfNone) { - var wherePrefix = ""; - var fromObj = this._from; + let wherePrefix = ""; + let fromStatement = this._from; if (this.isFullSelect()) { - fromObj = { - sqlStorage: "from " + this._from.sqlStorage, - preparedValues: this._from.preparedValues - }; - if (this._where.sqlStorage) + fromStatement = new PreparedSqlStatement("from " + fromStatement.sqlString, fromStatement.preparedValues); + if (!this._where.isEmpty()) + { wherePrefix = "where "; + } } - var whereSql = this._where.sqlStorage; + let whereSql = this._where.sqlString; if (!this.hasCondition() && pDefaultConditionIfNone) + { whereSql = wherePrefix + pDefaultConditionIfNone; + } - var whereObj = { - sqlStorage : wherePrefix + whereSql, - preparedValues : this._where.preparedValues - }; + let whereStatement = new PreparedSqlStatement(wherePrefix + whereSql, this._where.preparedValues); - var allParts = [ + let allParts = [ this._select, - fromObj - ].concat(this._joins).concat([ - whereObj, - this._groupBy, - this._having, - this._orderBy - ]).concat(this._unions); + fromStatement + ].concat(this._joins) + .concat([ + whereStatement, + this._groupBy, + this._having, + this._orderBy + ]) + .concat(this._unions); - var sqlStr = ""; - var preparedVals = []; - for (let i = 0, l = allParts.length; i < l; i++) + let fullSqlStatement = new PreparedSqlStatement(); + for (let part of allParts) { - let part = allParts[i]; if (part) { - if (sqlStr && part.sqlStorage) - sqlStr += " "; - sqlStr += part.sqlStorage; - if (part.preparedValues.length) - preparedVals = preparedVals.concat(part.preparedValues); + fullSqlStatement.appendStatement(part, " "); } } - return [sqlStr, preparedVals]; -} + return fullSqlStatement.toArray(); +}; /** - * Updates data in the database.<br/> + * Updates data in the database. * Note: the default for pExecuteOnlyIfConditionExists is true to prevent updating all rows if the SqlBuilder has no condition. * - * @param {boolean} [pExecuteOnlyIfConditionExists=true] If true, the update is only done if there is a condition.<br/> + * @param {boolean} pExecuteOnlyIfConditionExists - If true, the update is only done if there is a condition. * <strong>IMPORTANT: If this is set to false and there is no condition, every row in the table will be updated!</strong> - * @param {string} [pTableName] The table for updating data. If undefined, the from part of the SqlBuilder will be used (works only if it is a tablename). If no from is set, + * @param {string} pTableName - The table for updating data. If undefined, the from part of the SqlBuilder will be used (works only if it is a tablename). If no from is set, * the table of the first where-condition is used. - * @param {string[]} pColumns The columns where you want to update. - * @param {SQLTYPES[]} [pColumnTypes=null] normally you can set this to null as the types are calculated if not provided - * @param {string[]} pValues The values to be updated. - * @param {number} [pTimeout=-1] Specifies the period, in milliseconds, after which the query will be aborted. + * @param {string[]} pColumns - The columns where you want to update. + * @param {number[]} pColumnTypes - normally you can set this to null as the types are calculated if not provided + * @param {string[]} pValues - The values to be updated. + * @param {number} [pTimeout=-1] - Specifies the period, in milliseconds, after which the query will be aborted. * This timeout value overwrites DBSyncTimeout for the SQL command to be executed (this value was predefined in the * server defaults). If you omit this parameter, DBSyncTimeout will be used. * @return {number} the number of rows affected @@ -1885,10 +2138,14 @@ SqlBuilder.prototype.updateData = function(pExecuteOnlyIfConditionExists, pTable if (this._checkForUpdate(pExecuteOnlyIfConditionExists)) { if (!pTableName && !this._tableName) + { throw SqlBuilder._ERROR_NO_TABLE(); + } if (!pColumns) + { pColumns = null; + } return db.updateData( (pTableName ? pTableName : this._tableName), @@ -1901,54 +2158,62 @@ SqlBuilder.prototype.updateData = function(pExecuteOnlyIfConditionExists, pTable } return 0; -} +}; /** * Updates data in the database. This function calls SqlBuilder.prototype.updateData, but provides a shorter syntax to * improve the readability. * - * @param {Object|Map} pFieldValues Object with the columns to update as keys mapped to their values - * @param {string} [pTableName] The table for updating data. If undefined, the from part of the SqlBuilder will be used (works only if it is a tablename). If no from is set, + * @param {object | Map} pFieldValues - Object with the columns to update as keys mapped to their values + * @param {string} [pTableName] - The table for updating data. If undefined, the from part of the SqlBuilder will be used (works only if it is a tablename). If no from is set, * the table of the first where-condition is used. * @return {number} the number of rows affected * @example * newWhere("SALESORDER.SALESORDERID", "$field.SALESORDERID") * .updateFields({"ORDERSTATUS" : "1"}); //pTableName can be omitted here since it's clearly defined by the given condition */ -SqlBuilder.prototype.updateFields = function (pFieldValues, pTableName) +SqlBuilder.prototype.updateFields = function(pFieldValues, pTableName) { if (!pFieldValues || typeof(pFieldValues) !== "object") + { throw SqlBuilder._ERROR_UPDATE_VALUES_INVALID; + } - var columnValues = SqlBuilder._columnsValuesFromObject(pFieldValues, true); + let columnValues = SqlBuilder._columnsValuesFromObject(pFieldValues, true); if (columnValues.columns.length === 0) + { return 0; + } return this.updateData(true, pTableName, columnValues.columns, null, columnValues.values); -} +}; /** * Builds an array containing the table and condition for an update. * - * @param {Object|Map} pFieldValues Object with the columns to update as keys mapped to their values - * @param {string} [pTableName] The table for updating data. If undefined, the from part of the SqlBuilder will be used (works only if it is a tablename). If no from is set, + * @param {object | Map} pFieldValues - Object with the columns to update as keys mapped to their values + * @param {string} [pTableName] - The table for updating data. If undefined, the from part of the SqlBuilder will be used (works only if it is a tablename). If no from is set, * the table of the first where-condition is used. - * @return {Array} array of [tableName, columns, columnTypes, values, preparedCondition], like it is required by db.updates or null if there is no condition + * @return {[string, string[], number[], string[], Array]} array of [tableName, columns, columnTypes, values, preparedCondition], like it is required by db.updates or null if there is no condition * @example - * var updateStatements = []; + * let updateStatements = []; * updateStatements.push(newWhere("PERSON.PERSONID", pPersonId).buildUpdateStatement({"FIRSTNAME" : firstName})); * updateStatements.push(newWhere("ORGANISATION.ORGANISATIONID", pOrganisationId).buildUpdateStatement({"NAME" : organisationName})); * db.updates(updateStatements); */ -SqlBuilder.prototype.buildUpdateStatement = function (pFieldValues, pTableName) +SqlBuilder.prototype.buildUpdateStatement = function(pFieldValues, pTableName) { if (!pFieldValues || typeof(pFieldValues) !== "object") + { throw SqlBuilder._ERROR_UPDATE_VALUES_INVALID; + } - var columnValues = SqlBuilder._columnsValuesFromObject(pFieldValues, true); + let columnValues = SqlBuilder._columnsValuesFromObject(pFieldValues, true); if (columnValues.columns.length !== 0 && this._checkForUpdate()) { if (!pTableName && !this._tableName) + { throw SqlBuilder._ERROR_NO_TABLE(); + } return [ (pTableName ? pTableName : this._tableName), @@ -1959,30 +2224,36 @@ SqlBuilder.prototype.buildUpdateStatement = function (pFieldValues, pTableName) ]; } return null; -} +}; /** * Builds an array containing the data for an insert. * - * @param {Object|Map} pFieldValues Object with the columns to insert into as keys mapped to their values - * @param {string} [pTableName] The table for updating data. If undefined, the from part of the SqlBuilder will be used (works only if it is a tablename). If no from is set, + * @param {object | Map} pFieldValues - Object with the columns to insert into as keys mapped to their values + * @param {string} [pTableName] - The table for updating data. If undefined, the from part of the SqlBuilder will be used (works only if it is a tablename). If no from is set, * the table of the first where-condition is used. - * @param {string} [pAutoUidField] UID column that should be filled with a random UUID + * @param {string} [pAutoUidField] - UID column that should be filled with a random UUID * @return {Array} array of [tableName, columns, columnTypes, values, preparedCondition], like it is required by db.updates or null if there is no condition */ -SqlBuilder.prototype.buildInsertStatement = function (pFieldValues, pTableName, pAutoUidField) +SqlBuilder.prototype.buildInsertStatement = function(pFieldValues, pTableName, pAutoUidField) { if (!pFieldValues || !Utils.isObject(pFieldValues)) + { throw SqlBuilder._ERROR_UPDATE_VALUES_INVALID; + } if (pAutoUidField) + { pFieldValues[pAutoUidField] = util.getNewUUID(); + } - var columnValues = SqlBuilder._columnsValuesFromObject(pFieldValues); + let columnValues = SqlBuilder._columnsValuesFromObject(pFieldValues); if (columnValues.columns.length !== 0) { if (!pTableName && !this._tableName) + { throw SqlBuilder._ERROR_NO_TABLE(); + } return [ (pTableName ? pTableName : this._tableName), @@ -1992,17 +2263,17 @@ SqlBuilder.prototype.buildInsertStatement = function (pFieldValues, pTableName, ]; } return null; -} +}; /** - * Inserts data in the database. This function doesn't require any where-condition, it is intended to be called right after 'new SqlBuilder()'. <br/> + * Inserts data in the database. This function doesn't require any where-condition, it is intended to be called right after 'new SqlBuilder()'. * - * @param {string} [pTableName] The table for inserting data. If undefined, the from part of the SqlBuilder will be used (works only if it is a tablename). If no from is set, + * @param {string} pTableName - The table for inserting data. If undefined, the from part of the SqlBuilder will be used (works only if it is a tablename). If no from is set, * the table of the first where-condition is used. - * @param {string[]} pColumns The columns where you want to insert into. - * @param {SQLTYPES[]} [pColumnTypes=null] normally you can set this to null as the types are calculated if not provided - * @param {string[]} pValues The values to be inserted. - * @param {number} [pTimeout=-1] Specifies the period, in milliseconds, after which the query will be aborted. + * @param {string[]} pColumns - The columns where you want to insert into. + * @param {number[]} pColumnTypes - normally you can set this to null as the types are calculated if not provided + * @param {string[]} pValues - The values to be inserted. + * @param {number} [pTimeout=-1] - Specifies the period, in milliseconds, after which the query will be aborted. * This timeout value overwrites DBSyncTimeout for the SQL command to be executed (this value was predefined in the * server defaults). If you omit this parameter, DBSyncTimeout will be used. * @return {number} the number of rows affected @@ -2011,10 +2282,14 @@ SqlBuilder.prototype.buildInsertStatement = function (pFieldValues, pTableName, SqlBuilder.prototype.insertData = function(pTableName, pColumns, pColumnTypes, pValues, pTimeout) { if (!pTableName && !this._tableName) + { throw SqlBuilder._ERROR_NO_TABLE(); + } if (!pColumns) + { pColumns = null; + } return db.insertData( (pTableName ? pTableName : this._tableName), @@ -2023,16 +2298,16 @@ SqlBuilder.prototype.insertData = function(pTableName, pColumns, pColumnTypes, p pValues, (this.alias ? this.alias : db.getCurrentAlias()), (pTimeout ? pTimeout : -1)); -} +}; /** * Inserts data in the database. This function calls SqlBuilder.prototype.insertData, but provides a shorter syntax to * improve the readability. * - * @param {Object|Map} pFieldValues Object with the columns to update as keys mapped to their values - * @param {string} [pTableName] The table for updating data. If undefined, the from part of the SqlBuilder will be used (works only if it is a tablename). If no from is set, + * @param {object | Map} pFieldValues - Object with the columns to update as keys mapped to their values + * @param {string} [pTableName] - The table for updating data. If undefined, the from part of the SqlBuilder will be used (works only if it is a tablename). If no from is set, * the table of the first where-condition is used. - * @param {string} [pAutoUidField] UID column that should be filled with a random UUID + * @param {string} [pAutoUidField] - UID column that should be filled with a random UUID * @return {number} the number of rows affected * @example * new SqlBuilder().insertFields({ @@ -2041,27 +2316,40 @@ SqlBuilder.prototype.insertData = function(pTableName, pColumns, pColumnTypes, p * "OBJECT_TYPE" : pObjectType * }, "ACTIVITYLINK", "ACTIVITYLINKID"); */ -SqlBuilder.prototype.insertFields = function (pFieldValues, pTableName, pAutoUidField) +SqlBuilder.prototype.insertFields = function(pFieldValues, pTableName, pAutoUidField) { if (!pFieldValues || typeof(pFieldValues) !== "object") + { throw SqlBuilder._ERROR_UPDATE_VALUES_INVALID; + } if (pAutoUidField) + { pFieldValues[pAutoUidField] = util.getNewUUID(); + } - var columnValues = SqlBuilder._columnsValuesFromObject(pFieldValues); + let columnValues = SqlBuilder._columnsValuesFromObject(pFieldValues); if (columnValues.columns.length === 0) + { return 0; + } return this.insertData(pTableName, columnValues.columns, null, columnValues.values); -} +}; -SqlBuilder._columnsValuesFromObject = function (pFieldValues, pIncludeNullValues) +/** + * Maps an object of fields and values to two seperate arrays + * + * @param {object} pFieldValues - Object with fields and values, the values will be stringified + * @param {boolean} [pIncludeNullValues=false] - If properties with no value should be mapped as empty strings or if they should be omitted + * @return {{columns: string[], values: string[]}} Object containing arrays of columns and values + */ +SqlBuilder._columnsValuesFromObject = function(pFieldValues, pIncludeNullValues) { - var columns = []; - var values = []; + let columns = []; + let values = []; if (Utils.isMap(pFieldValues)) { - pFieldValues.forEach(function (value, key) + pFieldValues.forEach(function(value, key) { if (pIncludeNullValues || (value !== undefined && value !== null)) { @@ -2086,23 +2374,31 @@ SqlBuilder._columnsValuesFromObject = function (pFieldValues, pIncludeNullValues values: values }; - function _valueToString (pValue) + /** + * Stringifies a value + * + * @param {*} pValue - The value + * @returns {string} String representation of the value + */ + function _valueToString(pValue) { if (pValue === undefined || pValue === null) + { return ""; + } return pValue.toString(); } -} +}; /** - * Deletes data from the database.<br/> + * Deletes data from the database. * Note: the default for pExecuteOnlyIfConditionExists is true to prevent updating all rows if the SqlBuilder has no condition. - - * @param {boolean} [pExecuteOnlyIfConditionExists=true] If true, the deletion is only done if there is a condition.<br/> + * + * @param {boolean} [pExecuteOnlyIfConditionExists=true] - If true, the deletion is only done if there is a condition. * <strong>IMPORTANT: If this is set to false and there is no condition, every row in the table will be deleted!</strong> - * @param {string} [pTableName] The table for deleting data. If undefined, the from part of the SqlBuilder will be used. If no from is set, + * @param {string} [pTableName] - The table for deleting data. If undefined, the from part of the SqlBuilder will be used. If no from is set, * the table of the first where-condition is used. - * @param {number} [pTimeout=-1] Specifies the period, in milliseconds, after which the query will be aborted. + * @param {number} [pTimeout=-1] - Specifies the period, in milliseconds, after which the query will be aborted. * This timeout value overwrites DBSyncTimeout for the SQL command to be executed (this value was predefined in the * server defaults). If you omit this parameter, DBSyncTimeout will be used. * @return {number} the number of rows affected @@ -2116,7 +2412,9 @@ SqlBuilder.prototype.deleteData = function(pExecuteOnlyIfConditionExists, pTable if (this._checkForUpdate(pExecuteOnlyIfConditionExists)) { if (!pTableName && !this._tableName) + { throw SqlBuilder._ERROR_NO_TABLE(); + } return db.deleteData( (pTableName ? pTableName : this._tableName), @@ -2128,18 +2426,18 @@ SqlBuilder.prototype.deleteData = function(pExecuteOnlyIfConditionExists, pTable { return 0; } -} +}; /** * Builds an array containing the table and condition for a delete. * - * @param {boolean} [pOnlyIfConditionExists=true] If true and there is no condition, null is returned.<br/> + * @param {boolean} [pOnlyIfConditionExists=true] - If true and there is no condition, null is returned. * <strong>IMPORTANT: If this is set to false and there is no condition, every row in the table will be deleted!</strong> - * @param {string} [pTableName] The table for deleting data. If undefined, the from part of the SqlBuilder will be used. If no from is set, + * @param {string} [pTableName] - The table for deleting data. If undefined, the from part of the SqlBuilder will be used. If no from is set, * the table of the first where-condition is used. * @return {Array} array of [tableName, preparedCondition], like it is required by db.deletes * @example - * var deleteStatements = []; + * let deleteStatements = []; * deleteStatements.push(newWhere("PERSON.PERSONID", pPersonId).buildDeleteStatement()); * deleteStatements.push(newWhere("CONTACT.CONTACTID", pContactId).buildDeleteStatement()); * db.deletes(deleteStatements); @@ -2149,7 +2447,9 @@ SqlBuilder.prototype.buildDeleteStatement = function(pOnlyIfConditionExists, pTa if (this._checkForUpdate(pOnlyIfConditionExists)) { if (!pTableName && !this._tableName) + { throw SqlBuilder._ERROR_NO_TABLE(); + } return [ (pTableName ? pTableName : this._tableName), @@ -2157,15 +2457,17 @@ SqlBuilder.prototype.buildDeleteStatement = function(pOnlyIfConditionExists, pTa ]; } else + { return null; -} + } +}; /** - * Executes the SQL using db.cell and returns the result.<br/> + * Executes the SQL using db.cell and returns the result. * Note: the default for pExecuteOnlyIfConditionExists is false becausse it is more natural to select all rows if no condition exists. * - * @param {boolean} [pExecuteOnlyIfConditionExists=false] if true and there is no condition, "" or the provided FallbackValue is returned - * @param {any} [pFallbackValue=""] here you can provide a fallback value if pExecuteOnlyIfConditionExists is true and the SqlBuilder has no condition.<br/> + * @param {boolean} [pExecuteOnlyIfConditionExists=false] - if true and there is no condition, "" or the provided FallbackValue is returned + * @param {any} [pFallbackValue=""] - here you can provide a fallback value if pExecuteOnlyIfConditionExists is true and the SqlBuilder has no condition. * This is intended for e.g. select count(*) from ... because there a default value of "0" is more helpful * @return {string} the result of the query */ @@ -2180,54 +2482,54 @@ SqlBuilder.prototype.cell = function(pExecuteOnlyIfConditionExists, pFallbackVal { return (pFallbackValue ? pFallbackValue : ""); } -} +}; /** - * Executes the SQL using db.array(db.ROW, ...) and returns the result.<br/> + * Executes the SQL using db.array(db.ROW, ...) and returns the result. * Note: the default for pExecuteOnlyIfConditionExists is false becausse it is more natural to select all rows if no condition exists. * - * @param {boolean} [pExecuteOnlyIfConditionExists=false] if true and there is no condition, [] is returned - * @param {number} [pMaxRows=0] The maximum number of rows to be returned (ORDER BY must be defined in the SQL statement!). - * If you specify maxRows, this restriction might apply to all tables indicated in the JOIN part of the query. - * If you do not want to restrict this value, enter 0. - * @param {number} [pTimeout=-1] Specifies the period, in milliseconds, after which the query will be aborted. + * @param {boolean} [pExecuteOnlyIfConditionExists=false] - if true and there is no condition, [] is returned + * @param {number} [pMaxRows=0] - The maximum number of rows to be returned (ORDER BY must be defined in the SQL statement!). + * If you specify maxRows, this restriction might apply to all tables indicated in the JOIN part of the query. + * If you do not want to restrict this value, enter 0. + * @param {number} [pTimeout=-1] - Specifies the period, in milliseconds, after which the query will be aborted. * This timeout value overwrites DBSyncTimeout for the SQL command to be executed (this value was predefined in the * server defaults). If you omit this parameter, DBSyncTimeout will be used. * @return {string[]} the result of the query */ -SqlBuilder.prototype.arrayRow = function (pExecuteOnlyIfConditionExists, pMaxRows, pTimeout) +SqlBuilder.prototype.arrayRow = function(pExecuteOnlyIfConditionExists, pMaxRows, pTimeout) { return this.array(db.ROW, pExecuteOnlyIfConditionExists, pMaxRows, pTimeout); -} +}; /** - * Executes the SQL using db.array(db.COLUMN, ...) and returns the result.<br/> + * Executes the SQL using db.array(db.COLUMN, ...) and returns the result. * Note: the default for pExecuteOnlyIfConditionExists is false becausse it is more natural to select all rows if no condition exists. * - * @param {boolean} [pExecuteOnlyIfConditionExists=false] if true and there is no condition, [] is returned - * @param {number} [pMaxRows=0] The maximum number of rows to be returned (ORDER BY must be defined in the SQL statement!). - * If you specify maxRows, this restriction might apply to all tables indicated in the JOIN part of the query. - * If you do not want to restrict this value, enter 0. - * @param {number} [pTimeout=-1] Specifies the period, in milliseconds, after which the query will be aborted. + * @param {boolean} [pExecuteOnlyIfConditionExists=false] - if true and there is no condition, [] is returned + * @param {number} [pMaxRows=0] - The maximum number of rows to be returned (ORDER BY must be defined in the SQL statement!). + * If you specify maxRows, this restriction might apply to all tables indicated in the JOIN part of the query. + * If you do not want to restrict this value, enter 0. + * @param {number} [pTimeout=-1] - Specifies the period, in milliseconds, after which the query will be aborted. * This timeout value overwrites DBSyncTimeout for the SQL command to be executed (this value was predefined in the * server defaults). If you omit this parameter, DBSyncTimeout will be used. * @return {string[]} the result of the query */ -SqlBuilder.prototype.arrayColumn = function (pExecuteOnlyIfConditionExists, pMaxRows, pTimeout) +SqlBuilder.prototype.arrayColumn = function(pExecuteOnlyIfConditionExists, pMaxRows, pTimeout) { return this.array(db.COLUMN, pExecuteOnlyIfConditionExists, pMaxRows, pTimeout); -} +}; /** - * Executes the SQL using db.array and returns the result.<br/> + * Executes the SQL using db.array and returns the result. * Note: the default for pExecuteOnlyIfConditionExists is false becausse it is more natural to select all rows if no condition exists. * - * @param {number} pType db.ROW or db.COLUMN - * @param {boolean} [pExecuteOnlyIfConditionExists=false] if true and there is no condition, [] is returned - * @param {number} [pMaxRows=0] The maximum number of rows to be returned (ORDER BY must be defined in the SQL statement!). - * If you specify maxRows, this restriction might apply to all tables indicated in the JOIN part of the query. - * If you do not want to restrict this value, enter 0. - * @param {number} [pTimeout=-1] Specifies the period, in milliseconds, after which the query will be aborted. + * @param {number} pType - db.ROW or db.COLUMN + * @param {boolean} [pExecuteOnlyIfConditionExists=false] - if true and there is no condition, [] is returned + * @param {number} [pMaxRows=0] - The maximum number of rows to be returned (ORDER BY must be defined in the SQL statement!). + * If you specify maxRows, this restriction might apply to all tables indicated in the JOIN part of the query. + * If you do not want to restrict this value, enter 0. + * @param {number} [pTimeout=-1] - Specifies the period, in milliseconds, after which the query will be aborted. * This timeout value overwrites DBSyncTimeout for the SQL command to be executed (this value was predefined in the * server defaults). If you omit this parameter, DBSyncTimeout will be used. * @return {string[]} the result of the query @@ -2245,17 +2547,17 @@ SqlBuilder.prototype.array = function(pType, pExecuteOnlyIfConditionExists, pMax { return []; } -} +}; /** - * Executes the SQL using db.arrayPage and returns the result.<br/> + * Executes the SQL using db.arrayPage and returns the result. * Note: the default for pExecuteOnlyIfConditionExists is false becausse it is more natural to select all rows if no condition exists. * - * @param {number} pType db.ROW or db.COLUMN + * @param {number} pType - db.ROW or db.COLUMN * @param {number} pStartIndex * @param {number} pRowCount - * @param {boolean} [pExecuteOnlyIfConditionExists=false] if true and there is no condition, [] is returned - * @param {number} [pTimeout=-1] Specifies the period, in milliseconds, after which the query will be aborted. + * @param {boolean} [pExecuteOnlyIfConditionExists=false] - if true and there is no condition, [] is returned + * @param {number} [pTimeout=-1] - Specifies the period, in milliseconds, after which the query will be aborted. * This timeout value overwrites DBSyncTimeout for the SQL command to be executed (this value was predefined in the * server defaults). If you omit this parameter, DBSyncTimeout will be used. * @return {string[]} the result of the query @@ -2274,17 +2576,17 @@ SqlBuilder.prototype.arrayPage = function(pType, pStartIndex, pRowCount, pExecut { return []; } -} +}; /** - * Executes the SQL using db.table and returns the result.<br/> + * Executes the SQL using db.table and returns the result. * Note: the default for pExecuteOnlyIfConditionExists is false becausse it is more natural to select all rows if no condition exists. * - * @param {boolean} [pExecuteOnlyIfConditionExists=false] if true and there is no condition, [] is returned - * @param {number} [pMaxRows=0] The maximum number of rows to be returned (ORDER BY must be defined in the SQL statement!). - * If you specify maxRows, this restriction might apply to all tables indicated in the JOIN part of the query. - * If you do not want to restrict this value, enter 0. - * @param {number} [pTimeout=-1] Specifies the period, in milliseconds, after which the query will be aborted. + * @param {boolean} [pExecuteOnlyIfConditionExists=false] - if true and there is no condition, [] is returned + * @param {number} [pMaxRows=0] - The maximum number of rows to be returned (ORDER BY must be defined in the SQL statement!). + * If you specify maxRows, this restriction might apply to all tables indicated in the JOIN part of the query. + * If you do not want to restrict this value, enter 0. + * @param {number} [pTimeout=-1] - Specifies the period, in milliseconds, after which the query will be aborted. * This timeout value overwrites DBSyncTimeout for the SQL command to be executed (this value was predefined in the * server defaults). If you omit this parameter, DBSyncTimeout will be used. * @return {string[][]} the result of the query @@ -2302,16 +2604,16 @@ SqlBuilder.prototype.table = function(pExecuteOnlyIfConditionExists, pMaxRows, p { return []; } -} +}; /** - * Executes the SQL using db.tablePage and returns the result.<br/> + * Executes the SQL using db.tablePage and returns the result. * Note: the default for pExecuteOnlyIfConditionExists is false becausse it is more natural to select all rows if no condition exists. * * @param {number} pStartIndex * @param {number} pRowCount - * @param {boolean} [pExecuteOnlyIfConditionExists=false] if true and there is no condition, [] is returned - * @param {number} [pTimeout=-1] Specifies the period, in milliseconds, after which the query will be aborted. + * @param {boolean} [pExecuteOnlyIfConditionExists=false] - if true and there is no condition, [] is returned + * @param {number} [pTimeout=-1] - Specifies the period, in milliseconds, after which the query will be aborted. * This timeout value overwrites DBSyncTimeout for the SQL command to be executed (this value was predefined in the * server defaults). If you omit this parameter, DBSyncTimeout will be used. * @return {string[][]} the result of the query @@ -2330,7 +2632,7 @@ SqlBuilder.prototype.tablePage = function(pStartIndex, pRowCount, pExecuteOnlyIf { return []; } -} +}; /** * Sets the pagesize for paging @@ -2338,11 +2640,11 @@ SqlBuilder.prototype.tablePage = function(pStartIndex, pRowCount, pExecuteOnlyIf * @param {number} pPageSize * @return {SqlBuilder} current SqlBuilder object */ -SqlBuilder.prototype.pageSize = function (pPageSize) +SqlBuilder.prototype.pageSize = function(pPageSize) { this._pageSize = pPageSize; return this; -} +}; /** * Sets the start row for paging @@ -2350,35 +2652,41 @@ SqlBuilder.prototype.pageSize = function (pPageSize) * @param {number} pStartRow * @return {SqlBuilder} current SqlBuilder object */ -SqlBuilder.prototype.startRow = function (pStartRow) +SqlBuilder.prototype.startRow = function(pStartRow) { this._startRow = pStartRow; return this; -} +}; /** * Executes the SQL and returns the result. The startRow for paging will be increased by the pageSize, so you can use this method * for iterating over the table pages. You can use SqlBuilder.prototype.hasMoreRows() to check if the end of rows was reached. * - * @param {boolean} [pExecuteOnlyIfConditionExists=false] if true and there is no condition, [] is returned - * @param {number} [pTimeout=-1] Specifies the period, in milliseconds, after which the query will be aborted. + * @param {boolean} [pExecuteOnlyIfConditionExists=false] - if true and there is no condition, [] is returned + * @param {number} [pTimeout=-1] - Specifies the period, in milliseconds, after which the query will be aborted. * This timeout value overwrites DBSyncTimeout for the SQL command to be executed (this value was predefined in the * server defaults). If you omit this parameter, DBSyncTimeout will be used. * @return {string[][]} the result of the query */ -SqlBuilder.prototype.nextTablePage = function (pExecuteOnlyIfConditionExists, pTimeout) +SqlBuilder.prototype.nextTablePage = function(pExecuteOnlyIfConditionExists, pTimeout) { if (this._pageSize == null || isNaN(this._pageSize)) + { throw SqlBuilder._ERROR_PAGESIZE_INVALID(); + } if (this._startRow == null) + { this._startRow = 0; + } if (this._hasMoreRows && this._checkForSelect(pExecuteOnlyIfConditionExists)) { - var data = this.tablePage(this._startRow, this._pageSize, pExecuteOnlyIfConditionExists, pTimeout); + let data = this.tablePage(this._startRow, this._pageSize, pExecuteOnlyIfConditionExists, pTimeout); if (data.length < this._pageSize) + { this._hasMoreRows = false; + } this._startRow += this._pageSize; return data; } @@ -2387,77 +2695,88 @@ SqlBuilder.prototype.nextTablePage = function (pExecuteOnlyIfConditionExists, pT this._hasMoreRows = false; return []; } -} +}; /** * @return {boolean} whether there are rows left for paging */ -SqlBuilder.prototype.hasMoreRows = function () +SqlBuilder.prototype.hasMoreRows = function() { return this._hasMoreRows; -} +}; /** * Executes the SQL with paging and executes the given callback-function for every resultset until the last row has been reached or the function * returns false. * - * @param {Function} pCallBackFn CallBack-Function to execute for every page. If the function returns false, the execution will be stopped. - * @param {boolean} [pExecuteOnlyIfConditionExists=false] if true and there is no condition, [] is returned - * @param {number} [pTimeout=-1] Specifies the period, in milliseconds, after which the query will be aborted. + * @param {Function} pCallBackFn - CallBack-Function to execute for every page. If the function returns false, the execution will be stopped. + * @param {boolean} [pExecuteOnlyIfConditionExists=false] - if true and there is no condition, [] is returned + * @param {number} [pTimeout=-1] - Specifies the period, in milliseconds, after which the query will be aborted. * This timeout value overwrites DBSyncTimeout for the SQL command to be executed (this value was predefined in the * server defaults). If you omit this parameter, DBSyncTimeout will be used. */ -SqlBuilder.prototype.forEachPage = function (pCallBackFn, pExecuteOnlyIfConditionExists, pTimeout) +SqlBuilder.prototype.forEachPage = function(pCallBackFn, pExecuteOnlyIfConditionExists, pTimeout) { if (typeof pCallBackFn !== "function") + { throw SqlBuilder._ERROR_NOT_A_FUNCTION(); + } - var run = true; - var idx = 0; + let run = true; + let idx = 0; while (run && this.hasMoreRows()) { run = pCallBackFn.call(null, this.nextTablePage(pExecuteOnlyIfConditionExists, pTimeout), idx++) != false; } -} +}; /** * Sets an impossible where-condition, so that the query won't return any rows. * * @return {SqlBuilder} current object */ -SqlBuilder.prototype.noResult = function () +SqlBuilder.prototype.noResult = function() { - return this.clearWhere().where(SqlBuilder.NORESULT_CONDITION()); -} + return this.clearWhere() + .where(SqlBuilder.NORESULT_CONDITION()); +}; /** * checks if an update /delete statement should be called or not + * * @return {boolean} - * @private */ SqlBuilder.prototype._checkForUpdate = function(pExecuteOnlyIfConditionExists) { if (pExecuteOnlyIfConditionExists === undefined) + { pExecuteOnlyIfConditionExists = true; + } if (typeof pExecuteOnlyIfConditionExists !== "boolean") + { throw SqlBuilder._ERROR_NOT_BOOLEAN(); + } return !pExecuteOnlyIfConditionExists || this.hasCondition(); -} +}; /** * checks if a select statement should be called or not + * * @return {boolean} - * @private */ SqlBuilder.prototype._checkForSelect = function(pExecuteOnlyIfConditionExists) { if (pExecuteOnlyIfConditionExists == undefined) + { pExecuteOnlyIfConditionExists = false; + } if (typeof pExecuteOnlyIfConditionExists !== "boolean") + { throw SqlBuilder._ERROR_NOT_BOOLEAN(); + } if (this.isFullSelect()) { @@ -2467,13 +2786,14 @@ SqlBuilder.prototype._checkForSelect = function(pExecuteOnlyIfConditionExists) { throw SqlBuilder._ERROR_INCOMPLETE_SELECT(); } -} +}; /** * translates SqlBuilder to plain SQL. Use this if prepared statements are not supported. * For the db-functions (db.table, db.cell, etc.) use ".build()" as they support prepared statements. * It resolves all prepared values. - * @param {string} [pAlias=undefined] the alias to use for db.translateStatement + * + * @param {string} [pAlias=undefined] - the alias to use for db.translateStatement * @return {string} plain SQL statement * * @deprecated use .toString() @@ -2481,181 +2801,195 @@ SqlBuilder.prototype._checkForSelect = function(pExecuteOnlyIfConditionExists) SqlBuilder.prototype.translate = function(pAlias) { return SqlUtils.translateStatementWithQuotes(this.build(), pAlias); -} +}; /** * Creates an object for building a case-when statement. * - * @param {string|Array|SqlBuilder} [pFieldOrCond] If this is the only parameter, it is used as Subselect <br/> - * else it is used as Field. <br/> + * @param {string|PreparedSqlStatement|SqlBuilder} [pFieldOrCond] - If this is the only parameter, it is used as Subselect + * else it is used as Field. * Please see .where() for more information and examples. - * @param {string|SqlBuilder|Array} [pValue] This is the value whitch is used for the condition.<br/> - * Basically it can be nearly everything you need.<br/> + * @param {string|SqlBuilder|Array} [pValue] - This is the value whitch is used for the condition. + * Basically it can be nearly everything you need. * Please see .where() for more information and examples. - * @param {string} [pCondition="# = ?"] This is the condition which should be used.<br/> - * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/> - * ? will be replaced by pValue<br/> - * <strong>IMPORTANT: the # has to be before the ?</strong><br/> + * @param {string} [pCondition="# = ?"] - This is the condition which should be used. + * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit # + * ? will be replaced by pValue + * <strong>IMPORTANT: the # has to be before the ?</strong> * Please see .where() for more information and examples. - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] You can specify which datatype should be used for the prepared statement<br/> - * In most cases you don't need this.<br/> + * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] - You can specify which datatype should be used for the prepared statement + * In most cases you don't need this. * Please see .where() for more information and examples. * * @return {SqlBuilder._CaseWhen} */ -SqlBuilder.caseWhen = function (pFieldOrCond, pValue, pCondition, pFieldType) +SqlBuilder.caseWhen = function(pFieldOrCond, pValue, pCondition, pFieldType) { - return new SqlBuilder._CaseStatement().when(pFieldOrCond, pValue, pCondition, pFieldType); -} + return new SqlBuilder._CaseStatement() + .when(pFieldOrCond, pValue, pCondition, pFieldType); +}; /** * @return {SqlBuilder._CaseStatement} */ -SqlBuilder.caseStatement = function () +SqlBuilder.caseStatement = function() { return new SqlBuilder._CaseStatement(); -} +}; /** * Represents a case-when statement */ -SqlBuilder._CaseStatement = function () +SqlBuilder._CaseStatement = function() { this._whenCondition = null; this._whenThens = []; this._elseValue = null; this._afterWhenMask = new SqlBuilder._CaseWhen(this); -} +}; SqlBuilder.defineCanBuildSql(SqlBuilder._CaseStatement.prototype); /** - * @param {string|Array|SqlBuilder} [pFieldOrCond] If this is the only parameter, it is used as Subselect <br/> - * else it is used as Field. <br/> + * @param {string|PreparedSqlStatement|SqlBuilder} [pFieldOrCond] - If this is the only parameter, it is used as Subselect + * else it is used as Field. * Please see .where() for more information and examples. - * @param {string|SqlBuilder|Array} [pValue] This is the value whitch is used for the condition.<br/> - * Basically it can be nearly everything you need.<br/> + * @param {string|SqlBuilder|Array} [pValue] - This is the value whitch is used for the condition. + * Basically it can be nearly everything you need. * Please see .where() for more information and examples. - * @param {string} [pCondition="# = ?"] This is the condition which should be used.<br/> - * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/> - * ? will be replaced by pValue<br/> - * <strong>IMPORTANT: the # has to be before the ?</strong><br/> + * @param {string} [pCondition="# = ?"] - This is the condition which should be used. + * # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit # + * ? will be replaced by pValue + * <strong>IMPORTANT: the # has to be before the ?</strong> * Please see .where() for more information and examples. - * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] You can specify which datatype should be used for the prepared statement<br/> - * In most cases you don't need this.<br/> + * @param {SQLTYPES|number} [pFieldType=AutomaticallyLoadedType] - You can specify which datatype should be used for the prepared statement + * In most cases you don't need this. * Please see .where() for more information and examples. - * @param {string} [pAlias=currentAlias] This alias is used for fetching the ColumnTypes and also for the .table, .cell, .updateData, ... -functions * * @return {SqlBuilder._CaseWhen} */ -SqlBuilder._CaseStatement.prototype.when = function (pFieldOrCond, pValue, pCondition, pFieldType, pAlias) +SqlBuilder._CaseStatement.prototype.when = function(pFieldOrCond, pValue, pCondition, pFieldType) { - this._whenCondition = newWhere(pFieldOrCond, pValue, pCondition, pFieldType, pAlias); + this._whenCondition = newWhere(pFieldOrCond, pValue, pCondition, pFieldType); return this._afterWhenMask; -} +}; /** * Sets the expression used for the else-part * - * @param {string|SqlBuilder} pValue else-value + * @param {string|SqlBuilder} pValue - else-value * @return {SqlBuilder._CaseStatement} */ -SqlBuilder._CaseStatement.prototype.elseValue = function (pValue) +SqlBuilder._CaseStatement.prototype.elseValue = function(pValue) { + if (SqlUtils.isPreparedSqlArray(pValue)) + { + throw SqlBuilder._ERROR_SQL_ARRAY_AS_VALUE_NOT_ALLOWED(); + } this._elseValue = pValue; return this; -} +}; /** * Sets the value used for the else-part, but wraps the value in '' * - * @param {string} pValue else-value + * @param {string} pValue - else-value * @return {SqlBuilder._CaseStatement} */ -SqlBuilder._CaseStatement.prototype.elseString = function (pValue) +SqlBuilder._CaseStatement.prototype.elseString = function(pValue) { - return this.elseValue("'" + pValue + "'"); -} + return this.elseValue("'" + db.quote(pValue, db.getCurrentAlias()) + "'"); +}; /** * @return {string} the case-when expression */ -SqlBuilder._CaseStatement.prototype.toString = function (pAlias) +SqlBuilder._CaseStatement.prototype.toString = function() { - return db.translateStatement(this.build(), pAlias || db.getCurrentAlias()); -} + let built = this.build(); + + if (built[0] !== "") + { + return SqlUtils.translateConditionWithQuotes(built, db.getCurrentAlias()); + } + + return ""; +}; -SqlBuilder._CaseStatement.prototype.build = function (pParameters) +/** + * Builds a prepared sql array from the case statements + */ +SqlBuilder._CaseStatement.prototype.build = function() { - var caseStatement = ["case"]; - var preparedValues = []; - this._whenThens.forEach(function (whenThen) + let caseStatement = new PreparedSqlStatement("case"); + this._whenThens.forEach(function(whenThen) { - var when = SqlBuilder._getStatement(whenThen.condition, "when"); - var then = SqlBuilder._getStatement(whenThen.thenValue, "then"); - caseStatement.push(when.sqlStorage); - caseStatement.push(then.sqlStorage); - preparedValues = preparedValues.concat(when.preparedValues, then.preparedValues); + let when = SqlBuilder._getStatement(whenThen.condition, "when"); + caseStatement.appendStatement(when, " "); + let then = SqlBuilder._getStatement(whenThen.thenValue, "then"); + caseStatement.appendStatement(then, " "); }); if (this._elseValue) { let elseStatement = SqlBuilder._getStatement(this._elseValue, "else"); - caseStatement.push(elseStatement.sqlStorage); - preparedValues = preparedValues.concat(elseStatement.preparedValues); + caseStatement.appendStatement(elseStatement, " "); } - caseStatement.push("end"); + caseStatement.append(" end"); - return [ - caseStatement.join(" "), - preparedValues - ]; -} + return caseStatement.toArray(); +}; /** * Custom toJSON method that omits the property '_afterWhenMask', because cyclic references can't be stringified */ -SqlBuilder._CaseStatement.prototype.toJSON = function () +SqlBuilder._CaseStatement.prototype.toJSON = function() { return { _whenCondition: this._whenCondition, _whenThens: this._whenThens, _elseValue: this._elseValue }; -} +}; /** * Object providing the then-methods for the case-when expression. It can be only be accessed after calling .when to ensure a 'then' * can only be added after a 'when'. + * + * @param {SqlBuilder._CaseStatement} pCaseStatement */ -SqlBuilder._CaseWhen = function (pCaseStatement) +SqlBuilder._CaseWhen = function(pCaseStatement) { this._caseStatement = pCaseStatement; -} +}; /** * Sets the expression for the then * - * @param {string|SqlBuilder} pValue then-value + * @param {string|SqlBuilder} pValue - then-value * @return {SqlBuilder._CaseStatement} */ -SqlBuilder._CaseWhen.prototype.then = function (pValue) +SqlBuilder._CaseWhen.prototype.then = function(pValue) { - var condition = this._caseStatement._whenCondition; + if (SqlUtils.isPreparedSqlArray(pValue)) + { + throw SqlBuilder._ERROR_SQL_ARRAY_AS_VALUE_NOT_ALLOWED(); + } + let condition = this._caseStatement._whenCondition; this._caseStatement._whenCondition = null; - this._caseStatement._whenThens.push({condition: condition, thenValue: pValue}); + this._caseStatement._whenThens.push({ condition: condition, thenValue: pValue }); return this._caseStatement; -} +}; /** * Sets the value for the then, but wraps the value in '' * - * @param {string} pValue then-value + * @param {string} pValue - then-value * @return {SqlBuilder._CaseStatement} */ -SqlBuilder._CaseWhen.prototype.thenString = function (pValue) +SqlBuilder._CaseWhen.prototype.thenString = function(pValue) { - return this.then("'" + pValue + "'"); -} + return this.then("'" + db.quote(pValue, db.getCurrentAlias()) + "'"); +}; /** *provides functions for masking sql functions @@ -3644,6 +3978,35 @@ SqlMaskingUtils.prototype.hourFromDate = function(pField) } } +/** + * Get the sytem db name in the format "database.schema." + * + * @param {string} [pAlias] the alias + * + * @return {string} the system db name of the alias + */ +SqlMaskingUtils.prototype.getSytemDbName = function(pAlias) +{ + if (!pAlias) + { + pAlias = this.alias; + } + + var aliasModel = project.getAliasModel(pAlias); + var systemDbName = ""; + + if (aliasModel[project.ALIAS_PROPERTIES].database) + { + systemDbName += aliasModel[project.ALIAS_PROPERTIES].database + "."; + } + if (aliasModel[project.ALIAS_PROPERTIES].schema) + { + systemDbName += aliasModel[project.ALIAS_PROPERTIES].schema + "."; + } + + return systemDbName; +} + /** * gets the hour from a timestamp * @@ -3892,34 +4255,33 @@ SqlMaskingUtils.prototype.toChar = function(pColumnName) } /** - * functions for various Sql-actions - * Do not create an instance of this! + * Provides functions for various sql actions * - * @class - * @static + * @namespace */ -export function SqlUtils() {} +export function SqlUtils() +{} /** -* parses given name of table and name of column to clearly find out the tablename and columnanme -* -* @param {string|Array} pFieldOrTableName you've got several possibilites to pass here: -* <br/> 1. the name of the table if also a pColumnName is specified -* <br/> 2. the name of the table and columname as "tablename.columnname" (e.g. "ORGANISATION.NAME") if no pColumnName is specified -* <br/> 3. an array with 2 elements: [tablename, columnname] (e.g. ["ORGANISATION", "NAME"]) if no pColumnName is specified -* <br/> 4. an array with 3 elements: [tablename, columnname, tablealias] (e.g. ["ORGANISATION", "NAME", "org"]) if no pColumnName is specified -* <br/> Everything else will raise an error -* -* @param {string} [pColumnName] depending on pFieldOrTableName this should be undefined/null or the name of a column -* -* @return {Object|TypeError} TypeError if something wrong has been passed or returns a object with these properties: -* 1. "table" which is the tablename -* 2. "column" which is the columnname -* 3. "tableAlias" which is the tableAlias if it exists. else it is also the table -* e.g. {table: "ORGANISATION", column: "NAME", tableAlias: "org"} -* -* -*/ + * Parses given name of table and name of column to clearly find out the tablename and columnanme + * + * @param {string|Array} pFieldOrTableName - You've got several possibilites to pass here: + * <br/> 1. the name of the table if also a pColumnName is specified + * <br/> 2. the name of the table and columname as "tablename.columnname" (e.g. "ORGANISATION.NAME") if no pColumnName is specified + * <br/> 3. an array with 2 elements: [tablename, columnname] (e.g. ["ORGANISATION", "NAME"]) if no pColumnName is specified + * <br/> 4. an array with 3 elements: [tablename, columnname, tablealias] (e.g. ["ORGANISATION", "NAME", "org"]) if no pColumnName is specified + * <br/> Everything else will raise an error + * + * @param {string} [pColumnName] - Depending on pFieldOrTableName this should be undefined/null or the name of a column + * + * @return {object | TypeError} TypeError if something wrong has been passed or returns a object with these properties: + * 1. "table" which is the tablename + * 2. "column" which is the columnname + * 3. "tableAlias" which is the tableAlias if it exists. else it is also the table + * e.g. {table: "ORGANISATION", column: "NAME", tableAlias: "org"} + * + * + */ SqlUtils._parseFieldQualifier = function(pFieldOrTableName, pColumnName) { var fnName = "SqlUtils._parseFieldQualifier";//for return errors @@ -3927,10 +4289,12 @@ SqlUtils._parseFieldQualifier = function(pFieldOrTableName, pColumnName) if (typeof pFieldOrTableName == "string") { if (pFieldOrTableName.search(/[\s=\(\)<>!]/) != -1) + { return new TypeError(translate.withArguments("[%0]%1 has to be a string without empty spaces, (, ), =, <, > but it contains at least one of them", [fnName, "pFieldOrTableName"])); + } } - var tableName, columnName, tableAlias; + let tableName, columnName, tableAlias; if (pColumnName != undefined) { tableName = pFieldOrTableName; @@ -3938,8 +4302,8 @@ SqlUtils._parseFieldQualifier = function(pFieldOrTableName, pColumnName) } else { - var fieldVarType = typeof(pFieldOrTableName); - if (fieldVarType == "string") + let fieldVarType = typeof(pFieldOrTableName); + if (typeof pFieldOrTableName == "string") { pFieldOrTableName = text.split(pFieldOrTableName, "\\."); } @@ -3964,23 +4328,34 @@ SqlUtils._parseFieldQualifier = function(pFieldOrTableName, pColumnName) } else { - return new TypeError(translate.withArguments("[%0]has now an incorrect length; estimated 2 or 3 elements but got %1", [fnName, pFieldOrTableName.length ])); + return new TypeError(translate.withArguments("[%0]has now an incorrect length; estimated 2 or 3 elements but got %1", [fnName, pFieldOrTableName.length.toString()])); } } else //check for object happens since there exists JavaArrays and JavaScript arrays which are both valid + { return new TypeError(translate.withArguments("[%0]%1 is an object but seems not to be a valid array or array-like", [ - fnName, "pFieldOrTableName"])); + fnName, "pFieldOrTableName" + ])); + } } if (!tableAlias) + { tableAlias = tableName; + } if (typeof(columnName) != "string") + { return new TypeError(translate.withArguments("[%0]the columnName is not a string after interpreting", [fnName])); + } if (typeof(tableName) != "string") + { return new TypeError(translate.withArguments("[%0]the tableName is not a string after interpreting", [fnName])); + } if (typeof(tableAlias) != "string") + { return new TypeError(translate.withArguments("[%0]the tableAlias is not a string after interpreting", [fnName])); + } return { table: tableName, @@ -3990,186 +4365,167 @@ SqlUtils._parseFieldQualifier = function(pFieldOrTableName, pColumnName) }; /** -* determines if given values match a full field qualifier (name of table and name of column) -* -* @param {string|Array} pFieldOrTableName you've got several possibilites to pass here: -* <br/> 1. the name of the table if also a pColumnName is specified -* <br/> 2. the name of the table and columname as "tablename.columnname" (e.g. "ORGANISATION.NAME") if no pColumnName is specified -* <br/> 3. an array with 2 elements: [tablename, columnname] (e.g. ["ORGANISATION", "NAME"]) if no pColumnName is specified -* <br/> 4. an array with 3 elements: [tablename, columnname, tablealias] (e.g. ["ORGANISATION", "NAME", "org"]) if no pColumnName is specified -* <br/> Everything else will raise an error -* -* @param {string} [pColumnName] depending on pFieldOrTableName this should be undefined/null or the name of a column -* -* @return {boolean} returns true if it's a full qualifier or false if not -* -*/ + * Determines if given values match a full field qualifier (name of table and name of column) + * + * @param {string|Array} pFieldOrTableName - You've got several possibilites to pass here: + * <br/> 1. the name of the table if also a pColumnName is specified + * <br/> 2. the name of the table and columname as "tablename.columnname" (e.g. "ORGANISATION.NAME") if no pColumnName is specified + * <br/> 3. an array with 2 elements: [tablename, columnname] (e.g. ["ORGANISATION", "NAME"]) if no pColumnName is specified + * <br/> 4. an array with 3 elements: [tablename, columnname, tablealias] (e.g. ["ORGANISATION", "NAME", "org"]) if no pColumnName is specified + * <br/> Everything else will raise an error + * + * @param {string} [pColumnName] - Depending on pFieldOrTableName this should be undefined/null or the name of a column + * + * @return {boolean} True if it's a full qualifier or false if not + * + */ SqlUtils.isFullFieldQualifier = function(pFieldOrTableName, pColumnName) { - var parsed = SqlUtils._parseFieldQualifier(pFieldOrTableName, pColumnName); + let parsed = SqlUtils._parseFieldQualifier(pFieldOrTableName, pColumnName); return !(parsed instanceof TypeError); }; /** * Checks if the given value has the correct format for a prepared sql array * - * @param {Object|Array} pPreparedArray value to check - * @return {boolean} if the format is correct + * @param {object | Array} pPreparedArray - Value to check + * @return {pPreparedArray is [string, Array]} If the format is correct */ -SqlUtils.isPreparedSqlArray = function (pPreparedArray) +SqlUtils.isPreparedSqlArray = function(pPreparedArray) { - return pPreparedArray.length === 2 && Utils.isString(pPreparedArray[0]) && Array.isArray(pPreparedArray[1]); -} + return Array.isArray(pPreparedArray) && pPreparedArray.length === 2 && Utils.isString(pPreparedArray[0]) && Array.isArray(pPreparedArray[1]); +}; /** * Checks if the given value has the correct format for a prepared sql array and contains something * - * @param {Object|Array} pPreparedArray value to check - * @return {boolean} if the format is correct + * @param {object | Array} pPreparedArray - Value to check + * @return {pPreparedArray is [string, Array]} If the format is correct */ -SqlUtils.isNonEmptyPreparedSqlArray = function (pPreparedArray) +SqlUtils.isNonEmptyPreparedSqlArray = function(pPreparedArray) { return SqlUtils.isPreparedSqlArray(pPreparedArray) && pPreparedArray[0] != ""; -} +}; /** -* determines the type of a single database column in a table; if you want to get several columntypes at once use db.getColumnTypes instead -* -* @param {string|Array} pFieldOrTableName you've got several possibilites to pass here: -* <br/> 1. the name of the table if also a pColumnName is specified -* <br/> 2. the name of the table and columname as "tablename.columnname" (e.g. "ORGANISATION.NAME") if no pColumnName is specified -* <br/> 3. an array with 2 elements: [tablename, columnname] (e.g. ["ORGANISATION", "NAME"]) if no pColumnName is specified -* <br/> 4. an array with 3 elements: [tablename, columnname, tablealias] (e.g. ["ORGANISATION", "NAME", "org"]) if no pColumnName is specified -* <br/> Everything else will raise an error -* -* @param {string} [pColumnName] depending on pFieldOrTableName this should be undefined/null or the name of a column -* @param {string} [pAlias=the current alias] Database-Aliasname, where the SQL-Statement shall be executed; default is the current dbalias -* -* @throws TypeError if a wrong format is passed as table/column-combination -* -* @return {number} returns the corresponding SQLTYPES-value -* -*/ + * Determines the type of a single database column in a table; if you want to get several columntypes at once use db.getColumnTypes instead + * + * @param {string|Array} pFieldOrTableName - You've got several possibilites to pass here: + * <br/> 1. the name of the table if also a pColumnName is specified + * <br/> 2. the name of the table and columname as "tablename.columnname" (e.g. "ORGANISATION.NAME") if no pColumnName is specified + * <br/> 3. an array with 2 elements: [tablename, columnname] (e.g. ["ORGANISATION", "NAME"]) if no pColumnName is specified + * <br/> 4. an array with 3 elements: [tablename, columnname, tablealias] (e.g. ["ORGANISATION", "NAME", "org"]) if no pColumnName is specified + * <br/> Everything else will raise an error + * + * @param {string} [pColumnName] - Depending on pFieldOrTableName this should be undefined/null or the name of a column + * @param {string} [pAlias=currentalias] - Database alias name, where the SQL-Statement shall be executed; default is the current dbalias + * @return {number} returns the corresponding SQLTYPES-value + * + * @throws TypeError if a wrong format is passed as table/column-combination + */ SqlUtils.getSingleColumnType = function(pFieldOrTableName, pColumnName, pAlias) { - var fields = SqlUtils._parseFieldQualifier(pFieldOrTableName, pColumnName); + let fields = SqlUtils._parseFieldQualifier(pFieldOrTableName, pColumnName); if (fields instanceof TypeError) + { throw fields; + } if (pAlias == undefined) + { pAlias = db.getCurrentAlias(); + } return db.getColumnTypes(fields.table, [fields.column], pAlias)[0]; }; /** -* calls a given function for N blocks of sql-data as long as records are available or the paging-process is manually canceled -* -* @param {Object|string} sqlStatement the sql statement that shall be executed -* String: SQL-query in a simple text form -* Object: prepared-sql-query: [sqlStr, [[value1, type1], [valueN, typeN]]] -* @param {number} blockSize Amount of records that shall be read per block. (you need to specify an ORDER BY in your SQL-query) -* "0" <=> all records -* @param {Function} callbackFn a callback-function that is called for every block and has the following params: -* myCallback(myDataBlockAs2Darray, myLoopCountThatStartsWith1) -* If "false" is returned sqlPageData will abort the paging process and return false -* @param {string} [dbAlias=current_alias] Database-Aliasname, where the SQL-Statement shall be executed; default is the current dbalias -* @param {number} [timeout=configured_dbTimeout_in_Preferences] Timeout in milliseconds; When it's reached the SQL-Statement will abort; default is in PREFERENCES configured -* @param {number} [startOffset=0] Position where to begin with the data-reading-process; default is 0 -* -* -* @return {boolean} returns whether the function read all available data or not: -* false if the callback-function returned false, otherwise true -* -* @example -* var varValues = [];//you've got access to variables declared with 'var' -* let letValues = [];//you've got access to variables declared with 'let' -* var count = 0;//you cannot overwrite a variable of 'sqlPageData' by accident -* -* var sql = "select ORGNAME from ORGANISATION"; -* var blockSize = 5 * 1000; -* -* var allRows = +db.cell("select count(*) from ORGANISATION"); -* -* sqlPageData(sql, blockSize, function(pData, pRunNo){ -* var j = pData.length;//pData is the current block with data -* logMsg(pRunNo.toString() + "#" + j);//pRunNo is the amount how often the func. has been already called -* //you can calculate the progress easily by: progress = (blockSize* (pRunNo-1) + pData.length) / (allRows - startOffset) -* //example in per cent: -* var startOffset = 0;//we did not pass any startOffset to sqlPageData - this is equivalent to zero -* var progress = (blockSize* (pRunNo-1) + pData.length) / (allRows - startOffset); -* logMsg("progess: " + eMath.roundDec(progress * 100, 2, eMath.ROUND_CEILING) + "%"); -* -* for (var i = 0; i < j; i++) -* { -* varValues.push(pData[i][0]); -* letValues.push(pData[i][0]); -* } -* -* count += pRunNo * 100; -* logMsg("count:" + count);//you cannot overwrite a variable of 'sqlPageData' by accident -* }); -* -* logging.show(letValues);//contains orgnames -* logging.show(varValues);//contains orgnames -*/ -SqlUtils.pageTableData = function(sqlStatement, blockSize, callbackFn, dbAlias, timeout, startOffset) + * Calls a given function for N blocks of sql-data as long as records are available or the paging-process is manually canceled + * + * @param {object | string} pSqlStatement - The sql statement that shall be executed + * String: SQL-query in a simple text form + * Object: prepared-sql-query: [sqlStr, [[value1, type1], [valueN, typeN]]] + * @param {number} pBlockSize - Amount of records that shall be read per block. (you need to specify an ORDER BY in your SQL-query) + * "0" <=> all records + * @param {Function} pCallbackFn - A callback-function that is called for every block and has the following params: + * myCallback(myDataBlockAs2Darray, myLoopCountThatStartsWith1) + * If "false" is returned sqlPageData will abort the paging process and return false + * @param {string} [pDbAlias=current_alias] - Database-Aliasname, where the SQL-Statement shall be executed; default is the current dbalias + * @param {number} [pTimeout=configured_dbTimeout_in_Preferences] - Timeout in milliseconds; When it's reached the SQL-Statement will abort; default is in PREFERENCES configured + * @param {number} [pStartOffset=0] - Position where to begin with the data-reading-process; default is 0 + * + * + * @return {boolean} Whether the function read all available data or not: + * false if the callback-function returned false, otherwise true + */ +SqlUtils.pageTableData = function(pSqlStatement, pBlockSize, pCallbackFn, pDbAlias, pTimeout, pStartOffset) { - return SqlUtils._pageData(null, sqlStatement, blockSize, callbackFn, dbAlias, timeout, startOffset); + return SqlUtils._pageData(null, pSqlStatement, pBlockSize, pCallbackFn, pDbAlias, pTimeout, pStartOffset); }; /** -* calls a given function for N blocks of sql-data as long as records are available or the paging-process is manually canceled -* -* @param {Object|string} sqlStatement the sql statement that shall be executed -* String: SQL-query in a simple text form -* Object: prepared-sql-query: [sqlStr, [[value1, type1], [valueN, typeN]]] -* @param {number} blockSize Amount of records that shall be read per block. (you need to specify an ORDER BY in your SQL-query) -* "0" <=> all records -* @param {Function} callbackFn a callback-function that is called for every block and has the following params: -* myCallback(myColumnDataBlockAsArray, myLoopCountThatStartsWith1) -* If "false" is returned sqlPageData will abort the paging process and return false -* @param {string} [dbAlias=current_alias] Database-Aliasname, where the SQL-Statement shall be executed; default is the current dbalias -* @param {number} [timeout=configured_dbTimeout_in_Preferences] Timeout in milliseconds; When it's reached the SQL-Statement will abort; default is in PREFERENCES configured -* @param {number} [startOffset=0] Position where to begin with the data-reading-process; default is 0 -* -* -* @return {boolean} returns whether the function read all available data or not: -* false if the callback-function returned false, otherwise true -* -* @example -* similar to sqlTablePageData -> take a look at the example there -*/ -SqlUtils.pageColumnData = function(sqlStatement, blockSize, callbackFn, dbAlias, timeout, startOffset) + * Calls a given function for N blocks of sql-data as long as records are available or the paging-process is manually canceled + * + * @param {object | string} pSqlStatement - The sql statement that shall be executed + * String: SQL-query in a simple text form + * Object: prepared-sql-query: [sqlStr, [[value1, type1], [valueN, typeN]]] + * @param {number} pBlockSize - Amount of records that shall be read per block. (you need to specify an ORDER BY in your SQL-query) + * "0" <=> all records + * @param {Function} pCallbackFn - A callback-function that is called for every block and has the following params: + * myCallback(myColumnDataBlockAsArray, myLoopCountThatStartsWith1) + * If "false" is returned sqlPageData will abort the paging process and return false + * @param {string} [pDbAlias=current_alias] - Database-Aliasname, where the SQL-Statement shall be executed; default is the current dbalias + * @param {number} [pTimeout=configured_dbTimeout_in_Preferences] - Timeout in milliseconds; When it's reached the SQL-Statement will abort; default is in PREFERENCES configured + * @param {number} [pStartOffset=0] - Position where to begin with the data-reading-process; default is 0 + * @return {boolean} Whether the function read all available data or not: + * false if the callback-function returned false, otherwise true + * + * @see SqlUtils.pageTableData + */ +SqlUtils.pageColumnData = function(pSqlStatement, pBlockSize, pCallbackFn, pDbAlias, pTimeout, pStartOffset) { - return SqlUtils._pageData(db.COLUMN, sqlStatement, blockSize, callbackFn, dbAlias, timeout, startOffset); + return SqlUtils._pageData(db.COLUMN, pSqlStatement, pBlockSize, pCallbackFn, pDbAlias, pTimeout, pStartOffset); }; -//internal function for paging through data; for description take a look at sqlArrayPageData -SqlUtils._pageData = function(sqlType ,sqlStatement, blockSize, callbackFn, dbAlias, timeout, startOffset) +/** + * internal function for paging through data; for description take a look at sqlArrayPageData + */ +SqlUtils._pageData = function(sqlType, sqlStatement, blockSize, callbackFn, dbAlias, timeout, startOffset) { if (dbAlias == undefined) + { dbAlias = db.getCurrentAlias(); + } if (startOffset == undefined) + { startOffset = 0; + } - var count = 0; + let count = 0; while (startOffset > -1) { - var data; + let data; if (sqlType == null) { if (timeout == undefined) + { data = db.tablePage(sqlStatement, dbAlias, startOffset, blockSize); + } else + { data = db.tablePage(sqlStatement, dbAlias, startOffset, blockSize, timeout); + } } else { if (timeout == undefined) + { data = db.arrayPage(sqlType, sqlStatement, dbAlias, startOffset, blockSize); + } else + { data = db.arrayPage(sqlType, sqlStatement, dbAlias, startOffset, blockSize, timeout); + } } startOffset += blockSize; @@ -4177,158 +4533,193 @@ SqlUtils._pageData = function(sqlType ,sqlStatement, blockSize, callbackFn, dbAl //this happens when all-records % blockSize == 0 //we do not want to call the callback-fn if (data.length == 0) + { return true; + } else if (data.length < blockSize || blockSize == 0)//blocksize 0 is everything - startOffset = -1;//call callback the last time + { + startOffset = -1; + }//call callback the last time if (callbackFn.call(this, data, ++count) === false) - return false;//callback can return false to manually stop the paging-process + { + return false; + }//callback can return false to manually stop the paging-process } return true; -} +}; /** - * @return the alias for table asys_binaries + * @return {string} The alias for table asys_binaries */ SqlUtils.getBinariesAlias = function() { return SqlUtils.getSystemAlias(); -} +}; /** - * @return the sytemalias + * @return {string} The sytem alias */ SqlUtils.getSystemAlias = function() { return "_____SYSTEMALIAS"; -} +}; /** - * @return the dataalias + * @return {string} The data alias */ SqlUtils.getDataAlias = function() { return "Data_alias"; -} +}; /** - * Builds a SQL IN condition, while accounting for the 1000 elements maximum - * Single conditions are concatenated with OR, which can be devastating for performance! - * - * @param {string} pFieldname req name of the field with table alias - * z.B ORGREL.CONTACTID - * @param {string[]|string[][]} pData req Data as ID Array - * @param {string} [pQuoteSymbol=""] symbol for quoting values, - * Strings i.e.: ' default is no symbol - * @param {boolean} [pAsPrepared=undefined] true if result should be returned as prepared condition - * @param {boolean} [pPreparedDbType=undefined] if pAsPrepared is true, this param has to be filld with the correct db type - * - * @return {string|Array} SQL condition: where VALS in (1,2,3) OR as prepared Statement if pAsPrepared is true ["VALS in (1,2,3)", [...] - */ + * Builds a SQL IN condition, while accounting for the 1000 elements maximum. + * Single conditions are concatenated with OR, which can be devastating for performance! + * + * @param {string} pFieldname - Name of the field with table alias + * z.B ORGREL.CONTACTID + * @param {string[]|string[][]} pData - Data as ID Array + * @param {string} [pQuoteSymbol=""] - symbol for quoting values, + * Strings i.e.: ' default is no symbol + * @param {boolean} [pAsPrepared=undefined] - True if result should be returned as prepared condition + * @param {number} [pPreparedDbType=undefined] - If pAsPrepared is true, this param has to be filld with the correct db type + * + * @return {string|Array} SQL condition: where VALS in (1,2,3) OR as prepared Statement if pAsPrepared is true ["VALS in (1,2,3)", [...] + */ SqlUtils.getSqlInStatement = function(pFieldname, pData, pQuoteSymbol, pAsPrepared, pPreparedDbType) { - var MAX_COUNT = 1000; + const MAX_COUNT = 1000; if (pData.length > 1000) - logging.log(translate.text("SqlUtils.getSqlInStatement: WARNING: You should not create in-statements with more than 1000 values. As this has a very bad performance.")) + { + logging.log(translate.text("SqlUtils.getSqlInStatement: WARNING: You should not create in-statements with more than 1000 values. As this has a very bad performance.")); + } if (pData.length == 0) + { return " 1 = 2 "; + } - var res = ""; - var qs = pQuoteSymbol || ""; + let res = ""; + let qs = pQuoteSymbol || ""; - var preparedValues; + let preparedValues; if (pAsPrepared) { preparedValues = []; if (!pPreparedDbType) + { throw new Error(translate.text("SqlUtils.getSqlInStatement: if pAsPrepared is true, pPreparedDbType has to be filld with the correct db type")); + } } //pData.length -1 um für den Fall, dass MAX_COUNT == pData.length ist trotzdem nur einen Aufruf //zu machen - var count = ((pData.length -1) / MAX_COUNT) >> 0;//aus kommazahl eine ganzzahl machen + let count = ((pData.length - 1) / MAX_COUNT) >> 0;//aus kommazahl eine ganzzahl machen //<= verwenden, da bei einer Länge von "126" der Vorgang einmal ausgeführt werden soll - for (var i = 0; i <= count; i++) + for (let i = 0; i <= count; i++) { if (i > 0) + { res += " or "; + } if (pAsPrepared) { res += (pFieldname ? pFieldname + " in " : "") + "("; - var subData = pData.slice(i * MAX_COUNT, i * MAX_COUNT + MAX_COUNT); + let subData = pData.slice(i * MAX_COUNT, i * MAX_COUNT + MAX_COUNT); - subData.forEach(function(pVal, pIndex) { + subData.forEach(function(pVal, pIndex) + { res += "?"; - preparedValues.push([pVal, pPreparedDbType]) - if (pIndex != subData.length-1) + preparedValues.push([pVal, pPreparedDbType]); + if (pIndex != subData.length - 1) + { res += ", "; + } }); - res += ")" + res += ")"; } else { res += (pFieldname ? pFieldname + " in " : "") + "(" + qs + pData.slice(i * MAX_COUNT, i * MAX_COUNT + MAX_COUNT) - .join(qs + ", " + qs) + qs + ") "; + .join(qs + ", " + qs) + qs + ") "; } } //wenn mehrere Zeilen mit "or" verknüpft wurden nochmal klammern if (count > 0) + { res = "(" + res + ")"; + } if (pAsPrepared) + { return [res, preparedValues]; + } else + { return res; -} + } +}; /** -* resolves key-value pairs (of strings) into a case when expression; -* This function tries to get the columntype for better type comparison -* -* @param {string[][]} pKeyValueArray you've to pass a 2D-Array where each element has at pos0 the key and pos1 the value -* @param {string} pDbFieldName name fo the database field where the KEY-value is stored; prefers TABLENAME.COLUMNNAME -* @param {string|boolean} [pLocale=current_client_language] specifies the locale for translating the title; can be false if nothing shalle be translated -* @param {boolean} [pIsCurrency] -* @param {string} [pAlias=currentAlias] db alias -* -* @return {string} a SQL-expression (case-when-statement) that resolves the KEYID into the title -> as preparedSatement-elements -*/ + * Resolves key-value pairs (of strings) into a case when expression; + * This function tries to get the columntype for better type comparison + * + * @param {string[][]} pKeyValueArray - You have to pass a 2D-Array where each element has at pos0 the key and pos1 the value + * @param {string} pDbFieldName - Name fo the database field where the KEY-value is stored; prefers TABLENAME.COLUMNNAME + * @param {string|boolean} [pLocale=current_client_language] - Specifies the locale for translating the title; can be false if nothing shalle be translated + * @param {boolean} [pIsCurrency] + * @param {string} [pAlias=currentAlias] - Db alias + * @return {[string, [string, number][]]} SQL-expression (case-when-statement) that resolves the KEYID into the title -> as preparedSatement-elements + */ SqlUtils.getResolvingCaseWhen = function(pKeyValueArray, pDbFieldName, pLocale, pIsCurrency, pAlias) { - var keyData = pKeyValueArray; + let keyData = pKeyValueArray; if (keyData.length == 0) + { return ["''", []]; + } //a helper function for easy translation - var translateValue = function(value){ + let translateValue = function(value) + { if (pLocale === false) + { return value; + } else if (pLocale) + { return translate.text(value, pLocale); + } else + { return translate.text(value); + } }; //!SqlBuilder - var resSql = "case ", preparedValues = []; + let resSql = "case "; + /** @type {[string, number][]} */ + let preparedValues = []; - var colTypeKeyId = SQLTYPES.CHAR; //the standard type is char - var fields = SqlUtils._parseFieldQualifier(pDbFieldName); //validate the DB-field for proper form (CONTACT.CONTACTID) + let colTypeKeyId = SQLTYPES.CHAR; //the standard type is char + let fields = SqlUtils._parseFieldQualifier(pDbFieldName); //validate the DB-field for proper form (CONTACT.CONTACTID) if (!(fields instanceof TypeError)) + { colTypeKeyId = SqlUtils.getSingleColumnType(pDbFieldName, undefined, pAlias); - //some databases dont auto cast on their own so we need the proper type + } + //some databases dont auto cast on their own so we need the proper type - var colTypeTitle = SQLTYPES.NVARCHAR; - for (var i = 0, l = keyData.length; i < l; i++) + let colTypeTitle = SQLTYPES.NVARCHAR; + for (let i = 0, l = keyData.length; i < l; i++) { - var translatedTitle; + let translatedTitle; if(pIsCurrency) { - var titleWithiso = keyData[i][1]; //Euro (EUR) (iso ALWAYS has a length of 3 + the columns and one space) - var title = titleWithiso.substring(0, titleWithiso.length - 6); - var iso = titleWithiso.substring(titleWithiso.length - 6, titleWithiso.length); + let titleWithiso = keyData[i][1]; //Euro (EUR) (iso ALWAYS has a length of 3 + the columns and one space) + let title = titleWithiso.substring(0, titleWithiso.length - 6); + let iso = titleWithiso.substring(titleWithiso.length - 6, titleWithiso.length); translatedTitle = translateValue(title) + iso; } else @@ -4336,89 +4727,93 @@ SqlUtils.getResolvingCaseWhen = function(pKeyValueArray, pDbFieldName, pLocale, translatedTitle = translateValue(keyData[i][1]); } - resSql += " when " + pDbFieldName + " = ? then ? " + resSql += " when " + pDbFieldName + " = ? then ? "; preparedValues.push([keyData[i][0], colTypeKeyId]); preparedValues.push([translatedTitle, colTypeTitle]); } resSql += " else '' end "; - resSql = [resSql, preparedValues]; - return resSql; + return [resSql, preparedValues]; }; /** -* resolves an array of key-value pairs (of strings) into a sql case when expression<br/> -* This is useful for results of entities.getRows for example. -* -* @param {Array} pKeyValueObject <p/>you've to pass a 2D-Array where each element has to be an object with at least one key: value-pair, e.g.: -* <br/>[{uid: "uid1", value: "value1"}, {uid: "uidN", value: "valueN"}] -* @param {string} pUid <p/>name of the key where the rawvalue (the uid) is located in the object -* @param {string} pTranslatedValue <p/>name of the key where the already translated value is located in the object -* @param {string} pDbFieldName <p/>name fo the database field where the KEY-value is stored -* -* @return {string} <p/>a SQL-expression (case-when-statement) that resolves the KEYID into the title -> as -* preparedSatement-elements -* <br/>The else-value is "unassigned". -* -* @example -* var exampleDataStack = [ -* {keyVal: "PHONE", titleOriginal: "Phone", titleTranslated: "Telefon", origin: "MetaImporter"}, -* {keyVal: "EMAIL", titleOriginal: "email", titleTranslated: "E-Mail", origin: "MetaImporter"} -* ]; -* -* var sqlExpr = SqlUtils.getResolvingCaseWhenFromObject(exampleDataStack, "keyVal", "titleTranslated", "FORM.COMMUNICATION"); -* //results in a sql case when as prepared statement that is resolvedas following: -* //case when FORM.COMMUNICATION = 'PHONE' then 'Telefon' when FORM.COMMUNICATION = 'EMAIL' then 'E-Mail' else 'nicht zugeordnet' end -*/ + * Resolves an array of key-value pairs (of strings) into a sql case when expression. + * This is useful for results of entities.getRows for example. + * + * @param {Array} pKeyValueObject - You have to pass a 2D-Array where each element has to be an object with at least one key: value-pair, e.g.: + * [{uid: "uid1", value: "value1"}, {uid: "uidN", value: "valueN"}] + * @param {string} pUid - Name of the key where the rawvalue (the uid) is located in the object + * @param {string} pTranslatedValue - Name of the key where the already translated value is located in the object + * @param {string} pDbFieldName - Name fo the database field where the KEY-value is stored + * @return {[string, [string, number][]]} SQL-expression (case-when-statement) that resolves the KEYID into the title -> as preparedSatement-elements. + * The else-value is "unassigned". + * + * @example + * let exampleDataStack = [ + * {keyVal: "PHONE", titleOriginal: "Phone", titleTranslated: "Telefon", origin: "MetaImporter"}, + * {keyVal: "EMAIL", titleOriginal: "email", titleTranslated: "E-Mail", origin: "MetaImporter"} + * ]; + * + * let sqlExpr = SqlUtils.getResolvingCaseWhenFromObject(exampleDataStack, "keyVal", "titleTranslated", "FORM.COMMUNICATION"); + * //results in a sql case when as prepared statement that is resolvedas following: + * //case when FORM.COMMUNICATION = 'PHONE' then 'Telefon' when FORM.COMMUNICATION = 'EMAIL' then 'E-Mail' else 'nicht zugeordnet' end + */ SqlUtils.getResolvingCaseWhenFromObject = function(pKeyValueObject, pUid, pTranslatedValue, pDbFieldName) { - var keyData = pKeyValueObject; + let keyData = pKeyValueObject; if (keyData.length == 0) + { return ["''", []]; + } - var translateValue = pTranslatedValue; - var uid = pUid; - var unassigned = translate.text("unassigned") + let translateValue = pTranslatedValue; + let unassigned = translate.text("unassigned"); - var resSql = "case ", preparedValues = []; - var colTypeKeyId = SQLTYPES.CHAR; - var colTypeTitle = SQLTYPES.NVARCHAR; - for (var i = 0, l = keyData.length; i < l; i++) + let resSql = "case "; + /** @type {[string, number][]} */ + let preparedValues = []; + let colTypeKeyId = SQLTYPES.CHAR; + let colTypeTitle = SQLTYPES.NVARCHAR; + for (let i = 0, l = keyData.length; i < l; i++) { - var translatedTitle = keyData[i][translateValue]; - resSql += " when " + pDbFieldName + " = ? then ? " + let translatedTitle = keyData[i][translateValue]; + resSql += " when " + pDbFieldName + " = ? then ? "; preparedValues.push([keyData[i][pUid], colTypeKeyId]); preparedValues.push([translatedTitle, colTypeTitle]); - } - resSql += " else '"+ unassigned +"' end "; - resSql = [resSql, preparedValues]; - return resSql; + resSql += " else '" + unassigned + "' end "; + return [resSql, preparedValues]; }; /** * Will quote all prepared statement values from the given statement. * - * @param {Array} pStatement Same as first paraemter of db.translateStatement. - * @param {string} pAlias The database alias - * @param {Function} pExecutionCallback (PreparedSqlArray) => String A function which must return the final SQL. - * @return The SQL, same as the result of db.translateStatement. + * @param {[string, Array]} pStatement - Same as first paraemter of db.translateStatement. + * @param {string} pAlias - The database alias + * @param {(pStatement: Array)=>string} pExecutionCallback - A function which must return the final SQL + * @return {string} The SQL, same as the result of db.translateStatement. */ SqlUtils.translateWithQuotes = function(pStatement, pAlias, pExecutionCallback) { // Validate type of incoming paramter. if (!Array.isArray(pStatement)) + { return null; + } // The second element of the array has to be an array. if (!Array.isArray(pStatement[1])) + { return null; + } // As the second element represents the prepared statements we need to map it... - var preparedStatements = pStatement[1].map(function(pValue) + let preparedStatements = pStatement[1].map(function(pValue) { // Just in case as a fallback value.. if (!(Array.isArray(pValue))) + { return pValue; + } // As the first element represents the value it will be quoted here. if(pAlias) @@ -4432,13 +4827,13 @@ SqlUtils.translateWithQuotes = function(pStatement, pAlias, pExecutionCallback) }); return pExecutionCallback([pStatement[0], preparedStatements]); -} +}; /** * Will quote all prepared statement values from the given statement. * - * @param {Array|string} pStatement Same as the first parameter of db.translateStatement. - * @param {string} [pAlias] the alias which should be used for db.translateStatement() + * @param {[string, Array]} pStatement - Same as the first parameter of db.translateStatement. + * @param {string} [pAlias] - The alias which should be used for db.translateStatement() * @returns {string} The SQL, same as the result of db.translateStatement. */ SqlUtils.translateStatementWithQuotes = function(pStatement, pAlias) @@ -4446,17 +4841,21 @@ SqlUtils.translateStatementWithQuotes = function(pStatement, pAlias) return SqlUtils.translateWithQuotes(pStatement, pAlias, function(pValue) { if (pAlias) - return db.translateStatement(pValue, pAlias) + { + return db.translateStatement(pValue, pAlias); + } else - return db.translateStatement(pValue) + { + return db.translateStatement(pValue); + } }); -} +}; /** * Will quote all prepared statement values from the given statement. * - * @param {Array} pStatement Same as the first parameter of db.translateCondition. - * @param {string} [pAlias] the alias which should be used for db.translateStatement() + * @param {[string, Array]} pStatement - Same as the first parameter of db.translateCondition. + * @param {string} [pAlias] - The alias which should be used for db.translateStatement() * @returns {string} The SQL, same as the result of db.translateCondition. */ SqlUtils.translateConditionWithQuotes = function(pStatement, pAlias) @@ -4464,28 +4863,45 @@ SqlUtils.translateConditionWithQuotes = function(pStatement, pAlias) return SqlUtils.translateWithQuotes(pStatement, pAlias, function(pValue) { if (pAlias) - return db.translateCondition(pValue, pAlias) + { + return db.translateCondition(pValue, pAlias); + } else - return db.translateCondition(pValue) + { + return db.translateCondition(pValue); + } }); -} +}; +/** + * Parses the field name + * + * @param {string|string[]} pField - The field name, can be a string with the format "TABLENAME.COLUMNNAME", + * or an array: [tableName, columnName, alias], "alias" is optional + * @return {[string, string]} Array with the structure [columnIdentifierWithAlias, columnIdentifier] + */ SqlUtils.parseField = function(pField) { - var alias = ""; - if (typeof pField === 'string') + let alias = ""; + if (typeof pField === "string") { - var pointPos = pField.indexOf("."); + let pointPos = pField.indexOf("."); - if (pointPos > 0 && pointPos < pField.length-1) + if (pointPos > 0 && pointPos < pField.length - 1) + { alias = pField; + } else + { throw new Error(translate.text("${SQL_LIB_FIELD_WRONG_FORMAT}") + pField + translate.withArguments("${SQL_LIB_FIELD_WRONG_FORMAT} field: %0", [pField])); + } } else { if (pField.length == 2) + { pField.push(pField[0]); + } if (pField.length == 3) { @@ -4493,11 +4909,22 @@ SqlUtils.parseField = function(pField) pField = pField[0] + "." + pField[1]; } else - throw new Error(translate.text("${SQL_LIB_FIELD_WRONG_FORMAT}") + pField.toSource() + translate.withArguments("${SQL_LIB_FIELD_WRONG_FORMAT} field: %0", [pField.toSource()])); + { + throw new Error(translate.text("${SQL_LIB_FIELD_WRONG_FORMAT}") + JSON.stringify(pField) + + translate.withArguments("${SQL_LIB_FIELD_WRONG_FORMAT} field: %0", [JSON.stringify(pField)])); + } } - return [alias, pField] -} + return [alias, pField]; +}; +/** + * Replaces placeholders in a condition string + * + * @param {string} pCondition - The sql condition + * @param {string} pPlaceholder - The placeholder name + * @param {string} pReplacement - The value to replace the placeholder + * @return {string} Condition with filled placeholders + */ SqlUtils.replaceConditionTemplate = function(pCondition, pPlaceholder, pReplacement) { //SqlUtils.replaceConditionTemplate(pCondition, '#', SqlUtils.parseField(pFieldOrCond).join(".")) @@ -4516,63 +4943,71 @@ SqlUtils.replaceConditionTemplate = function(pCondition, pPlaceholder, pReplacem --------------------- */ //use replaceAll because it's faster and supports negative lookbehinds - var replacements = {}; + let replacements = {}; //manually readd the replaced backslashes by using a group reference, because they a part of the match and therefore replaced by "replaceAll" //since the field COULD contain already a group reference (I think this is extremely uncommon; //probably that never happens but better stay save): escape that references within the fieldname replacements["(?<!\\\\)((?:\\\\\\\\)*)" + pPlaceholder] = "$1" + text.replaceAll(pReplacement, { "$1": "\\$1" - }) - //now that we've replaced the correct field placeholder let's replace the escaped placeholder sign "\#" to a normal placeholder sign "#" - replacements["\\\\" + pPlaceholder] = pPlaceholder + }); + //now that we've replaced the correct field placeholder let's replace the escaped placeholder sign "\#" to a normal placeholder sign "#" + replacements["\\\\" + pPlaceholder] = pPlaceholder; return text.replaceAll(pCondition, replacements); -} +}; /** * Checks if the '#' is 0 or 1 time in pCondition, '?' has to be 1 time in pCondition. * Also checks if '#' is before '?' + * * @param {string} pCondition - * - * @return {boolean} true if the format is ok + * @return {boolean} True if the format is ok */ SqlUtils.checkConditionFormat = function(pCondition) { // replace by {@NUMBERSIGN@} / {@QUESTIONSIGN@} as the js-regexp cannot do lookbehind (needed by the regexp used in replaceConditionTemplate to check escapes) // so we just use replaceConditionTemplate to replace by something which never should occur anywhere (it uses text.replaceAll which supports lookbehind because it uses java) - pCondition = SqlUtils.replaceConditionTemplate(pCondition, "#", "{@NUMBERSIGN@}") - pCondition = SqlUtils.replaceConditionTemplate(pCondition, "\\?", "{@QUESTIONSIGN@}") + pCondition = SqlUtils.replaceConditionTemplate(pCondition, "#", "{@NUMBERSIGN@}"); + pCondition = SqlUtils.replaceConditionTemplate(pCondition, "\\?", "{@QUESTIONSIGN@}"); - var indexOfNumberSign = pCondition.indexOf("{@NUMBERSIGN@}"); - var indexOfQuestionSign = pCondition.indexOf("{@QUESTIONSIGN@}"); + let indexOfNumberSign = pCondition.indexOf("{@NUMBERSIGN@}"); + let indexOfQuestionSign = pCondition.indexOf("{@QUESTIONSIGN@}"); - return !(indexOfQuestionSign == -1 || indexOfNumberSign > indexOfQuestionSign || indexOfNumberSign != pCondition.lastIndexOf("{@NUMBERSIGN@}") || indexOfQuestionSign != pCondition.lastIndexOf("{@QUESTIONSIGN@}")) -} + return !(indexOfQuestionSign == -1 || indexOfNumberSign > indexOfQuestionSign || indexOfNumberSign != pCondition.lastIndexOf("{@NUMBERSIGN@}") || indexOfQuestionSign != pCondition.lastIndexOf("{@QUESTIONSIGN@}")); +}; /** * Escapes a jdito variable for the value of a SqlBuilder condition. SqlBuilder.prototype.where/and/or/... automatically resolve the value as a jdito * variable if it starts with a single '$', so you can use this function to make sure the value is used as it is. - * <br> + * * Note: The main purpose of this is to prevent errors resulting from unexpected user inputs. But if you are loading the input from a jdito variable * anyways, you can just wite the variable name as the condition value and it will be safe. * - * @param {string} pValue the value + * @param {string} pValue - the value * @return {string} the escaped string * @example * - * var sqlCond = newWhere("TABLE.COLUMN", SqlUtils.escapeVars(userInput)); //userInput could start with '$' + * let sqlCond = newWhere("TABLE.COLUMN", SqlUtils.escapeVars(userInput)); //userInput could start with '$' */ -SqlUtils.escapeVars = function (pValue) +SqlUtils.escapeVars = function(pValue) { if (typeof(pValue) == "string" && pValue.charAt(0) == "$") + { return "$" + pValue; + } return pValue; -} +}; +/** + * Maps the integer operator value from "$local.operator" to SqlBuilder constants + * + * @param {number} pOperator - The number value of the operator + * @return {string} Sql representation of the operator + */ SqlUtils.getSqlConditionalOperator = function(pOperator) { pOperator = pOperator || vars.get("$local.operator"); - switch(parseInt(pOperator)) + switch(Number(pOperator)) { case 1: return SqlBuilder.EQUAL(); @@ -4597,23 +5032,24 @@ SqlUtils.getSqlConditionalOperator = function(pOperator) default: throw new Error("Unsupported operator " + pOperator); } -} +}; /** - * Returns the pNullableExpr if pNullableExpr is not null + * Returns the pNullableExpr if pNullableExpr is not null, * otherwise it returns pDefaultExpr * - * @param {string|SqlBuilder} pNullableExpr a nullable expression - * @param {string|SqlBuilder} pDefaultExpr the default expression if pNullableExpr is null + * @param {string|SqlBuilder} pNullableExpr - A nullable expression + * @param {string|SqlBuilder} pDefaultExpr - The default expression if pNullableExpr is null * * @returns {SqlBuilder._CaseStatement} pNullableExpr with pDefaultExpr as fallback */ SqlUtils.nullableWithDefault = function(pNullableExpr, pDefaultExpr) { return SqlBuilder.caseStatement() - .when("(" + pNullableExpr.toString() + ") is null").then(pDefaultExpr) + .when("(" + pNullableExpr.toString() + ") is null") + .then(pDefaultExpr) .elseValue(pNullableExpr); -} +}; /** * Adds a whitelist condition to pCondition. This function handles empty whitelists differently than no whitelists: @@ -4621,7 +5057,7 @@ SqlUtils.nullableWithDefault = function(pNullableExpr, pDefaultExpr) * * @param {SqlBuilder} pCondition SqlBuilder where condition * @param {string} pField the field the whitelist should be compared to - * @param {array|string} [pWhitelist] whitelist to compare + * @param {Array | string} [pWhitelist] whitelist to compare * @returns {SqlBuilder} condition */ SqlUtils.addWhitelistCondition = function(pCondition, pField, pWhitelist) @@ -4643,9 +5079,7 @@ SqlUtils.addWhitelistCondition = function(pCondition, pField, pWhitelist) } } return cond; -} - - +};