An SQL template. An SQL template consists of nested blocks. The uppermost block is the queryString itself, you create subblocks by enclosing it in braces '{}'.
__Declaration part__
Each block might start with a declaration part. It does so if the first character in the block is a '$'. The declaration part ends with a colon ':'. It consists of declarations which start with '${' and end with '}'. Two types of declarations are allowed:
1. variable assignment of the form: ${a=5} This will result in a D expression in the generated code of the form 'auto a=5;'.
2. Foreach loop declaration. This one might only occur once in the declaration part and causes the block in which it is declared to be executed in a loop. It looks like: ${a in someArray}
3. Special assignment of ${queryGenSep=" or "} : This one is specially treated. If you specified a loop the generated string of each iteration will be concatenated and the queryGenSep will be used to separate them. If you don't specify one, the default will be " or ".
Variable assignments before the optional loop declaration will be declared outside the loop, the ones after the loop declaration will be declared within the loop and might access the loop variable, ('a' in our case).
Multiple declarations of the form '${}' might be present within the declaration part, they may be separated by white space.
__Body part__
If no declaration part is present, then the whole content of the block is the body part. (This is, if the first character is no '$'.) Otherwise it starts after the colon at the end of the declaration part.
Everything in the body part will be echoed to the resulting SQL so you can just write plain SQL now. There are only a few exceptions:
1. A '{' will introduce a child block.
2. A '#{' will start a D expression, which ends with another '}'
3. A '$' will trigger an error as it is only valid in the declaration part.
4. SQL string which can either start with " or with '. It might contain any of '{' '}' '$' '#' they all will be ignored within a string. Apart from this, no escaping of these characters is possible at the moment.
__D expression__
A D expression might occur any amount of times within the body part of a block. It contents will be evaluated as D code. Which must evaluate to some value. If this value is different from the value's types init value it is considered valid, then it will be included in the Variant[] array and a '?' gets inserted into the resulting SQL. If the returned value equals the type's init value then no '?' gets inserted and the value will not be present in the args array.
So if the D expression evaluates to a string and is the empty string, it will not be included.
The expression is contained within #{ D expression }.
The D code has access to the passed params via params[0] .. paramsn, all declarations made in the declarations parts of the blocks above it and the contents of the data_ struct. It is made available with D's 'with' statement so data_.foo will just be 'foo'.
__Blocks__
Blocks are the building blocks of the resulting expression. If a block contains a D expression or a subblock which contains D expressions, then its contents will only be included in the output if at least one of the D expressions are valid or if it contains a text only sub block. So if you have a SQL expression of the form {someColumn=#{some D code}} there will be no output at all if #{some D code} was not valid. If a block does not contain any D code and neither do any sub blocks then its output will be included in the output of the containing block. If you want to ensure that a particular text does not get dropped, include it in its own block.
The second property that makes blocks useful is that if a block produces no output, for the above mentioned reasons, then the text before it will be dropped if there were no valid sub blocks found already in the parent block. It will also be dropped if the preceding block vanished. This way you can concatenate blocks with ' and ' and ' or ' and they will be dropped if not applicable. A leading ' where ' on the other hand would not be dropped in any case, except the whole output vanishes.
For examples, see the unittests, a more complex example, I used in reality is here:
The contents of data_ will be made available to the D expressions and declarations in the queryString. (The code gets embedded withing a with(data_) { ... })
Additional data you need in the queryString. You can access it via params[0], params[1], ... If you happen to have a member of data_ which is named params, you will have to access it via data_.params.
Uses createQueryGenerator for actually doing the job. data_ will be made available with D's 'with' statement to the embedded code in queryString. So you can access the elements just as regular variables.