LP#1678638: technical documentation for qstore
authorGalen Charlton <gmc@equinoxinitiative.org>
Sun, 2 Apr 2017 18:59:00 +0000 (14:59 -0400)
committerMike Rylander <mrylander@gmail.com>
Fri, 19 May 2017 20:08:34 +0000 (16:08 -0400)
Technical documentation and sample stored queries for
the open-ils.qstore service, written by Scott McKellar
and copyright 2010 by the Equinox Open Library Initiative.

Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Signed-off-by: Mike Rylander <mrylander@gmail.com>

docs/TechRef/qstore/README [new file with mode: 0644]
docs/TechRef/qstore/qstore.odt [new file with mode: 0644]
docs/TechRef/qstore/query_dump.sh [new file with mode: 0755]
docs/TechRef/qstore/query_dump.sql [new file with mode: 0644]
docs/TechRef/qstore/query_schema.odt [new file with mode: 0644]
docs/TechRef/qstore/stored_queries.txt [new file with mode: 0644]
docs/TechRef/qstore/test_qstore.txt [new file with mode: 0644]

diff --git a/docs/TechRef/qstore/README b/docs/TechRef/qstore/README
new file mode 100644 (file)
index 0000000..a0209b1
--- /dev/null
@@ -0,0 +1,60 @@
+This archive includes documentation of the query schema and the
+qstore server, along with some examples of stored queries.
+
+The query schema stores database queries in an abstract form
+within the database itself.
+
+The qstore server loads queries from the query schema, constructs
+the corresponding SQL, and executes the queries.
+
+Table of contents:
+
+qstore.odt
+
+       Documentation of the qstore methods.
+
+query_dump.sh
+
+       A shell script used to generate query_dump.sql, using the
+       pg_dump utility.  I include this script mostly for my own
+       convenience in case I want to update this archive, but you may
+       find it useful to know exactly where the sql script came from.
+       (Note: after generating the sql script I manually edited it to
+       remove the loading of the datatype table.)
+
+query_dump.sql
+
+       An sql script to install a collection of examples.  The script
+       assumes that the query schema exists but has no data in it,
+       except for the datatypes in query.datatype as installed by
+       the installation script.
+
+query_schema.odt
+
+       Documentation of the query schema.
+
+README
+
+       You're looking at it.
+
+stored_queries.txt
+
+       SQL queries generated from the queries loaded by query_dump.sql.
+
+test_qstore.txt
+
+       Documentation of the test_qstore utility.  The same documentation
+       appears in a comment block at the top of test_qstore.c.
+
+The sql script loads 25 example queries, with query ids in the range 1-25.  
+These are the queries I used to develop and test qstore.  I used
+the test_qstore utility to generate each query, and then pasted the
+output into stored_queries.txt.
+
+The queries themselves are not particularly useful, and often don't
+even make much sense from an application standpoint.  They are just
+exercises in the generation of syntax.  In any case they all run
+successfully within psql (except for query # 12, which features
+an unsubstituted bind variable).
+
+Scott McKellar
diff --git a/docs/TechRef/qstore/qstore.odt b/docs/TechRef/qstore/qstore.odt
new file mode 100644 (file)
index 0000000..2f416e8
Binary files /dev/null and b/docs/TechRef/qstore/qstore.odt differ
diff --git a/docs/TechRef/qstore/query_dump.sh b/docs/TechRef/qstore/query_dump.sh
new file mode 100755 (executable)
index 0000000..f3e9676
--- /dev/null
@@ -0,0 +1,9 @@
+pg_dump -n query \
+       --file=query_dump.sql \
+       --data-only \
+       --schema=query \
+       --disable-triggers \
+       --host=localhost \
+       --username=evergreen \
+       --password \
+       evergreen
diff --git a/docs/TechRef/qstore/query_dump.sql b/docs/TechRef/qstore/query_dump.sql
new file mode 100644 (file)
index 0000000..4a7c062
--- /dev/null
@@ -0,0 +1,393 @@
+--
+-- PostgreSQL database dump
+--
+
+SET client_encoding = 'UTF8';
+SET standard_conforming_strings = off;
+SET check_function_bodies = false;
+SET client_min_messages = warning;
+SET escape_string_warning = off;
+
+SET search_path = query, pg_catalog;
+
+--
+-- Name: case_branch_id_seq; Type: SEQUENCE SET; Schema: query; Owner: evergreen
+--
+
+SELECT pg_catalog.setval('case_branch_id_seq', 3, true);
+
+
+--
+-- Name: expression_id_seq; Type: SEQUENCE SET; Schema: query; Owner: evergreen
+--
+
+SELECT pg_catalog.setval('expression_id_seq', 60, true);
+
+
+--
+-- Name: from_relation_id_seq; Type: SEQUENCE SET; Schema: query; Owner: evergreen
+--
+
+SELECT pg_catalog.setval('from_relation_id_seq', 10, true);
+
+
+--
+-- Name: function_param_def_id_seq; Type: SEQUENCE SET; Schema: query; Owner: evergreen
+--
+
+SELECT pg_catalog.setval('function_param_def_id_seq', 1, false);
+
+
+--
+-- Name: function_sig_id_seq; Type: SEQUENCE SET; Schema: query; Owner: evergreen
+--
+
+SELECT pg_catalog.setval('function_sig_id_seq', 6, true);
+
+
+--
+-- Name: order_by_item_id_seq; Type: SEQUENCE SET; Schema: query; Owner: evergreen
+--
+
+SELECT pg_catalog.setval('order_by_item_id_seq', 4, true);
+
+
+--
+-- Name: query_sequence_id_seq; Type: SEQUENCE SET; Schema: query; Owner: evergreen
+--
+
+SELECT pg_catalog.setval('query_sequence_id_seq', 4, true);
+
+
+--
+-- Name: record_column_id_seq; Type: SEQUENCE SET; Schema: query; Owner: evergreen
+--
+
+SELECT pg_catalog.setval('record_column_id_seq', 1, false);
+
+
+--
+-- Name: select_item_id_seq; Type: SEQUENCE SET; Schema: query; Owner: evergreen
+--
+
+SELECT pg_catalog.setval('select_item_id_seq', 47, true);
+
+
+--
+-- Name: stored_query_id_seq; Type: SEQUENCE SET; Schema: query; Owner: evergreen
+--
+
+SELECT pg_catalog.setval('stored_query_id_seq', 25, true);
+
+
+--
+-- Name: subfield_id_seq; Type: SEQUENCE SET; Schema: query; Owner: evergreen
+--
+
+SELECT pg_catalog.setval('subfield_id_seq', 1, false);
+
+
+--
+-- Data for Name: bind_variable; Type: TABLE DATA; Schema: query; Owner: evergreen
+--
+
+ALTER TABLE bind_variable DISABLE TRIGGER ALL;
+
+COPY bind_variable (name, type, description, default_value, label) FROM stdin;
+shortname      string  org unit shortname      "BR3"   lib shortname
+O'Leary        string  Ireland's kind of name  "O'Bryan"       nom d'Eire
+ou     number  org unit        \N      lib
+\.
+
+
+ALTER TABLE bind_variable ENABLE TRIGGER ALL;
+
+--
+-- Data for Name: case_branch; Type: TABLE DATA; Schema: query; Owner: evergreen
+--
+
+ALTER TABLE case_branch DISABLE TRIGGER ALL;
+
+COPY case_branch (id, parent_expr, seq_no, condition, result) FROM stdin;
+2      53      2       54      56
+3      53      3       \N      57
+1      53      1       58      55
+\.
+
+
+ALTER TABLE case_branch ENABLE TRIGGER ALL;
+
+--
+-- Data for Name: expression; Type: TABLE DATA; Schema: query; Owner: evergreen
+--
+
+ALTER TABLE expression DISABLE TRIGGER ALL;
+
+COPY expression (id, type, parenthesize, parent_expr, seq_no, literal, table_alias, column_name, left_operand, operator, right_operand, function_id, subquery, cast_type, negate, bind_variable) FROM stdin;
+1      xbool   f       \N      1       TRUE    \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+2      xcol    f       \N      1       \N      aou     id      \N      \N      \N      \N      \N      \N      f       \N
+3      xcol    f       \N      1       \N      aou     name    \N      \N      \N      \N      \N      \N      f       \N
+4      xcol    f       \N      1       \N      aou     shortname       \N      \N      \N      \N      \N      \N      f       \N
+5      xcol    f       \N      1       \N      aou     parent_ou       \N      \N      \N      \N      \N      \N      f       \N
+6      xnum    f       \N      1       3       \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+7      xop     f       \N      1       \N      \N      \N      5       >       6       \N      \N      \N      f       \N
+8      xop     f       \N      1       \N      \N      \N      2       =       6       \N      \N      \N      f       \N
+9      xsubq   f       \N      1       \N      \N      \N      \N      \N      \N      \N      3       \N      f       \N
+10     xcol    f       \N      1       \N      aout    id      \N      \N      \N      \N      \N      \N      f       \N
+11     xin     f       \N      1       \N      \N      \N      10      \N      \N      \N      3       \N      f       \N
+12     xcol    f       \N      1       \N      aou     ou_type \N      \N      \N      \N      \N      \N      f       \N
+13     xcol    f       \N      1       \N      au      id      \N      \N      \N      \N      \N      \N      f       \N
+14     xop     f       \N      1       \N      \N      \N      13      =       12      \N      \N      \N      f       \N
+15     xex     f       \N      1       \N      \N      \N      \N      \N      \N      \N      3       \N      f       \N
+16     xcol    f       \N      1       \N      aou     \N      \N      \N      \N      \N      \N      \N      f       \N
+17     xbind   f       \N      1       \N      \N      \N      \N      \N      \N      \N      \N      \N      f       ou
+18     xop     f       \N      1       \N      \N      \N      2       =       17      \N      \N      \N      f       \N
+19     xcol    f       \N      1       \N      aou     opac_visible    \N      \N      \N      \N      \N      \N      f       \N
+20     xbind   f       \N      1       \N      \N      \N      \N      \N      \N      \N      \N      \N      f       shortname
+21     xop     f       \N      1       \N      \N      \N      4       =       20      \N      \N      \N      f       \N
+23     xcol    f       \N      1       \N      aou     email   \N      \N      \N      \N      \N      \N      f       \N
+24     xcol    f       \N      1       \N      aou     holds_address   \N      \N      \N      \N      \N      \N      f       \N
+27     xser    f       \N      1       \N      \N      \N      \N      OR      \N      \N      \N      \N      f       \N
+22     xisnull f       27      1       \N      \N      \N      5       \N      \N      \N      \N      \N      f       \N
+25     xisnull f       27      2       \N      \N      \N      23      \N      \N      \N      \N      \N      f       \N
+26     xisnull f       27      3       \N      \N      \N      24      \N      \N      \N      \N      \N      f       \N
+32     xin     f       \N      1       \N      \N      \N      5       \N      \N      \N      \N      \N      f       \N
+29     xnum    f       32      1       1       \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+30     xnum    f       32      2       3       \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+31     xnum    f       32      3       6       \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+33     xfunc   f       \N      1       \N      \N      \N      \N      \N      \N      1       \N      \N      f       \N
+34     xcol    f       33      1       \N      aou     name    \N      \N      \N      \N      \N      \N      f       \N
+35     xop     f       \N      1       \N      \N      \N      2       =       6       \N      \N      \N      f       \N
+36     xfunc   f       \N      1       \N      \N      name    \N      \N      \N      2       \N      \N      f       \N
+37     xcol    f       36      1       \N      aou     id      \N      \N      \N      \N      \N      \N      f       \N
+38     xbet    f       \N      1       \N      \N      \N      5       \N      \N      \N      \N      \N      f       \N
+39     xnum    f       38      1       1       \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+40     xnum    f       38      2       4       \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+41     xfunc   f       \N      1       \N      \N      \N      \N      \N      \N      3       \N      \N      f       \N
+42     xstr    f       41      1       DOW     \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+43     xcol    f       41      2       \N      au      create_date     \N      \N      \N      \N      \N      \N      f       \N
+44     xfunc   f       \N      1       \N      \N      \N      \N      \N      \N      4       \N      \N      f       \N
+45     xnum    f       44      1       1       \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+46     xfunc   f       \N      1       \N      \N      \N      \N      \N      \N      1       \N      \N      f       \N
+47     xstr    f       46      1       goober  \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+48     xcol    f       \N      1       \N      \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+49     xfunc   f       \N      1       \N      \N      \N      \N      \N      \N      5       \N      \N      f       \N
+50     xfunc   f       \N      1       \N      \N      \N      \N      \N      \N      6       \N      \N      f       \N
+51     xstr    f       50      1       both    \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+52     xcol    f       50      2       \N      au      usrname \N      \N      \N      \N      \N      \N      f       \N
+54     xnum    f       \N      1       2       \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+55     xstr    f       \N      1       First   \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+56     xstr    f       \N      1       Second  \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+57     xstr    f       \N      1       Other   \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+53     xcase   f       \N      1       \N      \N      \N      2       \N      \N      \N      \N      \N      f       \N
+58     xnum    f       \N      1       1       \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+59     xcast   f       \N      1       \N      \N      \N      2       \N      \N      \N      \N      13      f       \N
+60     xnum    f       \N      1       100     \N      \N      \N      \N      \N      \N      \N      \N      f       \N
+\.
+
+
+ALTER TABLE expression ENABLE TRIGGER ALL;
+
+--
+-- Data for Name: from_relation; Type: TABLE DATA; Schema: query; Owner: evergreen
+--
+
+ALTER TABLE from_relation DISABLE TRIGGER ALL;
+
+COPY from_relation (id, type, table_name, class_name, subquery, function_call, table_alias, parent_relation, seq_no, join_type, on_clause) FROM stdin;
+1      RELATION        actor.org_unit  aou     \N      \N      aou     \N      1       \N      \N
+2      RELATION        actor.org_unit_type     aout    \N      \N      aout    1       1       INNER   1
+3      RELATION        actor.org_unit  aou     \N      \N      aou     \N      1       \N      \N
+4      RELATION        actor.org_unit_type     aout    \N      \N      aout    \N      1       \N      \N
+5      SUBQUERY        \N      \N      3       \N      aou     \N      1       \N      \N
+6      RELATION        actor.usr       au      \N      \N      au      \N      1       \N      \N
+7      SUBQUERY        \N      \N      3       \N      aou     6       1       INNER   14
+8      RELATION        \N      aou     \N      \N      aou     \N      1       \N      \N
+9      RELATION        actor.usr       au      \N      \N      au      \N      1       \N      \N
+10     FUNCTION        \N      \N      \N      46      \N      \N      1       \N      \N
+\.
+
+
+ALTER TABLE from_relation ENABLE TRIGGER ALL;
+
+--
+-- Data for Name: function_param_def; Type: TABLE DATA; Schema: query; Owner: evergreen
+--
+
+ALTER TABLE function_param_def DISABLE TRIGGER ALL;
+
+COPY function_param_def (id, function_id, seq_no, datatype) FROM stdin;
+\.
+
+
+ALTER TABLE function_param_def ENABLE TRIGGER ALL;
+
+--
+-- Data for Name: function_sig; Type: TABLE DATA; Schema: query; Owner: evergreen
+--
+
+ALTER TABLE function_sig DISABLE TRIGGER ALL;
+
+COPY function_sig (id, function_name, return_type, is_aggregate) FROM stdin;
+1      upper   13      f
+2      actor.org_unit_ancestors        \N      f
+4      COUNT   \N      t
+5      CURRENT_DATE    19      f
+3      EXTRACT 7       f
+6      TRIM    13      f
+\.
+
+
+ALTER TABLE function_sig ENABLE TRIGGER ALL;
+
+--
+-- Data for Name: order_by_item; Type: TABLE DATA; Schema: query; Owner: evergreen
+--
+
+ALTER TABLE order_by_item DISABLE TRIGGER ALL;
+
+COPY order_by_item (id, stored_query, seq_no, expression) FROM stdin;
+3      10      1       5
+4      10      2       2
+\.
+
+
+ALTER TABLE order_by_item ENABLE TRIGGER ALL;
+
+--
+-- Data for Name: query_sequence; Type: TABLE DATA; Schema: query; Owner: evergreen
+--
+
+ALTER TABLE query_sequence DISABLE TRIGGER ALL;
+
+COPY query_sequence (id, parent_query, seq_no, child_query) FROM stdin;
+3      2       1       1
+4      2       2       1
+\.
+
+
+ALTER TABLE query_sequence ENABLE TRIGGER ALL;
+
+--
+-- Data for Name: record_column; Type: TABLE DATA; Schema: query; Owner: evergreen
+--
+
+ALTER TABLE record_column DISABLE TRIGGER ALL;
+
+COPY record_column (id, from_relation, seq_no, column_name, column_type) FROM stdin;
+\.
+
+
+ALTER TABLE record_column ENABLE TRIGGER ALL;
+
+--
+-- Data for Name: select_item; Type: TABLE DATA; Schema: query; Owner: evergreen
+--
+
+ALTER TABLE select_item DISABLE TRIGGER ALL;
+
+COPY select_item (id, stored_query, seq_no, expression, column_alias, grouped_by) FROM stdin;
+1      1       1       2       id      f
+2      1       2       3       name    f
+3      1       3       4       short_name      f
+6      4       2       9       \N      f
+5      4       1       10      \N      f
+7      5       1       10      \N      f
+8      6       1       12      \N      f
+4      3       1       12      \N      f
+9      7       1       12      goober  f
+10     8       1       10      \N      f
+11     9       1       16      \N      f
+12     10      1       2       id      f
+13     10      2       5       parent  f
+14     10      3       4       short_name      f
+15     11      1       2       id      f
+16     12      1       2       \N      f
+17     12      2       3       \N      f
+18     12      3       4       \N      f
+19     12      4       19      \N      f
+20     12      5       5       \N      f
+21     13      1       2       \N      f
+22     13      2       3       \N      f
+23     13      3       4       \N      f
+24     13      4       19      \N      f
+25     13      5       5       \N      f
+26     14      1       2       \N      f
+27     15      1       2       id      f
+28     16      1       2       id      f
+29     17      1       2       id      f
+30     17      2       3       name    f
+31     17      3       33      name    f
+32     18      1       2       id      f
+33     18      2       3       id      f
+34     18      4       36      root_name       f
+35     19      1       2       id      f
+36     20      1       13      id      f
+37     20      2       41      create_day      f
+39     21      2       44      how_many        f
+38     21      1       5       parent  t
+40     23      1       48      \N      f
+41     22      1       44      how_many        f
+42     22      2       5       parent  t
+43     20      3       49      today   f
+45     24      2       53      Branch sequence f
+46     24      1       2       id      f
+47     25      1       59      cast_text       f
+\.
+
+
+ALTER TABLE select_item ENABLE TRIGGER ALL;
+
+--
+-- Data for Name: stored_query; Type: TABLE DATA; Schema: query; Owner: evergreen
+--
+
+ALTER TABLE stored_query DISABLE TRIGGER ALL;
+
+COPY stored_query (id, type, use_all, use_distinct, from_clause, where_clause, having_clause, limit_count, offset_count) FROM stdin;
+1      SELECT  f       f       1       7       \N      \N      \N
+2      UNION   f       f       \N      \N      \N      \N      \N
+3      SELECT  f       f       3       8       \N      \N      \N
+4      SELECT  f       f       4       \N      \N      \N      \N
+5      SELECT  f       f       4       11      \N      \N      \N
+6      SELECT  f       f       5       \N      \N      \N      \N
+7      SELECT  f       f       6       \N      \N      \N      \N
+8      SELECT  f       f       4       15      \N      \N      \N
+9      SELECT  f       f       3       \N      \N      \N      \N
+10     SELECT  f       f       3       \N      \N      \N      \N
+13     SELECT  f       f       8       21      \N      \N      \N
+14     SELECT  f       f       3       22      \N      \N      \N
+15     SELECT  f       f       3       27      \N      \N      \N
+16     SELECT  f       f       3       32      \N      \N      \N
+17     SELECT  f       f       3       32      \N      \N      \N
+18     SELECT  f       f       3       35      \N      \N      \N
+19     SELECT  f       f       3       38      \N      \N      \N
+20     SELECT  f       f       9       \N      \N      \N      \N
+21     SELECT  f       f       3       \N      \N      \N      \N
+23     SELECT  f       f       10      \N      \N      \N      \N
+22     SELECT  f       f       3       \N      \N      \N      \N
+24     SELECT  f       f       3       \N      \N      \N      \N
+25     SELECT  f       f       3       \N      \N      \N      \N
+12     SELECT  f       f       8       18      \N      \N      \N
+11     SELECT  f       f       8       \N      \N      60      58
+\.
+
+
+ALTER TABLE stored_query ENABLE TRIGGER ALL;
+
+--
+-- Data for Name: subfield; Type: TABLE DATA; Schema: query; Owner: evergreen
+--
+
+ALTER TABLE subfield DISABLE TRIGGER ALL;
+
+COPY subfield (id, composite_type, seq_no, subfield_type) FROM stdin;
+\.
+
+
+ALTER TABLE subfield ENABLE TRIGGER ALL;
+
+--
+-- PostgreSQL database dump complete
+--
+
diff --git a/docs/TechRef/qstore/query_schema.odt b/docs/TechRef/qstore/query_schema.odt
new file mode 100644 (file)
index 0000000..823fa80
Binary files /dev/null and b/docs/TechRef/qstore/query_schema.odt differ
diff --git a/docs/TechRef/qstore/stored_queries.txt b/docs/TechRef/qstore/stored_queries.txt
new file mode 100644 (file)
index 0000000..bdf84e7
--- /dev/null
@@ -0,0 +1,316 @@
+Stored Queries
+
+1: JOIN
+
+SELECT
+   "aou".id AS "id",
+   "aou".name AS "name",
+   "aou".shortname AS "short_name"
+FROM
+   actor.org_unit AS "aou"
+      INNER JOIN actor.org_unit_type AS "aout"
+         ON TRUE
+WHERE
+   "aou".parent_ou > 3;
+
+------------------------------------------------------------
+2: UNION
+
+SELECT
+   "aou".id AS "id",
+   "aou".name AS "name",
+   "aou".shortname AS "short_name"
+FROM
+   actor.org_unit AS "aou"
+      INNER JOIN actor.org_unit_type AS "aout"
+         ON TRUE
+WHERE
+   "aou".parent_ou > 3
+UNION
+SELECT
+   "aou".id AS "id",
+   "aou".name AS "name",
+   "aou".shortname AS "short_name"
+FROM
+   actor.org_unit AS "aou"
+      INNER JOIN actor.org_unit_type AS "aout"
+         ON TRUE
+WHERE
+   "aou".parent_ou > 3;
+
+-----------------------------------------------------------
+3: Simple query (to be used elsewhere as a subquery)
+
+SELECT
+   "aou".ou_type
+FROM
+   actor.org_unit AS "aou"
+WHERE
+   "aou".id = 3;
+
+-------------------------------------------------------------
+4: SELECTing a subquery
+
+SELECT
+   "aout".id,
+   (
+      SELECT
+         "aou".ou_type
+      FROM
+         actor.org_unit AS "aou"
+      WHERE
+         "aou".id = 3
+   )
+FROM
+   actor.org_unit_type AS "aout";
+
+--------------------------------------------------------------
+5: IN clause with a subquery
+
+SELECT
+   "aout".id
+FROM
+   actor.org_unit_type AS "aout"
+WHERE
+   "aout".id IN (
+      SELECT
+         "aou".ou_type
+      FROM
+         actor.org_unit AS "aou"
+      WHERE
+         "aou".id = 3
+   );
+
+---------------------------------------------------------------
+6: Subquery in the FROM clause
+
+SELECT
+   "aou".ou_type
+FROM
+   (
+      SELECT
+         "aou".ou_type
+      FROM
+         actor.org_unit AS "aou"
+      WHERE
+         "aou".id = 3
+   ) AS "aou";
+
+----------------------------------------------------------------
+7: JOINing to a subquery
+
+SELECT
+   "aou".ou_type AS "goober"
+FROM
+   actor.usr AS "au"
+      INNER JOIN (
+         SELECT
+            "aou".ou_type
+         FROM
+            actor.org_unit AS "aou"
+         WHERE
+            "aou".id = 3
+      ) AS "aou"
+         ON "au".id = "aou".ou_type;
+
+----------------------------------------------------------------
+8: EXISTS clause
+
+SELECT
+   "aout".id
+FROM
+   actor.org_unit_type AS "aout"
+WHERE
+   EXISTS (
+      SELECT
+         "aou".ou_type
+      FROM
+         actor.org_unit AS "aou"
+      WHERE
+         "aou".id = 3
+   );
+
+----------------------------------------------------------------
+9: SELECTing a wild card
+
+SELECT
+   "aou".*
+FROM
+   actor.org_unit AS "aou";
+
+------------------------------------------------------------------
+10: ORDER BY clause
+
+SELECT
+   "aou".id AS "id",
+   "aou".parent_ou AS "parent",
+   "aou".shortname AS "short_name"
+FROM
+   actor.org_unit AS "aou"
+ORDER BY
+   "aou".parent_ou,
+   "aou".id;
+
+--------------------------------------------------------------------
+11: Looking up table name in IDL
+
+SELECT
+   "aou".id AS "id"
+FROM
+   actor.org_unit AS "aou";
+
+--------------------------------------------------------------------
+12: numeric bind variable (also works if default value is available)
+
+SELECT
+   "aou".id,
+   "aou".name,
+   "aou".shortname,
+   "aou".opac_visible,
+   "aou".parent_ou
+FROM
+   actor.org_unit AS "aou"
+WHERE
+   "aou".id = :ou;
+
+--------------------------------------------------------------------
+13: string bind variable (using default value 'BRE' for :shortname)
+
+SELECT
+   "aou".id,
+   "aou".name,
+   "aou".shortname,
+   "aou".opac_visible,
+   "aou".parent_ou
+FROM
+   actor.org_unit AS "aou"
+WHERE
+   "aou".shortname = 'BR3';
+
+--------------------------------------------------------------------
+14: IS NULL expression
+
+SELECT
+   "aou".id
+FROM
+   actor.org_unit AS "aou"
+WHERE
+   "aou".parent_ou IS NULL;
+
+--------------------------------------------------------------------
+15: Series expression (chain of ORs)
+
+SELECT
+   "aou".id AS "id"
+FROM
+   actor.org_unit AS "aou"
+WHERE
+   "aou".parent_ou IS NULL
+   OR "aou".email IS NULL
+   OR "aou".holds_address IS NULL;
+
+--------------------------------------------------------------------
+16: IN list
+
+SELECT
+   "aou".id AS "id"
+FROM
+   actor.org_unit AS "aou"
+WHERE
+   "aou".parent_ou IN (1, 3, 6);
+
+--------------------------------------------------------------------
+17: Function call
+
+SELECT
+   "aou".id AS "id",
+   "aou".name AS "name",
+   upper("aou".name) AS "name"
+FROM
+   actor.org_unit AS "aou"
+WHERE
+   "aou".parent_ou IN (1, 3, 6);
+
+--------------------------------------------------------------------
+18: Function call with subfield
+
+SELECT
+   "aou".id AS "id",
+   "aou".name AS "id",
+   (actor.org_unit_ancestors("aou".id))."name" AS "root_name"
+FROM
+   actor.org_unit AS "aou"
+WHERE
+   "aou".id = 3;
+
+--------------------------------------------------------------------
+19: BETWEEN expression
+
+SELECT
+   "aou".id AS "id"
+FROM
+   actor.org_unit AS "aou"
+WHERE
+   "aou".parent_ou BETWEEN 1 AND 4;
+
+--------------------------------------------------------------------
+20: EXTRACT and CURRENT_DATE functions
+
+SELECT
+   "au".id AS "id",
+   EXTRACT(DOW FROM "au".create_date) AS "create_day",
+   CURRENT_DATE  AS "today"
+FROM
+   actor.usr AS "au";
+
+--------------------------------------------------------------------
+21: GROUP BY, with aggregate function last
+
+SELECT
+   "aou".parent_ou AS "parent",
+   COUNT(1) AS "how_many"
+FROM
+   actor.org_unit AS "aou"
+GROUP BY 1;
+
+--------------------------------------------------------------------
+22: GROUP BY, with aggregate function first
+
+SELECT
+   COUNT(1) AS "how_many",
+   "aou".parent_ou AS "parent"
+FROM
+   actor.org_unit AS "aou"
+GROUP BY 2;
+
+--------------------------------------------------------------------
+23: Function in the FROM clause
+
+SELECT
+   *
+FROM
+   upper('goober');
+
+--------------------------------------------------------------------
+24: CASE expression
+
+SELECT
+   "aou".id AS "id",
+   CASE "aou".id
+      WHEN 1
+         THEN 'First'
+      WHEN 2
+         THEN 'Second'
+      ELSE
+         'Other'
+   END AS "Branch sequence"
+FROM
+   actor.org_unit AS "aou";
+
+--------------------------------------------------------------------
+25: CAST expression
+
+SELECT
+   CAST ("aou".id AS TEXT) AS "cast_text"
+FROM
+   actor.org_unit AS "aou";
diff --git a/docs/TechRef/qstore/test_qstore.txt b/docs/TechRef/qstore/test_qstore.txt
new file mode 100644 (file)
index 0000000..6210767
--- /dev/null
@@ -0,0 +1,48 @@
+test_qstore
+
+This command-line utility exercises most of the code used in the qstore server, but
+without the complications of sending and receiving OSRF messages.
+
+Synopsis:
+
+test_qstore  [options]  query_id
+
+Query_id is the id of a row in the query.stored_query table, defining a stored query.
+
+The program reads the specified row in query.stored_query, along with associated rows
+in other tables, and displays the corresponding query as an SQL command.  Optionally it
+may execute the query, display the column names of the query result, and/or display the
+bind variables.
+
+In order to connect to the database, test_qstore uses various connection parameters
+that may be specified on the command line.  Any connection parameter not specified
+reverts to a plausible default.
+
+The database password may be read from a specified file or entered from the keyboard.
+
+Options:
+
+-b  Boolean; Display the name of any bind variables, and their default values.
+
+-D  Specifies the name of the database driver; defaults to "pgsql".
+
+-c  Boolean; display column names of the query results, as assigned by PostgreSQL.
+
+-d  Specifies the database name; defaults to "evergreen".
+
+-h  Specifies the hostname of the database; defaults to "localhost".
+
+-i  Specifies the name of the IDL file; defaults to "/openils/conf/fm_IDL.xml".
+
+-p  Specifies the port number of the database; defaults to 5432.
+
+-u  Specifies the database user name; defaults to "evergreen".
+
+-v  Boolean; Run in verbose mode, spewing various detailed messages.  This option is not
+       likely to be useful unless you are troubleshooting the code that loads the stored
+       query.
+
+-w  Specifies the name of a file containing the database password (no default).
+
+-x  Boolean: Execute the query and display the results.
+