Skip to content
Snippets Groups Projects
documentation.adoc 32.69 KiB

SqlBuilder

[ERROR: include::_default_attributes_EN.adoc[] - unresolved directive]

1. 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 Sql_test (also under process > libraries), as it contains many possible ways to use the SqlBuilder.

The tests included in the library Sql_test use the UnitTest_lib for unit testing. You can use this functionality also in other contexts, according to your requirements.

2. Benefits

2.1. Prepared Statements

The SQL builder applies Prepared Statements (if used correctly). These should always be used for reasons of

  • Security (main benefit):
    In SQL statements like
    "select * from PERSON where FIRSTNAME = '" + userInput + "'"
    the user could input also any - possibly harmful - SQL code ("SQL injection" see also AID068). 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.

2.2. Usability

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]]]

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

var type = db.getColumnTypes("PERSON", ["FIRSTNAME"])[0]
var persons = db.table(["select FIRSTNAME, LASTNAME from PERSON where FIRSTNAME = ?", [[usereInput, type]]]);

you can now use the functionality of the SqlBuilder:

var persons = newSelect("FIRSTNAME, LASTNAME")
                .from("PERSON")
                .where("PERSON.FIRSTNAME", userInput)
                .table();

which is much cleaner code and more easy to write.

3. Basic usage

3.1. new SqlBuilder creation

Basically, you can create a new builder by

var personSelect = new SqlBuilder()
                    .select("FIRSTNAME, LASTNAME")
                    .from("PERSON")
                    .where("PERSON.PERSONID", persId);

or with the shorthand function newSelect()

var personSelect = newSelect("FIRSTNAME, LASTNAME")
                    .from("PERSON")
                    .where("PERSON.PERSONID", persId);

It is also possible to use the builder only for conditions:

var personCond = new SqlBuilder()
                    .where("PERSON.PERSONID", persId);

or with the shorthand function newWhere()

var personCond = newWhere("PERSON.PERSONID", persId);

3.2. Conditions

For the conditions we have the methods

  • where

  • and

  • or

and for each of them also a variant with *IfSet: (see chapter "*IfSet")

  • whereIfSet

  • andIfSet

  • orIfSet

3.2.1. where

where has always to be used as the first condition. This is mainly for a better semantic:

new SqlBuilder().select("COL1")
                .from("TAB1")
                .where("TAB1.COL2", "myVal")
                .and("TAB1.COL3", "myVal");

looks better and more intuitive to read than

new SqlBuilder().select("COL1")
                .from("TAB1")
                .and("TAB1.COL2", "myVal")
                .and("TAB1.COL3", "myVal");

Especially if you are used to normal SQL strings the missing where may be confusing.

Therefore, if you add a condition, the where keyword is mandatory.

newWhere() already includes the where. So this is perfectly valid:

newWhere("TAB1.COL2", "myVal")
    .and("TAB1.COL3", "myVal");

It is also valid to use newWhere without parameters, if you want to add the parameters later:

var cond = newWhere();

if (someCondition...)
{
    cond.and("TAB1.COL3", "myVal");
}

