From d8774227c51834f3d1f6e5eaa1230d2b858a0c2e Mon Sep 17 00:00:00 2001
From: Johannes Hoermann <j.hoermann@adito.de>
Date: Tue, 22 Oct 2019 15:33:24 +0200
Subject: [PATCH] Added full subselect in pFieldOrCond if pValue and pFieldType
 are provided, validate format of pCondition

---
 process/SqlLib_tests/process.js |  70 +++++++++++++++-
 process/Sql_lib/process.js      | 138 +++++++++++++++++++++++++-------
 2 files changed, 175 insertions(+), 33 deletions(-)

diff --git a/process/SqlLib_tests/process.js b/process/SqlLib_tests/process.js
index 4d7bb4e7056..e3b2a0e0f33 100644
--- a/process/SqlLib_tests/process.js
+++ b/process/SqlLib_tests/process.js
@@ -829,7 +829,6 @@ var joinTests = new TestSuite([
             .from("PERSON")
             .join("ORGANISATION", subQuery)
             
-        logging.log(JSON.stringify([actual._joins], null, "\t"))
         pTester.assert("join ORGANISATION on ORGANISATION.NAME = ?", actual._joins[0]._sqlStorage, "prepared select-sql");
         pTester.assert(1, actual._joins[0].preparedValues.length, "number of params");
     }],
@@ -845,7 +844,6 @@ var joinTests = new TestSuite([
             .from("PERSON")
             .join(subQuery, "orgname.NAME = TABLE2.NAME", "orgname")
             
-        logging.log(JSON.stringify([actual._joins], null, "\t"))
         pTester.assert("join (select NAME from ORGANISATION where ORGANISATION.NAME = ?) orgname on orgname.NAME = TABLE2.NAME", actual._joins[0]._sqlStorage, "prepared select-sql");
         pTester.assert(1, actual._joins[0].preparedValues.length, "number of params");
     }],
@@ -857,10 +855,74 @@ var joinTests = new TestSuite([
             .from("PERSON")
             .join("TABLE1 on TABLE1.NAME = TABLE2.NAME")
             
-        logging.log(JSON.stringify([actual._joins], null, "\t"))
         pTester.assert("join TABLE1 on TABLE1.NAME = TABLE2.NAME", actual._joins[0]._sqlStorage, "prepared select-sql");
         pTester.assert(0, actual._joins[0].preparedValues.length, "number of params");
     }]
