QML
Query Markup Language
QML is designed to simplify creation of web
pages which access databases. It is based heavily on hyperSQL, and
steals freely from the ideas of hyperSQL's creators. A qml file,
though, looks more like an html file than a computer program, the idea
being to make it simpler for web page designers to use. In fact, a qml
file is html with some additional tags; anything which is not part of a
qml tag is simply sent to the browser as html (except that macros and
variables in the text are interpolated into variable references
(any word with a $ in front of it).
The inclusion of html text
obviates the need for hyperSQL statements such as banner, header,
footer, etc.
The current release of QML is version 2.0.
Variables
QML variables are set either by being passed to the script as
CGI parameters (typically having been set by a qml_input in
a previous screen),
or set in a qml_output construct to the current values of the
database fields.
QML tags
Most QML tags accept parameters in the form of attribute=value
pairs.
There are a few attributes which don't take values.
In all tags, the value must be double-quoted
if it contains white space or the character ">".
In some tags, the value is a comma-separated list of
parameters - white space may be left
around the commas, but if it is,
the list must be quoted.
The following tags are supported;
features shown in red are new in QML 2.0:
- <qml_usedatabase [name=foo] [default] database=ocid login=foo server=bar password=hello>
-
This sets parameters for subsequent database access.
If the "default" attribute is specified,
or if the name attribute is not specified,
the database will be
the default database.
Any number of databases may be specified,
but there may be at most one default database.
- <qml_screen name=foo [file="foo/bar"]
[default]> and </qml_screen>
-
These tags delineate a QML
screen. Text inside the screen will be triggered only if the name of
the screen matches the target specified by the previous submit, or if
the screen has the "default" attribute and no target has been
specified.
Text or tags which lie outside any screen will be printed, in the order
encountered, regardless of what target is specified
(but note that not all QML tags may be used outside of screens).
The "file" attribute allows screens to reside in individual files; if
it is used, no /qml_screen tag is used with it. Use of the file option
will speed loading of large interfaces, since only the necessary screens
need be parsed.
Screens may not be nested.
- <qml_condition name=foo [op=or] sql="...">
-
Sets a condition which is tied to a variable (which will usually be
set in a previous screen, typically via a qml_input). Conditions are
used to construct sql "where" clauses for inputs which allow multiple
values. For example, consider the following qml code:
<qml_condition name=genus sql="genus like '$genus'">
<qml_input name=genus sql="select genus where $genus">
If a previous qml_input sets genus to the values Felis or
Escherichia, the condition will be expanded to "(genus like 'Felis'
or genus like 'Escherichia')", and the sql for the qml_input will be
expanded to "select genus where (genus like 'Felis' or genus like
'Escherichia')". The default for op is "or", but "and" may also be
used. If you aren't using multiple inputs or refineable queries,
you probably don't need
conditions. If you place conditions outside of any qml screen, you
can use them in multiple screens (eg, to generate sql both for a
refinable input and an output).
- <qml_table name=foo [database=foo] sql="...">
-
Performs a database access and stores the result for later use in a
qml_input, qml_output, or qml_download statement.
Some advantages over supplying the
sql directly in the input or output statement are:
- you can use the
same table more than once
- when I figure out how to join tables, you'll have some way of
referring to them
- in future versions of qml, use of
qml_table may allow use of asynchronous database access for
increased performance
- <qml_input name=genus sql="..." [type=radio|checkbox] [multiple]
[size=n] [blank="..."] [default="..."]
[label="..." ] [value="..."]>
- or
- <qml_input name=genus table=foo [type=radio|checkbox] [multiple]
[size=n] [blank="..."] [default="..."]
[label="..." ] [value="..."]>
-
Acts just like an input statement in hyperSQL, except that it is
always a database access (since other input types can be done as
straightforward html), and it allows radio buttons and checkboxes.
The "multiple" and "size" attributes are passed through to the resulting
html, and the browser will choose between a pulldown or scrolled list
accordingly.
If the "table" attribute is set, this construct uses the contents of
the existing table; otherwise it queries the database (after
substituting the current variable values into the sql) to produce its
own table.
The "label" attribute is a string which, after variable substitution,
is used as the label for the selection list.
The "value" attribute is used as the value in the list
(i.e., is actually sent to the next form as a cgi param).
The "blank" attribute just supplies a label to use when the database
returns a blank (null or whitespace only) value.
The "default" attribute selects which, if any, value is pre-selected.
It may take on several different values: MIN, MAX,
FIRST, LAST, NONE, or an arbitrary string.
MIN and MAX, typically used with
numeric values, select the smallest or largest value of the returned
set. FIRST or LAST select the first or last value in the list as it
is returned from the database (useful with SQL's "order by" clause).
NONE specifies no default selection,
and any other string is used as a label for a wildcard value.
If the clause is omitted, the default is a wildcard
with '** All values **' as the label.
The qml_input tag may be used only inside a QML screen.
- <qml_output sql="..." [[index=bar] start=0]> and </qml_output>
- or
- <qml_output table=foo [[index=bar] start=0]> and </qml_output>
-
If the "table" attribute is set, this construct uses the contents of
the existing table; otherwise the "sql" attribute must be set,
and QML will query the database (after
substituting the current variable values into the sql) to produce its
own table.
In either case, it then loops through the rows of the
table. On each pass through the loop, it sets variables to the
fieldnames of the table, substitutes them into the enclosed text, and
prints it. These are nestable and variables are lexically scoped.
If the "index" attribute is set, it is the name of a variable which
will contain the index of each iteration through the loop;
the "start" attribute may be used to set
the starting value of the variable (eg, 0, 1, a, etc).
Note: though qml is case-sensitive, Oracle is case-sensitive upper.
If using Oracle, variable names must be in all caps inside
the qml_output construct.
- <qml_if param=xxx,yyy> and </qml_if>
-
Replaces the SUPPRESS_IF_EMPTY construct of hyperSQL. Text between
the tags is output only if all specified variables are set. Nestable.
Note that, before 2.0, the keyword for the param attribute was "var" -
this is still accepted, but "param" is preferred. The change has been
made for the sake of consistency with other constructs. "param" and
"var" are now synonyms in all QML tags where they occur.
- <qml_unless param=xxx,yyy> and </qml_unless>
-
The negation of a qml_if.
Text between the tags is output only if
none of the specified variables are set. Nestable.
You can use these along with a qml_quit to require that
the user make a selection for particular inputs.
I'd really like to replace this with a more flexible
version of qml_if.
See qml_if for discussion of the param attribute.
- <qml_ifselected name=foo field=bar> and </qml_ifselected>
-
This tag allows the script developer to test the value of a list
returned by a group of related inputs.
In a typical case, a select list for
use in an sql statement would be constructed using
a series of html checkboxes:
<input type=checkbox name=foo value=genus>Genus
<input type=checkbox name=foo value=species>Species
This creates a variable "foo" which can be used in an sql statement:
sql="select $foo from table"
and will select only those items which the user has checked. If the
script developer wishes to determine what the user has chosen - say, to
create the column headings for an output table - he can
use the qml_ifselect tag to look inside the selectlist:
<qml_ifselected name=foo field=genus><th>Genus</th></qml_ifselected>
- <qml_link target=yyy param=abc,def>
-
Creates a link reference - highlighted text which will contact
QML and produce the screen specified in the target parameter.
Variables which must be set should be given in the param
parameter (the values will be taken care of by QML).
This tag produces only the text <a href=....>;
the script developer must supply the linked text
and the closing </a> tag.
- <qml_submit target=foo [value="Retrieve Results"]>
-
Creates a submit button for the current query, formatting
it using the query page specified in the "target" attribute.
The "value" attribute, if it exists, is used as the label
for the button.
The qml_submit tag may be used only inside a QML screen.
- <qml_refine [value="Refine Query"]>
-
Creates a button which resubmits the query with the same
parameters, but updated with selections made from the current
page. This is typically used for refining the query; i.e.,
eliminating choices which are unavailable due to constraints
imposed by other selections.
The qml_refine tag may be used only inside a QML screen.
- <qml_new [value="New Query"]>
-
Creates a button which creates a new query form (i.e., which
brings up the default screen with the same initial values
as when first invoked).
The qml_new tag may be used only inside a QML screen.
- <qml_macro name=foo text="...">
-
Interpolates the given text anywhere where the variable $foo is
used. Variables are substituted when the macro is expanded,
not when it is read
(note that this was incorrectly documented in earlier versions
of QML).
Macros are global - if set inside a qml_output construct, they
exist after than construct is egressed.
- <qml_set name=foo text="...">
-
Interpolates the given text anywhere where the variable $foo is
used. Variables are substituted when the tag is read
(which is the only difference between qml_set and qml_macro).
- <qml_forward param=foo,bar,...>
-
Forwards the given variable to the next screen. This is really
just shorthand for a list of html statements of the form:
<input type=hidden name=foo value="$foo">
which are often needed in linked chains of qml screens.
The qml_forward tag may be used only inside a QML screen.
- <qml_debug [sql=on] [method=get]>
-
The qml_debug tag sets various parameters to help in debugging.
With the sql parameter set (either just "sql" or "sql=on"),
html comments are produced showing the sql which is
sent to the database server.
This can be turned off with "sql=off".
The cgi method used to invoke subsequent screens may be set
to either "post" (the default, and more secure) or "get"
(which shows the passed parameters in the browser window).
- <qml_download [type=application/csv] table=foo>
- or
- <qml_download [type=application/csv] sql="...">
- or
- <qml_download [type=application/csv]> and </qml_download>
-
Creates an anchor for a link which will
download the specified table as a file of comma-separated values
(usable by many spreadsheets).
The script developer must supply the text
after the anchor link and the closing </a> tag.
The last version allows arbitrary constructs enclosed
by the tags to produce the file.
The type param allows specification of an HTTP Content-Type: header
(the default is application/csv).
- <qml_quit>
-
Stop processing qml input.
Things hyperSQL does that qml doesn't do (yet)
- HyperSQL pages its output. Not clear how to do this in QML.
- HyperSQL handles image and sound data stored in databases.
Implementation of this functionality in qml will have to wait until
I've implemented a daemon that can cache multimedia files, since I
don't want to write them as temporary files.
Other functionality I'd like to add to qml
- Ability to use a database proxy daemon
- Ability to join tables
- A more flexible qml_if statement. It would be easy to
implement this as an arbitrary perl expression, but would present
some security risks
- Ability to massage data. It might be desirable to perform
some transformation of downloaded data, especially in order to
make tables join properly. Ideally, an eval'able chunk of
perl would be used, but, again, this presents considerations
of security
- A qml_automatic tag, analogous to hyperSQL smartlists, to
automatically produce no-frills sql
- Lots of caching to improve performance, but that shouldn't
affect the language
Acknowledgements
I am much indebted to
Drs.
Cherri Pancake
and Mark Newsome,
and to Joe Hanus and Sherry Pittam,
without whom QML wouldn't exist.
Mark, with much guidance from Cherri,
and much input from Joe and Sherry,
thought up
all the foregoing database
access ideas
and implemented them in the original hyperSQL.
He then
patiently explained to me what they were doing
as I subsequently stole his ideas for use in QML.
Joe and Sherry listened to me muse a lot
and helped me
figure out what sort of constructs would be useful and desirable
in QML.
Thanks, all.