if (someOtherCondition...)
{
    cond.and("TAB1.COL2", "myVal2");
}
Parameters

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 # <> ? (or SqlBuilder.NOT_EQUAL()).
    You can use SqlBuilder constants here (for example SqlBuilder.NOT_EQUAL(), SqlBuilder.EXISTS())
    For better useability, it is also possible to give the value without the bracets like this: SqlBuilder.NOT_EQUAL
    It is possible to escape the # symbol with a backslash, note that to write a backslash in a string it needs to be escaped itself.
    Example: cond.and("MYTABLE.MYCOLUMN", "XYZ", "\\## = ?"); results in ( #MYTABLE.MYCOLUMN = 'XYZ' )

  • 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.

3.2.2. and / or

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.

3.2.3. *IfSet

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:

var value = null;

var cond = newWhere("TAB1.COL2", null)
                .and("TAB1.COL3", value);
// execution stops with error

whereIfSet will just ignore the whole condition in these cases:

var value = null;

var cond = newWhereIfSet("TAB1.COL2", null)
                    .and("TAB1.COL3", value);

// code is executed and cond.build will result in
// ["TAB1.COL3 = ?", [['myVal', SQLTYPES.VARCHAR]]];

// As you can see, TAB1.COL2 is ignored completely.

3.3. select

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"):

  • 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")]

3.4. from

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.

3.5. join

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 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:

var costData = newSelect("CAMPAIGNCOSTID, CAMPAIGNSTEP_ID, CAMPAIGNSTEP.NAME, CATEGORY, NET")
                    .from("CAMPAIGNCOST")
                    .leftJoin("CAMPAIGNSTEP", newWhere("CAMPAIGNSTEP_ID = CAMPAIGNSTEPID"))
                    .where("CAMPAIGNCOST.CAMPAIGN_ID", campaignId)
                    .table();

3.6. apply

Not all databases support "outer apply" or "cross apply".

An apply can also be added via .join. If you set the parameter pReplacementForWordJoin, the given keyword will be used instead of "join".

Here’s an example for the usage of "outer apply":

var activitySelect = newSelect("SUBJECT, INFO, personLink.OBJECT_ROWID")
    .from("ACTIVITY")
    .join(
        newSelect("OBJECT_ROWID")
            .from("ACTIVITYLINK")
            .where("ACTIVITYLINK.OBJECT_TYPE", "Person"),
        null, "personLink", "outer", "apply"); //it would also be possible to omit pPrefix and then write "outer apply"

3.7. group by

groupBy(pFields) adds a group by statement to the SQL code. The parameter can be filled the same way as .select(pFields).

3.8. having

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).

3.9. order by

orderBy(pFields) adds an order by statement to the SQL code. The parameter can be filled the same way as .select(pFields).

3.10. union

union(pSelect) and unionAll(pSelect) can be used to create an union or union all statement. The given parameter can be either a SQL-String or another SqlBuilder with full select clause.

3.11. building the SQL statement

3.11.1. prepared array

.build(pDefaultConditionIfNone) generates a prepared-statement array from the SqlBuilder in the correct form for several db. functions.

3.11.2. string

.toString(pDefaultConditionIfNone) translates the query into a SQL-string.

3.11.3. When to use build and when to use toString

.build() has to be called before the statement is given to a db. method, like db.cell() or db.table(). But SqlBuilder also provides own functions for executing the query, if you use these, you don’t need .build(). These methods are explained in the next chapter. Because of a more concise syntax and additional checks that are done in these functions, you should prefer them over the default db. methods.

Because .build() returns an array, you can’t use it in combination with result.string().

.toString() is for cases where you need the query as string, but you should prefer .build() whenever possible. Processes like the "conditionProcess" in a dbRecordContainer and the "expression" from a dbRecordContainer field require a string as result, so you should use it there. If result.string() is used, you can omit the .toString(), since it would then be called implicitly.

.toString() can be useful for logging, as you get an easy to read SQL string.

3.12. db. function wrappers

3.12.1. 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.

The functions

.cell()
.arrayRow()
.arrayColumn()
.arrayPage()
.table()
.tablePage()

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 *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(*)

var count = newSelect("count(*)")
                .from("CLASSIFICATION")
                .whereIfSet("CLASSIFICATION.CLASSIFICATIONTYPE_ID", "$field.CLASSIFICATIONTYPEID")
                .cell(true, "0");

a default of "0" is more convenient than.

You can still use the above db. functions outside the SqlBuilder, if you like.

3.12.2. paging functions

The following functions support paging:

.arrayPage()
.tablePage()
.nextTablePage()
.forEachPage()

The methods .arrayPage() and .tablePage() are just wrappers for the equivalent db.-functions. However, the other functions should make the paging process a bit easier.
Before using them, you have to set the page size with .pageSize(pPageSize) and optionally set the start row with .startRow(pStartRow).

.nextTablePage() can be used to iterate over the table pages:

mySqlBuilder.pageSize(400);
var data;
while (mySqlBuilder.hasMoreRows())
{
    data = mySqlBuilder.nextTablePage();
    ...
}