+]);
+
+var subqueryAsFieldTests = new TestSuite([
+    ["Test if a Subselect as field works if pValue is provided. This should be added as subselect.", function(pTester)
+    {
+        var subQuery = newSelect("NAME")
+                                .from("ORGANISATION")
+                                .where("ORGANISATION.ORGANISATIONID = CONTACT.ORGANISATION_ID")
+                                .and("PERSON.FIRSTNAME", "val1") // test if the value is added at the correct place
+    
+        var actual = new SqlBuilder().where(subQuery, "val2", "# = ?", SQLTYPES.VARCHAR)
+                                     .and("PERSON.FIRSTNAME", "val3");
+                
+            
+        pTester.assert(" ( ( select NAME from ORGANISATION where ORGANISATION.ORGANISATIONID = CONTACT.ORGANISATION_ID and PERSON.FIRSTNAME = ? ) = ? )  and PERSON.FIRSTNAME = ?", actual._where._sqlStorage, "prepared select-sql");
+        pTester.assert(3, actual._where.preparedValues.length, "number of params");
+        pTester.assert("val1", actual._where.preparedValues[0][0], "param 1 is correct value");
+        pTester.assert("val2", actual._where.preparedValues[1][0], "param 2 is correct value");
+        pTester.assert("val3", actual._where.preparedValues[2][0], "param 3 is correct value");
+    }],
+
+    ["Test if a Subselect as field should error if no SQLTYPE is provided.", function(pTester)
+    {
+        var subQuery = newSelect("NAME")
+                                .from("ORGANISATION")
+                                .where("ORGANISATION.ORGANISATIONID = CONTACT.ORGANISATION_ID")
+                                .and("PERSON.FIRSTNAME", "val1") // test if the value is added at the correct place
+        new SqlBuilder().where(subQuery, "val2", "# = ?");
+    }, SqlBuilder.ERROR_SUBSELECT_AS_FIELD_NO_FIELD_TYPE()],
+
+    ["Test if a Subselect as field should error if it is not a full select.", function(pTester)
+    {
+        var subQuery = newSelect("NAME")
+                                .where("ORGANISATION.ORGANISATIONID = CONTACT.ORGANISATION_ID")
+                                .and("PERSON.FIRSTNAME", "val1") // test if the value is added at the correct place
+    
+        new SqlBuilder().where(subQuery, "val2", "# = ?", SQLTYPES.VARCHAR);
+    }, SqlBuilder.ERROR_SUBSELECT_AS_FIELD_NOT_COMPLETE()]
+]);
+
+var conditionFormatTests = new TestSuite([
+    ["pCondition should not fail if # an ? exist in correct order", function(pTester)
+    {
+        new SqlBuilder().where("PERSON.FIRSTNAME", "val1", "# = ?")
+    }],
+
+    ["pCondition should not fail if only ? exists", function(pTester)
+    {
+        new SqlBuilder().where("PERSON.FIRSTNAME", "val1", "?")
+    }],
+
+    ["pCondition should fail if more than one ? exists", function(pTester)
+    {
+        new SqlBuilder().where("PERSON.FIRSTNAME", "val1", "? test ?")
+    }, SqlBuilder.ERROR_CONDITION_WRONG_FORMAT()],
+
+    ["pCondition should fail if more than one # exists", function(pTester)
+    {
+        new SqlBuilder().where("PERSON.FIRSTNAME", "val1", "# test #")
+    }, SqlBuilder.ERROR_CONDITION_WRONG_FORMAT()],
+
+    ["pCondition should fail if # and ? are in wrong order", function(pTester)
+    {
+        new SqlBuilder().where("PERSON.FIRSTNAME", "val1", "? = #")
+    }, SqlBuilder.ERROR_CONDITION_WRONG_FORMAT()]
 ])
 
 var tester = new Tester("Test SqlBuilder");
@@ -875,6 +937,8 @@ tester.test(inStatementTests);
 tester.test(testConstantFunctions);
 tester.test(selectTests);
 tester.test(joinTests);
+tester.test(subqueryAsFieldTests);
+tester.test(conditionFormatTests);
 
 logging.log("-------------------------");
 tester.printResults();
diff --git a/process/Sql_lib/process.js b/process/Sql_lib/process.js
index 3b83a1f8c10..cf6c9feef16 100644
--- a/process/Sql_lib/process.js
+++ b/process/Sql_lib/process.js
@@ -39,6 +39,7 @@ function newSelect(pFields, pAlias)
  * @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/>
@@ -65,6 +66,7 @@ function newWhere(pFieldOrCond, pValue, pCondition, pFieldType, pAlias)
  * @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 .whereIfSet() 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/>
@@ -106,6 +108,8 @@ function SqlBuilder (pAlias)
     this._unions = [];
     this.alias = pAlias;
     
+    this._subselectAlias = null;
+
     this._where = {
         preparedValues: [],
         _sqlStorage: "",
@@ -156,32 +160,32 @@ SqlBuilder.ERROR_INSTANCIATE_WITH_NEW = function()
 
 SqlBuilder.ERROR_INVALID_CONDITION_VALUE_TYPE = function() 
 {
-    return new Error(translate.text("SqlBuilder.and/or: invalid value-type for pCondition"));
+    return new Error(translate.text("SqlBuilder: invalid value-type for pCondition"));
 }
 
 SqlBuilder.ERROR_NO_CONDITION = function() 
 {
-    return new Error(translate.text("SqlBuilder.and/or: if you use a subQuery (e.g. SqlBuilder) you have to provide pCondition (e.g. \"exists ?\")"));
+    return new Error(translate.text("SqlBuilder: if you use a subQuery (e.g. SqlBuilder) you have to provide pCondition (e.g. \"exists ?\")"));
 }
 
 SqlBuilder.ERROR_INVALID_SUBQUERY_TYPE = function() 
 {
-    return new Error(translate.text("SqlBuilder.and/or: invalid value-type for pFieldOrCond. It can be a fully qualified SqlBuilder (e.g. select, from, ... have to be set) or an jdito-prepared-statement array"));
+    return new Error(translate.text("SqlBuilder: invalid value-type for pFieldOrCond. It can be a fully qualified SqlBuilder (e.g. select, from, ... have to be set) or an jdito-prepared-statement array"));
 }
 
 SqlBuilder.ERROR_VALUE_IS_MANDATORY = function() 
 {
-    return new Error(translate.text("SqlBuilder.and/or: pValue (or pFieldOrCond if only one param) is not allowed to be null, undefined or []. (use *IfSet functions if you need optional conditions which are just ignored if value is null or undefined)"));
+    return new Error(translate.text("SqlBuilder: pValue (or pFieldOrCond if only one param) is not allowed to be null, undefined or []. (use *IfSet functions if you need optional conditions which are just ignored if value is null or undefined)"));
 }
 
 SqlBuilder.ERROR_VALUE_IS_MANDATORY_JDITO_VAR = function() 
 {
-    return new Error(translate.text("SqlBuilder.and/or: pValue has to be a jdito variable which returns something different than null. (use *IfSet functions if you need optional conditions which are just ignored if value is null or undefined)"));
+    return new Error(translate.text("SqlBuilder: pValue has to be a jdito variable which returns something different than null. (use *IfSet functions if you need optional conditions which are just ignored if value is null or undefined)"));
 }
 
 SqlBuilder.ERROR_UNSUPPORTED_PARAMETER_COMBINATION = function() 
 {
-    return new Error(translate.text("SqlBuilder.and/or: unsupportet parameter combination"));
+    return new Error(translate.text("SqlBuilder: unsupportet parameter combination"));
 }
 
 SqlBuilder.ERROR_NO_TABLE = function()
@@ -191,7 +195,7 @@ SqlBuilder.ERROR_NO_TABLE = function()
 
 SqlBuilder.ERROR_NO_PARAMETER_PROVIDED = function()
 {
-    return new Error(translate.text("SqlBuilder.and/or: You have to specify at least one parameter"));
+    return new Error(translate.text("SqlBuilder: You have to specify at least one parameter"));
 }
 
 SqlBuilder.ERROR_WHERE_NOT_FIRST = function()
@@ -211,9 +215,25 @@ SqlBuilder.ERROR_INCOMPLETE_SELECT = function ()
 
 SqlBuilder.ERROR_CONDITION_IS_MANDATORY = function ()
 {
-    return new Error(translate.text("SqlBuilder.and/or: You have to provide a subquery as SqlBuilder, prepared-array or string"));
+    return new Error(translate.text("SqlBuilder: You have to provide a subquery as SqlBuilder, prepared-array or string"));
+}
+
+SqlBuilder.ERROR_SUBSELECT_AS_FIELD_NOT_COMPLETE = function ()
+{
+    return new Error(translate.text("SqlBuilder: If pFieldOrCond is a SqlBuilder & pValue is provided, pFieldOrCond has to be a full SqlBuilder which will be used as subselect"));
+}
+
+SqlBuilder.ERROR_SUBSELECT_AS_FIELD_NO_FIELD_TYPE = function ()
+{
+    return new Error(translate.text("SqlBuilder: If pFieldOrCond is a SqlBuilder & pValue is provided, you have to provide also pFieldType, as the type cannot be calculated from pFieldOrCond because it is a subselect"));
 }
 
+SqlBuilder.ERROR_CONDITION_WRONG_FORMAT = function ()
+{
+    return new Error(translate.text("SqlBuilder: The '#' in pCondition has to occur before the '?' and '?' has to occur 1 time, '#' has to occur 1 or 0 times."));
+}
+
+
 /**
  * Builds the sql and uses SqlUtils.translateXXXWithQuotes to make a string out of it.
  * @return {String} the sql as string
@@ -248,6 +268,18 @@ SqlBuilder.prototype.select = function(pFields)
     return this;
 }
 
+/**
+ * sets an alias-name which is added to the built sql if it is a full select
+ * @param {String} pSubselectAlias
+ * 
+ * @return {SqlBuilder} current SqlBuilder object
+ */
+SqlBuilder.prototype.subselectAlias = function(pSubselectAlias)
+{
+    this._subselectAlias = pSubselectAlias;
+    return this;
+}
+
 /**
  * Sets the from clause of the sql.<br/>
  * <br/>
@@ -381,8 +413,8 @@ SqlBuilder.prototype.rightJoin = function(pTable, pCondition, pTableAlias)
  *                                                                           
  * @param {String} [pCondition="# = ?"] This is the condition which should be used to compare the field with the value.<br/>
  *                               # will be replaced by the field (pFieldOrCond) If pFieldOrCond is null, you can ommit #<br/>
- *                               ? will be replaced by pValue
- *                               
+ *                               ? will be replaced by pValue<br/>
+ *                               <strong>IMPORTANT: the # has to be before the ?</strong>
  * @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/>
  *                                                                This is helpful if you for example have a pCondition "year(#) = ?"<br/>
@@ -431,6 +463,7 @@ SqlBuilder.prototype.where = function(pFieldOrCond, pValue, pCondition, pFieldTy
  * @param {String} [pCondition="# = ?"] This is the condition which should be used to compare the field with the value.<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>
  *                               
  * @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/>
@@ -450,7 +483,7 @@ SqlBuilder.prototype.whereIfSet = function(pFieldOrCond, pValue, pCondition, pFi
  * 
  * @param {String|String[]|SqlBuilder|PreparedSqlArray|null} [pFieldOrCond] 
  * @param {String|SqlBuilder|PreparedSqlArray|Array|OtherTypes} [pValue] 
- * @param {String} [pCondition="# = ?"]
+ * @param {String} [pCondition="# = ?"] <strong>IMPORTANT: the # has to be before the ?</strong><br/>
  * @param {SQLTYPES|Numeric} [pFieldType=AutomaticallyLoadedType] 
  * @param {SQLTYPES|Numeric} pAddCondFn=AutomaticallyLoadedType This is a callback which is called if a condition should be added (needs to have same parameters as .or()
  *
@@ -503,7 +536,7 @@ SqlBuilder.prototype._whereCondition = function(pCondition, pMandatory, pAddPrep
     // the field is a simple string -> just add the string, no prepared statement
     if (typeofSql == "string")
     {
-        pAddPreparedConditionCallback([sql, []]);
+        pAddPreparedConditionCallback(this, [sql, []]);
         return this;
     }
 
@@ -518,7 +551,7 @@ SqlBuilder.prototype._whereCondition = function(pCondition, pMandatory, pAddPrep
             if (pBrackets)
                 sql[0] = " ( " + sql[0] + " ) ";
 
-            pAddPreparedConditionCallback([sql[0], []], pBrackets)
+            pAddPreparedConditionCallback(this, [sql[0], []], pBrackets)
             return this;
         }
         else if (pMandatory)
@@ -540,7 +573,7 @@ SqlBuilder.prototype._whereCondition = function(pCondition, pMandatory, pAddPrep
             if (pBrackets)
                 condString = " ( " + condString + " ) ";
             
-            pAddPreparedConditionCallback([condString, sql._where.preparedValues], pBrackets);
+            pAddPreparedConditionCallback(this, [condString, sql._where.preparedValues], pBrackets);
             return this;
         }
         else if (pMandatory)
@@ -578,7 +611,7 @@ SqlBuilder.prototype._whereSubquery = function(pSubquery, pMandatory, pCondition
     if (Array.isArray(sql))
     {        
         if (sql[0])
-            pAddPreparedConditionCallback(this._prepare(undefined, sql, pCondition));
+            pAddPreparedConditionCallback(this, this._prepare(undefined, sql, pCondition));
         else if (pMandatory)
             throw SqlBuilder.ERROR_VALUE_IS_MANDATORY();
         
@@ -597,7 +630,7 @@ SqlBuilder.prototype._whereSubquery = function(pSubquery, pMandatory, pCondition
         if (subQuery.isFullSelect())
         {
             var preparedObj = subQuery.build();
-            pAddPreparedConditionCallback(this._prepare(undefined, preparedObj, pCondition));
+            pAddPreparedConditionCallback(this, this._prepare(undefined, preparedObj, pCondition));
         } 
         else if (pMandatory)
             throw SqlBuilder.ERROR_VALUE_IS_MANDATORY();
@@ -623,6 +656,12 @@ SqlBuilder.prototype._whereSubquery = function(pSubquery, pMandatory, pCondition
  */
 SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pMandatory, pCondition, pFieldType, pAddPreparedConditionCallback)
 {
+    if (pCondition && !SqlUtils.checkConditionFormat(pCondition)) 
+        throw SqlBuilder.ERROR_CONDITION_WRONG_FORMAT();
+
+    if (pMandatory === undefined)
+        pMandatory = true;
+    
     if (!this._where._whereWasCalled)
         throw SqlBuilder.ERROR_WHERE_NOT_FIRST();
     
@@ -644,21 +683,40 @@ SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pMandatory, pCon
         else
             return this;
     }
