SQSL is essentially an augmented SQL with a number of feautures borrowed from various sources among which awk, sh, Informix 4GL and Informix SPL. The actual interpreter is in fact implemented as an SQL preinterpreter: as such it will pass to the engine in use anything that it cannot handle directly and that it does not readily recognise as incorrect. This makes it somewhat hard to define the entire syntax of the language, as it is dependent on the engine the interpreter is connected to. We will therefore describe the features above and beyond SQL:
CONNECT TO <expression>
[ SOURCE <source name> ]
[ AS <expression> ]
[ USER <expression>
USING <expression> ]
[ <source specific options> ];
DISCONNECT <expression>;
SET CONNECTION [ DEFAULT [ SOURCE <source name> ] | <expression> ];
Sources are implemented as shared objects. Currently there are two sources for Informix engines, three for DB2, and one for SolidDB.
The interpreter sports the concept of 'current source', which is
the source selected by the latest CONNECT statement explicitely
naming a source. Further CONNECT statements not specifically
selecting a source will use the current source.
Connections are named, which means that no two connections can share the
same name, even when they are related to different sources. Connections can
be renamed via the AS clause, which can be used to allow connections
to two identically named identifiers residing on two different sources.
Bear in mind that not all database engines support connection synonyms, which
means that, even renaming connections, SQSL cannot help getting past the
vendor's inability to connect multiple times from the same client to the same
database identifier. Fork a child
instead.
Certain database engines also sport the concept of a DEFAULT,
unnamed, connection. This is a connection that gets automatically established
upon receiving any sql statement. Default connections, if supported by the
source, cannot be explicitely connected to or disconnected, but they can be
set as current.
The initial current SQSL source is the IFXM one. The initial connection is the IFMX default connection, which is not physically established until a statement is passed to the engine.
FOREACH [ [ [ [ <identifier> CURSOR [ WITH HOLD ] FOR | WITH HOLD ] ] <select statement> |
<execute procedure> ] [ <using clause> ] [ <connection clause> ] |
EXECUTE <expression> [ <using clause> ] |
[ INPUT FROM <expression> |
READ FROM <expression> |
PIPE FROM <expression> ] <pattern clause> ]
[ <aggregate clause> ] [ <storage clause> ] [ <format clause> ];
[ <statement list> | BREAK | CONTINUE ];
DONE;
CLONE <expression> INTO <variable>, <variable>;
[ <statement list> | BREAK | CONTINUE ];
[ PARENT;
[ <statement list> | BREAK | CONTINUE ]; ]
DONE;
WAIT FOR <expression list> INTO <variable>, <variable>;
[ <statement list> | BREAK | CONTINUE ];
DONE;
FOR <variable> IN <expression list>;
[ <statement list> | BREAK | CONTINUE ];
DONE;
WHILE <expression>;
[ <statement list> | BREAK | CONTINUE ];
DONE;
IF <expression>;
<statement list>
[ ELIF <expression>;
<statement list>
... ]
[ ELSE;
<statement list> ]
FI;
BEGIN IMMEDIATE;
<statement list>
END IMMEDIATE;
BEGIN COMPOUND;
<statement list>
END COMPOUND [ <connection clause> ];
Few surprises on how the control statements work:
CLONE statement forks <expression>
children, each executing the same code up to the PARENT clause
(or the end of the loop, if none is specified). At each iteration, the
current child number and its process id are stored in the two target
variables respectively. The PARENT clause can be used to
specify parent actions for each spawned child, like error checking.WAIT FOR statement waits for each child whose process id
is specified in the <expression list>.
At each iteration the return code and process id are stored in the two
target variables.COMPOUND and IMMEDIATE code blocks skip SQSL
parsing altogether and just pass the enclosed statements to the engine,
the former as a single block, the latter one at a time.WHILE loops reevaluate expansions
that are part of the expression clause
at every iterationFOR's expression list is
determined at the beginning of the loop and does not change with
subsequent iterations, as in the bourne shell or awk, but unlike perl's
FOREACHFOR,
FOREACH, CLONE and WAIT FOR loops
target variables. These are determined at each iteration, so be
mindful of side effects that may be introduced by the
aggregate clause or the code inside the
loop blocks.BREAK statement, or their
reminder be skipped with the CONTINUE statement.[ <select statement> | <execute procedure> ]
[ <using clause> ]
[ <connection clause> ]
[ <aggregate clause> ]
[ [ <storage clause> ] [ <format clause> ] |
[ <redirection clause> ] ];
[ <insert statement> | <delete statement> | <update statement> | <select into temp> ] [ <using clause> ] [ <connection clause> ];
<other SQL> [ <connection clause> ];
By and large you are expected to be familiar with the statements above, with the exception, that is, of the interesting stuff: all those little extra clauses. These are described below, each in its own little section.
LET <assignment clause>;
DISPOSE <variable>;
INVOKE <function> [ ( <expression list> ) ] [ RETURNING <variable> [, <variable>... ] ];
DISPLAY <expression list> [ <format clause> ];
APPEND TO [ <expression> | DEFAULT ];
[ OUTPUT | WRITE | PIPE ] TO <expression>;
OUTPUT FORMAT [ PLAIN | HTML ];
OUTPUT WIDTH <expression>;
[ INPUT | READ | PIPE ] FROM <expression>
[ <pattern clause> ]
[ <aggregate clause> ]
[ [ <storage clause> ] [ <format clause> ] |
[ <redirection clause> ] ];
PREPARE <variable> FROM <sql statement> [ <using clause> ] [ <connection clause> ] [ <aggregate clause> ] [ <storage clause> ] [ <format clause> ];
EXECUTE <expression>
[ <using clause> ]
[ <connection clause> ]
[ <aggregate clause> ]
[ [ <storage clause> ] [ <format clause> ] |
[ <redirection clause> ] ];
FREE <expression>;
EXIT [ <expression> ];
WHENEVER ERROR [ CONTINUE | STOP ];
LET and DISPLAY are self explaining. The
only issue worth noting is that while the expansion
facility on its own is already capable of fairly unpredictable
side effects, couple it with the LET statements and things
could become pretty nasty.
INVOKE is the equivalent of Informix-4gl's CALL
(CALL is used to execute procedures in many SQL implementations,
hence the use of INVOKE)
DISPOSE physically obliterates items from the
storage space. You can DISPOSE of
anything: scalars, hashes, or even hash subsets.
APPEND, OUTPUT, WRITE and
PIPE TO all instruct the interpreter to redirect its output.
The expression passed to APPEND and OUTPUT is a
name of a file (OUTPUT will truncate it, APPEND
will add to it), while PIPE, as you would expect, pipes
the output to a child process.
In case of failure, the output is redirected to the default stream, and
the reason of the failure can be inspected via DBINFO("errno").
APPEND TO DEFAULT causes the new output generated by the
interpreter to be added to the default stream, which in the case of the demo
application is the viewer display, but can be defined to be a
file or pipe.
WRITE writes to a stream previously obtained with
FOPEN() or POPEN(). Upon switching to a different
output, the stream is not closed.
Conversely, INPUT, READ and PIPE FROM
read data from a file, a stream, or a pipe
Streams can be opened and closed via the FOPEN, POPEN
and SCLOSE functions. Custom streams can even be created via
skillfull combinations of the arbitrary stream
interface and the external function API.
PREPARE, EXECUTE and FREE have not
been inplemented in aid to dynamic sql (use the expansion
facility instead), but rather to improve performance of sql repeatedly
used.
Both the PREPARE and EXECUTE statements offer
USING,
AGGREGATE and
INTO clauses, however with the former
placeholders, aggregates and storage are determined once and for all at
prepare time (plus: it's fast(er)), while the latter allows multiple executions
of the same statement using different placeholders agregates and targets (plus:
it's flexible).
Be aware, however, that in both cases expressions will be evaluated at execution time, so watch out for those pesky side effects.
EXIT behaves very much like INFORMIX-4gl or bourne shell
exit however note that it has different meanings depending
whether the process is the result of a FORK (the process
terminates) or not (the script terminates and the process resumes normal
operation).
Identifiers start with an alphabetic character and continue with alphabetic characters, digits
or underscores. Indentifiers are not case sensitive.
A larger character set is allowed and case sensitivenes is enforced when the identifier is enclosed in
double quotes. Double quotes only have this special meaning when the environmetal variable
DELIMIDENT is set, viceversa they just introduce string constants.
Variables are dynamic and typed, ie they spring into existence when first referenced and change type at every new assignment (either by means the simple statements or the storage clause) as the case needs be.
Variables come in two flavours: scalars and hashes. Hashes elements can
too be scalar or hashes, thereby allowing jagged hashes.
Hashes and scalars share the same namespace, which means that you are not
allowed to use a hash where a scalar is expected, nor can you try to make
a hash a scalar by assigning to it a scalar value
(DISPOSE is your friend).
Hashes are subscripted with an expression list enclosed in round parenthesys.
SQSL offers a range of predefined functions. Identifiers representing function names are in fact reserved words.
An API to implement external functions is provided. External functions are
referenced as <library
name>:<function name>.
The API can be used to implement functions that open arbitrary data streams.
Parameters are passed to functions via an optional expression list enclosed in round parenthesys.
Expressions can be just about anything listed in the "Condition" and "Expression" segments of the Informix guide to sql: syntax, Volume 2, February 1998 (in case you are wondering, that's IDS 7.3) with the following variations:
CLIPPED is a postfix operator that taken an string
expression, returns one with trailing blanks removedSPACES is a postfix operator that returns a string of
spaces long as specified in the operand taken as an integer
expressionPICTURE is an infix operator that takes any expression and
a string expression and returns a string representing the first
expression formatted according to the picture specified by the
second expression::) operator allows to cast expressions to
different types. Valid types are INTEGER (INT),
FLOAT, DECIMAL (DEC),
MONEY,
DATE, DATETIME [<qualifier>],
INTERVAL [<qualifier>], STRING,
BYTE. Currently only NULL expressions can
be cast to BYTEFORK creates a child process as in fork(2). the child does not
EXEC(command_line) executes a system command without ever returning.
it maps to
execl("/bin/sh", "-c", command_line, NULL);
so it handles shell expansions, etcexit (see later)
at the end of the scriptWAIT waits for any child processes as in wait(2). returns the pid
of the child that has just terminated. return code and error number
are returned by DBINFO("$?") and DBINFO("errno") respectivelyWAITPID(pid) waits for child pid to terminate as in waitpid(2). Same return value and behaviour as WAITSPAWN(command_line) combines FORK and EXEC, and returns the result of the FORKRUN(command_line) combines FORK, EXEC and WAITPID, and returns the return code of the child processSLEEP(seconds) sleeps for the specified amount of secondsGETENV(env_var)returns the value of an environmental variableFOPEN(path, mode) opens a file in the specifies mode and
returns a stream identifierPOPEN(cmd, mode) executes a pipe in the specified mode and
returns a stream identifierSCLOSE(stream) closes the stream passed as an argument and
returns the exit code of the child process, if the stream is associated
to a pipe.ASCII(char) returns the character representation of the
integer expression char, as a one character stringINDEX(source_string, substring) returns the position of substring
within source_stringPAD(pattern, len) will build a len bytes string containing
repeated patternRANDOM(mod [, seed]) returns a random number in the range
0..mod-1DATETIME and INTERVAL do not introduce constants, but are functions
instead. This is consistent with date handling (there's no such thing
as a date constant) and offers added versatility with a simplified
grammar, as both functions perform conversion and qualifier extension
in one invocationFRACTION qualifier and the initial qualifier (intervals only) do not
support a precision specifier1 UNITS DAY - 1 UNITS HOUR returns INTERVAL(0 23) DAY TO HOUR
rather than a -1266DATETIME, INTERVAL and DATE accept an optional second parameter specifying
the input format a la TO_DATEREPLACE accepts optional start and length parameters having
the same meaning as in SUBSTRDBINFO only knows how to handle "sqlca.sqlerrd1",
"sqlca.sqlerrd2", "sqlca.sqlcode",
"$?" and "errno"
USER, TODAY and CURRENT do not
necessarily return the same value as they would on the engine sideDBSERVERNAME, SITENAME,
OCTET_LENGTH, EXTEND, TO_CHAR,
TO_DATE, TRIM, SUBSTRING[ <expression> | <expression>TO<expression> | <variable>.*] [, ... ]
Expression lists can include expanded hashes (denoted by the hash name
followed by .*), and integer range expressions.
<variable> [ <substring range> ] = <expression>
This is used as either part of the LET statement or the
aggregate clause.
PATTERN [ <expression> |
DELIMITED [<expression>] [ BY <expression> [ ESCAPE <expression> ] ] |
BINARY ]
The PATTERN clause is used to specify the format of the data
read by the READ, INPUT and PIPE FROM
commands. It's use is similar to the FORMAT clause.
USING <expression list>
As an alternative to the expansion facility, SQL
statements can use placeholders as you would on an OPEN or
EXECUTE statement.
Use the USING clause to list the expressions that
should substitute placeholders. This feature can be used to insert or update
(should you ever get a working version of the IFX_UPDDESC
feature) text or byte values on the fly.
CONNECTION [ DEFAULT [ SOURCE <source name> ] | <expression> ]
SQL statements are normally executed against the current connection. The
CONNECTION clause allows to execute a single statement against
a different connection, with the only obvious restriction that it needs to
be already established.
AGGREGATE [ [ (<boolean expression>) ] <assignment clause> |
(<boolean expression>) [ BREAK | CONTINUE ] ] [,...]
Part of the SELECT and INPUT classes of statements,
this is quite a flaxible clause, in that it can be used to
Input field are denoted by $
<expression>, with expression denoting
the field number, a la awk
[ <insert statement> [ <connection clause> ] | <execute procedure statement> [ <connection clause> ] | EXECUTE <expression> ]
Besides storing data, or writing it in a formatted manner, data retrieval statements can redirect it to a table, or can execute a stored procedure for each row retrieved.
There are a few limitations on the statements that can be executed:
INSERT statements must have a VALUES clauseEXECUTE PROCEDURE statements must not return any dataINTO <variable>[, <variable>...]
Retrieved data can be stored locally. Use the INTO clause to list the target variables.
FORMAT [ BRIEF |
[ FULL | VERTICAL ] [ <expression> ] [ HEADERS <expression> [, <expression>... ] ] |
DELIMITED [<expression>] [ BY <expression> [ ESCAPE <expression> ] ] |
BINARY ]
Queries and display statements output can be formatted.
The BRIEF format only outputs column values separated by commas,
with no formatting whatsoever, VERTICAL will output rows in a
header/column fashion (formatting columns in the way), DELIMITED
will produce a single line of delimited expressions (optionally formatted as
specified) and lastly FULL formats rows in a tabular fashion.
The first expression is a format specifier in which c and 4gl format specifiers
can be used. C formats are specified the usual way (see sprintf (3)), while
4gl formats have to be enclosed in square brackets. format specifiers can
be interspersed with plain text, hard tabs & new lines, or \n and \t.
To ease header formatting (in case plain text appears in the format, in particular
for vertical formats), columns
can be separated with the pipe (|) sign. This is not required though.
Special characters ([]%| and the quotes in use) can be escaped by doubling
them. Note that the backslash has special significance only when followed
by either 't' or 'n'.
Note that there's no need to specify as many format specifiers as columns,
while with full format, more than needed will combine multiple rows into
a single format.
The header clause specifies a list of headers to be output as the first row of the query in full format, and preceding each column in vertical format. Headers will be padded to the length of the appropriate field (full format) or the largest header (vertical).
As for formats, there's no need that the number of headers matches that of the items being selected.
The expansion facility caters for pick lists, query or application output
substitution anywhere in the script. Expansions are
performed whenever a <+ +> pair is encountered, can be
freely nested and can be any of the following:
READ file [ file... ] |
displays the contents of multiple files |
EXEC pipe |
executes an arbitrary application |
GET <expression> [, <expression>... ] |
returns the concatenated results of the expression list |
PROMPT <expression> |
opens a pop up window, prompts the evaluated <expression>
to the user, and returns the user response |
PASSWD <expression> |
behaves like PROMPT, except that the text input by the user
is not displayed |
a SELECT statement |
displays the results of a query. Queries are run against the current connection. |
prefix the above with any of the following options to change expansion behavior:
[ CODED | LONE | SILENT ] |
Specifies which expansion should be performed.
CODED will open a code/description pick list
(only the code is returned), LONE will open
a description only pick list, while SILENT
will just do a plain expansion. |
[ SINGLE | MULTI ] |
Specifies whether the expansion should return one or more values. For pick lists, this controls whether multiple selections are allowed. For silent expansions, whether only the first or all values should be returned. |
PUT <variable> |
stores the output of the expansion in the variable specified. |
QUOTES <expression> |
Specifies quotes to surround each value returned by get or multiple expansions. Default is none. Enter two characters to specify differring start and end quotes. |
SEPARATOR <expression> |
Specifies the entry separator between each value returned by get or multiple expansions. Default is blank. |
A few gotchas:
CODED SINGLE (only because I use it a
lot :-)QUOTES and SEPARATOR are illegal with
SINGLE, and imply MULTI (so, MULTI
usage is redundant, with them)GET and SILENT expansions can happily expand to
nothing. this is useful to silently execute external applicationsPUT clause can be source of unexpected side effectsExpansions can be commented using a <* *> pair. At par
to <+ +> pairs, such comments can be nested. Expansions
and comment tokens can be escaped with a backslash. Note that the backslash
does not escape anything else, thus you don't need to escape it to use it in
the context of a select statement or a shell script.
Finally, feel free to use whichever comment mechanism you like, as long as it
makes sense within the context in which it is used, eg {} or
--<CR> in
SQL statements, or #<CR> in perl/awk/sh scripts.
| Please address questions or comments to
marco greco (last updated Mon, 07 September 2009 15:21:54 BST) |