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

SqlBuilder & UnitTest documentation

parent 6fd92396
No related branches found
No related tags found
No related merge requests found
...@@ -3,14 +3,18 @@ ...@@ -3,14 +3,18 @@
:hardbreaks: :hardbreaks:
= SqlBuilder = SqlBuilder
Please read the documentation in the Sql_lib itelsf if something is unclear. 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.
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...)
== Why == Why
=== Prepared Statements === Prepared Statements
Prepared Statements should always be used because of Prepared Statements should *always* be used because of
* Security (Sql injection): If you are used to something like * *Security* (Sql injection): If you are used to something like
`"select * from PERSON where PERSONID = '" + persId + "'"` `"select * from PERSON where PERSONID = '" + persId + "'"`
you *WILL* forget to use prepared statements if it is really nessecary: you *WILL* forget to use prepared statements if it is really nessecary:
`"select * from PERSON where FIRSTNAME = '" + userInput + "'"` `"select * from PERSON where FIRSTNAME = '" + userInput + "'"`
...@@ -245,6 +249,54 @@ pCondition can be a simple string or a SqlBuilder containing a condition. (only ...@@ -245,6 +249,54 @@ pCondition can be a simple string or a SqlBuilder containing a condition. (only
orderBy(pFields) just adds a order by to the sql orderBy(pFields) just adds a order by to the sql
the parameter can be filled the same way as .select(pFields) 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.
Note: you can still use the db. functions if you like.
cell
arrayRow
arrayColumn
arrayPage
table
tablePage
All of them have an extra parameter pExecuteOnlyIfConditionExists, no pAlias and no 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
cell is a special case as it contains another param: pFallbackValue
I added it, because in the basic is often a count(*):
[source,js]
----
var count = newSelect("count(*)")
.from("CLASSIFICATION")
.whereIfSet("CLASSIFICATION.CLASSIFICATIONTYPE_ID", "$field.CLASSIFICATIONTYPEID")
.cell(true, "0");
----
and in this case a default of "0" is more convenient than ""
==== 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.
Example:
[source,js]
----
newWhereIfSet("AB_OBJECTRELATION.AB_OBJECTRELATIONID", objectRelationId)
.updateData(true, "AB_OBJECTRELATION", ["INFO"], null, [vars.get("$field.INFO")]);
----
== Examples and Use Cases == Examples and Use Cases
=== already complete condition === already complete condition
...@@ -391,4 +443,184 @@ cond.andIfSet("CONTACT.CONTACTID", onlyShowContactIds, SqlBuilder.IN()); ...@@ -391,4 +443,184 @@ cond.andIfSet("CONTACT.CONTACTID", onlyShowContactIds, SqlBuilder.IN());
// The conditionProcesss doesn't support prepared statements. So .build() is currently not possible. // The conditionProcesss doesn't support prepared statements. So .build() is currently not possible.
// using .toString() resolves all conditions and builds the finished select as string. // using .toString() resolves all conditions and builds the finished select as string.
result.string(cond.toString()); result.string(cond.toString());
---- ----
\ No newline at end of file
== SqlCondition to SqlBuilder conversion help
The SqlCondition is deprecated and removed completely in Basic 2020.
So you have to:
* 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
For the first point some advice:
It is not easily possible to replace the SqlCondition directly by the SqlBuilder. There are too many differences.
=== Differences SqlCondition -> SqlBuilder
Here a list of the main differences:
==== Instanciation
*SqlCondition*
new SqlCondition(...)
SqlCondition.begin(...)
*SqlBuilder*
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.
[source,js]
----
new SqlBuilder(...)
.where(...)
----
So this 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).
It is simply more similar to a normal SQL-string if the first condtion uses .where()
Notes for SqlCondition to SqlBuilder conversion:
* 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.
==== and / or
*SqlCondition*
has several of these
*SqlBuilder*
.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.
Basically you can put everything into .and / .or / .where
Here are some comparisons:
(for 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 now it works with SqlBuilders containing 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.
* 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:
*SqlBuilder: andIfSet(null, subQuery, SqlBuilder.EXISTS())*
==== build / translate
*SqlCondition*
.build
.buildSql
.translate
*SqlBuilder*
.build
.toString
.buildCondition
*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).
* SqlCondition: translate(alternativeCond)
*SqlBuilder: toString(alternativeCond)*
Only use this if you really need a string. Use .build() if you can use prepared statements!
=== Recommended steps for conversion
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(...).
* If it's a select you may use
[source,js]
----
newSelect(...)
.from(...)
(.join(...))
.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:
[source,js]
----
var myData = newSelect(...)
.from(...)
.join(...)
.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
...@@ -9,6 +9,8 @@ import("system.SQLTYPES"); ...@@ -9,6 +9,8 @@ import("system.SQLTYPES");
import("system.text"); import("system.text");
import("Util_lib"); import("Util_lib");
// see Documentation property of this lib for further explanation
/** /**
* Creates a new SqlBuilder object and sets the select clause of the sql. * Creates a new SqlBuilder object and sets the select clause of the sql.
* *
...@@ -406,6 +408,9 @@ SqlBuilder.prototype.rightJoin = function(pTable, pCondition, pTableAlias) ...@@ -406,6 +408,9 @@ SqlBuilder.prototype.rightJoin = function(pTable, pCondition, pTableAlias)
* Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case<br/> * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case<br/>
* Note2: if you need a table alias use the next variant (as array)<br/> * Note2: if you need a table alias use the next variant (as array)<br/>
* - a array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"]<br/> * - a array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"]<br/>
* - a SqlBuilder: the full select is used as subselect and compared with pValue. <br/>
* (e.g. select * from PERSON where (select "NAME" from ORGANISATION where ... ) = ?)<br/>
* Note: for this you have to provide pFieldType as the type cannot be calculated from the subselect!<br/>
* Note: this can also be null if you don't need the field and use a pCondition without a # * Note: this can also be null if you don't need the field and use a pCondition without a #
* *
* @param {String|SqlBuilder|PreparedSqlArray|Array|OtherTypes} [pValue] This is the value which is used for the condition.<br/> * @param {String|SqlBuilder|PreparedSqlArray|Array|OtherTypes} [pValue] This is the value which is used for the condition.<br/>
...@@ -455,7 +460,10 @@ SqlBuilder.prototype.where = function(pFieldOrCond, pValue, pCondition, pFieldTy ...@@ -455,7 +460,10 @@ SqlBuilder.prototype.where = function(pFieldOrCond, pValue, pCondition, pFieldTy
* Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case<br/> * Note1: you may have problems with names containing a '.' Use the next variant (as array) in this case<br/>
* Note2: if you need a table alias use the next variant (as array)<br/> * Note2: if you need a table alias use the next variant (as array)<br/>
* - a array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"]<br/> * - a array: ["TABLENAME", "COLUMNNAME", "tableAlias"] OR ["TABLENAME", "COLUMNNAME"]<br/>
* Note: this can also be null if you don't need the field and use a pCondition without a #<br/> * - a SqlBuilder: the full select is used as subselect and compared with pValue. <br/>
* (e.g. select * from PERSON where (select "NAME" from ORGANISATION where ... ) = ?)<br/>
* Note: for this you have to provide pFieldType as the type cannot be calculated from the subselect!<br/>
* Note: this can also be null if you don't need the field and use a pCondition without a #
* *
* @param {String|SqlBuilder|PreparedSqlArray|Array|OtherTypes} [pValue] This is the value which is used for the condition.<br/> * @param {String|SqlBuilder|PreparedSqlArray|Array|OtherTypes} [pValue] This is the value which is used for the condition.<br/>
* Basically it can be nearly everything you need.<br/> * Basically it can be nearly everything you need.<br/>
......
import("system.logging"); import("system.logging");
/**
* A TestSuite combines several tests
* @param {Array} pTests this is a n array of Tests in the following form:<br/>
* [<br/>
* ["Test description", function(pTester) <br/>
* {<br/>
* // the test which may use pTester.assert(...)<br/>
* }, {Optional: an expected error}<br/>
* ],<br/>
* [...]<br/>
* ]<br/>
* @param {functionCallback} [pPreAll] a callback, called once before all tests
* @param {functionCallback} [pPreTest] a callback, called once before each test
* @param {functionCallback} [pPostTest] a callback, called once after each test
* @param {functionCallback} [pPostAll] a callback, called once after all tests
*
* @class
*/
function TestSuite(pTests, pPreAll, pPreTest, pPostTest, pPostAll) function TestSuite(pTests, pPreAll, pPreTest, pPostTest, pPostAll)
{ {
this.tests = pTests; this.tests = pTests;
...@@ -9,6 +27,23 @@ function TestSuite(pTests, pPreAll, pPreTest, pPostTest, pPostAll) ...@@ -9,6 +27,23 @@ function TestSuite(pTests, pPreAll, pPreTest, pPostTest, pPostAll)
this.postAll = (pPostAll ? pPostAll : function() {}); this.postAll = (pPostAll ? pPostAll : function() {});
} }
/**
* The tester can test TestSuites.
* It will be passed as paramter to each test.
*
* @param {String} pCollectionName
*
* @example
* var tester = new Tester("Test SqlBuilder");
* tester.test(newSelectTests);
* tester.test(validAndUsageTests);
* tester.test(validOrUsageTests);
*
* logging.log("-------------------------");
* tester.printResults();
*
* @class
*/
function Tester(pCollectionName) function Tester(pCollectionName)
{ {
this.collectionName = pCollectionName; this.collectionName = pCollectionName;
...@@ -18,6 +53,17 @@ function Tester(pCollectionName) ...@@ -18,6 +53,17 @@ function Tester(pCollectionName)
this.currentTestHadAlreadyAssert = false; this.currentTestHadAlreadyAssert = false;
} }
/**
* With assert you can test if a variable is the same like an expected value.<br/>
* The test result is added to the Tester<br/>
*
* Note: the values are compared by === so also the type is checked.<br/>
* By using assert you can Test everything you want.<br/>
*
* @param {AnyValue} pExpect the expected value
* @param {AnyValue} pActual the actual value
* @param {String} [pTestDescription] this is an optional description. You should use it, if you have more than one .assert in your test.
*/
Tester.prototype.assert = function (pExpect, pActual, pTestDescription) Tester.prototype.assert = function (pExpect, pActual, pTestDescription)
{ {
this.currentTestHadAlreadyAssert = true; this.currentTestHadAlreadyAssert = true;
...@@ -33,6 +79,10 @@ Tester.prototype.assert = function (pExpect, pActual, pTestDescription) ...@@ -33,6 +79,10 @@ Tester.prototype.assert = function (pExpect, pActual, pTestDescription)
]); ]);
} }
/**
* Executes all tests in a TestSuite
* @param {TestSuite} pTestSuite the TestSuite which should be tested
*/
Tester.prototype.test = function (pTestSuite) Tester.prototype.test = function (pTestSuite)
{ {
pTestSuite.preAll(this); pTestSuite.preAll(this);
...@@ -120,6 +170,9 @@ Tester.prototype.test = function (pTestSuite) ...@@ -120,6 +170,9 @@ Tester.prototype.test = function (pTestSuite)
pTestSuite.postAll(this); pTestSuite.postAll(this);
} }
/**
* Prints the test results to the log
*/
Tester.prototype.printResults = function () Tester.prototype.printResults = function ()
{ {
var lastTestDescription = ""; var lastTestDescription = "";
......
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