-    
-    if (pMandatory === undefined)
-        pMandatory = true;
   
     // just call the andCondition function if it is only a Condition
     if (pFieldOrCond !== undefined && pValue === undefined && pCondition === undefined && pFieldType === undefined)
         return this._whereCondition(pFieldOrCond, pMandatory, pAddPreparedConditionCallback, true);
     
+    if (pFieldOrCond instanceof SqlBuilder)
+    {
+        if (!pFieldOrCond.isFullSelect())
+        {
+            throw SqlBuilder.ERROR_SUBSELECT_AS_FIELD_NOT_COMPLETE();
+        }
+        
+        if (!pFieldType)
+            throw SqlBuilder.ERROR_SUBSELECT_AS_FIELD_NO_FIELD_TYPE();
+            
+        var tmpCond = newWhere(this.alias)
+                            ._addWhere("SQL_LIB_DUMMY_TABLE.SQL_LIB_DUMMY_COLUMN", pValue, pMandatory, pCondition, pFieldType, pAddPreparedConditionCallback);
+                
+        var subSqlPrepared = pFieldOrCond.build();
+        
+        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)
+        return this;
+    }
+    
     // first check the default-mandatory-cases: null or undefined. everything else such as checking $-variables is done later
     if (pMandatory && (pValue === null || pValue === undefined))
         throw SqlBuilder.ERROR_VALUE_IS_MANDATORY();
     
     // a field is string or array -> normal case
     // OR !pFieldOrCond and pValue and pCondition is given -> preparedSQL/SqlBuilder can be used without field if pCondition is set
