From d9250f827785ea0958e99255ffe6f3764fafa1d8 Mon Sep 17 00:00:00 2001 From: Johannes Hoermann <j.hoermann@adito.de> Date: Wed, 23 Oct 2019 14:20:36 +0200 Subject: [PATCH] SqlLib Dokumentation & remove old SqlCondition docu --- others/guide/HowToSqlBuilder.adoc | 48 ------ others/guide/HowToSqlConditionLib.adoc | 229 ------------------------- process/Sql_lib/documentation.adoc | 8 +- process/Sql_lib/process.js | 61 ++++++- 4 files changed, 58 insertions(+), 288 deletions(-) delete mode 100644 others/guide/HowToSqlBuilder.adoc delete mode 100644 others/guide/HowToSqlConditionLib.adoc diff --git a/others/guide/HowToSqlBuilder.adoc b/others/guide/HowToSqlBuilder.adoc deleted file mode 100644 index b0abf23041..0000000000 --- a/others/guide/HowToSqlBuilder.adoc +++ /dev/null @@ -1,48 +0,0 @@ -How to use the SqlBuilder (state: 10.07.2019) -============================================== -:toc2: left -:numbered: - - -== What is SqlBuilder? == -A SqlBuilder object helps to build sql queries. - -== Why would I need that? == -SqlBuilder makes it easier to build complex queries while maintaining clean code, -especially if you want to append sql parts dynamically. With SqlBuilder you can also use SqlCondition -objects in many parts of the query at the same time, for example in join, where or having clauses. - -== How to use it == -* import the lib: -[source,javascript] ----- -import("Sql_lib"); ----- -* create an object () -[source,javascript] ----- -var myDescriptiveNameOfTheQuery = new SqlBuilder(); -// or -var myDescriptiveNameOfTheQuery = SqlBuilder.begin(); ----- -* use the object, set clauses -[source,javascript] ----- -myDescriptiveNameOfTheQuery.select(["PERSON.FIRSTNAME", "PERSON.LASTNAME"]) //you can use an array or a string here - .from("PERSON") - .join("CONTACT", SqlCondition.begin() - .and("CONTACT.PERSON_ID = PERSON.PERSONID")) - .where(SqlCondition.begin() - .andPrepare("CONTACT.CONTACTID", contactId) - .build("1=1")); ----- -* Using .build on the SqlCondition is optional, but you could use it to set the alternative condition. -* Before using the sql, call .build(): -[source,javascript] ----- -var names = db.table(myDescriptiveNameOfTheQuery.build()); ----- - -== available methods == -see the comments and documentation in the lib - diff --git a/others/guide/HowToSqlConditionLib.adoc b/others/guide/HowToSqlConditionLib.adoc deleted file mode 100644 index df7adebc5d..0000000000 --- a/others/guide/HowToSqlConditionLib.adoc +++ /dev/null @@ -1,229 +0,0 @@ -How to use the SqlCondition (state: 06.12.2018) -=============================================== -:toc2: left -:numbered: - -(This lib is work in progress and may change in the future.) - -== What is the SqlCondition == -It is a lib which helps to create SQL statements and especially simplifies prepared Statements. - -== When should prepared statements be used == -If possible ALWAYS. -Prepared statements improve the application security significantly. -See also AID068-DE - SQL Injections - -== basics == -* import the lib: -[source,javascript] ----- -import("Sql_lib"); ----- -* create an object (alias is optional) -[source,javascript] ----- -var myDescriptiveNameOfTheCondition = new SqlCondition(alias); -// or -var myDescriptiveNameOfTheCondition = SqlCondition.begin(alias); ----- -* use the object, add conditions -[source,javascript] ----- -myDescriptiveNameOfTheCondition.orPrepare("PERSON.FIRSTNAME", "Bob") - .orPrepare("PERSON.LASTNAME", "Meier"); // orPrepare explained in "available methods" below ----- -* You can also use table aliases, if you provide the table, column and alias as array. - The table name has to be provided for loading the data types but the alias is built into the sql. - You need this, if you need to call a table by the alias name instead of the full table name. - This is especially useful if you build a condition for joins. (See example at the end). -[source,javascript] ----- -myDescriptiveNameOfTheCondition.orPrepare(["PERSON", "FIRSTNAME", "bob"], "Bob") - .orPrepare(["PERSON", "LASTNAME", "bob"], "Meier"); ----- -* build sql -[source,javascript] ----- -var myPreparedSelect = myDescriptiveNameOfTheCondition.buildSql("select PERSONID from PERSON", "1 = 2", "order by FIRSTNAME"); ----- -* use the select -[source,javascript] ----- -var bobsId = db.cell(myPreparedSelect); ----- - -You can do everything in one command also: -[source,javascript] ----- -var bobsId = db.cell(SqlCondition.begin(alias) - .orPrepare("PERSON.FIRSTNAME", "Bob") - .orPrepare("PERSON.LASTNAME", "Meier"); - .buildSql("select PERSONID from PERSON")); ----- - -== available methods == -This is only a simple overview. -for more information see the comments and documentation in the lib! - -=== and / or === -Adds a condition. Doesn't use prepared Statements!! -!!Please prefer the prepared version!! - -[source,javascript] ----- -myDescriptiveNameOfTheCondition.or("FIRSTNAME = 'Bob'"); ----- - -=== andPrepared / orPrepared === -Same as and / or but uses prepared statements. -The field name needs to be given with -[TABLENAME].[COLUMNNAME] - -Or if you use only COLUMNNAME, you have to provide the fieldType. - -But keep in mind: Other than the original adito db. functions this lib caches the field types automatically (for the livetime of an SqlCondition Object). -So most times it is save to use the first method. Even in loops or if you use the same column several times. - -[source,javascript] ----- -myDescriptiveNameOfTheCondition.orPrepared("PERSON.FIRSTNAME", 'Bob', "#<?"); // #(field) < ?(value) ----- - -=== andPreparedVars / orPreparedVars === -Same as andPrepared / orPrepared but you can provide a jdito-variable instead of a value. -If this variable doesn't exist or is empty, no condition is added. - -[source,javascript] ----- -myDescriptiveNameOfTheCondition.andPrepareVars("COMM.RELATION_ID", "$param.RelId_param"); ----- - -=== andSqlCondition / orSqlCondition === -adds another SqlCondition object - -[source,javascript] ----- -myDescriptiveNameOfTheCondition.andSqlCondition(SqlCondition.begin() - .orPrepare("PRODUCTPRICE.VALID_TO", today, "# >= ?") - .or("PRODUCTPRICE.VALID_TO is null"), "1 = 2"); ----- - -=== toString === -Returns the condition as SQL string with ? for each prepared value. -You can provide a default condition if it is empty. - -[source,javascript] ----- -var myConditionSql = myDescriptiveNameOfTheCondition.toString("1=0"); ----- - -=== toWhereString === -same as toString, but appends a where in front of it - -[source,javascript] ----- -var myConditionSql = myDescriptiveNameOfTheCondition.toWhereString("1=0"); ----- - -=== preparedValues === -This is no method but a member variable. -It contains the array with the prepared values. - -[source,javascript] ----- -var myValues = myDescriptiveNameOfTheCondition.preparedValues; ----- - -=== build === -Combines toString with the prepared values just like Adito needs it. - -[source,javascript] ----- -var myPreparedStatementArray = myDescriptiveNameOfTheCondition.build("1=0"); ----- - -=== buildSql === -Same as build and adds a string before and after the condition. - -[source,javascript] ----- -var myPreparedStatementArray = myDescriptiveNameOfTheCondition.buildSql("select * from PERSON", "1=0", "order by FIRSTNAME"); ----- - -== real world example == -Some examples, how this lib can be used. - -[source,javascript] ----- -var productPriceData = (db.ROW, db.array(SqlCondition.begin() - .andPrepare("PRODUCTPRICE.BUYSELL", buySell) - .andPrepare("PRODUCTPRICE.PRODUCT_ID", pid) - .andPrepare("PRODUCTPRICE.VALID_FROM", today, "# <= ?") - .andSqlCondition(SqlCondition.begin() - .orPrepare("PRODUCTPRICE.VALID_TO", today, "# >= ?") - .or("PRODUCTPRICE.VALID_TO is null"), "1 = 2"); - .buildSql("select PRICE, CURRENCY from PRODUCTPRICE", "1 = 2", "order by VALID_FROM desc"))); ----- - -With a loop: -[source,javascript] ----- -function _getClassificationCondition(pInclude) -{ - var resultingCondition = new SqlCondition(); - resultingCondition.andPrepare("SALESPROJECT_CLASSIFICATION.SALESPROJECT_ID", salesprojectId) - .andPrepare("SALESPROJECT_CLASSIFICATION.CLASS", classId); - - var typeCondition = new SqlCondition(); - - entryKeywords.forEach(function(entry) - { - if (pInclude) - { - typeCondition.orPrepare("SALESPROJECT_CLASSIFICATION.TYPE", entry[0], "# = ?"); - } - else - { - typeCondition.andPrepare("SALESPROJECT_CLASSIFICATION.TYPE", entry[0], "# <> ?"); - } - }); - - resultingCondition.andSqlCondition(typeCondition, "1 = 0"); - return resultingCondition; -} ----- - -Condition as join is also possible. It is also an example on how to use aliases: -[source,javascript] ----- -joins.push("left join PRODUCTPRICE validPP on " - + db.translateCondition(SqlCondition.begin() - .and("validPP.PRODUCT_ID = PRODUCTID") - .andPrepare(["PRODUCTPRICE", "CURRENCY", "validPP"], priceListFilter.currency) - .andPrepare(["PRODUCTPRICE", "VALID_FROM", "validPP"], datetime.date().toString(), "# <= ?") - .andPrepare(["PRODUCTPRICE", "FROMQUANTITY", "validPP"], priceListFilter.quantity, "# <= ?") - .andSqlCondition(SqlCondition.begin() - .orPrepare(["PRODUCTPRICE", "RELATION_ID", "validPP"], priceListFilter.relationId, "# <= ?") - .orSqlCondition(SqlCondition.begin() - .and("validPP.RELATION_ID is null") - .andPrepare(["PRODUCTPRICE", "BUYSELL", "validPP"], 'SP'), - "1 = 2"), - "1 = 2") - .build("1 = 2"))) ----- - -== simple wrapper == -There are a few wrapper for often used conditions. -They return an already built prepared condition. - -equals -[source,javascript] ----- -db.updateData("OFFER", cols, null, offerItems.getNetAndVat(), SqlCondition.equals("OFFER.OFFERID", oid, "1 = 2")); ----- - -equalsNot -[source,javascript] ----- -db.updateData("OFFER", cols, null, offerItems.getNetAndVat(), SqlCondition.equalsNot("OFFER.OFFERID", oid, "1 = 2")); ----- \ No newline at end of file diff --git a/process/Sql_lib/documentation.adoc b/process/Sql_lib/documentation.adoc index c02447e967..4071937329 100644 --- a/process/Sql_lib/documentation.adoc +++ b/process/Sql_lib/documentation.adoc @@ -146,6 +146,7 @@ If used as pField, you can provide the table and collumn in different ways. * as string: "TABLENAME.COLUMNNAME" (this only works if you have no '.' in your names and if you don't need a table alias * as array: ["TABLENAME", "COLUMNNAME"] * as array with tableAlias: ["TABLENAME", "COLUMNNAME", "tableAlias"] Here the TABLENAME is used to load the SQLTYPE and tableAlias is used for generating the sql. +* as SqlBuilder containing a full subselect. -> pFieldType and pValue have to be set also if you are using this variant. * pValue This is the value for the condition: @@ -196,8 +197,8 @@ The following examples are all perfectly valid: * just an array of strings: ["CAMPAIGNCOSTID", "CAMPAIGNSTEP_ID", "CAMPAIGNSTEP.NAME", "CATEGORY", "NET"] * additional keywords like distinct: "distinct CAMPAIGNCOSTID, CAMPAIGNSTEP_ID, CAMPAIGNSTEP.NAME, CATEGORY, NET" * array and additional keyword: ["distinct CAMPAIGNCOSTID", "CAMPAIGNSTEP_ID", "CAMPAIGNSTEP.NAME", "CATEGORY", "NET"] -* subselect: newSelect("ORGANISATION.\"NAME\").from("ORGANISATION").where("ORGANISATION.CONTACT_ID = CONTACT.CONTACTID") -* array with strings and subselects: ["CAMPAIGNCOSTID", "CAMPAIGNSTEP_ID", "CAMPAIGNSTEP.NAME", "CATEGORY", "NET", newSelect("ORGANISATION.\"NAME\").from("ORGANISATION").where("ORGANISATION.CONTACT_ID = CONTACT.CONTACTID")] +* subselect: newSelect("ORGANISATION.\"NAME\").from("ORGANISATION").where("ORGANISATION.CONTACT_ID = CONTACT.CONTACTID").subselectAlias("orgname") +* array with strings and subselects: ["CAMPAIGNCOSTID", "CAMPAIGNSTEP_ID", "CAMPAIGNSTEP.NAME", "CATEGORY", "NET", newSelect("ORGANISATION.\"NAME\").from("ORGANISATION").where("ORGANISATION.CONTACT_ID = CONTACT.CONTACTID").subselectAlias("orgname")] === from from(pTable, pTableAlias) adds a from-part to the sql. @@ -597,7 +598,8 @@ Only use this if you really need a string. Use .build() if you can use prepared === Recommended steps for conversion -Search for "SqlConditon" for each find: +Search for "SqlConditon". +For each find: * See if it is used as condition or if there is somewhere a .buildSql with a full select. * If it's a condition you may use newWhere(...) / newWhereIfSet(...). diff --git a/process/Sql_lib/process.js b/process/Sql_lib/process.js index 0daa4583b7..d46cfdfb0e 100644 --- a/process/Sql_lib/process.js +++ b/process/Sql_lib/process.js @@ -21,6 +21,14 @@ import("Util_lib"); * Please see .select() for more information and examples. * @param {String} [pAlias=currentAlias] This alias is used for fetching the ColumnTypes and also for the .table, .cell, .updateData, ... -functions * @return {SqlBuilder} A new SqlBuilder object already containing the provided fields + * + * @example + * var lastname = "Huber"; + * + * var persons = newSelect("FIRSTNAME") + * .from("PERSON") + * .where("PERSON.LASTNAME", lastname) + * .arrayColumn(); */ function newSelect(pFields, pAlias) { @@ -48,6 +56,26 @@ function newSelect(pFields, pAlias) * Please see .where() for more information and examples. * @param {String} [pAlias=currentAlias] This alias is used for fetching the ColumnTypes and also for the .table, .cell, .updateData, ... -functions * @return {SqlBuilder} A new SqlBuilder object which already called .where + * + * @example + * // examples, how where / whereIfSet could be used in a conditionProcess. + * //////Example 1///// + * var cond = newWhereIfSet("CONTACT.PERSON_ID", "$param.PersonId_param") + * .andIfSet("CONTACT.PERSON_ID", JSON.parse(vars.getString("$param.BlacklistPersons_param")), SqlBuilder.NOT_IN()) + * + * result.string(cond.toString()); + * + * //////Example 2///// + * var cond = newWhere(); + * + * // note: we can use .and* now without an extra .where + * if (SOMECHECKS) + * cond.andIfSet(...) + * + * if (SOME_MORE_CHECKS) + * cond.and(...) + * + * result.string(cond.toString()); */ function newWhere(pFieldOrCond, pValue, pCondition, pFieldType, pAlias) { @@ -57,7 +85,7 @@ function newWhere(pFieldOrCond, pValue, pCondition, pFieldType, pAlias) /** * Creates a new SqlBuilder object and calls .whereIfSet on it.<br/> * This is very useful if you just need a condition as you can pass the first condition directly.<br/> - * Note: Even if you ommit all parameters, you do not have to call .whereIfSet again. You can directly write .and / .or after newWhere(). + * Note: Even if you ommit all parameters, you do not have to call .where again. You can directly write .and / .or after newWhere(). * * @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/> @@ -75,6 +103,26 @@ function newWhere(pFieldOrCond, pValue, pCondition, pFieldType, pAlias) * Please see .whereIfSet() for more information and examples. * @param {String} [pAlias=currentAlias] This alias is used for fetching the ColumnTypes and also for the .table, .cell, .updateData, ... -functions * @return {SqlBuilder} A new SqlBuilder object which already called .whereIfSet + * + * @example + * // examples, how where / whereIfSet could be used in a conditionProcess. + * //////Example 1///// + * var cond = newWhereIfSet("CONTACT.PERSON_ID", "$param.PersonId_param") + * .andIfSet("CONTACT.PERSON_ID", JSON.parse(vars.getString("$param.BlacklistPersons_param")), SqlBuilder.NOT_IN()) + * + * result.string(cond.toString()); + * + * //////Example 2///// + * var cond = newWhere(); + * + * // note: we can use .and* now without an extra .where + * if (SOMECHECKS) + * cond.andIfSet(...) + * + * if (SOME_MORE_CHECKS) + * cond.and(...) + * + * result.string(cond.toString()); */ function newWhereIfSet(pFieldOrCond, pValue, pCondition, pFieldType, pAlias) { @@ -303,7 +351,6 @@ SqlBuilder.prototype.from = function(pTable, pTableAlias) /** * Adds a join clause to the sql. -´* * @param {String|SqlBuilder} pTable if it is a String, it is used as it is as table<br/> * if it is a SqlBuilder, it is used as subselect: e.g. select * from Table1 join (select FIRSTNAME from PERSON) on ... * @param {String|SqlBuilder} [pCondition] The where condition. This can be<br/> @@ -699,6 +746,7 @@ SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pMandatory, pCon if (pFieldOrCond !== undefined && pValue === undefined && pCondition === undefined && pFieldType === undefined) return this._whereCondition(pFieldOrCond, pMandatory, pAddPreparedConditionCallback, true); + // Subselects containing full select can be used as field, if pValue and pFieldType are provided. if (pFieldOrCond instanceof SqlBuilder) { if (!pFieldOrCond.isFullSelect()) @@ -726,7 +774,7 @@ SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pMandatory, pCon 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 + // OR !pFieldOrCond and pValue and pCondition is given -> preparedSQL/SqlBuilder can be used without field if pCondition is set (e.g. with "exists ?") if(((typeof pFieldOrCond == "string" || Array.isArray(pFieldOrCond) || (!pFieldOrCond && pFieldType)) || (!pFieldOrCond && (pCondition && pValue instanceof SqlBuilder || !(pValue instanceof SqlBuilder))))) { var field = pFieldOrCond; @@ -746,10 +794,7 @@ SqlBuilder.prototype._addWhere = function(pFieldOrCond, pValue, pMandatory, pCon return this; } - - if (!pCondition) - pCondition = "# in ?"; - + // if it is null -> ignore it. -> the pCondition should not contain a # in this case if (field != null) { @@ -2775,7 +2820,7 @@ SqlUtils.replaceConditionTemplate = function(pCondition, pPlaceholder, pReplacem 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 + // so we just use replaceConditionTemplate to replace by something which never should occur anywhere (it uses text.replaceAll which supports lookbehind because it uses java) pCondition = SqlUtils.replaceConditionTemplate(pCondition, "#", "{@NUMBERSIGN@}") pCondition = SqlUtils.replaceConditionTemplate(pCondition, "\\?", "{@QUESTIONSIGN@}") -- GitLab