You can use .hasMoreRows() to check if there are rows left that can be fetched.
Alternatively you can use .forEachPage(), this method requires a callback-function that is called for every table page:

mySqlBuilder.pageSize(400)
    .forEachPage(function (pData)
    {
        ...
    });

3.12.3. update/delete-functions

The functions

updateData()
deleteData()

also have the parameter pExecuteOnlyIfConditionExists.

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.
The parameter is true by default if you omit it.

Example:

newWhereIfSet("AB_OBJECTRELATION.AB_OBJECTRELATIONID", objectRelationId)
        .updateData(true, "AB_OBJECTRELATION", ["INFO"], null, [vars.get("$field.INFO")]);

The table name can be omitted, if the SqlBuilder has a table as from part, or if the table is unambiguously defined by the where condition.

There is also the function updateFields() that provides a shorter syntax compared to .updateData(). It requires the columns and values as an object and pExecuteOnlyIfConditionExists is always true for this function.
With that, you can achieve relatively concise sql updates and deletions, like in the following example:

newWhere("SALESORDER.SALESORDERID", "$field.SALESORDERID")
    .updateFields({"ORDERSTATUS" : "1"});

3.12.4. insert-functions

SqlBuilder also provides functions for inserts which don’t require a where-condition, they can be called on a new SqlBuilder() directly.

The function insertData() is a wrapper for db.insertData() and has similar parameters:

new SqlBuilder()
    .insertData("AB_OBJECTRELATION", ["AB_OBJECTRELATIONID", "INFO"], null, [util.getNewUUID(), vars.get("$field.INFO")]);

To make the code more readable and more compact, there is another function, insertFields(). The syntax is similar to updateFields(), you have to pass the columns and values as an object. You can also set the parameter pAutoUidField, then the given column will be set to a random UUID:

new SqlBuilder().insertFields({
        "ACTIVITY_ID" : pActivityId,
        "OBJECT_ROWID" : pRowId,
        "OBJECT_TYPE" : pObjectType
    }, "ACTIVITYLINK", "ACTIVITYLINKID");

4. Examples and use cases

4.1. Complete condition

If the first parameter is the only one you set, it is treated as a complete condition:

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 = ?", [["Smith",  SQLTYPES.VARCHAR]]]); // a prepared statement array

//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 useful when using brackets, e.g. for something like "(FIRSTNAME = 'John' or FIRSTNAME = 'Paul') and LASTNAME = 'Admin'"

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(), 'John' and 'Paul' are grouped together.

As comparison without the grouping:

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' );

4.2. Simple condition with and & or

You can combine and & or as you like:

var cond = newWhere("PERSON.FIRSTNAME", "John")
                .and("PERSON.LASTNAME", "Admin")
                .or("PERSON.FIRSTNAME", "Peter")
                .and("PERSON.LASTNAME", "Smith");

//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]]];

4.3. JDito variables

It is also possible to just pass JDito variables directly:

newWhere("PERSON.FIRSTNAME", "$field.FIRSTNAME")

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.

4.4. other condition than "# = ?"

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. For many conditions there are already SqlBuilder constants that can be used (e. g. SqlBuilder.NOT_EQUAL(), SqlBuilder.GREATER()).

4.5. 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 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.

4.6. Subquery

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):

  • 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

var aSqlBuilderContainingFullSelect = newSelect("LASTNAME")
                                        .from("PERSON")
                                        .where("PERSON.FIRSTNAME", "John");
var cond = newWhere("TABLE.FIELD", aSqlBuilderContainingFullSelect);

// cond.build() results in ["( TABLE.FIELD =  ( select LASTNAME from PERSON where PERSON.FIRSTNAME = ?  ) ) ", [['John', SQLTYPES.VARCHAR]]]

With PreparedSqlArray

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 = ?  ) ) ", [['John', SQLTYPES.VARCHAR]]]

4.7. 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:
["(?, ?, ?)", [["Peter", SQLTYPES.VARCHAR], ["Paul", SQLTYPES.VARCHAR], ["Mary", SQLTYPES.VARCHAR]]]