-    if((typeof pFieldOrCond == "string" || Array.isArray(pFieldOrCond)) || (!pFieldOrCond && (pCondition && pValue instanceof SqlBuilder || !(pValue instanceof SqlBuilder))))
+    if(((typeof pFieldOrCond == "string" || Array.isArray(pFieldOrCond) || (!pFieldOrCond && pFieldType)) || (!pFieldOrCond && (pCondition && pValue instanceof SqlBuilder || !(pValue instanceof SqlBuilder)))))
     {
         var field = pFieldOrCond;
         var typeofValue = typeof pValue;
@@ -681,6 +739,7 @@ SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pMandatory, pCon
                 if (!pCondition)
                     pCondition = "# in ?";
                 
+                // if it is null -> ignore it. -> the pCondition should not contain a # in this case
                 if (field != null)
                 {
                     [alias, parsedField] = SqlUtils.parseField(field)
@@ -733,11 +792,13 @@ SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pMandatory, pCon
         // ... everything else -> just pass it
         if (pValue === false || pValue === 0 || pValue === "" || pValue)
         {
-            this._whereCondition(this._prepare(field, pValue, pCondition, pFieldType), undefined, pAddPreparedConditionCallback);
+            let prep = this._prepare(field, pValue, pCondition, pFieldType)
+            this._whereCondition(prep, undefined, pAddPreparedConditionCallback);
         }
             
         return this;
     }
+
     
     throw SqlBuilder.ERROR_UNSUPPORTED_PARAMETER_COMBINATION();
 }
