From 60f50fdb4e4cfc89f38ae881aad628305b85021e Mon Sep 17 00:00:00 2001
From: Sebastian Listl <s.listl@adito.de>
Date: Wed, 28 Oct 2020 14:55:50 +0100
Subject: [PATCH] Sql_lib case when

---
 process/Sql_lib/process.js | 217 +++++++++++++++++++++++++++++++++----
 1 file changed, 193 insertions(+), 24 deletions(-)

diff --git a/process/Sql_lib/process.js b/process/Sql_lib/process.js
index 3b2f720defa..35b87999459 100644
--- a/process/Sql_lib/process.js
+++ b/process/Sql_lib/process.js
@@ -801,6 +801,26 @@ function SqlBuilder (pAlias)
     
     this._where = {};
     this._initWhere();
+    
+    SqlBuilder.defineCanBuildSql(this);
+}
+
+/**
+ * @return {Symbol}
+ */
+SqlBuilder.getCanBuildSqlSymbol = function ()
+{
+    return Symbol["for"]("canBuildSql");
+}
+
+SqlBuilder.defineCanBuildSql = function (pObject)
+{
+    pObject[SqlBuilder.getCanBuildSqlSymbol()] = true;
+}
+
+SqlBuilder.checkCanBuildSql = function (pObject)
+{
+    return pObject[SqlBuilder.getCanBuildSqlSymbol()];
 }
 
 /**
@@ -1097,11 +1117,11 @@ SqlBuilder.prototype.join = function(pTable, pCondition, pTableAlias, pPrefix, p
     if (pCondition)
     {
         if (pCondition instanceof SqlBuilder)
-            pCondition = [pCondition._where._sqlStorage, pCondition._where.preparedValues]
+            pCondition = [pCondition._where.sqlStorage, pCondition._where.preparedValues]
 
         var conditionPart = SqlBuilder._getStatement(pCondition);
 
-        joinPart._sqlStorage += " " + conditionPart._sqlStorage;
+        joinPart.sqlStorage += " " + conditionPart.sqlStorage;
         joinPart.preparedValues = joinPart.preparedValues.concat(conditionPart.preparedValues);
     }
     
@@ -1200,7 +1220,7 @@ SqlBuilder.prototype.where = function(pFieldOrCond, pValue, pCondition, pFieldTy
         
         copiedCondition._where.preparedValues = pFieldOrCond.preparedValues;
         copiedCondition._where._lastWasOr = pFieldOrCond._lastWasOr;
-        copiedCondition._where._sqlStorage = pFieldOrCond._sqlStorage;
+        copiedCondition._where.sqlStorage = pFieldOrCond.sqlStorage;
         
         pFieldOrCond = copiedCondition;
         
@@ -1351,7 +1371,7 @@ SqlBuilder.prototype._whereCondition = function(pCondition, pMandatory, pAddPrep
     if (sql instanceof SqlBuilder)
     {
         // add only brackets if needed
-        var sqlString = sql._where._sqlStorage;
+        var sqlString = sql._where.sqlStorage;
         
         
         var condString = sqlString;
@@ -1509,7 +1529,7 @@ SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pMandatory, pCon
                 
         var subSqlPrepared = pFieldOrCond.build();
         
-        tmpCond._where._sqlStorage = SqlUtils.replaceConditionTemplate(tmpCond._where._sqlStorage, 'SQL_LIB_DUMMY_TABLE.SQL_LIB_DUMMY_COLUMN', "( " + subSqlPrepared[0] + " )");
+        tmpCond._where.sqlStorage = SqlUtils.replaceConditionTemplate(tmpCond._where.sqlStorage, 'SQL_LIB_DUMMY_TABLE.SQL_LIB_DUMMY_COLUMN', "( " + subSqlPrepared[0] + " )");
         tmpCond._where.preparedValues = subSqlPrepared[1].concat(tmpCond._where.preparedValues)
         
         this._whereCondition(tmpCond, pMandatory, pAddPreparedConditionCallback, true)
@@ -1629,9 +1649,9 @@ SqlBuilder.prototype._and = function(pFieldOrCond, pValue, pMandatory, pConditio
         if (pPreparedCondition.length == 2 && typeof pPreparedCondition[0] == "string" && pPreparedCondition[0] != "" && Array.isArray(pPreparedCondition[1]))
         {
             if (that.hasCondition())
-                that._where._sqlStorage += " and ";
+                that._where.sqlStorage += " and ";
             
-            that._where._sqlStorage += pPreparedCondition[0];
+            that._where.sqlStorage += pPreparedCondition[0];
             that._where.preparedValues = that._where.preparedValues.concat(pPreparedCondition[1]);
         }
     });
@@ -1657,7 +1677,7 @@ SqlBuilder.prototype._or = function(pFieldOrCond, pValue, pMandatory, pCondition
         {   
             if (that._where._previouslyOnlyOr)
             {
-                that._where._sqlStorage = that._where._sqlStorage + " or " + pPreparedCondition[0];
+                that._where.sqlStorage = that._where.sqlStorage + " or " + pPreparedCondition[0];
                 that._where._lastWasOr = true;
             }
             else if (that.hasCondition())
@@ -1668,9 +1688,9 @@ SqlBuilder.prototype._or = function(pFieldOrCond, pValue, pMandatory, pCondition
                     cond = "(" + cond + ")";
                 
                 if (that._where._lastWasOr)
-                    that._where._sqlStorage = that._where._sqlStorage + " or " + cond;
+                    that._where.sqlStorage = that._where.sqlStorage + " or " + cond;
                 else
-                    that._where._sqlStorage = "(" + that._where._sqlStorage + ") or " + cond;
+                    that._where.sqlStorage = "(" + that._where.sqlStorage + ") or " + cond;
                 
                 that._where._lastWasOr = true;
             } 
@@ -1679,7 +1699,7 @@ SqlBuilder.prototype._or = function(pFieldOrCond, pValue, pMandatory, pCondition
                 if (!that.hasCondition())
                     that._where._previouslyOnlyOr = true;
                 
-                that._where._sqlStorage = pPreparedCondition[0];
+                that._where.sqlStorage = pPreparedCondition[0];
             }
             that._where.preparedValues = that._where.preparedValues.concat(pPreparedCondition[1]);
         }
@@ -2170,7 +2190,7 @@ SqlBuilder.prototype.having = function(pCondition)
  * @return {Boolean} true if conditions have been added, false when not
  */
 SqlBuilder.prototype.hasCondition = function() {
-    if (this._where._sqlStorage)
+    if (this._where.sqlStorage)
         return true;
     return false;
 }