With this it is very easy to create a in-statement:

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 treated like null or undefined: using .where throws an error, using .whereIfSet ignores the whole condition.

Passing nothing as condition will result in an SqlBuilder.IN when the values are an array.
For example myCondition.and("PERSON.FIRSTNAME", ["Peter", "Paul", "Mary"])

4.8. IN statement with subquery

You can use a subquery together with the pCondition SqlBuilder.IN() or SqlBuilder.NOT_IN()

4.9. 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:

// 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("CONTACT.CONTACTID", "$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 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" operator.
// onlyShowContactIds contains an array.
var onlyShowContactIds = JSON.parse(vars.get("$param.OnlyShowContactIds_param"));
cond.andIfSet("CONTACT.CONTACTID", onlyShowContactIds, SqlBuilder.IN());

// 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());

5. SqlCondition to SqlBuilder refactoring help

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").

SqlCondition is deprecated and will be removed completely in ADITO xRM 2020. Therefore, you have to replace all SqlCondition usages by using the SqlBuilder.

5.1. Differences between SqlCondition and SqlBuilder

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:

5.1.1. Instantiation

SqlCondition
new SqlCondition(…​)
SqlCondition.begin(…​)

SqlBuilder
new SqlBuilder(…​)
newSelect(…​)
newWhere(…​)

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.,

new SqlBuilder(...)
          .where(...)

Therefore, do not use SqlBuilder.begin() anymore - even if it might be still included in your version of Sql_lib, as deprecated method. Use exclusively new SqlBuilder(…​) or the shortcut functions newSelect(…​), newWhere(…​) or newWhereIfSet(…​).

5.1.2. the new .where

SqlCondition
doesn’t have this

SqlBuilder
.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 refactoring:

  • 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.

5.1.3. and / or

SqlCondition
has several of these

SqlBuilder
.where
.whereIfSet
.and / .or
.andIfSet / .orIfSet

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.

Here are some comparisons:
(for .and and for .or it’s the same)

  • SqlCondition: and(pCond)
    SqlBuilder: andIfSet(pCond)

  • SqlCondition: andPrepare(…​)
    SqlBuilder: and(…​)

  • SqlCondition: andPrepareIfSet(…​)
    SqlBuilder: andIfSet(…​)

  • SqlCondition: andPrepareVars(…​)
    SqlBuilder: andIfSet(…​)

  • SqlCondition: andIn(field, values, fieldType, true)
    SqlBuilder: andIfSet(field, values, SqlBuilder.IN(), fieldType)

  • SqlCondition: andIn(field, values, fieldType, false)
    SqlBuilder: andIfSet(field, values, SqlBuilder.NOT_IN(), fieldType)

  • SqlCondition: andAttachPrepared(preparedObj)
    SqlBuilder: andIfSet(preparedObj)

  • SqlCondition: orSqlCondition (cond, alternativeCond)
    As no SqlCondition exists anymore, it works with SqlBuilders which contain conditions now:
    SqlBuilder: andIfSet(cond)
    or
    SqlBuilder: and(cond)

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:
    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())

5.1.4. build / translate

SqlCondition
.build
.buildSql
.translate

SqlBuilder
.build
.toString
.buildCondition

Information
Here are some comparisons:

  • SqlCondition: build(subQuery, alternativeCond)
    SqlBuilder: buildCondition()
    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(pDefaultConditionIfNone)

    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.

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:

  1. 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

      newSelect(...)
          .from(...)
          (.join(...))
          .where(...)
          ...
  2. Build your condition, deciding if you need `IfSet´ or not (e.g., if it’s an essential condition).

  3. If there is a db.table, db.cell, etc., consider to use the .table, .cell, .array methods of the SqlBuilder itself:

    var myData = newSelect(...)
                    .from(...)
                    .join(...)
                    .where(...)
                    .table()
  4. If you are in a conditionProcess, use .toString instead of .translate.

  5. If there is an alternativeCondition, decide if you really need it.

  6. Test all functionality after finishing the refactoring.