From f6811904b9743597617a60e72e3f0ef0f2828e7f Mon Sep 17 00:00:00 2001 From: "j.goderbauer" <j.goderbauer@adito.de> Date: Wed, 14 Nov 2018 08:33:12 +0100 Subject: [PATCH] refactoring of some SQL-library functions --- entity/History_entity/conditionProcess.js | 28 +- .../product_id/possibleItemsProcess.js | 4 +- entity/Pers_entity/conditionProcess.js | 26 +- .../children/relid_param/code.js | 10 +- process/Sql_lib/process.js | 2396 +++++++++-------- 5 files changed, 1245 insertions(+), 1219 deletions(-) diff --git a/entity/History_entity/conditionProcess.js b/entity/History_entity/conditionProcess.js index 495433af6a8..9df40aadc1b 100644 --- a/entity/History_entity/conditionProcess.js +++ b/entity/History_entity/conditionProcess.js @@ -4,27 +4,13 @@ import("system.result"); import("system.logging"); import("Sql_lib"); -var rowId, conditionStr, preparedValues - ,sqlHelper; -conditionStr = ""; -preparedValues = []; -sqlHelper = new LegacySqlUtils(); +var rowId, cond, preparedValues, sqlHelper; +cond = new SqlCondition(); -if ((rowId = vars.getString("$param.RowId_param"))){ - conditionStr += " and HISTORYLINK.ROW_ID = ? "; - preparedValues.push([rowId, sqlHelper.getSingleColumnType("HISTORYLINK.ROW_ID")]); - +rowId = vars.getString("$param.RowId_param"); +if (rowId){ + cond.andPrepare("HISTORYLINK.ROW_ID", "# = ?", rowId); } -//TODO; add OBJECT_ID (probably another param) -//conditionStr += " and HISTORYLINK.OBJECT_ID = ? " -//preparedValues.push(["1", sqlHelper.getSingleColumnType("HISTORYLINK.OBJECT_ID")]); - -if (conditionStr){ - conditionStr = " 1 = 1 " + conditionStr; - result.string(db.translateCondition([conditionStr, preparedValues])); -} -else - result.string("1 = 1"); -//TODO: use the preparedStatemenet when available: -//result.object([conditionStr, preparedValues]); \ No newline at end of file +//TODO: use a preparedCondition when available +result.string(db.translateCondition([cond.toString("1 = 1"), cond.preparedValues])); \ No newline at end of file diff --git a/entity/Offeritem_entity/entityfields/product_id/possibleItemsProcess.js b/entity/Offeritem_entity/entityfields/product_id/possibleItemsProcess.js index ece8c2eca10..f9ee3edbce8 100644 --- a/entity/Offeritem_entity/entityfields/product_id/possibleItemsProcess.js +++ b/entity/Offeritem_entity/entityfields/product_id/possibleItemsProcess.js @@ -4,8 +4,8 @@ import("system.db"); import("Sql_lib"); //TODO: change field to lookup field -var sqlUtils = new LegacySqlUtils(); -var prodsSql = "select PRODUCTID, " + sqlUtils.concat(["PRODUCTCODE", "'/'", "PRODUCTNAME"]) +var sqlUtils = new SqlMaskingUtils(); +var prodsSql = "select PRODUCTID, " + sqlUtils.concat(["PRODUCTCODE", "PRODUCTNAME"], "/") + " from PRODUCT"; var prods = db.table(prodsSql); diff --git a/entity/Pers_entity/conditionProcess.js b/entity/Pers_entity/conditionProcess.js index f2d2356e392..17bc706a5ea 100644 --- a/entity/Pers_entity/conditionProcess.js +++ b/entity/Pers_entity/conditionProcess.js @@ -3,25 +3,17 @@ import("system.vars"); import("system.result"); import("Sql_lib"); -var orgId, conditionStr, preparedValues - ,sqlHelper; -conditionStr = ""; -preparedValues = []; -sqlHelper = new LegacySqlUtils(); +var orgId, cond; -if (vars.exists("$param.OrgId_param") && (orgId = vars.getString("$param.OrgId_param"))){ - conditionStr += " and RELATION.ORG_ID = ? "; - preparedValues.push([orgId, sqlHelper.getSingleColumnType("RELATION.ORG_ID")]); - +cond = new SqlCondition(); +if (vars.exists("$param.OrgId_param")){ + orgId = vars.getString("$param.OrgId_param"); + if (orgId != "" && orgId != null){ + cond.andPrepare("RELATION.ORG_ID", "# = ?", orgId); + } } //TODO; add OBJECT_ID (probably another param) -//conditionStr += " and HISTORYLINK.OBJECT_ID = ? " -//preparedValues.push(["1", sqlHelper.getSingleColumnType("HISTORYLINK.OBJECT_ID")]); -if (conditionStr){ - conditionStr = " 1 = 1 " + conditionStr; - result.string(db.translateCondition([conditionStr, preparedValues])); -} -else - result.string("1 = 1"); \ No newline at end of file +//TODO: use preparedStatements when available +result.string(db.translateCondition([cond.toString("1 = 1"), cond.preparedValues])); diff --git a/entity/Pers_entity/entityfields/orgaddress_dfo/children/relid_param/code.js b/entity/Pers_entity/entityfields/orgaddress_dfo/children/relid_param/code.js index 22ba1eb2fb3..8ffe7d0d15b 100644 --- a/entity/Pers_entity/entityfields/orgaddress_dfo/children/relid_param/code.js +++ b/entity/Pers_entity/entityfields/orgaddress_dfo/children/relid_param/code.js @@ -3,14 +3,12 @@ import("system.db"); import("system.vars"); import("Sql_lib"); -var orgId, orgRelId, sqlHelper; +var orgId, orgRelId, sqlHelper, cond; -sqlHelper = new LegacySqlUtils(); orgId = vars.get("$field.ORGID"); if (orgId){ - orgRelId = db.cell(["select RELATION.RELATIONID from RELATION where RELATION.PERS_ID is null and RELATION.ORG_ID = ?", - [ - [orgId, sqlHelper.getSingleColumnType("RELATION", "ORG_ID")] - ]]); + cond = new SqlCondition(); + cond.and("RELATION.PERS_ID is null").andPrepare("RELATION.ORG_ID", "# = ?", orgId) + orgRelId = db.cell(["select RELATION.RELATIONID from RELATION" + cond.toWhereString(), cond.preparedValues]); result.string(orgRelId); } \ No newline at end of file diff --git a/process/Sql_lib/process.js b/process/Sql_lib/process.js index 4181790956d..d7ee4f067d8 100644 --- a/process/Sql_lib/process.js +++ b/process/Sql_lib/process.js @@ -1,1174 +1,1224 @@ -import("system.translate"); -import("system.vars"); -import("system.db"); -import("system.datetime"); -import("system.tools"); -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; - * @example //TODO: add missing example - */ -function SqlCondition(alias){ - this._sqlStorage = ""; - this.preparedValues = []; - this.alias = alias; -} -/** - * append with SQL-and; no paranthesize of existing conditions is done - * @param {String} cond the condition string which shall be appended - * @return {Object} current SqlCondition-object - */ -SqlCondition.prototype.and = function(cond){ - if (!cond) - return this; - if (this._sqlStorage) - this._sqlStorage += " and "; - this._sqlStorage += cond; - return this; -} - -/** - * same as the "and"-function but with preparedStatement functionality - * @param {String} field the database field as "tablename.columnname"; e.g. "ORG.NAME" - * @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 {String} value the value that shall be set into the prepared statement - * @param {Numeric} [fieldType] SQL-column-type; if the fieldType is not given it's loaded automatically; - * please note that this can be a performace issue if it happens a lot of times (e.g. in aloop) - * @return {Object} current SqlCondition-object - */ -SqlCondition.prototype.andPrepare = function(field, cond, value, fieldType){ - cond = this._prepare(field, cond, value, fieldType); - return this.and(cond); -} - -/** - * append with SQL-or; Also paranthesize the existing conditions - * @param {String} cond the condition string which shall be appended - * @return {Object} current SqlCondition-object - */ -SqlCondition.prototype.or = function(cond){ - if (!cond) - return this; - if (this._sqlStorage) - this._sqlStorage = "(" + this._sqlStorage + ") or (" + cond + ")"; - else - this._sqlStorage = cond; - return this; -} - -/** - * same as the "or"-function but with preparedStatement functionality - * @param {String} field the database field as "tablename.columnname"; e.g. "ORG.NAME" - * @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 {String} value the value that shall be set into the prepared statement - * @param {Numeric} [fieldType] SQL-column-type; if the fieldType is not given it's loaded automatically; - * please note that this can be a performace issue if it happens a lot of times (e.g. in aloop) - * @return {Object} current SqlCondition-object - */ -SqlCondition.prototype.orPrepare = function(field, cond, value, fieldType){ - cond = this._prepare(field, cond, value, fieldType); - return this.or(cond); -} -/** - * 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._sqlStorage && 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 {String} 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; -} - -/** - * hidden function for composing preparedStatements - * @param {String} field the database field as "tablename.columnname"; e.g. "ORG.NAME" - * @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 {String} value the value that shall be set into the prepared statement - * @param {Numeric} [fieldType] SQL-column-type; if the fieldType is not given it's loaded automatically; - * please note that this can be a performace issue if it happens a lot of times (e.g. in aloop) - * @return {String} the replaced SQL-condition string (replace # by the fieldname) - */ -SqlCondition.prototype._prepare = function(field, cond, value, fieldType){ - //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(field, {"$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 || SqlUtils.getSingleColumnType(field, undefined, this.alias); - this.preparedValues.push([value.toString(), type]); - return cond; -} - -function SqlMaskingUtils(){ - //TODO: use callbacks for different handling? - /** - * masks the function cast of standard sql - * - * @param {String} field name of the database field that shall be castet - * @param {String} [targetDatatype] a SQLTYPES-value of the following: SQLTYPES.CHAR, SQLTYPES.VARCHAR, SQLTYPES.INTEGER, - * SQLTYPES.DECIMAL, SQLTYPES.DATE - * @param {int|int[]} targetLength specifies the length of the target data type; - * <br/>- char/varchar: length - * <br/>- decimal: [length, decimals] - * @param {String} [alias=the current alias] the alias where the statement shall be executed (this is needed to determine the database-type) - * - * @return {String} sql part to be included in sql-statements - */ - this.cast = function (field, targetDatatype, targetLength, alias){ - /* Some informations if you want to add supported databaseTypes or dataTypes: - * You should consider using the _mapDefaults function-expression (details in the functions doc) - * However you shouldn't use the function in a "default"-Block of a switch-case because of the following behaviour: - * If a datatype is not supported you just have to NOT specify "sqlDataType" (leave it "undefined") -> an error is then raised - * Therefore you should explicitly define which Data-type is supported and which is not - */ - var dbType, functionName, sqlPart, sqlDataType, _mapDefaults; - if (alias == undefined) - alias = vars.getString("$sys.dbalias"); - dbType = db.getDatabaseType(alias); - functionName = "cast";//overwrite this in the "switch (dbType)" if needed with your DBMS - - /** - * handles default-scenarios for mapping input-targetDatatype to a string for a sql-statement - * e.g. SQLTYPES.INTEGER --> int - * @param {Number} dataType input as a value of "SQLTYPES." that will be mapped to a string - * @return {String} the mapped dataType for using in a sql-statement - */ - _mapDefaults = function (dataType){ - var res; - switch(dataType) - { - case SQLTYPES.CHAR: - res = "char"; - break; - case SQLTYPES.VARCHAR: - res = "char"; - break; - case SQLTYPES.INTEGER: - res = "int"; - break; - case SQLTYPES.DECIMAL: - res = "decimal"; - break; - case SQLTYPES.DATE: - res = "date"; - break; - } - return res; - } - switch (dbType) { - case db.DBTYPE_DERBY10: - switch(targetDatatype) { - case SQLTYPES.VARCHAR: - // Because of a Derby bug, you can't cast INTEGER into VARCHAR - // Therefor first cast to char then to varchar - // https://issues.apache.org/jira/browse/DERBY-2072 - field = "rtrim(" + cast(field, SQLTYPES.CHAR, targetLength, alias) + ")"; - sqlDataType = "varchar"; - break; - case SQLTYPES.CHAR: - //TODO: throw error if(targetLength > 254)? https://db.apache.org/derby/docs/10.14/ref/rrefsqlj13733.html - sqlDataType = "char"; - break; - case SQLTYPES.DECIMAL: - case SQLTYPES.INTEGER: - case SQLTYPES.DATE: - sqlDataType = _mapDefaults(dataType); - break; - } - break; - case db.DBTYPE_MARIADB10: - case db.DBTYPE_MYSQL4: - switch(targetDatatype) { - case SQLTYPES.VARCHAR: - case SQLTYPES.CHAR: - case SQLTYPES.INTEGER: - case SQLTYPES.DECIMAL: - case SQLTYPES.DATE: - sqlDataType = _mapDefaults(targetDatatype); - break; - } - break; - case db.DBTYPE_ORACLE10_CLUSTER: - case db.DBTYPE_ORACLE10_THIN: - case db.DBTYPE_ORACLE10_OCI: - switch(pDatatype) - { - case SQLTYPES.VARCHAR: - datatype = "varchar2"; - break; - case SQLTYPES.INTEGER: - datatype = "number"; - targetLength = "10" - break; - case SQLTYPES.CHAR: - case SQLTYPES.DECIMAL: - case SQLTYPES.DATE: - sqlDataType = _mapDefaults(targetDatatype); - break; - } - break; - case db.DBTYPE_POSTGRESQL8: - switch(pDatatype) - { - case SQLTYPES.DATE: - case SQLTYPES.DECIMAL: - case SQLTYPES.INTEGER: - case SQLTYPES.CHAR: - case SQLTYPES.VARCHAR: - sqlDataType = _mapDefaults(targetDatatype); - break; - } - break; - case db.DBTYPE_SQLSERVER2000: - case SQLTYPES.DATE: - case SQLTYPES.DECIMAL: - case SQLTYPES.INTEGER: - case SQLTYPES.CHAR: - case SQLTYPES.VARCHAR: - sqlDataType = _mapDefaults(targetDatatype); - break; - case db.DBTYPE_FIREBIRD250: - //TODO: firebird support? - break; - } - - if (sqlDataType == undefined) { - throw new Error("sqlDataType");//TODO: add usefull message - } - - if(targetLength == undefined) - targetLength = ""; - else if(targetLength != "") - { - if(typeof(targetLength == "object") && (targetLength instanceof Array)) - targetLength = "(" + targetLength.join(", ") + ")"; - else - targetLength = "(" + targetLength + ")"; - } - - sqlPart = functionName + "(" + field + " as " + sqlDataType + targetLength + ")"; - return sqlPart; - } -} - -function SqlUtils(){ -} - -SqlUtils.getSingleColumnType = function(fieldOrTableName, columnName, alias) -{ - var tableName, fieldVarType; - if (columnName == undefined){ - fieldVarType = typeof(fieldOrTableName); - if (fieldVarType == "string") - fieldOrTableName = text.split(fieldOrTableName, "\\."); - else if (fieldVarType != "object"){ - throw new TypeError();//TODO: add message - } - - if (fieldOrTableName.hasOwnProperty("length") && fieldOrTableName.length == 2) - { - tableName = fieldOrTableName[0]; - columnName = fieldOrTableName[1]; - } - else - throw new TypeError();//TODO: add message - } - else - tableName = fieldOrTableName; - - if (typeof(columnName) != "string") - throw new TypeError();//TODO: add message - if (typeof(tableName) != "string") - throw new TypeError();//TODO: add message - - if (alias == undefined) - alias = db.getCurrentAlias(); - - return db.getColumnTypes(tableName, [columnName], alias)[0]; -} - -/** - *Class containing utilities for SQL - *@deprecated use SqlMaskingUtils - *@class - */ -function LegacySqlUtils() -{ - var that = this; - /** - * masks the cast function for lob datatypes(clob, blob) - * - * @param {String} pField req the data field - * @param {Integer|Interger[]} pLength req the length of the data - * decimal: [length, decimals] - * @param {String} pAlias req the database alias - * - * @return {String} (Teil einer SQL-Anweisung) - */ - this.castLob = function(pField, pLength, pAlias) - { - if(pAlias == undefined) - pAlias = vars.getString("$sys.dbalias"); - - var dbtype = db.getDatabaseType(pAlias); - - switch(Number(dbtype)) - { - case db.DBTYPE_ORACLE10_CLUSTER: - case db.DBTYPE_ORACLE10_THIN: - case db.DBTYPE_ORACLE10_OCI: - return "DBMS_LOB.SUBSTR(" + pField + ", " + pLength + ", 1)"; - break; - default: - return cast(pField, "varchar", pLength, pAlias); - break; - } - } - /** - * masks the function substring. - * - * @param {String }pExpression req - * @param {Integer} pStart req index of the beginning - * @param {String} pAlias req database alias - * @param {Integer} pLength req - * - * @return {String} - */ - this.substring = function(pExpression, pStart, pLength, pAlias) - { - if(pLength == undefined) - pLength = 100; - if(pAlias == undefined) - pAlias = vars.getString("$sys.dbalias"); - - var dbtype = db.getDatabaseType(pAlias); - var string = ""; - - switch(Number(dbtype)) - { - case db.DBTYPE_ORACLE10_CLUSTER: - case db.DBTYPE_ORACLE10_THIN: - case db.DBTYPE_ORACLE10_OCI: - string = "substr"; - break; - case db.DBTYPE_DERBY10: - string = "substr"; - break; - case db.DBTYPE_POSTGRESQL8: - string = "substr"; - break; - case db.DBTYPE_SQLSERVER2000: - string = "substring"; - break; - case db.DBTYPE_MYSQL4: - case db.DBTYPE_MARIADB10: - string = "substring"; - break; - } - - return string + "(" + pExpression + ", " + pStart + ", " + pLength + ")"; - } - - /** - * masks the function concat. - * - * @param {Array} pFields req fields that should be concatenated - * @param {String} pSeparator opt field separator - * @param {String} pAlias opt database alias - * - * @return {String} part of SQL-querey - */ - this.concat = function(pFields, pSeparator, pAlias) - { - var i; - if(pAlias == undefined || pAlias == "") - pAlias = vars.getString("$sys.dbalias"); - - var dbtype = db.getDatabaseType(pAlias); - var concat_string = " || "; - var retstr = ""; - var blank = "' '"; - - //it must be checked for empty string and null - var isNotEmpty = " != '' "; - var isNotNull = " is not null "; - if(pSeparator == undefined) - pSeparator = " "; - - switch(Number(dbtype)) - { - case db.DBTYPE_MYSQL4: - case db.DBTYPE_MARIADB10: - retstr = " concat_ws( '" + pSeparator + "'"; - for(i=0; i<pFields.length; i++) - { - retstr += ", " + pFields[i]; - } - return retstr + ") "; - break; - - case db.DBTYPE_ORACLE10_CLUSTER: - case db.DBTYPE_ORACLE10_THIN: - case db.DBTYPE_ORACLE10_OCI: - blank = "trim(' ')"; - isNotEmpty = " is not null "; //needed for oracle - break; - - case db.DBTYPE_SQLSERVER2000: - concat_string = " + "; - break; - } - pSeparator = concat_string + "'" + pSeparator + "'"; - for(i = 0; i < pFields.length; i++) - { - if(retstr != "") - retstr += concat_string; - if ( i < pFields.length - 1 ) //Check if another value follows, if not separator is not needed - retstr += " case when " + pFields[i] + isNotEmpty + " then case when " + this.trimSql(pFields[i+1], pAlias) + isNotEmpty + " and " - + this.trimSql(pFields[i+1], pAlias) + isNotNull + " then " + pFields[i] + pSeparator + " else " + pFields[i] + " end" + " else " + blank + " end "; - else - retstr += " case when " + pFields[i] + isNotEmpty + " then " + pFields[i] + " else " + blank + " end "; - } - return retstr; - } - /** - * builds a condition out of multiple conditions - * - * @param {Array} pArray req Array containing the conditions - * @param {String} pOperator req Operator that concatenates the conditions (AND/OR) - * - * @return {String} concatenated Condition - */ - this.concatConditions = function(pArray, pOperator) - { - var resultCondition = ""; - - for(var i = 0; i < pArray.length; i++) - { - if(pArray[i] != null && pArray[i] != '') - { - if(resultCondition.length > 0) - resultCondition += (" " + pOperator + " "); - - resultCondition += pArray[i]; - } - } - - return resultCondition; - } - /** - * Checks if a new entry already exists - * - * @param {String} pTable req Databasetable(z.B. "comm") - * @param {Array} pColumns req colums, like sqlInsert - * @param {Array} pTypes req die datatypes, like sqlInsert - * @param {Array} pValues req values, like sqlInsert - * @param {Array} pExcludeFields opt columns, that should not be checked - * @param {String} pAlias opt Database alias - * - * @return {Integer} - */ - this.isDuplicat = function(pTable, pColumns, pTypes, pValues, pExcludeFields, pAlias) - { - var col = new Array(); - var typ = new Array(); - var val = new Array(); - var excludefields = ["DATE_NEW" ,"DATE_EDIT" ,"USER_NEW" ,"USER_EDIT", "KEYVALUE", "KEYSORT", pTable.toUpperCase() + "ID"]; - - if(pExcludeFields != undefined) - excludefields = excludefields.concat(pExcludeFields); - if(pAlias == undefined) - pAlias = vars.getString("$sys.dbalias"); - - for(var i = 0; i < pColumns.length; i++) - { - if(!hasElement(excludefields, pColumns[i], true) && pValues[i] != "" && pTypes[i] != SQLTYPES.LONGVARCHAR && pTypes[i] != SQLTYPES.CLOB) - { - col.push(pColumns[i]); - typ.push(pTypes[i]); - val.push(pValues[i]); - } - } - var count = db.getRowCount(pTable, col, typ, val, pAlias); - return count; - } - /** - * returns the trim function depending on the database - * - * @param {String} pField field the should be trimmed - * @param {String} pAlias opt database alias - * - * @return {String} - */ - this.trimSql = function(pField, pAlias) - { - if(pAlias == undefined || pAlias == "") - pAlias = vars.getString("$sys.dbalias"); - - var dbtype = db.getDatabaseType(pAlias); - var string; - - switch(Number(dbtype)) - { - case db.DBTYPE_SQLSERVER2000: - string = "ltrim(rtrim(" + pField + "))"; - break; - default: - string = "trim(" + pField + ")" - break; - } - return string; - } - /** - * returns the day of a date - * - * @param {Datetime} pDate req the date - * @param {String} pAlias req database alias - * - * @return {String} - */ - this.dayFromDate = function(pDate, pAlias) - { - var usedAlias = pAlias; - - if(pAlias == undefined) - usedAlias = vars.getString("$sys.dbalias"); - - var dbAlias = db.getDatabaseType(usedAlias); - var string = ""; - - switch(Number(dbAlias)) - { - case db.DBTYPE_ORACLE10_CLUSTER: - case db.DBTYPE_ORACLE10_THIN: - case db.DBTYPE_ORACLE10_OCI: - string = "to_char(" + pDate + ",'dd')"; - break; - case db.DBTYPE_DERBY10: - case db.DBTYPE_SQLSERVER2000: - case db.DBTYPE_MYSQL4: - case db.DBTYPE_MARIADB10: - string = "DAY(" + pDate + ")"; - break; - case db.DBTYPE_POSTGRESQL8: - string = "EXTRACT (DAY from " + pDate + ")"; - break; - } - return string; - } - /** - * returns the month of a date - * - * @param {Datetime} pDate req the date - * @param {String} pAlias req database alias - * - * @return {String} - */ - this.monthFromDate = function(pDate, pAlias) - { - var usedAlias = pAlias; - if(pAlias == undefined) - usedAlias = vars.getString("$sys.dbalias"); - - var dbAlias = db.getDatabaseType(usedAlias); - var string = ""; - - switch(Number(dbAlias)) - { - case db.DBTYPE_ORACLE10_CLUSTER: - case db.DBTYPE_ORACLE10_THIN: - case db.DBTYPE_ORACLE10_OCI: - string = "to_char(" + pDate + ",'MM')"; - break; - case db.DBTYPE_DERBY10: - case db.DBTYPE_SQLSERVER2000: - case db.DBTYPE_MYSQL4: - case db.DBTYPE_MARIADB10: - string = "MONTH(" + pDate + ")"; - break; - case db.DBTYPE_POSTGRESQL8: - string = "EXTRACT (MONTH FROM " + pDate + ")"; - break; - } - return string; - } - /** - * returns the year of a date - * - * @param {Datetime} pDate req the date - * @param {String} pAlias req database alias - * - * @return {String} - */ - this.yearFromDate = function(pDate, pAlias) - { - var usedAlias = pAlias; - if(pAlias == undefined) - usedAlias = vars.getString("$sys.dbalias"); - - var dbAlias = db.getDatabaseType(usedAlias); - var string = ""; - - switch(Number(dbAlias)) - { - case db.DBTYPE_ORACLE10_CLUSTER: - case db.DBTYPE_ORACLE10_THIN: - case db.DBTYPE_ORACLE10_OCI: - string = "to_char(" + pDate + ",'yyyy')"; - break; - case db.DBTYPE_DERBY10: - case db.DBTYPE_SQLSERVER2000: - case db.DBTYPE_MYSQL4: - case db.DBTYPE_MARIADB10: - string = "YEAR(" + pDate + ")"; - break; - case db.DBTYPE_POSTGRESQL8: - string = "EXTRACT (YEAR FROM " + pDate + ")"; - break; - } - return string; - } - /** - * returns the function for current date depending on database - * - * @return {String} expression - */ - this.currentDate = function() - { - var dbtype = db.getDatabaseType(vars.getString("$sys.dbalias")); - var expression = ""; - - switch (Number(dbtype)) - { - case db.DBTYPE_ORACLE10_CLUSTER: - case db.DBTYPE_ORACLE10_THIN: - case db.DBTYPE_ORACLE10_OCI: - case db.DBTYPE_DERBY10: - expression = "CURRENT_DATE"; - break; - case db.DBTYPE_SQLSERVER2000: - expression = "GETDATE()"; - break; - case db.DBTYPE_MYSQL4: - case db.DBTYPE_MARIADB10: - expression = "NOW()"; - break; - } - return expression; - } - /** - * returns the current search string incl placeholders - * - * @param {String} pfield req the search field - * @param {String} pfind req the search string - * @param {String} pIgnoreCase opt (true/false) - * @param {String} pPlaceHolder opt (Platzhalter config) - * - * @return {String} - */ - this.getPlacerholderCondition = function( pfield, pfind, pIgnoreCase, pPlaceHolder ) - { - var user = tools.getCurrentUser(); - var IgCa; - var PlHo; - - //wenn optoinal IgnoreCase und PlaceHolder vorhanden, dann diese verwenden - if(pIgnoreCase != undefined) - IgCa = pIgnoreCase; - else - IgCa = user[tools.PARAMS][tools.SELECTION_IGNORECASE]; - - if(pPlaceHolder != undefined) - PlHo = pPlaceHolder; - else - PlHo = user[tools.PARAMS][tools.SELECTION_PLACEHOLDER]; - - if ( pfind ) - { - pfind = pfind.replace( new RegExp("\\'", "g"), "''"); - pfind = pfind.replace( new RegExp("\\*", "g"), "%"); - var ic = (IgCa == "true" ? "UPPER" : ""); - var cond = ""; - switch( PlHo ) - { - case "1": - cond = ic + "(" + pfield + ") like " + ic + "('" + pfind + "%')"; - break; - case "2": - cond = ic + "(" + pfield + ") like " + ic + "('%" + pfind + "')"; - break; - case "3": - cond = ic + "(" + pfield + ") like " + ic + "('%" + pfind + "%')"; - break; - case "4": - cond = ic + "(" + pfield + ") like " + ic + "('" + pfind + "')"; - break; - default: - cond = ic + "(" + pfield + ") = " + ic + "('" + pfind + "')"; - break; - } - } - return cond; - } - /** - * returns SQLSystax for a date. - * - * @param {String} pColumn req Column name - * @param {String} pAlias opt Database alias - * @param {Boolean} pWithTime opt if true, then add time hh:mm:ss - * @param {String} pFormat opt type of the format, e.g. yyyy-MM-dd; possible placeholers are: -dd -MM -yyyy - * - *@return {String} sqlstr, fully functional select for sql for different types of databases - */ - this.getSqlFormattedDate = function(pColumn, pAlias, pWithTime, pFormat) - { - if(pAlias == undefined) - pAlias = vars.getString("$sys.dbalias"); - - var pDatabaseType = db.getDatabaseType(pAlias); - - if (pFormat == undefined) - pFormat = translate.text("yyyy-MM-dd"); - - var str = ""; - - switch(Number(pDatabaseType)) - { - case db.DBTYPE_SQLSERVER2000: - day = "right('0' + cast(day(" + pColumn + ") as varchar(2)) , 2)"; - month = "right('0' + cast(month(" + pColumn + ") as varchar(2)) , 2)"; - year = "cast(year(" + pColumn + ") as char(4))"; - time = pWithTime == true? that.concat(["' '","cast(cast(" + pColumn + " as time) as char(8))"], "", pAlias) : ""; - break; - case db.DBTYPE_POSTGRESQL8: - day = "extract(day from " + pColumn + ")"; - month = "extract(month from " + pColumn + ")"; - year = "extract(year from " + pColumn + ")"; - time = pWithTime == true? that.concat(["' '","extract(time from" + pColumn + ")"], "", pAlias) : ""; - break; - case db.DBTYPE_DERBY10: - case db.DBTYPE_MYSQL4: - case db.DBTYPE_MARIADB10: - // concat will try to have a leading blank space if the number is => 10. This is why we had to use substr. - day = that.substring(that.concat(["case when day(" + pColumn + ") <= 9 then '00' else '0' end " - , " trim(cast(day(" + pColumn + ") as char(2)))"], "", pAlias), 2, 2, pAlias); - month = that.substring(that.concat(["case when month(" + pColumn + ") <= 9 then '00' else '0' end " - , "trim(cast(month(" + pColumn + ") as char(2)))"], "", pAlias), 2, 2, pAlias); - year = "trim(cast(year(" + pColumn + ") as char(4)))"; - time = pWithTime == true? that.concat(["cast(' ' as char(1))", "trim(cast(time(" + pColumn + ") as char(8)))"], "", pAlias) : ""; - break; - case db.DBTYPE_ORACLE10_CLUSTER: - case db.DBTYPE_ORACLE10_OCI: - case db.DBTYPE_ORACLE10_THIN: - - day = "to_char(" + pColumn + ", 'dd') "; - month = "to_char(" + pColumn + ", 'MM') "; - year = "to_char(" + pColumn + ", 'yyyy') "; - time = pWithTime == true ? " to_char(" + pColumn + ", ' hh24:mi:ss')" : ""; - break; - default: - str = "cast(" + pColumn + " as varchar (10))"; - return str; - break; - } - - var re = /(dd)|(MM)|(yyyy)/g // Regex to check the date - var matchResult; - var endOfLastMatch = 0; - var res = []; - - while ((matchResult = re.exec(pFormat)) !== null) - { - if( endOfLastMatch != matchResult.index) - { - res.push("'" + db.quote(pFormat.substring(endOfLastMatch, matchResult.index), pAlias) + "'"); // making sure we get the correct amount of quotations - } - switch(matchResult[0]) - { - case "dd": - res.push(day); - break; - case "MM": - res.push(month); - break; - case "yyyy": - res.push(year); - break; - } - endOfLastMatch = re.lastIndex; - } - // making sure we get the correct amount of quotations - // allows us to add custom strings behind the format which will be shown in the output - // e.g. "yyyy-MM-dd 00:00", "date: MM/dd/yyyy" - res.push("'" + db.quote(pFormat.slice(endOfLastMatch), pAlias) + "'"); - - if(time != "") - res.push(time); - - str = concat(res, "", pAlias); - - return str; - } - /** - * returns a SQL operator depending on an integer value, i.e. $local.operator - * - * @param {int} pVal - * - * @return {string} - */ - this.getSQLOperator = function(pVal) - { - var retval = ""; - switch(Number(pVal)) - { - case 1: - retval = "="; - break; //equals - case 2: - retval = "<>"; - break; //not equal - case 3: - retval = ">"; - break; //greater - case 4: - retval = "<"; - break; //lesser - case 5: - retval = "<="; - break; //lesser or equal - case 6: - retval = ">="; - break; //greater or equal - case 7: - retval = "like"; - break; //contains - case 8: - retval = "not like"; - break; //contains not - case 9: - retval = ""; - break; - case 10: - retval = ""; - break; - case 11: - retval = "is not null"; - break; - case 12: - retval = "is null"; - break; - } - return retval; - } - /** - * returns the function which determines the length of binary data, depending on db type - * - * @param {String} pField name of the checked field - * - * @return {String} - */ - this.binDataLength = function(pField) - { - var dbtype = db.getDatabaseType(vars.getString("$sys.dbalias")); - var length; - - switch(Number(dbtype)) - { - case db.DBTYPE_MARIADB10: - case db.DBTYPE_MYSQL4: - case db.DBTYPE_ORACLE10_CLUSTER: - case db.DBTYPE_ORACLE10_THIN: - case db.DBTYPE_ORACLE10_OCI: - case db.DBTYPE_POSTGRESQL8: - case db.DBTYPE_DERBY10: - length = "LENGTH("+pField+")"; - break; - case db.DBTYPE_SQLSERVER2000: - length = "DATALENGTH("+pField+")"; - break; - } - return length; - } - /** - * returns the function for replacing a null value - * - * @param {String} pValue req - * @param {String} pReplaceWith - * - * @return {string} - */ - this.isNull = function(pValue, pReplaceWith) - { - var pAlias = vars.getString("$sys.dbalias"); - - if(pReplaceWith == undefined) - pReplaceWith = 0; - - var pDatabaseType = db.getDatabaseType(pAlias); - - switch(Number(pDatabaseType)) - { - case db.DBTYPE_SQLSERVER2000: - str = "isnull("+ pValue +", " + pReplaceWith+ ")"; - break; - case db.DBTYPE_ORACLE10_CLUSTER: - case db.DBTYPE_ORACLE10_OCI: - case db.DBTYPE_ORACLE10_THIN : - str = "NVL("+ pValue +", "+ pReplaceWith+ ")"; - break; - case db.DBTYPE_POSTGRESQL8: - case db.DBTYPE_DERBY10: - case db.DBTYPE_MYSQL4: - case db.DBTYPE_MARIADB10: - default: - str = "COALESCE("+ pValue +", "+ pReplaceWith+ ")"; - break; - } - return str; - } - /** - * returns the concat symbol depending on database type - * - * @param {String} pAlias opt database alias - * - * @return {String} Concat Symbol - */ - this.getConcatSymbol = function(pAlias) - { - if(pAlias == undefined || pAlias == "") - pAlias = vars.getString("$sys.dbalias"); - - var dbtype = db.getDatabaseType(pAlias); - var concatSymbol = " "; - - switch(Number(dbtype)) - { - case db.DBTYPE_SQLSERVER2000: - concatSymbol = " + "; - break; - case db.DBTYPE_MARIADB10: - case db.DBTYPE_MYSQL4: - case db.DBTYPE_ORACLE10_CLUSTER: - case db.DBTYPE_ORACLE10_THIN: - case db.DBTYPE_ORACLE10_OCI: - case db.DBTYPE_POSTGRESQL8: - case db.DBTYPE_DERBY10: - default: - concatSymbol = " || "; - break; - } - - return concatSymbol; - } - /** - * 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.RELATIONID - * @param {String[]|String[][]} pData req Data as ID Array - * @param {String} pQuoteSymbol opt symbol for quoting values, - * Strings i.e.: ' default is no symbol - * - * @return {String} SQL condition: where VALS in (1,2,3) - */ - this.getSqlInStatement = function(pFieldname, pData, pQuoteSymbol) - { - if (pData.length == 0) - return " 1 = 2 "; - - var res = ""; - var qs = pQuoteSymbol || ""; - - var MAX_COUNT = 1000; - //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 - //<= verwenden, da bei einer Länge von "126" der Vorgang einmal ausgeführt werden soll - for (var i = 0; i <= count; i++) - { - if (i > 0) - res += "or "; - - res += pFieldname + " in (" + qs + pData.slice(i * MAX_COUNT, i * MAX_COUNT + MAX_COUNT) - .join(qs + ", " + qs) + qs + ") "; - } - - //wenn mehrere Zeilen mit "or" verknüpft wurden nochmal klammern - if (count > 0) - res = "(" + res + ")"; - - return res; - } - /** - * Setzt eine Condition zusammen und liefert sie zurück - * builds a conditions and returns it - * - * @param {Object} pValue req Filtervalue - * @param {String} pCondition req variable in which the condition should be written - * @param {String} pWhere req additional condition - * @param {Integer} pSQLType opt SQLTYPES type of pValue - * @param {Array} pPreparedValues opt Value for the condition, if it's a prepared statement - * - * @return {String} - */ - this.makeCondition = function( pValue, pCondition, pWhere, pSQLType, pPreparedValues) - { - if ( pValue != "" ) - { - if ( pCondition != "" ) - pCondition += " and "; - - pCondition += pWhere; - - if(pPreparedValues != undefined) - { - pPreparedValues.push([pValue, pSQLType]); - } - } - return pCondition; - } - /** - * returns a type of column in the database - * - * @param {String} pTableName req name of a table (e.g. "EVENT") OR if pColumnName is not passed table.column (e.g. "EVENT.STATUS") - * @param {String} pColumnName opt name of column (e.g. "STATUS") if in pTableName only tablename is passed - * @param {String} pAlias opt Alias to the database where the type should be loaded; default is current alias - * - * @return {String} type of column such as SQLTYPES.xyz - */ - this.getSingleColumnType = function(pTableName, pColumnName, pAlias) - { - if (pColumnName == undefined) - { - pColumnName = pTableName.substring(pTableName.indexOf(".") + 1); - pTableName = pTableName.substring(0, pTableName.indexOf(".")); - } - if (pAlias == undefined) - pAlias = db.getCurrentAlias(); - - return db.getColumnTypes(pTableName, [pColumnName], 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} pSql req 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 req Amount of records that shall be read per block. (you need to specify an ORDER BY in your SQL-query) - * "0" <=> all records - * @param {Object (function)} pCallback req 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 opt Database-Aliasname, where the SQL-Statement shall be executed; default is the current dbalias - * @param {Number} pTimeout opt Timeout in milliseconds; When it's reached the SQL-Statement will abort; default is in PREFERENCES configured - * @param {Number} pStartOffset opt Position where to begin with the data-reading-process; default is 0 - * - * - * @return {bool} 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 ORG"; - * var blockSize = 5 * 1000; - * - * var allRows = +db.cell("select count(*) from ORG"); - * - * sqlPageData(sql, blockSize, function (pData, pRunNo){ - * var j = pData.length;//pData is the current block with data - * logging.log(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); - * logging.log("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; - * logging.log("count:" + count);//you cannot overwrite a variable of 'sqlPageData' by accident - * }); - * - * logging.show(letValues);//contains orgnames - * logging.show(varValues);//contains orgnames - */ - this.sqlPageData = function(pSql, pBlockSize, pCallback, pDbAlias, pTimeout, pStartOffset) - { - if (pDbAlias == undefined) - pDbAlias = db.getCurrentAlias(); - - if (pStartOffset == undefined) - pStartOffset = 0; - - let count = 0; - while (pStartOffset > -1) - { - let data; - if (pTimeout == undefined) - data = db.tablePage(pSql, pDbAlias, pStartOffset, pBlockSize); - else - data = db.tablePage(pSql, pDbAlias, pStartOffset, pBlockSize, pTimeout); - - pStartOffset += pBlockSize; - - //this happens when all-records % pBlockSize == 0 - //we do not want to call the callback-fn - if (data.length == 0) - return true; - else if (data.length < pBlockSize || pBlockSize == 0)//blocksize 0 is everything - pStartOffset = -1;//call callback the last time - - if (pCallback.call(this, data, ++count) === false) - return false;//callback can return false to manually stop the paging-process - - } - return true; - } +import("system.translate"); +import("system.vars"); +import("system.db"); +import("system.datetime"); +import("system.tools"); +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 conditions + * @example //TODO: add missing example + */ +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; +} +/** + * append with SQL-and; no paranthesize of existing conditions is done + * @param {String} cond the condition string which shall be appended + * @return {Object} current SqlCondition-object + */ +SqlCondition.prototype.and = function(cond){ + if (!cond) + return this; + if (this._sqlStorage) + this._sqlStorage += " and "; + this._sqlStorage += cond; + return this; +} + +/** + * same as the "and"-function but with preparedStatement functionality + * @param {String} field the database field as "tablename.columnname"; e.g. "ORG.NAME" + * @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 {String} value the value that shall be set into the prepared statement + * @param {Numeric} [fieldType] SQL-column-type; if the fieldType is not given it's loaded automatically; + * please note that this can be a performace issue if it happens a lot of times (e.g. in aloop) + * @return {Object} current SqlCondition-object + */ +SqlCondition.prototype.andPrepare = function(field, cond, value, fieldType){ + cond = this._prepare(field, cond, value, fieldType); + return this.and(cond); +} + +/** + * append with SQL-or; Also paranthesize the existing conditions + * @param {String} cond the condition string which shall be appended + * @return {Object} current SqlCondition-object + */ +SqlCondition.prototype.or = function(cond){ + if (!cond) + return this; + if (this._sqlStorage) + this._sqlStorage = "(" + this._sqlStorage + ") or (" + cond + ")"; + else + this._sqlStorage = cond; + return this; +} + +/** + * same as the "or"-function but with preparedStatement functionality + * @param {String} field the database field as "tablename.columnname"; e.g. "ORG.NAME" + * @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 {String} value the value that shall be set into the prepared statement + * @param {Numeric} [fieldType] SQL-column-type; if the fieldType is not given it's loaded automatically; + * please note that this can be a performace issue if it happens a lot of times (e.g. in aloop) + * @return {Object} current SqlCondition-object + */ +SqlCondition.prototype.orPrepare = function(field, cond, value, fieldType){ + cond = this._prepare(field, cond, value, fieldType); + return this.or(cond); +} +/** + * 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._sqlStorage && 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 {String} 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; +} + +/** + * hidden function for composing preparedStatements + * @param {String} field the database field as "tablename.columnname"; e.g. "ORG.NAME" + * @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 {String} value the value that shall be set into the prepared statement + * @param {Numeric} [fieldType] SQL-column-type; if the fieldType is not given it's loaded automatically; + * please note that this can be a performace issue if it happens a lot of times (e.g. in aloop) + * @return {String} the replaced SQL-condition string (replace # by the fieldname) + */ +SqlCondition.prototype._prepare = function(field, cond, value, fieldType){ + var type; + //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(field, {"$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 || SqlUtils.getSingleColumnType(field, undefined, this.alias); + 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 null; +} + +/** + * hidden function for initializing all properties for the sql conditions + * @return {null} + */ +SqlCondition.prototype._init = function(){ + //init only wraps the clear function to avoid confusion in the constructor (and provide better extensibility) + return this.clear(); +} +/** + provides functions for masking sql functions +* +* @param {String} [alias=currentAlias] database alias, you can specify null if you have no alias available and you can manually set the dbType property +*/ +function SqlMaskingUtils(alias){ + this.alias = null; + Object.defineProperty(this, "alias", { + set: function(v){ + this._alias = v; + if (v != null) + this._dbType = db.getDatabaseType(this._alias); + }, + get: function (){ + return this._alias; + } + }); + this.dbType = null; + Object.defineProperty(this, "dbType", { + set: function(v){ + this._alias = null; + this._dbType = v; + }, + get: function (){ + return this._dbType; + } + }); + + if (alias === undefined) + this.alias = vars.getString("$sys.dbalias"); + else + this.alias = alias; +} + +/** +* returns the trim function depending on the database behin the given alias +* note that this function does not verifiy where the types of your expression are trimable or not +* +* @param {String} field expression that shall be trimmed +* +* @return {String} +*/ +SqlMaskingUtils.prototype.trim = function(field) { + var dbType, resultStr; + dbType = this.dbType; + switch(dbType) { + case db.DBTYPE_SQLSERVER2000: + resultStr = "ltrim(rtrim(" + field + "))"; + break; + default: + resultStr = "trim(" + field + ")" + break; + } + return resultStr; +} + + +/** + * masks the function cast of standard sql + * please note that this function does not do any validation if it's possible to cast the expression's datatype you pass to the function in every supported DBMS + * + * @param {String} field name of the database field that shall be castet + * @param {String} [targetDatatype] a SQLTYPES-value of the following: SQLTYPES.CHAR, SQLTYPES.VARCHAR, SQLTYPES.INTEGER, + * SQLTYPES.DECIMAL, SQLTYPES.DATE + * @param {int|int[]} targetLength specifies the length of the target data type; + * <br/>- char/varchar: length + * <br/>- decimal: [length, decimals] + * + * @return {String} sql part to be included in sql-statements + */ +SqlMaskingUtils.prototype.cast = function (field, targetDatatype, targetLength){ + //TODO: use callbacks for different handling? + /* Some informations if you want to add supported databaseTypes or dataTypes: + * You should consider using the _mapDefaults function-expression (details in the functions doc) + * However you shouldn't use the function in a "default"-Block of a switch-case because of the following behaviour: + * If a datatype is not supported you just have to NOT specify "sqlDataType" (leave it "undefined") -> an error is then raised + * Therefore you should explicitly define which Data-type is supported and which is not + */ + var dbType, functionName, sqlPart, sqlDataType, _mapDefaults; + dbType = this.dbType; + functionName = "cast";//overwrite this in the "switch (dbType)" if needed with your DBMS + + /** + * handles default-scenarios for mapping input-targetDatatype to a string for a sql-statement + * e.g. SQLTYPES.INTEGER --> int + * @param {Number} dataType input as a value of "SQLTYPES." that will be mapped to a string + * @return {String} the mapped dataType for using in a sql-statement + */ + _mapDefaults = function (dataType){ + var res; + switch(dataType) { + case SQLTYPES.CHAR: + res = "char"; + break; + case SQLTYPES.VARCHAR: + res = "char"; + break; + case SQLTYPES.INTEGER: + res = "int"; + break; + case SQLTYPES.DECIMAL: + res = "decimal"; + break; + case SQLTYPES.DATE: + res = "date"; + break; + } + return res; + } + + switch (dbType) { + case db.DBTYPE_DERBY10: + switch(targetDatatype) { + case SQLTYPES.VARCHAR: + // Because of a Derby bug, you can't cast INTEGER into VARCHAR + // Therefor first cast to char then to varchar + // https://issues.apache.org/jira/browse/DERBY-2072 + field = "rtrim(" + this.cast(field, SQLTYPES.CHAR, targetLength) + ")"; + sqlDataType = "varchar"; + break; + case SQLTYPES.CHAR: + //TODO: throw error if(targetLength > 254)? https://db.apache.org/derby/docs/10.14/ref/rrefsqlj13733.html + sqlDataType = "char"; + break; + case SQLTYPES.DECIMAL: + case SQLTYPES.INTEGER: + case SQLTYPES.DATE: + sqlDataType = _mapDefaults(dataType); + break; + } + break; + case db.DBTYPE_MARIADB10: + case db.DBTYPE_MYSQL4: + switch(targetDatatype) { + case SQLTYPES.VARCHAR: + case SQLTYPES.CHAR: + case SQLTYPES.INTEGER: + case SQLTYPES.DECIMAL: + case SQLTYPES.DATE: + sqlDataType = _mapDefaults(targetDatatype); + break; + } + break; + case db.DBTYPE_ORACLE10_CLUSTER: + case db.DBTYPE_ORACLE10_THIN: + case db.DBTYPE_ORACLE10_OCI: + switch(targetDatatype) + { + case SQLTYPES.VARCHAR: + datatype = "varchar2"; + break; + case SQLTYPES.INTEGER: + datatype = "number"; + targetLength = "10" + break; + case SQLTYPES.CHAR: + case SQLTYPES.DECIMAL: + case SQLTYPES.DATE: + sqlDataType = _mapDefaults(targetDatatype); + break; + } + break; + case db.DBTYPE_POSTGRESQL8: + switch(targetDatatype) + { + case SQLTYPES.DATE: + case SQLTYPES.DECIMAL: + case SQLTYPES.INTEGER: + case SQLTYPES.CHAR: + case SQLTYPES.VARCHAR: + sqlDataType = _mapDefaults(targetDatatype); + break; + } + break; + case db.DBTYPE_SQLSERVER2000: + case SQLTYPES.DATE: + case SQLTYPES.DECIMAL: + case SQLTYPES.INTEGER: + case SQLTYPES.CHAR: + case SQLTYPES.VARCHAR: + sqlDataType = _mapDefaults(targetDatatype); + break; + case db.DBTYPE_FIREBIRD250: + //TODO: firebird support? + break; + } + + if (sqlDataType == undefined) { + throw new Error("sqlDataType");//TODO: add usefull message + } + + if(targetLength == undefined) + targetLength = ""; + else if(targetLength != "") + { + if(typeof(targetLength == "object") && (targetLength instanceof Array)) + targetLength = "(" + targetLength.join(", ") + ")"; + else + targetLength = "(" + targetLength + ")"; + } + + sqlPart = functionName + "(" + field + " as " + sqlDataType + targetLength + ")"; + return sqlPart; +} + +/** + * masks the cast function for lob datatypes(clob, blob) into varchar or similar + * + * @param {String} field expression that shall be casted + * @param {Integer|Interger[]} targetLength dessired length of the datatype + * decimal: [length, decimals] + * + * @return {String} part of sql-expression that can be used + */ +SqlMaskingUtils.prototype.castLob = function(field, targetLength) { + var res; + switch(this.dbType) { + case db.DBTYPE_ORACLE10_CLUSTER: + case db.DBTYPE_ORACLE10_THIN: + case db.DBTYPE_ORACLE10_OCI: + res = "DBMS_LOB.SUBSTR(" + field + ", " + targetLength + ", 1)"; + break; + default: + res = this.cast(field, "varchar", targetLength); + break; + } + return res; +} + +/** + * masks the sql function substring + * + * @param {String } field the expression that shall be substringed + * @param {Number} start posistion where the substring starts + * @param {Number} length amount of characters of the expression will be returned by the sql function + * + * @return {String} part of sql-expression that can be used for substringing + */ +SqlMaskingUtils.prototype.substring = function(field, start, length){ + var sqlFnName; + + switch(this.dbType) + { + case db.DBTYPE_ORACLE10_CLUSTER: + case db.DBTYPE_ORACLE10_THIN: + case db.DBTYPE_ORACLE10_OCI: + sqlFnName = "substr"; + break; + case db.DBTYPE_DERBY10: + sqlFnName = "substr"; + break; + case db.DBTYPE_POSTGRESQL8: + sqlFnName = "substr"; + break; + case db.DBTYPE_SQLSERVER2000: + sqlFnName = "substring"; + break; + case db.DBTYPE_MYSQL4: + case db.DBTYPE_MARIADB10: + sqlFnName = "substring"; + break; + default: + throw new Error();//ToDo: add message + } + + return sqlFnName + "(" + field + ", " + start + ", " + length + ")"; +} + + +/** +* masks the function concat +* if a sql field is empty no separator will be added +* note that this function will often create a lot of sql-code +* +* @param {Array} fields req fields (or expressions) that should be concatenated +* @param {String} [separatorCharacter=space-character] character for separating the fields; warning: the character will not be quoted +* @param {String} [autoTrimFields=true] autoTrimFields if true the expressions are always trimmed, false no change will be applied +* +* @return {String} part of SQL-querey +*/ +SqlMaskingUtils.prototype.concat = function(fields, separatorCharacter, autoTrimFields) { + var i, concatSql, retSql, isNotEmptyStrSql, isNotNullSql, separatorSql, _isNotEmpty, _trimIfAutoTrimEnabled; + if (fields.length == 0) + return "''"; + else if (fields.length == 1) + return fields[0]; + concatSql = " || "; + isNotEmptyStrSql = " != '' "; + isNotNullSql = " is not null "; + separatorSql = separatorCharacter == undefined ? " " : separatorCharacter; + retSql = ""; + if (autoTrimFields == undefined) + autoTrimFields = true; + + switch(this.dbType) + { + case db.DBTYPE_MYSQL4: + case db.DBTYPE_MARIADB10: + retSql = " concat_ws( '" + separatorSql + "'"; + for (i = 0; i < fields.length; i++) { + retSql += ", " + fields[i]; + } + return retSql + ") "; + break; + case db.DBTYPE_ORACLE10_CLUSTER: + case db.DBTYPE_ORACLE10_THIN: + case db.DBTYPE_ORACLE10_OCI: + isNotEmptyStrSql = null; //empty strings are changed to DB-null-values internally in oracle; by specifing JS-null we disable this check + break; + case db.DBTYPE_SQLSERVER2000: + //MS SQL Server supports "concat_ws" (and ignoring null values) from version SQL Server 2017 and newer: + //https://docs.microsoft.com/de-de/sql/t-sql/functions/concat-ws-transact-sql?view=sql-server-2017 + concatSql = " + "; + break; + case db.DBTYPE_DERBY10: + break; + default: + throw new Error();//TODO: add Message + } + separatorSql = concatSql + "'" + separatorSql + "'"; + _trimIfAutoTrimEnabled = function(f){ + if (autoTrimFields) + return this.trim(f); + else + return f; + } + _trimIfAutoTrimEnabled = _trimIfAutoTrimEnabled.bind(this); + _isNotEmpty = function(f){ + return _trimIfAutoTrimEnabled(f) + isNotEmptyStrSql + " and " + f + isNotNullSql; + } + _isNotEmpty = _isNotEmpty.bind(this); + + for (i = 0; i < fields.length; i++) { + if (retSql != "") + retSql += concatSql; + retSql += "case when " + _isNotEmpty(fields[i]) + " then "; + if ( i < fields.length - 1 ) //Prüfen, ob ein nachfolgender Wert kommt, sonst braucht man keinen Separator + retSql += " case when " + _isNotEmpty(fields[i + 1]) + " then " + _trimIfAutoTrimEnabled(fields[i]) + separatorSql + " else " + _trimIfAutoTrimEnabled(fields[i]) + " end "; + else + retSql += _trimIfAutoTrimEnabled(fields[i]); + retSql += " else '' end "; + } + return retSql; +} + +/** + * returns the function for replacing a null value + * + * @param {String} field expression that shall be checked for a null value + * @param {String} [replaceWith=empty string] expression that shall be used if the field contains null + * + * @return {string} + */ +SqlMaskingUtils.prototype.isNull = function(field, replaceWith) { + var retSql; + + if (replaceWith == undefined) + replaceWith = "''"; + switch(this.dbType) { + case db.DBTYPE_SQLSERVER2000: + retSql = "isnull(" + field + ", " + replaceWith + ")"; + break; + case db.DBTYPE_ORACLE10_CLUSTER: + case db.DBTYPE_ORACLE10_OCI: + case db.DBTYPE_ORACLE10_THIN : + retSql = "nvl(" + field + ", " + replaceWith + ")"; + break; + case db.DBTYPE_POSTGRESQL8: + case db.DBTYPE_DERBY10: + case db.DBTYPE_MYSQL4: + case db.DBTYPE_MARIADB10: + default: + retSql = "coalesce(" + field + ", " + replaceWith + ")"; + break; + } + return retSql; +} + +function SqlUtils(){ +} + +SqlUtils.getSingleColumnType = function(fieldOrTableName, columnName, alias) { + var tableName, fieldVarType; + if (columnName == undefined){ + fieldVarType = typeof(fieldOrTableName); + if (fieldVarType == "string") + fieldOrTableName = text.split(fieldOrTableName, "\\."); + else if (fieldVarType != "object"){ + throw new TypeError();//TODO: add message + } + + if (fieldOrTableName.hasOwnProperty("length") && fieldOrTableName.length == 2) + { + tableName = fieldOrTableName[0]; + columnName = fieldOrTableName[1]; + } + else + throw new TypeError();//TODO: add message + } + else + tableName = fieldOrTableName; + + if (typeof(columnName) != "string") + throw new TypeError();//TODO: add message + if (typeof(tableName) != "string") + throw new TypeError();//TODO: add message + + if (alias == undefined) + alias = db.getCurrentAlias(); + + return db.getColumnTypes(tableName, [columnName], alias)[0]; +} + +/** + *Class containing utilities for SQL + *@deprecated use SqlMaskingUtils + *@class + */ +function LegacySqlUtils() +{ + var that = this; + /** + * builds a condition out of multiple conditions + * + * @param {Array} pArray req Array containing the conditions + * @param {String} pOperator req Operator that concatenates the conditions (AND/OR) + * + * @return {String} concatenated Condition + */ + this.concatConditions = function(pArray, pOperator) + { + var resultCondition = ""; + + for(var i = 0; i < pArray.length; i++) + { + if(pArray[i] != null && pArray[i] != '') + { + if(resultCondition.length > 0) + resultCondition += (" " + pOperator + " "); + + resultCondition += pArray[i]; + } + } + + return resultCondition; + } + /** + * Checks if a new entry already exists + * + * @param {String} pTable req Databasetable(z.B. "comm") + * @param {Array} pColumns req colums, like sqlInsert + * @param {Array} pTypes req die datatypes, like sqlInsert + * @param {Array} pValues req values, like sqlInsert + * @param {Array} pExcludeFields opt columns, that should not be checked + * @param {String} pAlias opt Database alias + * + * @return {Integer} + */ + this.isDuplicat = function(pTable, pColumns, pTypes, pValues, pExcludeFields, pAlias) + { + var col = new Array(); + var typ = new Array(); + var val = new Array(); + var excludefields = ["DATE_NEW" ,"DATE_EDIT" ,"USER_NEW" ,"USER_EDIT", "KEYVALUE", "KEYSORT", pTable.toUpperCase() + "ID"]; + + if(pExcludeFields != undefined) + excludefields = excludefields.concat(pExcludeFields); + if(pAlias == undefined) + pAlias = vars.getString("$sys.dbalias"); + + for(var i = 0; i < pColumns.length; i++) + { + if(!hasElement(excludefields, pColumns[i], true) && pValues[i] != "" && pTypes[i] != SQLTYPES.LONGVARCHAR && pTypes[i] != SQLTYPES.CLOB) + { + col.push(pColumns[i]); + typ.push(pTypes[i]); + val.push(pValues[i]); + } + } + var count = db.getRowCount(pTable, col, typ, val, pAlias); + return count; + } + + /** + * returns the day of a date + * + * @param {Datetime} pDate req the date + * @param {String} pAlias req database alias + * + * @return {String} + */ + this.dayFromDate = function(pDate, pAlias) + { + var usedAlias = pAlias; + + if(pAlias == undefined) + usedAlias = vars.getString("$sys.dbalias"); + + var dbAlias = db.getDatabaseType(usedAlias); + var string = ""; + + switch(Number(dbAlias)) + { + case db.DBTYPE_ORACLE10_CLUSTER: + case db.DBTYPE_ORACLE10_THIN: + case db.DBTYPE_ORACLE10_OCI: + string = "to_char(" + pDate + ",'dd')"; + break; + case db.DBTYPE_DERBY10: + case db.DBTYPE_SQLSERVER2000: + case db.DBTYPE_MYSQL4: + case db.DBTYPE_MARIADB10: + string = "DAY(" + pDate + ")"; + break; + case db.DBTYPE_POSTGRESQL8: + string = "EXTRACT (DAY from " + pDate + ")"; + break; + } + return string; + } + /** + * returns the month of a date + * + * @param {Datetime} pDate req the date + * @param {String} pAlias req database alias + * + * @return {String} + */ + this.monthFromDate = function(pDate, pAlias) + { + var usedAlias = pAlias; + if(pAlias == undefined) + usedAlias = vars.getString("$sys.dbalias"); + + var dbAlias = db.getDatabaseType(usedAlias); + var string = ""; + + switch(Number(dbAlias)) + { + case db.DBTYPE_ORACLE10_CLUSTER: + case db.DBTYPE_ORACLE10_THIN: + case db.DBTYPE_ORACLE10_OCI: + string = "to_char(" + pDate + ",'MM')"; + break; + case db.DBTYPE_DERBY10: + case db.DBTYPE_SQLSERVER2000: + case db.DBTYPE_MYSQL4: + case db.DBTYPE_MARIADB10: + string = "MONTH(" + pDate + ")"; + break; + case db.DBTYPE_POSTGRESQL8: + string = "EXTRACT (MONTH FROM " + pDate + ")"; + break; + } + return string; + } + /** + * returns the year of a date + * + * @param {Datetime} pDate req the date + * @param {String} pAlias req database alias + * + * @return {String} + */ + this.yearFromDate = function(pDate, pAlias) + { + var usedAlias = pAlias; + if(pAlias == undefined) + usedAlias = vars.getString("$sys.dbalias"); + + var dbAlias = db.getDatabaseType(usedAlias); + var string = ""; + + switch(Number(dbAlias)) + { + case db.DBTYPE_ORACLE10_CLUSTER: + case db.DBTYPE_ORACLE10_THIN: + case db.DBTYPE_ORACLE10_OCI: + string = "to_char(" + pDate + ",'yyyy')"; + break; + case db.DBTYPE_DERBY10: + case db.DBTYPE_SQLSERVER2000: + case db.DBTYPE_MYSQL4: + case db.DBTYPE_MARIADB10: + string = "YEAR(" + pDate + ")"; + break; + case db.DBTYPE_POSTGRESQL8: + string = "EXTRACT (YEAR FROM " + pDate + ")"; + break; + } + return string; + } + /** + * returns the function for current date depending on database + * + * @return {String} expression + */ + this.currentDate = function() + { + var dbtype = db.getDatabaseType(vars.getString("$sys.dbalias")); + var expression = ""; + + switch (Number(dbtype)) + { + case db.DBTYPE_ORACLE10_CLUSTER: + case db.DBTYPE_ORACLE10_THIN: + case db.DBTYPE_ORACLE10_OCI: + case db.DBTYPE_DERBY10: + expression = "CURRENT_DATE"; + break; + case db.DBTYPE_SQLSERVER2000: + expression = "GETDATE()"; + break; + case db.DBTYPE_MYSQL4: + case db.DBTYPE_MARIADB10: + expression = "NOW()"; + break; + } + return expression; + } + /** + * returns the current search string incl placeholders + * + * @param {String} pfield req the search field + * @param {String} pfind req the search string + * @param {String} pIgnoreCase opt (true/false) + * @param {String} pPlaceHolder opt (Platzhalter config) + * + * @return {String} + */ + this.getPlacerholderCondition = function( pfield, pfind, pIgnoreCase, pPlaceHolder ) + { + var user = tools.getCurrentUser(); + var IgCa; + var PlHo; + + //wenn optoinal IgnoreCase und PlaceHolder vorhanden, dann diese verwenden + if(pIgnoreCase != undefined) + IgCa = pIgnoreCase; + else + IgCa = user[tools.PARAMS][tools.SELECTION_IGNORECASE]; + + if(pPlaceHolder != undefined) + PlHo = pPlaceHolder; + else + PlHo = user[tools.PARAMS][tools.SELECTION_PLACEHOLDER]; + + if ( pfind ) + { + pfind = pfind.replace( new RegExp("\\'", "g"), "''"); + pfind = pfind.replace( new RegExp("\\*", "g"), "%"); + var ic = (IgCa == "true" ? "UPPER" : ""); + var cond = ""; + switch( PlHo ) + { + case "1": + cond = ic + "(" + pfield + ") like " + ic + "('" + pfind + "%')"; + break; + case "2": + cond = ic + "(" + pfield + ") like " + ic + "('%" + pfind + "')"; + break; + case "3": + cond = ic + "(" + pfield + ") like " + ic + "('%" + pfind + "%')"; + break; + case "4": + cond = ic + "(" + pfield + ") like " + ic + "('" + pfind + "')"; + break; + default: + cond = ic + "(" + pfield + ") = " + ic + "('" + pfind + "')"; + break; + } + } + return cond; + } + /** + * returns SQLSystax for a date. + * + * @param {String} pColumn req Column name + * @param {String} pAlias opt Database alias + * @param {Boolean} pWithTime opt if true, then add time hh:mm:ss + * @param {String} pFormat opt type of the format, e.g. yyyy-MM-dd; possible placeholers are: -dd -MM -yyyy + * + *@return {String} sqlstr, fully functional select for sql for different types of databases + */ + this.getSqlFormattedDate = function(pColumn, pAlias, pWithTime, pFormat) + { + if(pAlias == undefined) + pAlias = vars.getString("$sys.dbalias"); + + var pDatabaseType = db.getDatabaseType(pAlias); + + if (pFormat == undefined) + pFormat = translate.text("yyyy-MM-dd"); + + var str = ""; + + switch(Number(pDatabaseType)) + { + case db.DBTYPE_SQLSERVER2000: + day = "right('0' + cast(day(" + pColumn + ") as varchar(2)) , 2)"; + month = "right('0' + cast(month(" + pColumn + ") as varchar(2)) , 2)"; + year = "cast(year(" + pColumn + ") as char(4))"; + time = pWithTime == true? that.concat(["' '","cast(cast(" + pColumn + " as time) as char(8))"], "", pAlias) : ""; + break; + case db.DBTYPE_POSTGRESQL8: + day = "extract(day from " + pColumn + ")"; + month = "extract(month from " + pColumn + ")"; + year = "extract(year from " + pColumn + ")"; + time = pWithTime == true? that.concat(["' '","extract(time from" + pColumn + ")"], "", pAlias) : ""; + break; + case db.DBTYPE_DERBY10: + case db.DBTYPE_MYSQL4: + case db.DBTYPE_MARIADB10: + // concat will try to have a leading blank space if the number is => 10. This is why we had to use substr. + day = that.substring(that.concat(["case when day(" + pColumn + ") <= 9 then '00' else '0' end " + , " trim(cast(day(" + pColumn + ") as char(2)))"], "", pAlias), 2, 2, pAlias); + month = that.substring(that.concat(["case when month(" + pColumn + ") <= 9 then '00' else '0' end " + , "trim(cast(month(" + pColumn + ") as char(2)))"], "", pAlias), 2, 2, pAlias); + year = "trim(cast(year(" + pColumn + ") as char(4)))"; + time = pWithTime == true? that.concat(["cast(' ' as char(1))", "trim(cast(time(" + pColumn + ") as char(8)))"], "", pAlias) : ""; + break; + case db.DBTYPE_ORACLE10_CLUSTER: + case db.DBTYPE_ORACLE10_OCI: + case db.DBTYPE_ORACLE10_THIN: + + day = "to_char(" + pColumn + ", 'dd') "; + month = "to_char(" + pColumn + ", 'MM') "; + year = "to_char(" + pColumn + ", 'yyyy') "; + time = pWithTime == true ? " to_char(" + pColumn + ", ' hh24:mi:ss')" : ""; + break; + default: + str = "cast(" + pColumn + " as varchar (10))"; + return str; + break; + } + + var re = /(dd)|(MM)|(yyyy)/g // Regex to check the date + var matchResult; + var endOfLastMatch = 0; + var res = []; + + while ((matchResult = re.exec(pFormat)) !== null) + { + if( endOfLastMatch != matchResult.index) + { + res.push("'" + db.quote(pFormat.substring(endOfLastMatch, matchResult.index), pAlias) + "'"); // making sure we get the correct amount of quotations + } + switch(matchResult[0]) + { + case "dd": + res.push(day); + break; + case "MM": + res.push(month); + break; + case "yyyy": + res.push(year); + break; + } + endOfLastMatch = re.lastIndex; + } + // making sure we get the correct amount of quotations + // allows us to add custom strings behind the format which will be shown in the output + // e.g. "yyyy-MM-dd 00:00", "date: MM/dd/yyyy" + res.push("'" + db.quote(pFormat.slice(endOfLastMatch), pAlias) + "'"); + + if(time != "") + res.push(time); + + str = concat(res, "", pAlias); + + return str; + } + /** + * returns a SQL operator depending on an integer value, i.e. $local.operator + * + * @param {int} pVal + * + * @return {string} + */ + this.getSQLOperator = function(pVal) + { + var retval = ""; + switch(Number(pVal)) + { + case 1: + retval = "="; + break; //equals + case 2: + retval = "<>"; + break; //not equal + case 3: + retval = ">"; + break; //greater + case 4: + retval = "<"; + break; //lesser + case 5: + retval = "<="; + break; //lesser or equal + case 6: + retval = ">="; + break; //greater or equal + case 7: + retval = "like"; + break; //contains + case 8: + retval = "not like"; + break; //contains not + case 9: + retval = ""; + break; + case 10: + retval = ""; + break; + case 11: + retval = "is not null"; + break; + case 12: + retval = "is null"; + break; + } + return retval; + } + /** + * returns the function which determines the length of binary data, depending on db type + * + * @param {String} pField name of the checked field + * + * @return {String} + */ + this.binDataLength = function(pField) + { + var dbtype = db.getDatabaseType(vars.getString("$sys.dbalias")); + var length; + + switch(Number(dbtype)) + { + case db.DBTYPE_MARIADB10: + case db.DBTYPE_MYSQL4: + case db.DBTYPE_ORACLE10_CLUSTER: + case db.DBTYPE_ORACLE10_THIN: + case db.DBTYPE_ORACLE10_OCI: + case db.DBTYPE_POSTGRESQL8: + case db.DBTYPE_DERBY10: + length = "LENGTH("+pField+")"; + break; + case db.DBTYPE_SQLSERVER2000: + length = "DATALENGTH("+pField+")"; + break; + } + return length; + } + + /** + * returns the concat symbol depending on database type + * + * @param {String} pAlias opt database alias + * + * @return {String} Concat Symbol + */ + this.getConcatSymbol = function(pAlias) + { + if(pAlias == undefined || pAlias == "") + pAlias = vars.getString("$sys.dbalias"); + + var dbtype = db.getDatabaseType(pAlias); + var concatSymbol = " "; + + switch(Number(dbtype)) + { + case db.DBTYPE_SQLSERVER2000: + concatSymbol = " + "; + break; + case db.DBTYPE_MARIADB10: + case db.DBTYPE_MYSQL4: + case db.DBTYPE_ORACLE10_CLUSTER: + case db.DBTYPE_ORACLE10_THIN: + case db.DBTYPE_ORACLE10_OCI: + case db.DBTYPE_POSTGRESQL8: + case db.DBTYPE_DERBY10: + default: + concatSymbol = " || "; + break; + } + + return concatSymbol; + } + /** + * 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.RELATIONID + * @param {String[]|String[][]} pData req Data as ID Array + * @param {String} pQuoteSymbol opt symbol for quoting values, + * Strings i.e.: ' default is no symbol + * + * @return {String} SQL condition: where VALS in (1,2,3) + */ + this.getSqlInStatement = function(pFieldname, pData, pQuoteSymbol) + { + if (pData.length == 0) + return " 1 = 2 "; + + var res = ""; + var qs = pQuoteSymbol || ""; + + var MAX_COUNT = 1000; + //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 + //<= verwenden, da bei einer Länge von "126" der Vorgang einmal ausgeführt werden soll + for (var i = 0; i <= count; i++) + { + if (i > 0) + res += "or "; + + res += pFieldname + " in (" + qs + pData.slice(i * MAX_COUNT, i * MAX_COUNT + MAX_COUNT) + .join(qs + ", " + qs) + qs + ") "; + } + + //wenn mehrere Zeilen mit "or" verknüpft wurden nochmal klammern + if (count > 0) + res = "(" + res + ")"; + + return res; + } + /** + * Setzt eine Condition zusammen und liefert sie zurück + * builds a conditions and returns it + * + * @param {Object} pValue req Filtervalue + * @param {String} pCondition req variable in which the condition should be written + * @param {String} pWhere req additional condition + * @param {Integer} pSQLType opt SQLTYPES type of pValue + * @param {Array} pPreparedValues opt Value for the condition, if it's a prepared statement + * + * @return {String} + */ + this.makeCondition = function( pValue, pCondition, pWhere, pSQLType, pPreparedValues) + { + if ( pValue != "" ) + { + if ( pCondition != "" ) + pCondition += " and "; + + pCondition += pWhere; + + if(pPreparedValues != undefined) + { + pPreparedValues.push([pValue, pSQLType]); + } + } + return pCondition; + } + /** + * returns a type of column in the database + * + * @param {String} pTableName req name of a table (e.g. "EVENT") OR if pColumnName is not passed table.column (e.g. "EVENT.STATUS") + * @param {String} pColumnName opt name of column (e.g. "STATUS") if in pTableName only tablename is passed + * @param {String} pAlias opt Alias to the database where the type should be loaded; default is current alias + * + * @return {String} type of column such as SQLTYPES.xyz + */ + this.getSingleColumnType = function(pTableName, pColumnName, pAlias) + { + if (pColumnName == undefined) + { + pColumnName = pTableName.substring(pTableName.indexOf(".") + 1); + pTableName = pTableName.substring(0, pTableName.indexOf(".")); + } + if (pAlias == undefined) + pAlias = db.getCurrentAlias(); + + return db.getColumnTypes(pTableName, [pColumnName], 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} pSql req 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 req Amount of records that shall be read per block. (you need to specify an ORDER BY in your SQL-query) + * "0" <=> all records + * @param {Object (function)} pCallback req 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 opt Database-Aliasname, where the SQL-Statement shall be executed; default is the current dbalias + * @param {Number} pTimeout opt Timeout in milliseconds; When it's reached the SQL-Statement will abort; default is in PREFERENCES configured + * @param {Number} pStartOffset opt Position where to begin with the data-reading-process; default is 0 + * + * + * @return {bool} 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 ORG"; + * var blockSize = 5 * 1000; + * + * var allRows = +db.cell("select count(*) from ORG"); + * + * sqlPageData(sql, blockSize, function (pData, pRunNo){ + * var j = pData.length;//pData is the current block with data + * logging.log(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); + * logging.log("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; + * logging.log("count:" + count);//you cannot overwrite a variable of 'sqlPageData' by accident + * }); + * + * logging.show(letValues);//contains orgnames + * logging.show(varValues);//contains orgnames + */ + this.sqlPageData = function(pSql, pBlockSize, pCallback, pDbAlias, pTimeout, pStartOffset) + { + if (pDbAlias == undefined) + pDbAlias = db.getCurrentAlias(); + + if (pStartOffset == undefined) + pStartOffset = 0; + + let count = 0; + while (pStartOffset > -1) + { + let data; + if (pTimeout == undefined) + data = db.tablePage(pSql, pDbAlias, pStartOffset, pBlockSize); + else + data = db.tablePage(pSql, pDbAlias, pStartOffset, pBlockSize, pTimeout); + + pStartOffset += pBlockSize; + + //this happens when all-records % pBlockSize == 0 + //we do not want to call the callback-fn + if (data.length == 0) + return true; + else if (data.length < pBlockSize || pBlockSize == 0)//blocksize 0 is everything + pStartOffset = -1;//call callback the last time + + if (pCallback.call(this, data, ++count) === false) + return false;//callback can return false to manually stop the paging-process + + } + return true; + } } \ No newline at end of file -- GitLab