@@ -2217,7 +2237,7 @@ SqlBuilder.prototype.clearWhere = function()
 SqlBuilder.prototype._initWhere = function ()
 {
     //TODO: maybe put conditions in an object/array for better internal object structure
-    this._where._sqlStorage = "";
+    this._where.sqlStorage = "";
     this._where.preparedValues = [];
     this._where._lastWasOr = false; // save, if the last condition was an OR. For better bracket-placement
     this._where._previouslyOnlyOr = false; // also for better bracket-placement
@@ -2329,24 +2349,22 @@ SqlBuilder._getStatement = function (pElement, pPrefix, pPostfix, pAutoJoin, pUs
     
     return {
         preparedValues: preparedValues,
-        _sqlStorage: pElement.toString()
+        sqlStorage: pElement.toString()
     };
 
     function _getElement (element)
     {
         var isSubQuery = false;
         var subselectAlias = "";
-        if (element instanceof SqlBuilder)
+        if (SqlBuilder.checkCanBuildSql(element))
         {
-            if (element.isFullSelect())
+            if (element instanceof SqlBuilder && element.isFullSelect())
             {
                 isSubQuery = true;
                 
                 if (pUseSubselectAlias && element._subselectAlias)
                     subselectAlias = " " + element._subselectAlias;
             }
-                
-                
             element = element.build();
         }
         preparedValues = preparedValues.concat(element[1]);
@@ -2363,7 +2381,7 @@ SqlBuilder._getStatement = function (pElement, pPrefix, pPostfix, pAutoJoin, pUs
  */
 SqlBuilder.prototype.buildCondition = function()
 {   
-    return [this._where._sqlStorage, this._where.preparedValues];
+    return [this._where.sqlStorage, this._where.preparedValues];
 }
 
 /**
@@ -2378,17 +2396,17 @@ SqlBuilder.prototype.build = function(pDefaultConditionIfNone)
     
     if (this.isFullSelect())
     {
-        if (this._where._sqlStorage)
+        if (this._where.sqlStorage)
             wherePrefix = "where ";
     }
     
-    var whereSql = this._where._sqlStorage;
+    var whereSql = this._where.sqlStorage;
     
     if (!this.hasCondition() && pDefaultConditionIfNone)
         whereSql = wherePrefix + pDefaultConditionIfNone;
     
     var whereObj = {
-        _sqlStorage : wherePrefix + whereSql,
+        sqlStorage : wherePrefix + whereSql,
         preparedValues : this._where.preparedValues
     }
     
@@ -2409,9 +2427,9 @@ SqlBuilder.prototype.build = function(pDefaultConditionIfNone)
         let part = allParts[i];
         if (part)
         {
-            if (sqlStr && part._sqlStorage)
+            if (sqlStr && part.sqlStorage)
                 sqlStr += " ";
-            sqlStr += part._sqlStorage;
+            sqlStr += part.sqlStorage;
             if (part.preparedValues.length)
                 preparedVals = preparedVals.concat(part.preparedValues);
         }
@@ -3057,6 +3075,157 @@ SqlBuilder.prototype.translate = function(pAlias)
     return SqlUtils.translateStatementWithQuotes(this.build(), pAlias);
 }
 
+/**
+ * Creates an object for building a case-when statement.
+ * 
+ * @param {String|String[]|SqlBuilder|PreparedSqlArray} [pFieldOrCond] If this is the only parameter, it is used as Subselect <br/>
+ *                                                                     else it is used as Field. <br/>
+ *                                                                     Please see .where() for more information and examples.
+ * @param {String|SqlBuilder|PreparedSqlArray|Array|OtherTypes} [pValue] This is the value whitch is used for the condition.<br/>
+ *                                                                       Basically it can be nearly everything you need.<br/>
+ *                                                                       Please see .where() for more information and examples.
+ * @param {String} [pCondition="# = ?"] This is the condition which should be used.<br/>
+ *                               # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/>
+ *                               ? will be replaced by pValue<br/>
+ *                               <strong>IMPORTANT: the # has to be before the ?</strong><br/>
+ *                               Please see .where() for more information and examples.
+ * @param {SQLTYPES|Numeric} [pFieldType=AutomaticallyLoadedType] You can specify which datatype should be used for the prepared statement<br/>
+ *                                                                In most cases you don't need this.<br/>
+ *                                                                Please see .where() for more information and examples.
+ * 
+ * @return {SqlBuilder._CaseWhen}
+ */
+SqlBuilder.caseWhen = function (pFieldOrCond, pValue, pCondition, pFieldType)
+{
+    return new SqlBuilder._CaseStatement().when(pFieldOrCond, pValue, pCondition, pFieldType);
+}
+
+/**
+ * Represents a case-when statement
+ */
+SqlBuilder._CaseStatement = function ()
+{
+    this._whenCondition = null;
+    this._whenThens = [];
+    this._elseValue = null;
+    this._afterWhenMask = new SqlBuilder._CaseWhen(this);
+    SqlBuilder.defineCanBuildSql(this);
+}
+
+/**
+ * @param {String|String[]|SqlBuilder|PreparedSqlArray} [pFieldOrCond] If this is the only parameter, it is used as Subselect <br/>
+ *                                                                     else it is used as Field. <br/>
+ *                                                                     Please see .where() for more information and examples.
+ * @param {String|SqlBuilder|PreparedSqlArray|Array|OtherTypes} [pValue] This is the value whitch is used for the condition.<br/>
+ *                                                                       Basically it can be nearly everything you need.<br/>
+ *                                                                       Please see .where() for more information and examples.
+ * @param {String} [pCondition="# = ?"] This is the condition which should be used.<br/>
+ *                               # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/>
+ *                               ? will be replaced by pValue<br/>
+ *                               <strong>IMPORTANT: the # has to be before the ?</strong><br/>
+ *                               Please see .where() for more information and examples.
+ * @param {SQLTYPES|Numeric} [pFieldType=AutomaticallyLoadedType] You can specify which datatype should be used for the prepared statement<br/>
+ *                                                                In most cases you don't need this.<br/>
+ *                                                                Please see .where() for more information and examples.
+ * 
+ * @return {SqlBuilder._CaseWhen}
+ */
+SqlBuilder._CaseStatement.prototype.when = function (pFieldOrCond, pValue, pCondition, pFieldType)
+{
+    this._whenCondition = newWhere(pFieldOrCond, pValue, pCondition, pFieldType);
+    return this._afterWhenMask;
+}
+
+/**
+ * Sets the expression used for the else-part
+ * 
+ * @param {String|SqlBuilder} pValue else-value
+ * @return {SqlBuilder._CaseStatement}
+ */
+SqlBuilder._CaseStatement.prototype.elseValue = function (pValue)
+{
+    this._elseValue = pValue;
+    return this;
+}
+
+/**
+ * Sets the value used for the else-part, but wraps the value in ''
+ * 
+ * @param {String} pValue else-value
+ * @return {SqlBuilder._CaseStatement}
+ */
+SqlBuilder._CaseStatement.prototype.elseString = function (pValue)
+{
+    return this.elseValue("'" + pValue + "'");
+}
+
+/**
+ * @return {String} the case-when expression
+ */
+SqlBuilder._CaseStatement.prototype.toString = function (pAlias)
+{
+    return db.translateStatement(this.build(), pAlias || db.getCurrentAlias());
+}
+
+SqlBuilder._CaseStatement.prototype.build = function ()
+{
+    var caseStatement = ["case"];
+    var preparedValues = [];
+    this._whenThens.forEach(function (whenThen)
+    {
+        var when = SqlBuilder._getStatement(whenThen.condition, "when");
+        var then = SqlBuilder._getStatement(whenThen.thenValue, "then");
+        caseStatement.push(when.sqlStorage);
+        caseStatement.push(then.sqlStorage);
+        preparedValues = preparedValues.concat(when.preparedValues, then.preparedValues);
+    });
+    if (this._elseValue)
+    {
+        let elseStatement = SqlBuilder._getStatement(this._elseValue, "else");
+        caseStatement.push(elseStatement.sqlStorage);
+        preparedValues = preparedValues.concat(elseStatement.preparedValues);
+    }
+    caseStatement.push("end");
+    
+    return [
+        caseStatement.join(" "),
+        preparedValues
+    ];
+}
+
+/**
+ * Object providing the then-methods for the case-when expression. It can be only be accessed after calling .when to ensure a 'then' 
+ * can only be added after a 'when'.
+ */
+SqlBuilder._CaseWhen = function (pCaseStatement)
+{
+    this._caseStatement = pCaseStatement;
+}
+
+/**
+ * Sets the expression for the then
+ * 
+ * @param {String|SqlBuilder} pValue then-value
+ * @return {SqlBuilder._CaseStatement}
+ */
+SqlBuilder._CaseWhen.prototype.then = function (pValue)
+{
+    var condition = this._caseStatement._whenCondition;
+    this._caseStatement._whenCondition = null;
+    this._caseStatement._whenThens.push({condition: condition, thenValue: pValue});
+    return this._caseStatement;
+}
+
+/**
+ * Sets the value for the then, but wraps the value in ''
+ * 
+ * @param {String} pValue then-value
+ * @return {SqlBuilder._CaseStatement}
+ */
+SqlBuilder._CaseWhen.prototype.thenString = function (pValue)
+{
+    return this.then("'" + pValue + "'");
+}
 
 /**
  *provides functions for masking sql functions
-- 
GitLab