Skip to content
Snippets Groups Projects
Commit f83c9b37 authored by Johannes Hörmann's avatar Johannes Hörmann
Browse files

Sql lib docu refactor

parent b44a43c2
No related branches found
No related tags found
No related merge requests found
......@@ -3,124 +3,145 @@
:hardbreaks:
= SqlBuilder
This Description may not contain all features of the Builder. Most usages should be possible in an intuitive way, so just try if it already works like you think it should work.
////
(required only for separate PDF generation)
////
All parameters are documented in the lib itself.
*Please Read it*.
You may also take a look at the *SqlLib_tests* as it contains many possible ways to use the SqlBuilder. (By the way this tests use the UnitTest_lib for unittesting. It may be something you could use somewhere else, too...)
include::_default_attributes_EN.adoc[]
== Why
<<<
== Preface
This document describes the functionality and the usage of the SqlBuilder, which is included in the library *Sql_lib* of the ADITO xRM project (see "Projects" window, under process > libraries). The documentation may not contain all features of the SqlBuilder. It is supplemental to the documentation you find in the code itself: A usage will often be possible in an intuitive way, so just try coding using code completion and JSDoc, where all parameters are documented.
You may also take a look at the library *SqlLib_tests* (also under process > libraries), as it contains many possible ways to use the SqlBuilder.
[NOTE]
The tests included in the library SqlLib_tests use the UnitTest_lib for unit testing. You can use this functionality also in other contexts, according to your requirements.
== Benefits
=== Prepared Statements
Prepared Statements should *always* be used because of
* *Security* (Sql injection): If you are used to something like
`"select * from PERSON where PERSONID = '" + persId + "'"`
you *WILL* forget to use prepared statements if it is really nessecary:
The SQL builder applies Prepared Statements (if used correctly). These should *always* be used for reasons of
* *Security* (prevention of "SQL injection" see also `AID068`):
This is the *most important* aspect.
In SQL statements like
`"select * from PERSON where FIRSTNAME = '" + userInput + "'"`
Here the user could input also any Sql.
If you are used to Prepared Statements even if you just select for ID's, the security and quality of your code will be much higher.
* Speed: If the server executes the prepared statement, it may improve the performance compared to executing the sql-string.
the user could input also any - possibly harmful - SQL code. This will be avoided by using Prepared Statements: Even if you just select for IDs, the security and the quality of your code will be much higher. Because you are used to using Prepared Statements and won't forget them when they are really needed. (e.g. direct user input)
* *Speed*: If the server executes a Prepared Statement, the performance will, in many cases, be higher compared to executing a self-made SQL code.
For me the most important aspect is the security.
=== Usability
=== Problem
Prepared Statements in JDito are not very easy to use, because you have to load the datatypes for the columns manually and create an array like this:
Creating Prepared Statements manually in JDito requires to load the data types of the columns and to create an array like this:
`["select * from PERSON where FIRSTNAME = ?", \[[userinput, SQLTYPES.VARCHAR]]]`
=== Solution
The SqlBuilder (former SqlCondition) provides methods to add conditions without manually loading the type.
So this:
This is much easier using the SqlBuilder (formerly named SqlCondition), as it provides methods to add conditions without having to load the data type manually.
Instead of formerly writing
[source,js]
----
var type = db.getColumnTypes("PERSON", ["FIRSTNAME"])[0]
var persons = db.table(["select FIRSTNAME, LASTNAME from PERSON where FIRSTNAME = ?", [[usereInput, type]]]);
----
becomes this:
you can now use the functionality of the SqlBuilder:
[source,js]
----
var persons = newSelect("FIRSTNAME, LASTNAME")
.from("PERSON")
.where("PERSON.FIRSTNAME", userInput)
.table()
.table();
----
which is much cleaner code.
which is much cleaner code and more easy to write.
<<<
== Basic usage
=== new SqlBuilder creation
Basically you can create a new builder by
Basically, you can create a new builder by
[source,js]
----
var personSelect = new SqlBuilder()
.select("FIRSTNAME, LASTNAME")
.from("PERSON")
.where("PERSON.PERSONID", persId)
.where("PERSON.PERSONID", persId);
----
or with the shorthand function newSelect()
or with the shorthand function `newSelect()`
[source,js]
----
var personSelect = newSelect("FIRSTNAME, LASTNAME")
.from("PERSON")
.where("PERSON.PERSONID", persId)
.where("PERSON.PERSONID", persId);
----
It is also possible to only use the builder for *conditions*:
It is also possible to use the builder only for *conditions*:
[source,js]
----
var personCond = new SqlBuilder()
.where("PERSON.PERSONID", persId)
.where("PERSON.PERSONID", persId);
----
or with the shorthand function newWhere()
or with the shorthand function `newWhere()`
[source,js]
----
var personCond = newWhere("PERSON.PERSONID", persId)
var personCond = newWhere("PERSON.PERSONID", persId);
----
=== Conditions
For the conditions we have the methods:
* where
* and
* or
For the conditions we have the methods
and for each of them also a version with *IfSet: (see 2.2.3 for the difference)
* `where`
* `and`
* `or`
* whereIfSet
* andIfSet
* orIfSet
and for each of them also a variant with *IfSet: (see chapter "*IfSet")
* `whereIfSet`
* `andIfSet`
* `orIfSet`
==== where
Has to be always used for the first condition.
This is mainly for a better semantic:
`where` has always to be used as the first condition. This is mainly for a better semantic:
[source,js]
----
new SqlCondition.select("COL1")
.from("TAB1")
.where("TAB1.COL2", "myVal")
.and("TAB1.COL3", "myVal")
.and("TAB1.COL3", "myVal");
----
looks better and more intuitive than:
looks better and more intuitive to read than
[source,js]
----
new SqlCondition.select("COL1")
.from("TAB1")
.and("TAB1.COL2", "myVal")
.and("TAB1.COL3", "myVal")
.and("TAB1.COL3", "myVal");
----
-> if you add a condition, the where keyword is mandatory.
Especially if you are used to normal SQL strings the missing where may be confusing.
newWhere() already includes the where. So this is perfectly valid:
Therefore, if you add a condition, the `where` keyword is mandatory.
`newWhere()` already includes the where. So this is perfectly valid:
[source,js]
----
newWhere("TAB1.COL2", "myVal")
.and("TAB1.COL3", "myVal")
.and("TAB1.COL3", "myVal");
----
It is also valid to use .where without parameters. So something like this is possible:
It is also valid to use `newWhere` without parameters, if you want to add the parameters later:
[source,js]
----
var cond = newWhere();
......@@ -136,98 +157,101 @@ if (someOtherCondition...)
}
----
===== Parameters
where() can have the following parameters:
* pFieldOrCond
This is eithoer for specifying the field for the condition
(e.g. if you need the sql-condition: "FIRSTNAME = 'Fritz'" then this parameter can be "PERSON.FIRSTNAME").
Or you can provide a already complete condition if all other parameters are not used.
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:
(e.g. if you need the sql-condition: "FIRSTNAME = 'Fritz'" then this parameter is "Fritz")
* pCondition
This parameter defines which condition is used. You can use any SQL. # will be replaced by the pFieldOrCond and ? is used for the pValue (default is "# = ?")
(e.g. if you need the sql-condition: "FIRSTNAME <> 'Fritz'" then this parameter is "# <> ?")
* pFieldType
This parameter is for specifying the SQLTYPE explicitely.
Most of the time you won't need it, as the type is loaded from the db by the Tablename and Collumname you specified in pFieldOrCond
`where()` can have the following parameters:
* *pFieldOrCond*
This is either for specifying the field for the condition
(e.g. if you need the SQL condition "FIRSTNAME = 'John'" then this parameter can be "PERSON.FIRSTNAME").
Or you can provide an already complete condition if all other parameters are not used.
If used as pField, you can provide the table and column 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 SQL data type, and tableAlias is used for generating the SQL code.
* *pValue*
This is the value of the condition: If, e.g., you need the SQL condition "FIRSTNAME = 'John'" then this parameter is "John")
* *pCondition*
This parameter defines what condition is used. You can use any SQL. # will be replaced by the pFieldOrCond and ? is used for the pValue (default is "# = ?"). If, e.g., you need the SQL condition "FIRSTNAME <> 'John'" then this parameter is "# <> ?")
* *pFieldType*
This parameter is for specifying the SQLTYPE explicitly. In most cases, you won't need it, as the type is loaded from the db by the table name and the column name you specified in pFieldOrCond.
==== and / or
After you called .where somehow, you can add more conditions by using .and() / .or() they work exactly like .where() and also have the same parameters
After you have called `.where`, you can add further conditions by using `.and()` or `.or()`. They work exactly like `.where()` and also have the same parameters.
==== *IfSet
The addition ot "IfSet" to the methods .where, .and, .or makes the condition _optional_.
This means:
While calling .where with a pValue which is null or undefined will result in an error:
The addition of the suffix "IfSet" to the methods `.where`, `.and`, or `.or` makes the condition _optional_. This means: While calling `.where` with a pValue that is `null`, `undefined` or an empty Array `[]` will result in an *error*:
[source,js]
----
var value = null;
var cond = newWhere("TAB1.COL2", null)
.and("TAB1.COL3", value)
.and("TAB1.COL3", value);
// execution stops with error
----
whereIfSet will just ignore the whole condition
`whereIfSet` will just *ignore* the whole condition in these cases:
[source,js]
----
var value = null;
var cond = newWhereIfSet("TAB1.COL2", null)
.and("TAB1.COL3", value)
.and("TAB1.COL3", value);
// code is executed and cond.build will result in
// ["TAB1.COL3 = ?", [['myVal', SQLTYPES.VARCHAR]]];
// code is executet and cond.build will result in ["TAB1.COL3 = ?", [['myVal', SQLTYPES.VARCHAR]]]
// As you can see. TAB1.COL2 is just ignored completely
// As you can see, TAB1.COL2 is ignored completely.
----
=== select
select(pFields) just sets all fields defined to the select-section of the sql.
You may pass them as string, SqlBuilder (for subselects) or array (of strings and sqlbuilders)
The following examples are all perfectly valid:
`select(pFields)` just sets all fields required in the select part of the SQL code.
You may pass them as string, SqlBuilder (for subselects), or array (of strings and SqlBuilder objects).
The following examples for pFields are all perfectly valid:
* just a *string* (note: aliases are possible): "CAMPAIGNCOSTID idAlias, CAMPAIGNSTEP_ID, CAMPAIGNSTEP.NAME, CATEGORY, NET"
* just an *array of strings*: ["CAMPAIGNCOSTID", "CAMPAIGNSTEP_ID", "CAMPAIGNSTEP.NAME", "CATEGORY", "NET"]
* *string* with *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")]
If you use subselects, you may add an alias for the whole subselecct by using *.subselectAlias("anTableAlias")*:
* just a string (note: aliases are possible): "CAMPAIGNCOSTID idAlias, CAMPAIGNSTEP_ID, CAMPAIGNSTEP.NAME, CATEGORY, NET"
* 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").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")]
* *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.
* pTable This can either be a String or a SqlBuilder
You may use only the tablename or (not recommended) a full from clause already containing joins. (It is better to use .join for this)
Subselects by using a SqlBuilder are also working
* pTableAlias just adds an alias-name for the table.
`from(pTable, pTableAlias)` adds a `from` part to the SQL code.
* *pTable*
This can either be a string or an SqlBuilder object. You may use only the table name. (It would be valid, but it is not recommended, to use joins here.)
Subselects using an SqlBuilder are also working.
* *pTableAlias* just adds the name of an alias to be applied for the table.
=== join
A join can be added by using
* .join(pTable, pCondition, pTableAlias, pPrefix, pReplacementForWordJoin)
* .leftJoin(pTable, pCondition, pTableAlias)
* .rightJoin(pTable, pCondition, pTableAlias)
A join can be applied by using
* `.join(pTable, pCondition, pTableAlias, pPrefix, pReplacementForWordJoin)`
* `.leftJoin(pTable, pCondition, pTableAlias)`
* `.rightJoin(pTable, pCondition, pTableAlias)`
The parameters are:
* pTable is just the table name as string.
(it is possible but not recommended to use a whole join-string for the table e.g. "PERSON on PERSONID = PERSON_ID". Only use this, if you don't have a seperate string for the on-condition.)
Subselects are also possible by using a SqlBuilder
(e.g. newSelect("FIRSTNAME").from("PERSON"))
* pCondition is the on-condition.
You can provide it as String (e.g. "PERSONID = PERSON_ID")
or as SqlBuilder (e.g. newWhere("PERSON.PERSONID", myPersonid))
* pTableAlias just adds an alias-name for the table.
* pPrefix here you can specify a prefix for the join. (e.g. "left")
.leftJoin / .rightJoin fill this automatically
* pReplacementForWordJoin is for using something different as the word "join". This is added to support db-specific use cases.
* *pTable* is just the table name as string. It is possible, but not recommended to use a whole join string for the table e.g. "PERSON on PERSONID = PERSON_ID"; only use this if you don't have a separate string for the `on` condition.
Subselects are also possible by using an SqlBuilder object (e.g. `newSelect("FIRSTNAME").from("PERSON")`).
* *pCondition* is the on-condition.
You can provide it as string (e.g., "PERSONID = PERSON_ID") or as SqlBuilder object (e.g., `newWhere("PERSON.PERSONID", myPersonid)`)
* *pTableAlias* just adds an alias name to be applied for the table.
* *pPrefix*: Here, you can specify a prefix for the join (e.g., "left").
`.leftJoin` / `.rightJoin` fulfill this automatically.
* *pReplacementForWordJoin* is for replacing the word "join" by another term. This possibility had been added to support database specific use cases.
An example:
[source,js]
......@@ -238,42 +262,46 @@ var costData = newSelect("CAMPAIGNCOSTID, CAMPAIGNSTEP_ID, CAMPAIGNSTEP.NAME, CA
.where("CAMPAIGNCOST.CAMPAIGN_ID", campaignId)
.table();
----
=== group by
groupBy(pFields) just adds a group by to the sql
the parameter can be filled the same way as .select(pFields)
`groupBy(pFields)` adds a group by statement to the SQL code. The parameter can be filled the same way as `.select(pFields)`.
=== having
having(pCondition) adds a having condition to the sql.
pCondition can be a simple string or a SqlBuilder containing a condition. (only the condition from it is used)
`having(pCondition)` adds a having condition to the SQL code. pCondition can be a simple string or an SqlBuilder object containing a condition (only its condition is used).
=== order by
orderBy(pFields) just adds a order by to the sql
the parameter can be filled the same way as .select(pFields)
`orderBy(pFields)` adds an order by statement to the SQL code. The parameter can be filled the same way as `.select(pFields)`.
=== db. function wrappers
==== select-functions
There are some wrappers for db. functions directly in the SqlBuilder mainly for convenience and to prevent sending selects like "select FIRSTNAME from PERSON where 1 = 2" to the dbms.
==== select functions
As you probably know, JDito includes functions for accessing the database, starting with `db.`. The structure of the result is different from function to function. For example, `db.cell("SELECT ... from ...")` returns value of the first column of the first row of the SQL statement's result, i.e., one single value, as string.
Now, there are some wrappers for `db.` functions available directly in the SqlBuilder, mainly for convenience reasons, and to prevent sending select statements like "select FIRSTNAME from PERSON where 1 = 2" to the database.
Note: you can still use the db. functions if you like.
The functions
cell
arrayRow
arrayColumn
arrayPage
table
tablePage
`.cell()`
`.arrayRow()`
`.arrayColumn()`
`.arrayPage()`
`.table()`
`.tablePage()`
All of them have an extra parameter pExecuteOnlyIfConditionExists, no pAlias and no pSQL.
determine the structure of the result in the same way as the do when called on `db.`. But, when used in the SqlBuilder, all these functions have an extra parameter pExecuteOnlyIfConditionExists; however, they do not have the parameters pAlias and pSQL.
* pExecuteOnlyIfConditionExists is a boolean which defaults to false
- If it is false and the builder currently has no condition (e.g. because of *IfSet's), the executed select will also contain no condition -> selects all
- If it is true the select will only be executed if there is a condition. Else it will return "" or [] based on which method you used.
This is to prevent sending selects like "select FIRSTNAME from PERSON where 1 = 2" to the dbms
-> Note: if not all of your conditions are *IfSet's, this case will never happen, as the Builder always contains a condition in this case. -> you can ommit the param in most cases
pExecuteOnlyIfConditionExists is a boolean, which defaults to false.
cell is a special case as it contains another param: pFallbackValue
I added it, because in the basic is often a count(*):
* If it is false and the builder currently has no condition (e.g. because of *IfSets), the executed select statement will also contain no condition and therefore select all data sets.
* If it is true, the select will only be executed if there is a condition; else it will return "" or [] based on which method you used. This is to prevent sending selects like "select FIRSTNAME from PERSON where 1 = 2" to the data base.
Note: If not all of your conditions are *IfSets, this case will never happen, as the builder always contains a condition in this case. Thus, you can omit the parameter in most cases.
`cell` is a special case, as it contains another parameter: pFallbackValue
This parameter was introduced, because in SQL code using count(*)
[source,js]
----
var count = newSelect("count(*)")
......@@ -281,15 +309,25 @@ var count = newSelect("count(*)")
.whereIfSet("CLASSIFICATION.CLASSIFICATIONTYPE_ID", "$field.CLASSIFICATIONTYPEID")
.cell(true, "0");
----
and in this case a default of "0" is more convenient than ""
a default of "0" is more convenient than.
[NOTE]
You can still use the above `db.` functions outside the SqlBuilder, if you like.
==== update/delete-functions
updateData
deleteData
they also have the pExecuteOnlyIfConditionExists.
In most cases you will set it to true because you will not want to delte or update ALL DATA.
-> if you set it to true and only *IfSet's are used, no update/delete will be executed if the SqlBuilder contains no condition.
The functions
`updateData()`
`deleteData()`
also have the parameter pExecuteOnlyIfConditionExists.
[WARNING]
In most cases you will set the parameter pExecuteOnlyIfConditionExists to true, because you never want to delete or update *all data*.
If you set the parameter pExecuteOnlyIfConditionExists to true and only *IfSets are used, no update/delete will be executed, if the SqlBuilder contains no condition.
Example:
[source,js]
......@@ -298,9 +336,12 @@ newWhereIfSet("AB_OBJECTRELATION.AB_OBJECTRELATIONID", objectRelationId)
.updateData(true, "AB_OBJECTRELATION", ["INFO"], null, [vars.get("$field.INFO")]);
----
== Examples and Use Cases
=== already complete condition
<<<
== Examples and use cases
=== Complete condition
If the first parameter is the only one you set, it is treated as a complete condition:
[source,js]
......@@ -308,227 +349,218 @@ If the first parameter is the only one you set, it is treated as a complete cond
var name = 'Admin'
var cond = newWhere("PERSON.FIRSTNAME is not null") // a simple String
.and(newWhere("PERSON.LASTNAME", name)) // another SqlBuilder (note: only the condition is used from it)
.or(["FIRSTNAME is null and LASTNAME = ?", [["Meier", SQLTYPES.VARCHAR]]]) // a prepared statement array
.or(["FIRSTNAME is null and LASTNAME = ?", [["Smith", SQLTYPES.VARCHAR]]]); // a prepared statement array
//cond.build() will result in ["( (PERSON.FIRSTNAME = 'Tim' and PERSON.LASTNAME = 'Admin' ) or (PERSON.FIRSTNAME = 'Peter' ) and PERSON.LASTNAME = 'Meier' ) ", [['Tim', SQLTYPES.VARCHAR], ['Admin', SQLTYPES.VARCHAR], ['Peter', SQLTYPES.VARCHAR], ['Meier', SQLTYPES.VARCHAR]]]
//cond.build() will result in ["( (PERSON.FIRSTNAME = 'John' and PERSON.LASTNAME = 'Admin' ) or (PERSON.FIRSTNAME = 'Peter' ) and PERSON.LASTNAME = 'Smith' ) ", [['John', SQLTYPES.VARCHAR], ['Admin', SQLTYPES.VARCHAR], ['Peter', SQLTYPES.VARCHAR], ['Smith', SQLTYPES.VARCHAR]]];
----
This is also usefull for creating Brackets e.g. for smth. like "(FIRSTNAME = 'Tim' or FIRSTNAME = 'Fritz) and LASTNAME = 'Admin'"
This is also useful when using brackets, e.g. for something like "(FIRSTNAME = 'John' or FIRSTNAME = 'Paul') and LASTNAME = 'Admin'"
[source,js]
----
var cond = newWhere(newWhere("PERSON.FIRSTNAME", "Tim")
.or("PERSON.FIRSTNAME", "Fritz"))
.and("PERSON.FIRSTNAME", "Peter")
// ( ( PERSON.FIRSTNAME = 'Tim' or PERSON.FIRSTNAME = 'Fritz' ) and PERSON.FIRSTNAME = 'Peter' )
var cond = newWhere(newWhere("PERSON.FIRSTNAME", "John")
.or("PERSON.FIRSTNAME", "Paul"))
.and("PERSON.FIRSTNAME", "Peter");
// ( ( PERSON.FIRSTNAME = 'John' or PERSON.FIRSTNAME = 'Paul' ) and PERSON.FIRSTNAME = 'Peter' ) ;
----
By using another newWhere the Tim and Fritz are grouped together.
By using another `newWhere()`, 'John' and 'Paul' are grouped together.
As comparison without the grouping:
[source,js]
----
var cond = newWhere("PERSON.FIRSTNAME", "Tim")
.or("PERSON.FIRSTNAME", "Fritz")
.and("PERSON.FIRSTNAME", "Peter")
// ( PERSON.FIRSTNAME = 'Tim' or PERSON.FIRSTNAME = 'Fritz' and PERSON.FIRSTNAME = 'Peter' )
var cond = newWhere("PERSON.FIRSTNAME", "John")
.or("PERSON.FIRSTNAME", "Paul")
.and("PERSON.FIRSTNAME", "Peter");
// ( PERSON.FIRSTNAME = 'John' or PERSON.FIRSTNAME = 'Paul' and PERSON.FIRSTNAME = 'Peter' );
----
=== simple condition with and & or
You can just combine and & or as you like:
=== Simple condition with and & or
You can combine and & or as you like:
[source,js]
----
var cond = newWhere("PERSON.FIRSTNAME", "Tim")
var cond = newWhere("PERSON.FIRSTNAME", "John")
.and("PERSON.LASTNAME", "Admin")
.or("PERSON.FIRSTNAME", "Peter")
.and("PERSON.LASTNAME", "Meier")
.and("PERSON.LASTNAME", "Smith");
//cond.build() will result in ["( (PERSON.FIRSTNAME = 'Tim' and PERSON.LASTNAME = 'Admin' ) or (PERSON.FIRSTNAME = 'Peter' ) and PERSON.LASTNAME = 'Meier' ) ", [['Tim', SQLTYPES.VARCHAR], ['Admin', SQLTYPES.VARCHAR], ['Peter', SQLTYPES.VARCHAR], ['Meier', SQLTYPES.VARCHAR]]]
//cond.build() will result in ["( (PERSON.FIRSTNAME = 'John' and PERSON.LASTNAME = 'Admin' ) or (PERSON.FIRSTNAME = 'Peter' ) and PERSON.LASTNAME = 'Smith' ) ", [['John', SQLTYPES.VARCHAR], ['Admin', SQLTYPES.VARCHAR], ['Peter', SQLTYPES.VARCHAR], ['Smith', SQLTYPES.VARCHAR]]];
----
(Note: the AND before OR: (Tim && Admin) || (Peter && Meier))
=== JDito variables
=== JDito Variables
It is also possible to just pass JDito variables directly:
[source,js]
----
newWhere("PERSON.FIRSTNAME", "$field.FIRSTNAME")
----
Note: it is checked for vars.getString(...) != null and vars.getString(...) != undefined
If you want to check also for != "" and vars.exists(...) you have to do it by yourself explicitely.
Note: This function includes checks for `vars.getString(...) != null` and `vars.getString(...) != undefined`.
If you want to check also for `!= ""` and `vars.exists(...)`, you have to do it by yourself explicitly.
=== other condition than "# = ?"
If you need something different as the default "# = ?" condition just pass a string to pCondition.
Note that the # is replaced by the Table.Column from the first param and ? is the place where the value schould be inserted.
If you need something different from the default "# = ?" condition just pass a string to pCondition.
Note that # is replaced by the column of the table in the first parameter and ? is the place in which the value is to be inserted.
=== condition like "year(#) = ?" with specific FieldType
It is also possible to use more complex sql such as "year(#) = ?" But keep in mind that this may change the SQLTYPE needed and you may have to provede it manually by the 4th param if it cannot be loaded by the table and column name.
It is also possible to use more complex SQL, such as `year(#) = ?`. But keep in mind that this may change the required SQL data type, and you may have to provide it manually by the 4th parameter if it cannot be loaded by the table and column name.
=== Subquery
Subqueries are also possible by providing either another Sql condition or a prepared-statement array.
Some possible combinations:
* where("TABLE.FIELD", aSqlBuilderContainingFullSelect)
* where("TABLE.FIELD", aSqlBuilderContainingFullSelect, "# <> ?") -> with different condition
* where(null, aSqlBuilderContainingFullSelect, "exists ?") -> without a field: note that pCondition doesn't contain a # now.
* where(null, aSqlBuilderContainingFullSelect, SqlBuilder.EXISTS()) -> this is the same, but uses a predefined pCondition for exists.
* where("TABLE.FIELD", aPreparedSqlArray)
* ...
Subqueries are also possible by providing either another SQL condition or a Prepared Statement array.
Some possible combinations are (with aSqlBuilderContainingFullSelect and aPreparedSqlArray being a subselect):
aSqlBuilderContainingFullSelect or aPreparedSqlArray is the subselect.
* `where("TABLE.FIELD", aSqlBuilderContainingFullSelect)`
* `where("TABLE.FIELD", aSqlBuilderContainingFullSelect, "# <> ?")` -> with different condition
* `where(null, aSqlBuilderContainingFullSelect, "exists ?")` -> without a field: Note that pCondition shouldn't contain a # in this case.
* `where(null, aSqlBuilderContainingFullSelect, SqlBuilder.EXISTS())` -> This is the same as the preceding example, but uses a predefined pCondition for exists.
* `where("TABLE.FIELD", aPreparedSqlArray)`
Examples:
With SqlBuilder
[source,js]
----
var aSqlBuilderContainingFullSelect = newSelect("LASTNAME")
.from("PERSON")
.where("PERSON.FIRSTNAME", "Fritz")
var cond = newWhere("TABLE.FIELD", aSqlBuilderContainingFullSelect)
.where("PERSON.FIRSTNAME", "John");
var cond = newWhere("TABLE.FIELD", aSqlBuilderContainingFullSelect);
// cond.build() results in ["( TABLE.FIELD = ( select LASTNAME from PERSON where PERSON.FIRSTNAME = ? ) ) ", [['Fritz', SQLTYPES.VARCHAR]]]
// cond.build() results in ["( TABLE.FIELD = ( select LASTNAME from PERSON where PERSON.FIRSTNAME = ? ) ) ", [['John', SQLTYPES.VARCHAR]]]
----
With PreparedSqlArray
[source,js]
----
var aSqlBuilderContainingFullSelect = ["select LASTNAME from PERSON where PERSON.FIRSTNAME = ?", [['Fritz', SQLTYPES.VARCHAR]]]
var cond = newWhere("TABLE.FIELD", aSqlBuilderContainingFullSelect)
var aSqlBuilderContainingFullSelect = ["select LASTNAME from PERSON where PERSON.FIRSTNAME = ?", [['John', SQLTYPES.VARCHAR]]];
var cond = newWhere("TABLE.FIELD", aSqlBuilderContainingFullSelect);
// cond.build() results in ["( TABLE.FIELD = ( select LASTNAME from PERSON where PERSON.FIRSTNAME = ? ) ) ", [['Fritz', SQLTYPES.VARCHAR]]]
// cond.build() results in ["( TABLE.FIELD = ( select LASTNAME from PERSON where PERSON.FIRSTNAME = ? ) ) ", [['John', SQLTYPES.VARCHAR]]]
----
=== IN statement with array of values
pValue can also be an array of values which is automatically transfered into a SQL prepared statement SQL like this:
["(?, ?, ?)", [["Fritz", SQLTYPES.VARCHAR], ["Franz", SQLTYPES.VARCHAR], ["Max", SQLTYPES.VARCHAR]]]
["(?, ?, ?)", [["Peter", SQLTYPES.VARCHAR], ["Paul", SQLTYPES.VARCHAR], ["Mary", SQLTYPES.VARCHAR]]]
With this it is very easy to create a in-statement:
[source,js]
----
var cond = newWhere("PERSON.FIRSTNAME", ["Fritz", "Franz", "Max"], SqlBuilder.IN()) // SqlBuilder.IN() == "in ?"
// Note also SqlBuilder.NOT_IN() is possible
var cond = newWhere("PERSON.FIRSTNAME", ["Peter", "Paul", "Mary"], SqlBuilder.IN()) // SqlBuilder.IN() == "in ?"
// Note that also SqlBuilder.NOT_IN() is possible here
----
Note that an empty array is threated like null or undefined: using .where throws an error, using .whereIfSet ignores the whole condition.
=== IN statement with subquery
You can just use a Subquery together with the pCondition SqlBuilder.IN() or SqlBuilderNOT_.IN()
=== common usage in condition processes with $param variables
In the conditionProcess often conditions are built based on which parameter is given. This can be done very easy by using the *IfSet methods:
You can use a subquery together with the pCondition `SqlBuilder.IN()` or `SqlBuilder.NOT_IN()`
=== Common usage in condition processes with $param variables
In the property conditionProcess of a db record container, conditions are often built dependent from a given parameter. This can be done very easy by using the *IfSet methods. However, the conditionProcess currently does not support Prepared Statements.
Here is an example code of a conditionProcess:
[source,js]
----
// This is a example condition process containing some possible ways to use the SqlBuilder.
// Using newWhereIfSet() to create a new Condition.
// This is an example condition process containing some possible ways to use the SqlBuilder.
// newWhereIfSet() is used to create a new condition.
// The 'IfSet' ignores the condition "CONTACT.ORGANISATION_ID" = "$param.OrgId_param" if "$param.OrgId_param" is null
var cond = newWhereIfSet("CONTACT.ORGANISATION_ID", "$param.OrgId_param")
.andIfSet("PERSON.CONTACT_ID", "$param.ContactId_param"); // same again: if "$param.ContactId_param" is null, this line is ignored
// --> so if both parameters where null (not filled) the SqlBuilder cond won't contain any condition!
.andIfSet("PERSON.CONTACT_ID", "$param.ContactId_param"); // if "$param.ContactId_param" is null, this line is ignored
// --> If both parameters are null (not filled), then the SqlBuilder condition will not contain any condition!
// Now we add a parameter containing a json Array
// we have to parse it
var excludedContacts = JSON.parse(vars.getString("$param.ExcludedContactIds_param"));
// and add it to cond using not in --------------------------------V
// and add it to the condition using a "not in" operator
cond.andIfSet("CONTACT.CONTACTID", excludedContacts, SqlBuilder.NOT_IN());
// if excludedContacts is an empty array (or null), no condition is added as we used "andIfSet()"
// if you need a different behaviour you may wrap it by an if
// Same for onlyShowContactIds but by using an in-statement
// same for onlyShowContactIds, but by using an "in" operator.
// onlyShowContactIds contains an array.
var onlyShowContactIds = JSON.parse(vars.get("$param.OnlyShowContactIds_param"));
cond.andIfSet("CONTACT.CONTACTID", onlyShowContactIds, SqlBuilder.IN());
// The conditionProcesss doesn't support prepared statements. So .build() is currently not possible.
// The conditionProcess doesn't support Prepared Statements. Thus, .build() is currently not possible.
// using .toString() resolves all conditions and builds the finished select as string.
result.string(cond.toString());
----
== SqlCondition to SqlBuilder conversion help
<<<
== SqlCondition to SqlBuilder refactoring help
The SqlCondition is deprecated and removed completely in Basic 2020.
So you have to:
This chapter is only relevant for programmers who previously used functionality of the class SqlCondition. If you haven't used it before, you can skip this chapter, but, in any case, use the SqlBuilder instead of non-secure code like "...'" + myVal + "'..." (see chapter "Benefits").
* replace all SqlCondition usages by the SqlBuilder
* if you haven't used it before and use code like ...'" + myVal + "'...
START TO USIE IT. (See 1. Why)
* if you find something which cannot be built by the SqlBuilder -> new Featureticket for Basic
SqlCondition is deprecated and will be removed completely in ADITO xRM 2020. Therefore, you have to replace all SqlCondition usages by using the SqlBuilder.
For the first point some advice:
=== Differences between SqlCondition and SqlBuilder
It is not easily possible to replace the SqlCondition directly by the SqlBuilder. There are too many differences.
It is not possible just to replace the SqlCondition by the SqlBuilder. There are many differences in syntax and usage. The main differences are explained in the following sub-chapters:
=== Differences SqlCondition -> SqlBuilder
Here a list of the main differences:
==== Instantiation
==== Instanciation
*SqlCondition*
new SqlCondition(...)
SqlCondition.begin(...)
`new SqlCondition(...)`
`SqlCondition.begin(...)`
*SqlBuilder*
new SqlBuilder(...)
newSelect(...)
newWhere(...)
`new SqlBuilder(...)`
`newSelect(...)`
`newWhere(...)`
*Iformation*
Why no SqlBuilder.begin(...):
Because this was needed when we had a older jscript-engine in Adito.
It was for writing the next methods directly after creation.
e.g.
[source,js]
----
SqlBuilder.begin(...)
.where(..)
----
Now, with a newer Rhino jscript-engine it is possible to write
e.g.
*Information*
`.begin(...)` was only needed by a (meanwhile) outdated JavaScript engine in ADITO. Now, with a newer Rhino-based JavaScript engine, it is possible to write, e.g.,
[source,js]
----
new SqlBuilder(...)
.where(...)
----
So this workaround is not needed anymore.
So the previous workaround is not needed anymore.
==== the new .where
*SqlCondition*
doesn't have this
*SqlBuilder*
.where(...)
*Iformation*
The .where was a feedback I got while creating the SqlBuilder.
It exists just for semantic reasons (see documentation of where above).
`.where(...)`
It is simply more similar to a normal SQL-string if the first condtion uses .where()
*Information*
`.where` exists just for semantic reasons (see documentation of `where` above). It is simply more similar to a normal SQL string when the first condition uses `.where()`
Notes for SqlCondition to SqlBuilder conversion:
Notes for SqlCondition to SqlBuilder refactoring:
* It may happen that you forget to use .where as first condition. In this case you will get an error explaining the problem.
* Sometimes it can be a problem, because the first condition you add is wraped in a if. (Then you would need a check if the where is already called and use .and for the next one else use .where... -> thats bad)
Solution: just add a empty .where() or use newWhere(). This won't add a condition but you can continue with .and / .or without any problem.
* If you forget to use `.where` as first condition, you will get an error explaining the problem.
* Sometimes it can be a problem, if the first condition you add is wrapped in an `if`. Then you would need a check if `.where` is already called and use `.and` for the next one, else use `.where` - but that's not practicable.
Solution: Just add an empty `.where()` or use `newWhere()`. This won't add a condition but your code is valid if you continue with `.and` or `.or`.
==== and / or
*SqlCondition*
has several of these
*SqlBuilder*
.where
.whereIfSet
.and / .or
.andIfSet / .orIfSet
`.where`
`.whereIfSet`
`.and` / `.or`
`.andIfSet` / `.orIfSet`
*Iformation*
One of the main Problems with the SqlCondition was that no one knows which of the .and / .or methods does what. (It was everything documented but no one reads the documentation...)
And it was inconsistent in some cases.
*Information*
One of the main problems with the SqlCondition was that it was hard to understand which of the `.and` / `.or` methods performs what functionality, and it was inconsistent in some cases.
Basically you can put everything into .and / .or / .where
Basically, you can put everything into `.and` / `.or` / `.where`.
Here are some comparisons:
(for and. for or it's the same)
(for `.and` and for `.or` it's the same)
* SqlCondition: and(pCond)
*SqlBuilder: andIfSet(pCond)*
......@@ -552,58 +584,71 @@ Here are some comparisons:
*SqlBuilder: andIfSet(preparedObj)*
* SqlCondition: orSqlCondition (cond, alternativeCond)
As no SqlCondition exists now it works with SqlBuilders containing conditions now:
As no SqlCondition exists anymore, it works with SqlBuilders which contain conditions now:
*SqlBuilder: andIfSet(cond)*
or
*SqlBuilder: and(cond)*
Note: prevously alternativeCond was often used as "1=1" or "1=2"
Now use .andIfSet and or .and
But be aware that they are not a 1-1 replacement:
.andIfSet will just ignore the condition (so similar as a 1=1 would be added)
.and throws an *error*.
If you need a different behaviour, work with additional if's around the condition.
Note: Previously, the parameter alternativeCond was often set as "1=1" or "1=2". Instead, use `.andIfSet` or `.and` now. But be aware that this is not a totally equal replacement:
* `.andIfSet` will just ignore the condition (similar to specifying "1=1" as alternativeCond previously)
* `.and` throws an *error*.
If you need a different behavior, use additional if statements surrounding the condition.
* SqlCondition: andSqlBuilder(subQuery, cond)
*SqlBuilder: andIfSet(null, subQuery, cond)*
Note: the null. We have no field here, so it should be null and cond should only contain '?' no '#'
Npote2: if no cond is provided, you have to use this now:
Note:
`null`: We have no field here, so it must be `null`, and cond must only contain '?' no '#'
If no parameter cond is not provided, you have to use this:
*SqlBuilder: andIfSet(null, subQuery, SqlBuilder.EXISTS())*
==== build / translate
*SqlCondition*
.build
.buildSql
.translate
`.build`
`.buildSql`
`.translate`
*SqlBuilder*
.build
.toString
.buildCondition
`.build`
`.toString`
`.buildCondition`
*Information*
Here are some comparisons:
*Iformation*
* SqlCondition: build(subQuery, alternativeCond)
SqlCondition: buildSql(subQuery, alternativeCond)
Now we have:
*SqlBuilder: buildCondition()*
just returns the condition, nothing else, even if the sqlBuilder contains more.
*SqlBuilder: build(alternativeCond)*
Note: often alternativeCond was used. In most cases this is not needed:
- If you have at least one condition without IfSet, it will never use alternativeCond because it will throw an error before.
- if you have only *IfSet and use "1=1" the "1=1" isn't needed, because no condition at all also selects everything
- This is often used in conditionProcesses. There you should always select everything, and not nothing, as Adito may add additional conditions to it (for e.g. permissions or selecting only one specific id).
`buildCondition()` just returns the condition, nothing else, even if the sqlBuilder contains more.
* SqlCondition: buildSql(subQuery, alternativeCond)
*SqlBuilder: build(pDefaultConditionIfNone)*
`build()` returns a Prepared Sql based on what was added to it. So if you only add conditions, it returns the same as `buildCondition()`.
* SqlCondition: translate(alternativeCond)
*SqlBuilder: toString(alternativeCond)*
Only use this if you really need a string. Use .build() if you can use prepared statements!
*SqlBuilder: toString(pDefaultConditionIfNone)*
[NOTE]
Only use `toString` if you really need use an SQL code as string. Whenever you can use Prepared Statements, use `.build()`.
Previously, the parameter alternativeCond was often used, but in most cases this is not needed:
* If you have at least one condition without `IfSet`, it will never use alternativeCond, because it will throw an error before.
* If you have only `IfSet` and use "1=1", then the "1=1" isn't needed, because no condition at all also selects everything. This is often used in the conditionProcesses of a db record container. There, the alternative is mostly that all data sets are to be selected (rather than nothing), as ADITO may add additional conditions to it - e.g., for permissions or selecting only one specific id.
=== Recommended steps for conversion
Search for "SqlConditon".
For each find:
=== Recommended steps for refactoring
* 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(...).
[TIP]
We recommend you to use the ADITO xRM project as reference, as it includes many best practice examples for the application of the SqlBuilder. In this project, all SqlCondition-related code has been replaced.
The following steps are recommended for a refactoring from SqlCondition to SqlBuilder:
. Perform a full text search for the term "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(...)`.
* If it's a select you may use
+
[source,js]
----
newSelect(...)
......@@ -612,8 +657,11 @@ newSelect(...)
.where(...)
...
----
* Build your condition, think if you need *IfSet or not (e.g. if it's an essential condition).
* If there is a db.table, db.cell, etc. consider to use the .table, .cell, .array methods from the sqlBuilder itself:
. Build your condition, deciding if you need `IfSet´ or not (e.g., if it's an essential condition).
. If there is a `db.table`, `db.cell`, etc., consider to use the `.table`, `.cell`, `.array` methods of the SqlBuilder itself:
+
[source,js]
----
var myData = newSelect(...)
......@@ -622,7 +670,6 @@ var myData = newSelect(...)
.where(...)
.table()
----
* If you are in an conditionProcess, use .toString instead of .translate
if there is a alternativeCondition, think, if you really need it.
* after all you can use the basic as reference as there is everything replaced.
* TEST EVERYTHING after conversion
. If you are in a conditionProcess, use `.toString` instead of `.translate`.
. If there is an alternativeCondition, decide if you really need it.
. Test all functionality after finishing the refactoring.
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment