diff --git a/process/JditoFilter_lib/process.js b/process/JditoFilter_lib/process.js index 28657e5ceeca29d86881a28abcb28979095b4fe0..b318e287639603ed313d77088be28239a1b7a6e1 100644 --- a/process/JditoFilter_lib/process.js +++ b/process/JditoFilter_lib/process.js @@ -214,16 +214,163 @@ JditoFilterUtils.filterRecords = function (pColumns, pRecords, pFilter, pCustomC */ JditoFilterUtils.getSqlCondition = function (pFilter, pTable, pTableAlias, pColumnOrFnMap) { - var condition = newWhere(); + var filterTranslator = new FilterSqlTranslator(pFilter) + .table(pTable, pTableAlias); - var ignoreCase = JditoFilterUtils.isUserIgnoreCase(); - - if (!pFilter) + if (pColumnOrFnMap) + { + for (let fieldName in pColumnOrFnMap) + { + var columnOrFn = pColumnOrFnMap[fieldName]; + if (typeof columnOrFn === "function") + filterTranslator.addSpecialFieldConditionFn(fieldName, columnOrFn); + else + filterTranslator.addSqlFieldMapping(fieldName, columnOrFn); + } + } + return filterTranslator.getSqlCondition(); +} + +/** + * @return {boolean} the selectionIgnoreCase property of the current user, defaults to true + */ +JditoFilterUtils.isUserIgnoreCase = function () +{ + var user = tools.getCurrentUser(); + var ignoreCase = user ? user[tools.PARAMS][tools.SELECTION_IGNORECASE] : ""; + return ignoreCase == "" || /true/i.test(ignoreCase); +} + +/** + * Object for translating a filter object to a sql condition. + * + * @param {Object} pFilter the filter object that should be used + * @param {String} pTable the database table to build the condition for + */ +function FilterSqlTranslator (pFilter, pTable) +{ + this._filter = null; + this.filter(pFilter); + this._table = pTable; + this._tableAlias = null; + this._dbAlias = null; + this._sqlFieldMappings = {}; + this._fieldConditionFns = {}; + this._ignoreCase = JditoFilterUtils.isUserIgnoreCase(); +} + +/** + * Sets the filter of the object + * + * @param {Object} pFilter the filter object that should be used + * @return {FilterSqlTranslator} current object + */ +FilterSqlTranslator.prototype.filter = function (pFilter) +{ + if (pFilter) + { + if (typeof pFilter !== "object") + throw new TypeError("FilterSqlTranslator: Wrong type for the filter, expected 'object' but got '" + (typeof pFilter) + "'"); + this._filter = pFilter.filter || pFilter; + } + return this; +} + +/** + * Sets the filter of the object + * + * @param {String} pFilter the filter object that should be used as JSON string + * @return {FilterSqlTranslator} current object + */ +FilterSqlTranslator.prototype.filterJSON = function (pFilter) +{ + return this.filter(JSON.parse(pFilter)); +} + +/** + * Sets the table of the object + * + * @param {String} pTable the database table to build the condition for + * @param {String} [pTableAlias] the alias of the table + * @return {FilterSqlTranslator} current object + */ +FilterSqlTranslator.prototype.table = function (pTable, pTableAlias) +{ + this._table = pTable; + if (pTableAlias) + this._tableAlias = pTableAlias; + return this; +} + +/** + * Adds a special database field mapping for the given field that will be used for the sql condition + * + * @param {String} pFieldName the field name + * @param {String|String[]} the database field ("TABLE.COLUMN" or ["TABLE", "COLUMN", "alias"]) + * @return {FilterSqlTranslator} current object + */ +FilterSqlTranslator.prototype.addSqlFieldMapping = function (pFieldName, pDBField) +{ + this._sqlFieldMappings[pFieldName] = pDBField; + return this; +} + +/** + * Adds a special function for building the condition for the given field. The function must return a sql condition (SqlBuilder or String) + * + * @param {String} pFieldName the field name + * @param {Function} pConditionFn a function that generates the condition + * @return {FilterSqlTranslator} current object + */ +FilterSqlTranslator.prototype.addSpecialFieldConditionFn = function (pFieldName, pConditionFn) +{ + this._fieldConditionFns[pFieldName] = pConditionFn; + return this; +} + +/** + * Sets the database alias + * + * @param {String} pAlias the alias to be used + * @return {FilterSqlTranslator} current object + */ +FilterSqlTranslator.prototype.dbAlias = function (pAlias) +{ + this._dbAlias = pAlias; + return this; +} + +/** + * Changes whether the condition should be case-insensitive for text + * + * @param {boolean} [pIgnoreCase=true] if it should be case-insensitive + * @return {FilterSqlTranslator} current object + */ +FilterSqlTranslator.prototype.ignoreCase = function (pIgnoreCase) +{ + //"", 0 , false -> false, everything else is considered true + this._ignoreCase = pIgnoreCase != false; + return this; +} + +/** + * Builds the sql condition from the filter + * + * @return {SqlBuilder} the sql condition + */ +FilterSqlTranslator.prototype.getSqlCondition = function () +{ + var condition = new SqlBuilder(this._dbAlias).where(); + if (!this._filter) return condition; - if (!pColumnOrFnMap) - pColumnOrFnMap = {}; - _addCondition.call(condition, pFilter, pFilter.operator); + var table = this._table; + var tableAlias = this._tableAlias; + var ignoreCase = this._ignoreCase; + var sqlFieldMappings = this._sqlFieldMappings; + var fieldConditionFns = this._fieldConditionFns; + + _addCondition.call(condition, this._filter, this._filter.operator); return condition; @@ -233,53 +380,55 @@ JditoFilterUtils.getSqlCondition = function (pFilter, pTable, pTableAlias, pColu { if (pFilter.type == "row") { - var sqlField; - if (pFilter.name in pColumnOrFnMap) + var sqlField, condition; + var filterValue = (pFilter.key || pFilter.value); + if (pFilter.name in fieldConditionFns) { - sqlField = pColumnOrFnMap[pFilter.name]; + var conditionFn = fieldConditionFns[pFilter.name]; + + condition = conditionFn.call(null, filterValue, pFilter.operator); + if (pOperator == "AND") + this.andIfSet(condition); + else if (pOperator == "OR") + this.orIfSet(condition); + + return; + } + + if (pFilter.name in sqlFieldMappings) + { + sqlField = sqlFieldMappings[sqlField]; //possibility to explicitly set the value to null/false so that the field is ignored if (sqlField === null || sqlField === false) return; } - else if (pTable && pTableAlias) - sqlField = [pTable, pFilter.name, pTableAlias]; - else if (pTable) - sqlField = pTable + "." + pFilter.name; + else if (table && tableAlias) + sqlField = [table, pFilter.name, tableAlias]; + else if (table) + sqlField = table + "." + pFilter.name; - var filterValue = (pFilter.key || pFilter.value); - - var condition; - if (typeof(sqlField) === "function") + var generatedCondition = _getCondition(filterValue, pFilter.operator, sqlField); + if (generatedCondition instanceof SqlBuilder || typeof generatedCondition === "string") { - condition = sqlField.call(null, filterValue, pFilter.operator); if (pOperator == "AND") - this.andIfSet(condition); + this.andIfSet(generatedCondition); else if (pOperator == "OR") - this.orIfSet(condition); + this.orIfSet(generatedCondition); } else { - var generatedCondition = _getCondition(filterValue, pFilter.operator, sqlField); - if (generatedCondition instanceof SqlBuilder) - { - if (pOperator == "AND") - this.andIfSet(generatedCondition); - else if (pOperator == "OR") - this.orIfSet(generatedCondition); - } - else - { - var isStringType; - [condition, filterValue, isStringType] = generatedCondition; - if (isStringType && ignoreCase) - condition = condition.replace("#", "UPPER(#)").replace("?", "UPPER(?)"); + var isStringType = pFilter.contenttype != "NUMBER" + && pFilter.contenttype != "DATE" + && pFilter.contenttype != "BOOLEAN"; + [condition, filterValue] = generatedCondition; + if (isStringType && ignoreCase) + condition = condition.replace("#", "UPPER(#)").replace("?", "UPPER(?)"); - if (pOperator == "AND") - this.andIfSet(sqlField, filterValue, condition); - else if (pOperator == "OR") - this.orIfSet(sqlField, filterValue, condition); - } + if (pOperator == "AND") + this.andIfSet(sqlField, filterValue, condition); + else if (pOperator == "OR") + this.orIfSet(sqlField, filterValue, condition); } } else if (pFilter.type == "group") @@ -297,53 +446,41 @@ JditoFilterUtils.getSqlCondition = function (pFilter, pTable, pTableAlias, pColu } } - //returns [condition, value with wildcards, is a string type] depending on the operator + //returns [condition, value with wildcards] depending on the operator function _getCondition (pValue, pOperator, pField) { switch (pOperator) { case "CONTAINS": - return [SqlBuilder.LIKE(), "%" + pValue + "%", true]; + return [SqlBuilder.LIKE(), "%" + pValue + "%"]; case "CONTAINSNOT": - return [SqlBuilder.NOT_LIKE(), "%" + pValue + "%", true]; + return [SqlBuilder.NOT_LIKE(), "%" + pValue + "%"]; case "STARTSWITH": - return [SqlBuilder.LIKE(), pValue + "%", true]; + return [SqlBuilder.LIKE(), pValue + "%"]; case "ENDSWITH": - return [SqlBuilder.LIKE(), "%" + pValue, true]; + return [SqlBuilder.LIKE(), "%" + pValue]; case "EQUAL": - return [SqlBuilder.EQUAL(), pValue, true]; + return [SqlBuilder.EQUAL(), pValue]; case "NOT_EQUAL": - return [SqlBuilder.NOT_EQUAL(), pValue, true]; + return [SqlBuilder.NOT_EQUAL(), pValue]; case "LESS": - return [SqlBuilder.LESS(), pValue, false]; + return [SqlBuilder.LESS(), pValue]; case "LESS_OR_EQUAL": - return [SqlBuilder.LESS_OR_EQUAL(), pValue, false]; + return [SqlBuilder.LESS_OR_EQUAL(), pValue]; case "GREATER": - return [SqlBuilder.GREATER(), pValue, false]; + return [SqlBuilder.GREATER(), pValue]; case "GREATER_OR_EQUAL": - return [SqlBuilder.GREATER_OR_EQUAL(), pValue, false]; + return [SqlBuilder.GREATER_OR_EQUAL(), pValue]; case "ISNULL": - return ["# is null", pValue, false]; + return pField + " is null"; case "ISNOTNULL": - return ["# is not null", pValue, false]; + return pField + " is not null"; case "TIMEFRAME_EQUAL": case "TIMEFRAME_COMING": case "TIMEFRAME_PAST": var [start, end] = datetime.resolveRelativeDateExpression(pValue); return newWhere(pField, start, SqlBuilder.GREATER_OR_EQUAL()) .and(pField, end, SqlBuilder.LESS_OR_EQUAL()); - - } } -} - -/** - * @return {boolean} the selectionIgnoreCase property of the current user, defaults to true - */ -JditoFilterUtils.isUserIgnoreCase = function () -{ - var user = tools.getCurrentUser(); - var ignoreCase = user ? user[tools.PARAMS][tools.SELECTION_IGNORECASE] : ""; - return ignoreCase == "" || /true/i.test(ignoreCase); -} +} \ No newline at end of file