Something went wrong on our end
-
Andre Loreth authoredAndre Loreth authored
process.js 72.86 KiB
import("system.logging");
import("system.translate");
import("system.vars");
import("system.util");
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 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
*/
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"));
*/
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 db.translateCondition(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);
}
/**
* Object for building sqls. The main purpose of this is to make it
* possible to use SqlCondition objects inside join conditions or sub sqls.
* This can also be useful to build complex sqls where parts should be added
* dynamically while keeping the code clean.
*
* @class
*/
function SqlBuilder ()
{
if(!(this instanceof SqlBuilder))
throw new Error(translate.text("SqlBuilder must be instanciated with 'new'"));
this._sqlStr = "";
this._select = null;
this._from = null;
this._joins = [];
this._where = null;
this._groupBy = null;
this._having = null;
this._orderBy = null;
this._unions = [];
}
/**
* 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());
*/
SqlBuilder.begin = function ()
{
return new SqlBuilder();
}
/**
* Builds the sql and uses db.translateStatement to make a string out of it.
* @return {String} the sql as string
*/
SqlBuilder.prototype.toString = function ()
{
return db.translateStatement(this.build());
}
/**
* Sets the select clause of the sql.
* @param {String|String[]} pFields
* @return {SqlBuilder} current SqlBuilder object
*/
SqlBuilder.prototype.select = function (pFields)
{
this._select = this._getClause(pFields, "select", true);
return this;
}
/**
* Sets the select clause of the sql with distinct.
* @param {String|String[]} pFields
* @return {SqlBuilder} current SqlBuilder object
*/
SqlBuilder.prototype.selectDistinct = function (pFields)
{
this._select = this._getClause(pFields, "select distinct", true);
return this;
}
/**
* Sets the from clause of the sql.
* @param {String} pTable
* @param {String} [pAlias] table alias
* @return {SqlBuilder} current SqlBuilder object
*/
SqlBuilder.prototype.from = function (pTable, pAlias)
{
if (pAlias)
pTable += " " + pAlias;
this._from = this._getClause(pTable, "from");
return this;
}
/**
* Adds a join clause to the sql.
* @param {String} pTable
* @param {String|String[]|SqlCondition} pCondition The where condition. This can be
* a string (without the where keyword), a SqlCondition or an array (for prepared queries).
* @param {String} [pAlias] table alias
* @return {SqlBuilder} current SqlBuilder object
*/
SqlBuilder.prototype.join = function (pTable, pCondition, pAlias)
{
var joinStr = "join " + pTable;
if (pAlias)
joinStr += " " + pAlias;
this._joins.push(this._getClause(pCondition, joinStr + " on"));
return this;
}
/**
* Adds a left join clause to the sql.
* @param {String} pTable
* @param {String|String[]|SqlCondition} pCondition The where condition. This can be
* a string (without the where keyword), a SqlCondition or an array (for prepared queries).
* @param {String} [pAlias] table alias
* @return {SqlBuilder} current SqlBuilder object
*/
SqlBuilder.prototype.leftJoin = function (pTable, pCondition, pAlias)
{
var joinStr = "left join " + pTable;
if (pAlias)
joinStr += " " + pAlias;
this._joins.push(this._getClause(pCondition, joinStr + " on"));
return this;
}
/**
* Sets the where clause of the sql.
*
* @param {String|String[]|SqlCondition} pCondition The where condition. This can be
* a string (without the where keyword), a SqlCondition or an array (for prepared queries).
*
* @return {SqlBuilder} current SqlBuilder object
*/
SqlBuilder.prototype.where = function (pCondition)
{
this._where = this._getClause(pCondition, "where");
return this;
}
/**
* Sets the order by clause of the sql.
* @param {String} pOrderBy
* @return {SqlBuilder} current SqlBuilder object
*/
SqlBuilder.prototype.orderBy = function (pOrderBy)
{
this.orderBy = this._getClause(pOrderBy, "order by");
return this;
}
/**
* Sets the group by clause of the sql.
* @param {String|String[]} pFields
* @return {SqlBuilder} current SqlBuilder object
*/
SqlBuilder.prototype.groupBy = function (pFields)
{
this._groupBy = this._getClause(pFields, "group by", true);
return this;
}
/**
* Adds another SqlBuilder object or select string with union.
* @param {SqlBuilder|String} pSelect
* @return {SqlBuilder} current SqlBuilder object
*/
SqlBuilder.prototype.union = function (pSelect)
{
this._unions.push(this._getClause(pSelect, "union"));
return this;
}
/**
* Adds another SqlBuilder object or select string with union all.
* @param {SqlBuilder|String} pSelect
* @return {SqlBuilder} current SqlBuilder object
*/
SqlBuilder.prototype.unionAll = function (pSelect)
{
this._unions.push(this._getClause(pSelect, "union all"));
return this;
}
/**
* Adds a having clause to the sql.
*
* @param {String|String[]|SqlCondition} pCondition The where condition. This can be
* a string (without the where keyword), a SqlCondition or an array (for prepared queries).
*
* @return {SqlBuilder} current SqlBuilder object
*/
SqlBuilder.prototype.having = function (pCondition)
{
this._having = this._getClause(pCondition, "having");
return this;
}
/**
* 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
*/
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];
}
}
/**
* builds a prepared statement out of the object
*
* @return {String[]} prepared statement
*/
SqlBuilder.prototype.build = function ()
{
if (!this._select)
throw new Error(translate.text("SqlBuilder must contain a select clause!"));
if (!this._from)
throw new Error(translate.text("SqlBuilder must contain a from clause!"));
var sqlStr = "";
var preparedVals = [];
var allParts = [
this._select,
this._from
].concat(this._joins).concat([
this._where,
this._groupBy,
this._having,
this._orderBy
]).concat(this._unions);
for (let i = 0, l = allParts.length; i < l; i++)
{
let part = allParts[i];
if (part && part.length)
{
if (sqlStr)
sqlStr += " ";
sqlStr += part[0];
preparedVals = preparedVals.concat(part[1]);
}
}
return [sqlStr, preparedVals];
}
/**
* 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
*/
SqlBuilder.prototype.translate = function(pAlias)
{
return db.translateStatement(this.build(), pAlias);
}
/**
*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
*
* @class
*/
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;
//provide the possibility to just set dbType (e.g. for testing) with no association to an alias
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 concat symbol depending on database type
* @return {String} Concat Symbol
*/
SqlMaskingUtils.prototype.getConcatSymbol = function() {
var concatSymbol;
switch(Number(this.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;
}
/**
* 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;
}
/**
* returns the max-value sql expressions depending on the database behind the given alias
* note that this function does not verifiy if the field (and type) usage is valid at all
*
* @param {String} field expression
*
* @return {String} sql-part that can be used in a select
*/
SqlMaskingUtils.prototype.max = function(field)
{
return "max(" + field + ")";
}
/**
* returns the min-value sql expressions depending on the database behind the given alias
* note that this function does not verifiy if the field (and type) usage is valid at all
*
* @param {String} field expression
*
* @return {String} sql-part that can be used in a select
*/
SqlMaskingUtils.prototype.min = function(field)
{
return "min(" + field + ")";
}
/**
* 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
*
* Problems:
* Derby has problems with casting to CHAR({> 254}) https://db.apache.org/derby/docs/10.14/ref/rrefsqlj13733.html
*
* @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) {
/* 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:
sqlDataType = "char";
break;
case SQLTYPES.DECIMAL:
case SQLTYPES.INTEGER:
case SQLTYPES.DATE:
sqlDataType = _mapDefaults(targetDatatype);
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(translate.withArguments("${SQL_LIB_UNSUPPORTED_DBTYPE} function: %0", ["SqlMaskingUtils.prototype.cast._mapDefaults"]));
}
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, SQLTYPES.VARCHAR, targetLength);
break;
}
return res;
}
/**
* returns the function which determines the length of binary data
*
* @param {String} fieldName name of the checked field
*
* @return {String}
*/
SqlMaskingUtils.prototype.binDataLength = function(fieldName) {
var res;
switch(this.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:
res = "LENGTH(" + fieldName + ")";
break;
case db.DBTYPE_SQLSERVER2000:
res = "DATALENGTH(" + fieldName + ")";
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(translate.withArguments("${SQL_LIB_UNSUPPORTED_DBTYPE} function: %0", ["SqlMaskingUtils.prototype.substring"]));
}
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 = this.getConcatSymbol();
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:
case db.DBTYPE_POSTGRESQL8:
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
break;
case db.DBTYPE_DERBY10:
break;
default:
throw new Error(translate.withArguments("${SQL_LIB_UNSUPPORTED_DBTYPE} function: %0", ["SqlMaskingUtils.prototype.concat"]));
}
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]);
//this blank is used just as in the old concat function which means this concat function has the same (wrong) behaviour
//TODO: find way to fix the case when separator is not a whitepsace (e.g. space)
//this concat-function does not work properly if you concat [<<value>>, <<null>>, <<value>>] by comma
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;
}
/**
* gets the day from a timestamp
*
* @param {String} pField timestamp to get the day from
*
* @return {String} sql expression that extracts the day from a timestamp
*/
SqlMaskingUtils.prototype.dayFromDate = function(pField)
{
var retSql = "";
switch (this.dbType)
{
case db.DBTYPE_ORACLE10_CLUSTER:
case db.DBTYPE_ORACLE10_THIN:
case db.DBTYPE_ORACLE10_OCI:
retSql = "to_char(" + pField + ",'dd')";
break;
case db.DBTYPE_DERBY10:
case db.DBTYPE_SQLSERVER2000:
case db.DBTYPE_MYSQL4:
case db.DBTYPE_MARIADB10:
retSql = "DAY(" + pField + ")";
break;
case db.DBTYPE_POSTGRESQL8:
retSql = "EXTRACT (DAY from " + pField + ")";
break;
}
return retSql;
}
/**
* gets the month from a timestamp
*
* @param {String} pField timestamp to get the month from
*
* @return {String} sql expression that extracts the month from a timestamp
*/
SqlMaskingUtils.prototype.monthFromDate = function(pField)
{
var retSql = "";
switch (this.dbType)
{
case db.DBTYPE_ORACLE10_CLUSTER:
case db.DBTYPE_ORACLE10_THIN:
case db.DBTYPE_ORACLE10_OCI:
retSql = "to_char(" + pField + ",'MM')";
break;
case db.DBTYPE_DERBY10:
case db.DBTYPE_SQLSERVER2000:
case db.DBTYPE_MYSQL4:
case db.DBTYPE_MARIADB10:
retSql = "MONTH(" + pField + ")";
break;
case db.DBTYPE_POSTGRESQL8:
retSql = "EXTRACT (MONTH FROM " + pField + ")";
break;
}
return retSql;
}
/**
* gets the year from a timestamp
*
* @param {String} pField timestamp to get the year from
*
* @return {String} sql expression that extracts the year from a timestamp
*/
SqlMaskingUtils.prototype.yearFromDate = function(pField)
{
var retSql = "";
switch (this.dbType)
{
case db.DBTYPE_ORACLE10_CLUSTER:
case db.DBTYPE_ORACLE10_THIN:
case db.DBTYPE_ORACLE10_OCI:
retSql = "to_char(" + pField + ",'yyyy')";
break;
case db.DBTYPE_DERBY10:
case db.DBTYPE_SQLSERVER2000:
case db.DBTYPE_MYSQL4:
case db.DBTYPE_MARIADB10:
retSql = "YEAR(" + pField + ")";
break;
case db.DBTYPE_POSTGRESQL8:
retSql = "EXTRACT (YEAR FROM " + pField + ")";
break;
}
return retSql;
}
/**
* functions for various Sql-actions
* Do not create an instance of this!
*
* @class
* @static
*/
function SqlUtils() {}
/**
* parses given name of table and name of column to clearly find out the tablename and columnanme
*
* @param {String|Array} pFieldOrTableName you've got several possibilites to pass here:
* <br/> 1. the name of the table if also a pColumnName is specified
* <br/> 2. the name of the table and columname as "tablename.columnname" (e.g. "ORGANISATION.NAME") if no pColumnName is specified
* <br/> 3. an array with 2 elements: [tablename, columnname] (e.g. ["ORGANISATION", "NAME"]) if no pColumnName is specified
* <br/> Everything else will raise an error
*
* @param {String} [pColumnName] depending on pFieldOrTableName this should be undefined/null or the name of a column
*
* @return {Object|TypeError} TypeError if something wrong has been passed or returns a object with these properties:
* 1. "table" which is the tablename
* 2. "column" which is the columnname
* e.g. {table: "ORGANISATION", column: "NAME"}
*
*
*/
SqlUtils._parseFieldQualifier = function(pFieldOrTableName, pColumnName)
{
var tableName, columnName;
if (pColumnName != undefined)
{
tableName = pFieldOrTableName;
columnName = pColumnName;
}
else
{
var fnName = "SqlUtils._parseFieldQualifier";//for return errors
var fieldVarType = typeof(pFieldOrTableName);
if (fieldVarType == "string")
{
pFieldOrTableName = text.split(pFieldOrTableName, "\\.");
}
else if (fieldVarType != "object") //check for object since there exists JavaArrays and JavaScript arrays which are both valid
{
return new TypeError(translate.withArguments("[%0]%1 has to be a string or array but it is %2", [fnName, "pFieldOrTableName",
fieldVarType]));
}
if (pFieldOrTableName.hasOwnProperty("length"))
{
if (pFieldOrTableName.length != 2)
return new TypeError(translate.withArguments("[%0]has now an incorrect length; estimated 2 elements but got %1", [
fnName, pFieldOrTableName.length ]));
tableName = pFieldOrTableName[0];
columnName = pFieldOrTableName[1];
}
else //check for object happens since there exists JavaArrays and JavaScript arrays which are both valid
return new TypeError(translate.withArguments("[%0]%1 is an object but seems not to be a valid array or array-like", [
fnName, "pFieldOrTableName"]));
}
if (typeof(columnName) != "string")
return new TypeError(translate.withArguments("[%0]the columnName is not a string after interpreting", [fnName]));
if (typeof(tableName) != "string")
return new TypeError(translate.withArguments("[%0]the tableName is not a string after interpreting", [fnName]));
return {
table: tableName,
column: columnName
};
};
/**
* determines if given values match a full field qualifier (name of table and name of column)
*
* @param {String|Array} pFieldOrTableName you've got several possibilites to pass here:
* <br/> 1. the name of the table if also a pColumnName is specified
* <br/> 2. the name of the table and columname as "tablename.columnname" (e.g. "ORGANISATION.NAME") if no pColumnName is specified
* <br/> 3. an array with 2 elements: [tablename, columnname] (e.g. ["ORGANISATION", "NAME"]) if no pColumnName is specified
* <br/> Everything else will raise an error
*
* @param {String} [pColumnName] depending on pFieldOrTableName this should be undefined/null or the name of a column
*
* @return {Boolean} returns true if it's a full qualifier or false if not
*
*/
SqlUtils.isFullFieldQualifier = function(pFieldOrTableName, pColumnName)
{
var parsed = SqlUtils._parseFieldQualifier(pFieldOrTableName, pColumnName);
if (parsed instanceof TypeError)
return false;
return true;
};
/**
* determines the type of a single database column in a table; if you want to get several columntypes at once use db.getColumnTypes instead
*
* @param {String|Array} pFieldOrTableName you've got several possibilites to pass here:
* <br/> 1. the name of the table if also a pColumnName is specified
* <br/> 2. the name of the table and columname as "tablename.columnname" (e.g. "ORGANISATION.NAME") if no pColumnName is specified
* <br/> 3. an array with 2 elements: [tablename, columnname] (e.g. ["ORGANISATION", "NAME"]) if no pColumnName is specified
* <br/> Everything else will raise an error
*
* @param {String} [pColumnName] depending on pFieldOrTableName this should be undefined/null or the name of a column
* @param {String} [pAlias=the current alias] Database-Aliasname, where the SQL-Statement shall be executed; default is the current dbalias
*
* @throws TypeError if a wrong format is passed as table/column-combination
*
* @return {Number} returns the corresponding SQLTYPES-value
*
*/
SqlUtils.getSingleColumnType = function(pFieldOrTableName, pColumnName, pAlias) {
var fields = SqlUtils._parseFieldQualifier(pFieldOrTableName, pColumnName);
if (fields instanceof TypeError)
throw fields;
if (pAlias == undefined)
pAlias = db.getCurrentAlias();
return db.getColumnTypes(fields.table, [fields.column], pAlias)[0];
};
/**
* calls a given function for N blocks of sql-data as long as records are available or the paging-process is manually canceled
*
* @param {Object|String} sqlStatement the sql statement that shall be executed
* String: SQL-query in a simple text form
* Object: prepared-sql-query: [sqlStr, [[value1, type1], [valueN, typeN]]]
* @param {Number} blockSize Amount of records that shall be read per block. (you need to specify an ORDER BY in your SQL-query)
* "0" <=> all records
* @param {Function} callbackFn a callback-function that is called for every block and has the following params:
* myCallback(myDataBlockAs2Darray, myLoopCountThatStartsWith1)
* If "false" is returned sqlPageData will abort the paging process and return false
* @param {String} [dbAlias=the current alias] Database-Aliasname, where the SQL-Statement shall be executed; default is the current dbalias
* @param {Number} [timeout=configured dbTimeout in Preferences] Timeout in milliseconds; When it's reached the SQL-Statement will abort; default is in PREFERENCES configured
* @param {Number} [startOffset=0] Position where to begin with the data-reading-process; default is 0
*
*
* @return {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 ORGANISATION";
* var blockSize = 5 * 1000;
*
* var allRows = +db.cell("select count(*) from ORGANISATION");
*
* sqlPageData(sql, blockSize, function (pData, pRunNo){
* var j = pData.length;//pData is the current block with data
* logMsg(pRunNo.toString() + "#" + j);//pRunNo is the amount how often the func. has been already called
* //you can calculate the progress easily by: progress = (blockSize* (pRunNo-1) + pData.length) / (allRows - startOffset)
* //example in per cent:
* var startOffset = 0;//we did not pass any startOffset to sqlPageData - this is equivalent to zero
* var progress = (blockSize* (pRunNo-1) + pData.length) / (allRows - startOffset);
* logMsg("progess: " + eMath.roundDec(progress * 100, 2, eMath.ROUND_CEILING) + "%");
*
* for (var i = 0; i < j; i++)
* {
* varValues.push(pData[i][0]);
* letValues.push(pData[i][0]);
* }
*
* count += pRunNo * 100;
* logMsg("count:" + count);//you cannot overwrite a variable of 'sqlPageData' by accident
* });
*
* logging.show(letValues);//contains orgnames
* logging.show(varValues);//contains orgnames
*/
SqlUtils.pageTableData = function(sqlStatement, blockSize, callbackFn, dbAlias, timeout, startOffset) {
return SqlUtils._pageData(null, sqlStatement, blockSize, callbackFn, dbAlias, timeout, startOffset);
};
/**
* calls a given function for N blocks of sql-data as long as records are available or the paging-process is manually canceled
*
* @param {Object|String} sqlStatement the sql statement that shall be executed
* String: SQL-query in a simple text form
* Object: prepared-sql-query: [sqlStr, [[value1, type1], [valueN, typeN]]]
* @param {Number} blockSize Amount of records that shall be read per block. (you need to specify an ORDER BY in your SQL-query)
* "0" <=> all records
* @param {Function} callbackFn a callback-function that is called for every block and has the following params:
* myCallback(myColumnDataBlockAsArray, myLoopCountThatStartsWith1)
* If "false" is returned sqlPageData will abort the paging process and return false
* @param {String} [dbAlias=the current alias] Database-Aliasname, where the SQL-Statement shall be executed; default is the current dbalias
* @param {Number} [timeout=configured dbTimeout in Preferences] Timeout in milliseconds; When it's reached the SQL-Statement will abort; default is in PREFERENCES configured
* @param {Number} [startOffset=0] Position where to begin with the data-reading-process; default is 0
*
*
* @return {bool} returns whether the function read all available data or not:
* false if the callback-function returned false, otherwise true
*
* @example
* similar to sqlTablePageData -> take a look at the example there
*/
SqlUtils.pageColumnData = function(sqlStatement, blockSize, callbackFn, dbAlias, timeout, startOffset) {
return SqlUtils._pageData(db.COLUMN, sqlStatement, blockSize, callbackFn, dbAlias, timeout, startOffset);
};
//internal function for paging through data; for description take a look at sqlArrayPageData
SqlUtils._pageData = function(sqlType ,sqlStatement, blockSize, callbackFn, dbAlias, timeout, startOffset) {
if (dbAlias == undefined)
dbAlias = db.getCurrentAlias();
if (startOffset == undefined)
startOffset = 0;
var count = 0;
while (startOffset > -1) {
var data;
if (sqlType == null) {
if (timeout == undefined)
data = db.tablePage(sqlStatement, dbAlias, startOffset, blockSize);
else
data = db.tablePage(sqlStatement, dbAlias, startOffset, blockSize, timeout);
}
else {
if (timeout == undefined)
data = db.arrayPage(sqlType, sqlStatement, dbAlias, startOffset, blockSize);
else
data = db.arrayPage(sqlType, sqlStatement, dbAlias, startOffset, blockSize, timeout);
}
startOffset += blockSize;
//this happens when all-records % blockSize == 0
//we do not want to call the callback-fn
if (data.length == 0)
return true;
else if (data.length < blockSize || blockSize == 0)//blocksize 0 is everything
startOffset = -1;//call callback the last time
if (callbackFn.call(this, data, ++count) === false)
return false;//callback can return false to manually stop the paging-process
}
return true;
}
/**
* @return the alias for table asys_binaries
*/
SqlUtils.getBinariesAlias = function()
{
return SqlUtils.getSystemAlias();
}
/**
* @return the sytemalias
*/
SqlUtils.getSystemAlias = function()
{
return "_____SYSTEMALIAS";
}
/**
* Builds a SQL IN condition, while accounting for the 1000 elements maximum
* Single conditions are concatenated with OR, which can be devastating for performance!
*
* @param {String} pFieldname req name of the field with table alias
* z.B ORGREL.CONTACTID
* @param {String[]|String[][]} pData req Data as ID Array
* @param {String} [pQuoteSymbol=""] symbol for quoting values,
* Strings i.e.: ' default is no symbol
* @param {Boolean} [pAsPrepared=undefined] true if result should be returned as prepared condition
* @param {Boolean} [pPreparedDbType=undefined] if pAsPrepared is true, this param has to be filld with the correct db type
*
* @return {String|Array} SQL condition: where VALS in (1,2,3) OR as prepared Statement if pAsPrepared is true ["VALS in (1,2,3)", [...]
*/
SqlUtils.getSqlInStatement = function(pFieldname, pData, pQuoteSymbol, pAsPrepared, pPreparedDbType) {
if (pData.length == 0)
return " 1 = 2 ";
var res = "";
var qs = pQuoteSymbol || "";
var preparedValues;
if (pAsPrepared)
{
preparedValues = [];
if (!pPreparedDbType)
{
throw new Error(translate.text("SqlUtils.getSqlInStatement: if pAsPrepared is true, pPreparedDbType has to be filld with the correct db type"));
}
}
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 ";
if (pAsPrepared)
{
res += pFieldname + " in (";
var subData = pData.slice(i * MAX_COUNT, i * MAX_COUNT + MAX_COUNT);
subData.forEach(function(pVal, pIndex) {
res += "?";
preparedValues.push([pVal, pPreparedDbType])
if (pIndex != subData.length-1)
res += ", ";
});
res += ")"
}
else
{
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 + ")";
if (pAsPrepared)
return [res, preparedValues];
else
return res;
}
/**
* resolves key-value pairs (of strings) into a case when expression
*
* @param {String[][]} pKeyValueArray you've to pass a 2D-Array where each element has at pos0 the key and pos1 the value
* @param {String} pDbFieldName name fo the database field where the KEY-value is stored
* @param {String} [pLocale=current client language] specifies the locale for translating the title; can be false if nothing shalle be translated
*
* @return {String} a SQL-expression (case-when-statement) that resolves the KEYID into the title -> as preparedSatement-elements
*/
SqlUtils.getResolvingCaseWhen = function(pKeyValueArray, pDbFieldName, pLocale)
{
var keyData = pKeyValueArray;
if (keyData.length == 0)
return ["''", []];
//a helper function for easy translation
var translateValue = function(value){
if (pLocale === false)
return value;
else if (pLocale)
return translate.text(value, pLocale);
else
return translate.text(value);
};
var resSql = "case ", preparedValues = [];
var colTypeKeyId = SQLTYPES.CHAR;
var colTypeTitle = SQLTYPES.NVARCHAR;
for (var i = 0, l = keyData.length; i < l; i++)
{
var translatedTitle = translateValue(keyData[i][1]);
resSql += " when " + pDbFieldName + " = ? then ? "
preparedValues.push([keyData[i][0], colTypeKeyId]);
preparedValues.push([translatedTitle, colTypeTitle]);
}
resSql += " else '' end ";
resSql = [resSql, preparedValues];
return resSql;
};
/**
* Will quote all prepared statement values from the given statement.
* @param {[String, String[]]} pStatement Same as first paraemter of db.translateStatement.
* @param {([String, String[]]) => String} pExecutionCallback A function which must return the final SQL.
* @return The SQL, same as the result of db.translateStatement.
*/
SqlUtils.translateWithQuotes = function (pStatement, pExecutionCallback) {
// Validate type of incoming paramter.
if (!(pStatement instanceof Array))
return null;
// The second element of the array has to be an array.
if (!(pStatement[1] instanceof Array))
return null;
// As the second element represents the prepared statements we need to map it...
var preparedStatements = pStatement[1].map(function (pValue) {
// Just in case as a fallback value..
if (!(pValue instanceof Array))
return pValue;
// As the first element represents the value it will be quoted here.
return [db.quote(pValue[0]), pValue[1]];
});
return pExecutionCallback([pStatement[0], preparedStatements]);
}
/**
* Will quote all prepared statement values from the given statement.
* @param {[String, String[]]} pStatement Same as the first parameter of db.translateStatement.
* @returns {String} The SQL, same as the result of db.translateStatement.
*/
SqlUtils.translateStatementWithQuotes = function (pStatement) {
return SqlUtils.translateWithQuotes(pStatement, db.translateStatement);
}
/**
* Will quote all prepared statement values from the given statement.
* @param {[String, String[]]} pStatement Same as the first parameter of db.translateCondition.
* @returns {String} The SQL, same as the result of db.translateCondition.
*/
SqlUtils.translateConditionWithQuotes = function (pStatement) {
return SqlUtils.translateWithQuotes(pStatement, db.translateCondition);
}