diff --git a/process/Sql_lib/process.js b/process/Sql_lib/process.js index a61bc1aa625f99ebce9ca0759965eb4b016942dd..1a55a386f9d48a5ea2e61246bc900eae1d121fde 100644 --- a/process/Sql_lib/process.js +++ b/process/Sql_lib/process.js @@ -9,6 +9,636 @@ import("system.SQLTYPES"); import("system.text"); import("Util_lib"); +/** + * object for easier handling of conditions; + * With this object you do not have to check if the string is empty or not; + * you don't need to append a "1=1" condition or similar; + * this objects gains most benefit if you have a lot of conditions that are added (or not) depending on tons of JDito-conditions + * + * You can also use SqlCondition.begin(alias) for simpler object creation without new and without the need for an extra variable to save the object. + * + * @class + * @param {String} [alias=the current alias] the database alias where the condition shall be executed later (important for column types of preparedStatements) + * @example + * see others/guide/HowToSqlConditionLib.adoc + * + * @deprecated The SqlCondition will be removed in version >= 2020.x + * Use the SqlBuilder instead. + * For SqlBuilder usage see the documentation-property of the Sql_lib. + */ +function SqlCondition(alias) { + // setting null is only needed to provide autocomplete for the ADITO-designer + this.preparedValues = null; + this._init(); // the properties are initalized in an extra function because init is nearly the same as resetting (clearing) the SqlConditions + this.alias = alias; + + // save, if the last condition was an OR. For better bracket-placement + this._lastWasOr = false; +} + +/** + * Alternative possibility to crate a new condition. + * With this you don't need new SqlCondition and you can use the object directly after it's creation + * --> cleaner code + * + * It is very usefull for the orSqlCondition() and andSqlCondition() because now you can create a new condition inline. + * You can also use it for simple selects without the need to save the conditionObject in an extra variable. + * See Examples! + * + * @param {String} [alias=the current alias] the database alias where the condition shall be executed later (important for column types of preparedStatements) + * @return {SqlCondition} the new SqlCondition-object + * + * @example + * vars mySelect = SqlCondition.begin(alias) + * .and("MYID = '123'") + * .and(SqlCondition.begin() + * .or("NAME = 'Max'") + * .or("NAME = 'Bob'") + * ) + * .buildSql("select * from MYTABLE"); + * + * // Or use it for simple selects: + * var sum = db.cell(SqlCondition.begin() + * .andPrepared("STOCK.PRODUCT_ID", pid) + * .buildSql("select sum(QUANTITY * IN_OUT) from STOCK")); + * + * @deprecated The SqlCondition will be removed in version >= 2020.x + * Use the SqlBuilder instead. + * For SqlBuilder usage see the documentation-property of the Sql_lib." + */ +SqlCondition.begin = function(alias) { + return new SqlCondition(alias); +} + +/** + * checks if conditions have been added to the object + * @return {Boolean} true if conditions have been added, false when not + */ +SqlCondition.prototype.isSet = function() { + if (this._sqlStorage) + return true; + return false; +} + + +/** + * append with SQL-and; no paranthesize of existing conditions is done + * @param {String} cond the condition string which shall be appended + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.and = function(cond) { + if (!cond) + return this; + if (this.isSet()) + this._sqlStorage += " and "; + this._sqlStorage += cond; + return this; +} + +/** + * append with SQL-or; Also paranthesize the existing conditions + * @param {String} cond the condition string which shall be appended + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.or = function(cond) { + if (!cond) + return this; + + if (this.isSet() && !this._lastWasOr) { + this._sqlStorage = "(" + this._sqlStorage + ") or (" + cond + ")"; + this._lastWasOr = true; + + } else if (this.isSet() && this._lastWasOr) { + this._sqlStorage = this._sqlStorage + " or (" + cond + ")"; + this._lastWasOr = true; + + } else { + this._sqlStorage = cond; + } + return this; +} + +/** + * append a prepared-array to this sql condition with SQL-and + * @param {Array} preparedObj a prepared condition-array + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.andAttachPrepared = function(preparedObj) { + if (preparedObj) + { + this.preparedValues = this.preparedValues.concat(preparedObj[1]); + return this.and(preparedObj[0]); + } + + return this; +} + +/** + * append a prepared-array to this sql condition with SQL-or + * @param {Array} preparedObj a prepared condition-array + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.orAttachPrepared = function(preparedObj) { + if (preparedObj) + { + this.preparedValues = this.preparedValues.concat(preparedObj[1]); + return this.or(preparedObj[0]); + } + + return this; +} + +/** + * append another condition with SQL-and + * + * @param {SqlCondition} cond the condition which shall be appended + * @param {String} [alternativeCond=""] condition if the given SqlCondition has none + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.andSqlCondition = function(cond, alternativeCond) { + if (!cond) + return this + + var otherCondition = cond.toString(alternativeCond); + if (otherCondition.trim() != "") + { + this.and(" ( " + cond.toString(alternativeCond) + " ) "); + if (cond.preparedValues) { + this.preparedValues = this.preparedValues.concat(cond.preparedValues); + } + } + return this; +} + +/** + * append another condition with SQL-or; Also paranthesize the existing conditions + * + * @param {SqlCondition} cond the condition which shall be appended + * @param {String} [alternativeCond=""] condition if the given SqlCondition has none + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.orSqlCondition = function(cond, alternativeCond) { + var otherCondition = cond.toString(alternativeCond); + if (otherCondition.trim() != "") + { + this.or(" ( " + cond.toString(alternativeCond) + " ) "); + if (cond.preparedValues) { + this.preparedValues = this.preparedValues.concat(cond.preparedValues); + } + } + return this; +} + +/** + * append an condition that uses a subQuery with SQL-and + * + * @param {SqlBuilder} subQuery the SqlBuilder object that will be used as a subquery + * @param {String} [cond="exists"] condition that is used (e. g. exists, not exists, COLUMN = any, COLUMN in, ...) + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.andSqlBuilder = function(subQuery, cond) { + if (!cond) + cond = "exists"; + + var preparedObj = subQuery.build(); + preparedObj[0] = cond + " ( " + preparedObj[0] + " ) "; + this.andAttachPrepared(preparedObj); + + return this; +} + +/** + * append an condition that uses a subQuery with SQL-or + * + * @param {SqlBuilder} subQuery the SqlBuilder object that will be used as a subquery + * @param {String} [cond="exists"] condition that is used (e. g. exists, not exists, COLUMN = any, COLUMN in, ...) + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.orSqlBuilder = function(subQuery, cond) { + if (!cond) + cond = "exists"; + + var preparedObj = subQuery.build(); + preparedObj[0] = cond + " ( " + preparedObj[0] + " ) "; + this.orAttachPrepared(preparedObj); + + return this; +} + +/** + * same as the "and"-function but with preparedStatement functionality + * @param {String | String[]} field the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" or as array with column-alias: ["ORGANISATION", "NAME", "myorgAlias"] + * @param {String} value the value that shall be set into the prepared statement + * @param {String} [cond="# = ?"] 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 "\" + * @param {Numeric | Boolean} [fieldType] 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, "# <> ?") + * } + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.andPrepare = function(field, value, cond, fieldType) { + cond = this._prepare(field, value, cond, fieldType); + return this.and(cond); +} + +/** + * same as the "or"-function but with preparedStatement functionality + * @param {String | String[]} field the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" or as array with column-alias: ["ORGANISATION", "NAME", "myorgAlias"] + * @param {String} value the value that shall be set into the prepared statement + * @param {String} [cond="# = ?"] 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 "\" + * @param {Numeric | Boolean} [fieldType] 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, "# <> ?") + * } + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.orPrepare = function(field, value, cond, fieldType) { + cond = this._prepare(field, value, cond, fieldType); + return this.or(cond); +} + +/** + * same as the "andPrepare"-function but only applied if the passed "value" is truely + * @param {String | String[]} field the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" or as array with column-alias: ["ORGANISATION", "NAME", "myorgAlias"] + * @param {String} value the value that shall be set into the prepared statement + * @param {String} [cond="# = ?"] 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 "\" + * @param {Numeric | Boolean} [fieldType] 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, "# <> ?") + * } + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.andPrepareIfSet = function(field, value, cond, fieldType) { + if (value) + return this.andPrepare(field, value, cond, fieldType); + return this; +} + +/** + * same as the "orPrepare"-function but only applied if the passed "value" is truely + * @param {String | String[]} field the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" or as array with column-alias: ["ORGANISATION", "NAME", "myorgAlias"] + * @param {String} value the value that shall be set into the prepared statement + * @param {String} [cond="# = ?"] 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 "\" + * @param {Numeric | Boolean} [fieldType] 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, "# <> ?") + * } + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.orPrepareIfSet = function(field, value, cond, fieldType) { + if (value) + return this.orPrepare(field, value, cond, fieldType); + return this; +} + +/** + * same as the "andPrepare"-function but with validation of adito-variables functionality + * @param {String | String[]} field the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" or as array with column-alias: ["ORGANISATION", "NAME", "myorgAlias"] + * @param {String} variable the adito-variable that shall be set into the prepared statement + * @param {String} [cond = "# = ?" ] 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 "\" + * @param {Numeric | Boolean} [fieldType] 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, "# <> ?") + * } + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.andPrepareVars = function(field, variable, cond, fieldType) { + variable = this._checkVars(variable) + if (variable) { + return this.andPrepare(field, variable, cond, fieldType); + } + return this; +} + +/** + * same as the "orPrepare"-function but with validation of adito-variables functionality + * @param {String | String[]} field the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" or as array with column-alias: ["ORGANISATION", "NAME", "myorgAlias"] + * @param {String} variable the adito-variable that shall be set into the prepared statement + * @param {String} [cond="# = ?"] 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 "\" + * @param {Numeric | Boolean} [fieldType] 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, "# <> ?") + * } + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.orPrepareVars = function(field, variable, cond, fieldType) { + variable = this._checkVars(variable) + if (variable) { + return this.orPrepare(field, variable, cond, fieldType); + } + return this; +} + +/** + * creates a IN-statement out of a field and an array of values. + * Be carefull with a big number of values. This may have a bad performance. + * + * @param {String | String[]} field the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" or as array with column-alias: ["ORGANISATION", "NAME", "myorgAlias"] + * @param {String[]} values the value that shall be set into the prepared statement + * @param {Numeric | Boolean} [fieldType] 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} [not = undefined] if true, add not before in + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.andIn = function(field, values, fieldType, not) { + return this.andAttachPrepared(this._in(field, values, fieldType, not)); +} + +/** + * creates a IN-statement out of a field and an array of values. + * Be carefull with a big number of values. This may have a bad performance. + * + * @param {String | String[]} field the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" or as array with column-alias: ["ORGANISATION", "NAME", "myorgAlias"] + * @param {String[]} values the value that shall be set into the prepared statement + * @param {Numeric | Boolean} [fieldType] 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} [not = undefined] if true, add not before in + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype.orIn = function(field, values, fieldType, not) { + return this.orAttachPrepared(this._in(field, values, fieldType, not)); +} + +/** + * creates a IN-statement out of a field and an array of values. + * Be carefull with a big number of values. This may have a bad performance. + * + * @param {String | String[]} field the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" or as array with column-alias: ["ORGANISATION", "NAME", "myorgAlias"] + * @param {String[]} values the value that shall be set into the prepared statement + * @param {Numeric | Boolean} [fieldType] 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} [not = undefined] if true, add not before in + * @return {SqlCondition} current SqlCondition-object + */ +SqlCondition.prototype._in = function(field, values, fieldType, not) { + if (values && values.length > 0) + { + if (fieldType == undefined) + fieldType = SqlUtils.getSingleColumnType(field, undefined, this.alias); + + preparedStatement = SqlUtils.getSqlInStatement(field, values, undefined, true, fieldType); + if (not) + preparedStatement[0] = " not " + preparedStatement[0]; + return preparedStatement; + } + + return null; +} + +/** + * ready to use string; does not contain a where keyword at the beginning + * @param {String} [alternativeCond=""] condition that is returned when nothing has been appended. + * @return {String} concatenated SQL-condition; empty string if nothing has been appended or - if passed - the alternativeCond + */ +SqlCondition.prototype.toString = function(alternativeCond) { + if (!this.isSet() && alternativeCond) + return alternativeCond + else + return this._sqlStorage; +} + +/** + * ready to use string; does contain a where keyword at the beginning + * @param {String} [alternativeCond=""] condition that is returned when nothing has been appended. + * @return {SqlCondition} concatenated SQL-condition; empty string if nothing has been appended or - if passed - the alternativeCond + */ +SqlCondition.prototype.toWhereString = function(alternativeCond) { + var cond = this.toString(alternativeCond); + if (cond) + return " where " + cond; + else + return cond; +} + +/** + * ready to use prepared condition; does not contain a where keyword at the beginning + * @param {String} [alternativeCond=""] Condition that is returned when nothing has been appended. + * @return {Array[][][]} Prepared condition with [condition, [[field1, type1], [field2, type2]]] + */ +SqlCondition.prototype.build = function(alternativeCond) { + return [this.toString(alternativeCond), this.preparedValues]; +} + +/** + * ready to use prepared select + * @param {String} pBeforeCondition Part of the sql before the condition without where (e.g. "select FIRSTNAME from PERSON") + * @param {String} [pAlternativeCond=""] Condition that is returned when nothing has been appended. + * @param {String} [pAfterCondition=""] Part of the sql after the condition (e.g. "order by FIRSTNAME"). + * @param {Boolean} [pWithWere=true] true if where should be added to the bginning + * @return {Array[][][]} Prepared condition with [condition, [[field1, type1], [field2, type2]]] + */ +SqlCondition.prototype.buildSql = function(pBeforeCondition, pAlternativeCond, pAfterCondition, pWithWere) { + if (pAfterCondition == undefined) + pAfterCondition = ""; + + if (pWithWere == undefined) + pWithWere = true; + + return [pBeforeCondition + " " + + (pWithWere ? this.toWhereString(pAlternativeCond) : this.toString(pAlternativeCond)) + + " " + pAfterCondition, this.preparedValues]; +} + +/** + * translates SqlCondition to plain SQL. Use this if prepared statements are not supported. + * It resolves all prepared values. + * @param {String} pAlternativeCond used if the SqlCondition does not contain any condition. + * @return {String} plain SQL condition + */ +SqlCondition.prototype.translate = function(pAlternativeCond) +{ + return SqlUtils.translateConditionWithQuotes(this.build(pAlternativeCond, this.alias)); +} + +/** + * Check if (adito-)variable exists and vars.getString is not empty + * @param {String} variable the variable name (e.g. "$field.CONTACT_ID") + * @return {String | Boolean} The value of the field as string OR false if it doesn't exist. + * + * @ignore + */ +SqlCondition.prototype._checkVars = function(variable) { + if (vars.exists(variable)) { + var value = vars.getString(variable); + if (value) { + return value; + } + } + return false; +} + +/** + * hidden function for composing preparedStatements + * @param {String | String[]} field the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" or as array with column-alias: ["ORGANISATION", "NAME", "myorgAlias"] + * @param {String} value the value that shall be set into the prepared statement + * @param {String} cond 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 {Numeric | Boolean} [fieldType] 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, "# <> ?") + * } + * @return {String} the replaced SQL-condition string (replace # by the fieldname) + * @ignore + */ +SqlCondition.prototype._prepare = function(field, value, cond, fieldType) { + if (value == undefined) + { + throw new Error(translate.withArguments("${SQL_LIB_UNDEFINED_VALUE} field: %0", [field])); + } + + if (cond == undefined) { + cond = "# = ?" + } + + var alias; + + if (typeof field === 'string') + { + var pointPos = field.indexOf("."); + + if (pointPos > 0 && pointPos < field.length-1) + { + alias = field; + } + else + { + throw new Error(translate.withArguments("${SQL_LIB_FIELD_WRONG_FORMAT} field: %0", [field])); + } + } + else + { + if (field.length == 3) + { + alias = field[2] + "." + field[1]; + field = field[0] + "." + field[1]; + } + else + { + throw new Error(translate.withArguments("${SQL_LIB_FIELD_WRONG_FORMAT} field: %0", [field.toSource()])); + } + } + + var type; + + if (fieldType == undefined) + fieldType = SqlUtils.getSingleColumnType(field, undefined, this.alias); + + //this function looks more complex (and slower) than it actually is + /* the following regex looks like this after javascript-escaping of the backslash: (?<!\\)((?:\\\\)*)# + the regexp searches for the unescaped character and these characters are replaced by the field name + + examples: + --------------------- + | # --match | + | \# --no-match | + | \\# --match | + | \\\# --no-match | + | \\\\# --match | + --------------------- + */ + //use replaceAll because it's faster and supports negative lookbehinds + cond = text.replaceAll(cond, { + //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 + "(?<!\\\\)((?:\\\\\\\\)*)#": "$1" + text.replaceAll(alias, { + "$1": "\\$1" + }), + //now that we've replaced the correct field placeholder let's replace the escaped number sign "\#" to a normal number sign "#" + "\\\\#": "#" + }); + + + + type = fieldType + this.preparedValues.push([value.toString(), type]); + return cond; +} + + +/** + * function that resets the current SqlCondition as if no conditions would have been added + * this is usefull if you want to reuse the same object over and over + * @return {null} + */ +SqlCondition.prototype.clear = function() { + this._sqlStorage = ""; + this.preparedValues = []; + return this; +} + +/** + * hidden function for initializing all properties for the sql conditions + * @return {null} + * + * @ignore + */ +SqlCondition.prototype._init = function() { + //init only wraps the clear function to avoid confusion in the constructor (and provide better extensibility) + return this.clear(); +} + +// some static functions for often used tasks. They are only provided for very simple tasks. + +/** + * pField = pValue + * @param {String} pField the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" + * @param {String} pValue the value that shall be set into the prepared statement + * @param {String} [pAlternativeCond=""] Condition that is returned when nothing has been appended. + * @param {String} [pAlias=the current alias] the database alias where the condition shall be executed later (important for column types of preparedStatements) + * + * @return {Array[][][]} Prepared condition with [condition, [[field, type]]] + */ +SqlCondition.equals = function(pField, pValue, pAlternativeCond, pAlias) { + return SqlCondition.begin(pAlias).andPrepare(pField, pValue).build(pAlternativeCond); +} + +/** + * pField <> pValue + * @param {String} pField the database field as "tablename.columnname"; e.g. "ORGANISATION.NAME" + * @param {String} pValue the value that shall be set into the prepared statement + * @param {String} [pAlternativeCond=""] Condition that is returned when nothing has been appended. + * @param {String} [pAlias=the current alias] the database alias where the condition shall be executed later (important for column types of preparedStatements) + * + * @return {Array[][][]} Prepared condition with [condition, [[field, type]]] + */ +SqlCondition.equalsNot = function(pField, pValue, pAlternativeCond, pAlias) { + return SqlCondition.begin(pAlias).andPrepare(pField, pValue, "# <> ?").build(pAlternativeCond); +} + // see Documentation property of this lib for further explanation /** @@ -287,6 +917,42 @@ 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)")); } +/** + * 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. + */ +SqlBuilder.begin = function () +{ + return new SqlBuilder(); +} + /** * Builds the sql and uses SqlUtils.translateXXXWithQuotes to make a string out of it. @@ -322,6 +988,17 @@ SqlBuilder.prototype.select = function(pFields) 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) +{ + this._select = this._getClause(pFields, "select distinct", 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 @@ -368,6 +1045,14 @@ SqlBuilder.prototype.from = function(pTable, pTableAlias) */ 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"); if (pPrefix) prefix = pPrefix + " " + prefix; @@ -482,7 +1167,22 @@ SqlBuilder.prototype.rightJoin = function(pTable, pCondition, pTableAlias) * @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); } @@ -1748,6 +2448,68 @@ SqlBuilder.prototype._checkForSelect = function(pExecuteOnlyIfConditionExists) } } +/** + * generates a part of the sql + * + * @param {String|String[]|SqlBuilder|SqlCondition} pElement the element to append + * @param {String} [pPrefix] string to be added before pElement + * @param {Boolean} [pAutoJoin] if this is true and pElement is an array, it will be automatically + * joined together to a string + * + * @private + * @deprecated this method is only needed by deprecated methods + */ +SqlBuilder.prototype._getClause = function (pElement, pPrefix, pAutoJoin) +{ + var preparedValues = []; + if (typeof pElement !== "string") + { + if (pElement.length !== undefined && pAutoJoin) //array of fields + { + for (let i = 0, l = pElement.length; i < l; i++) + { + if (typeof pElement[i] !== "string") + pElement[i] = _getElement(pElement[i]); + } + pElement = pElement.join(", "); + } + else + { + pElement = _getElement(pElement); + } + } + + if (pPrefix && pElement) + pElement = pPrefix + " " + pElement; + + return [pElement.toString(), preparedValues]; + + function _getElement (element) + { + if (element instanceof SqlBuilder || element instanceof SqlCondition) + element = element.build(); + preparedValues = preparedValues.concat(element[1]); + if (element instanceof SqlBuilder || pAutoJoin) + return "(" + element[0] + ")"; + return element[0]; + } +} + +/** + * 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 + * @return {String} plain SQL statement + * + * @deprecated use .toString() + */ +SqlBuilder.prototype.translate = function(pAlias) +{ + return SqlUtils.translateStatementWithQuotes(this.build(), pAlias); +} + + /** *provides functions for masking sql functions *