@@ -755,9 +816,7 @@ SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pMandatory, pCon
  */
 SqlBuilder.prototype._and = function(pFieldOrCond, pValue, pMandatory, pCondition, pFieldType)
 {
-    var that = this;
- 
-    return this._addWhere(pFieldOrCond, pValue, pMandatory, pCondition, pFieldType, function(pPreparedCondition) 
+    return this._addWhere(pFieldOrCond, pValue, pMandatory, pCondition, pFieldType, function(that, pPreparedCondition) 
     {
         that._where._previouslyOnlyOr = false;
         if (pPreparedCondition.length == 2 && typeof pPreparedCondition[0] == "string" && pPreparedCondition[0] != "" && Array.isArray(pPreparedCondition[1]))
@@ -784,10 +843,8 @@ SqlBuilder.prototype._and = function(pFieldOrCond, pValue, pMandatory, pConditio
  * @ignore
  */
 SqlBuilder.prototype._or = function(pFieldOrCond, pValue, pMandatory, pCondition, pFieldType)
-{
-    var that = this;
-    
-    return this._addWhere(pFieldOrCond, pValue, pMandatory, pCondition, pFieldType, function(pPreparedCondition, pAlreadySurroundedByBrackets) 
+{    
+    return this._addWhere(pFieldOrCond, pValue, pMandatory, pCondition, pFieldType, function(that, pPreparedCondition, pAlreadySurroundedByBrackets) 
     {
         if (pPreparedCondition.length == 2 && typeof pPreparedCondition[0] == "string" && pPreparedCondition[0] != "" && Array.isArray(pPreparedCondition[1]))
         {   
@@ -1306,9 +1363,15 @@ SqlBuilder.prototype.buildCondition = function()
 SqlBuilder.prototype.build = function(pDefaultConditionIfNone)
 {
     var wherePrefix = "";
+    var subselectAlias = "";
     
-    if (this.isFullSelect() && this._where._sqlStorage)
-        wherePrefix = "where ";
+    if (this.isFullSelect())
+    {
+        if (this._where._sqlStorage)
+            wherePrefix = "where ";
+    
+        subselectAlias = this._subselectAlias;
+    }
     
     var whereSql = this._where._sqlStorage;
     
@@ -1344,6 +1407,8 @@ SqlBuilder.prototype.build = function(pDefaultConditionIfNone)
                 preparedVals = preparedVals.concat(part.preparedValues);
         }
     }
+    
+    sqlStr += (subselectAlias ? " " + subselectAlias : "");
     return [sqlStr, preparedVals];
 }
 
@@ -2683,4 +2748,17 @@ SqlUtils.replaceConditionTemplate = function(pCondition, pPlaceholder, pReplacem
     replacements["\\\\" + pPlaceholder] = pPlaceholder
     
     return text.replaceAll(pCondition, replacements);
+}
+
+SqlUtils.checkConditionFormat = function(pCondition) 
+{
+    // replace by {@NUMBERSIGN@} / {@QUESTIONSIGN@} as the js-regexp cannot do lookbehind (needed by the regexp used in replaceConditionTemplate to check escapes)
+    // so we just use replaceConditionTemplate to replace by something which never should occur anywhere
+    pCondition = SqlUtils.replaceConditionTemplate(pCondition, "#", "{@NUMBERSIGN@}")
+    pCondition = SqlUtils.replaceConditionTemplate(pCondition, "\\?", "{@QUESTIONSIGN@}")
+    
+    var indexOfNumberSign = pCondition.indexOf("{@NUMBERSIGN@}");
+    var indexOfQuestionSign = pCondition.indexOf("{@QUESTIONSIGN@}");
+    
+    return !(indexOfQuestionSign == -1 || indexOfNumberSign > indexOfQuestionSign || indexOfNumberSign != pCondition.lastIndexOf("{@NUMBERSIGN@}") || indexOfQuestionSign != pCondition.lastIndexOf("{@QUESTIONSIGN@}"))
 }
\ No newline at end of file
-- 
GitLab