summaryrefslogtreecommitdiff
path: root/Docs
diff options
context:
space:
mode:
authorunknown <pem@mysql.telia.com>2003-09-16 14:26:08 +0200
committerunknown <pem@mysql.telia.com>2003-09-16 14:26:08 +0200
commit4deedf6263df02229a30a0aa2f6621074f140b19 (patch)
treedccbb3bbe8e061d9d2956a24883ae6f2b5002f9e /Docs
parente68197450d5f42f1b07138248fff46455190ebce (diff)
downloadmariadb-git-4deedf6263df02229a30a0aa2f6621074f140b19.tar.gz
Implemented SP CONDITIONs and HANDLERs, with the extension of handling
MySQL error codes as well. (No UNDO HANDLERs yet, and no SIGNAL or RESIGNAL.) WL#850 Docs/sp-imp-spec.txt: Spec of CONDITIONs and HANDLERs (and updated some old stuff too). Docs/sp-implemented.txt: Updated info about caching, CONDITIONs and HANDLERs. include/mysqld_error.h: New error for undeclared CONDITION. libmysqld/Makefile.am: New file: sp_rcontext.cc. mysql-test/r/sp-error.result: New tests for CONDITIONs and HANDLERs. mysql-test/r/sp.result: New tests for CONDITIONs and HANDLERs. mysql-test/t/sp-error.test: New tests for CONDITIONs and HANDLERs. mysql-test/t/sp.test: New tests for CONDITIONs and HANDLERs. sql/Makefile.am: New file: sp_rcontext.cc. sql/lex.h: New symbols for CONDITIONs, HANDLERs and CURSORs. sql/mysqld.cc: Catch error if we have a handler for it. sql/protocol.cc: Catch error if we have a handler for it. sql/share/czech/errmsg.txt: New error for undeclared CONDITION. sql/share/danish/errmsg.txt: New error for undeclared CONDITION. sql/share/dutch/errmsg.txt: New error for undeclared CONDITION. sql/share/english/errmsg.txt: New error for undeclared CONDITION. sql/share/estonian/errmsg.txt: New error for undeclared CONDITION. sql/share/french/errmsg.txt: New error for undeclared CONDITION. sql/share/german/errmsg.txt: New error for undeclared CONDITION. sql/share/greek/errmsg.txt: New error for undeclared CONDITION. sql/share/hungarian/errmsg.txt: New error for undeclared CONDITION. sql/share/italian/errmsg.txt: New error for undeclared CONDITION. sql/share/japanese/errmsg.txt: New error for undeclared CONDITION. sql/share/korean/errmsg.txt: New error for undeclared CONDITION. sql/share/norwegian-ny/errmsg.txt: New error for undeclared CONDITION. sql/share/norwegian/errmsg.txt: New error for undeclared CONDITION. sql/share/polish/errmsg.txt: New error for undeclared CONDITION. sql/share/portuguese/errmsg.txt: New error for undeclared CONDITION. sql/share/romanian/errmsg.txt: New error for undeclared CONDITION. sql/share/russian/errmsg.txt: New error for undeclared CONDITION. sql/share/serbian/errmsg.txt: New error for undeclared CONDITION. sql/share/slovak/errmsg.txt: New error for undeclared CONDITION. sql/share/spanish/errmsg.txt: New error for undeclared CONDITION. sql/share/swedish/errmsg.txt: New error for undeclared CONDITION. sql/share/ukrainian/errmsg.txt: New error for undeclared CONDITION. sql/sp_head.cc: New HANDLER code. sql/sp_head.h: New HANDLER code. sql/sp_pcontext.cc: New CONDITION and HANDLER code. sql/sp_pcontext.h: New CONDITION and HANDLER code. sql/sp_rcontext.h: New CONDITION and HANDLER code. sql/sql_yacc.yy: New CONDITION and HANDLER code.
Diffstat (limited to 'Docs')
-rw-r--r--Docs/sp-imp-spec.txt431
-rw-r--r--Docs/sp-implemented.txt20
2 files changed, 356 insertions, 95 deletions
diff --git a/Docs/sp-imp-spec.txt b/Docs/sp-imp-spec.txt
index 385625464f1..1b72026c427 100644
--- a/Docs/sp-imp-spec.txt
+++ b/Docs/sp-imp-spec.txt
@@ -1,6 +1,6 @@
- Implementation specification for Stored Procedures
- ==================================================
+ Implementation specification for Stored Procedures
+ ==================================================
- How parsing and execution of queries work
@@ -45,7 +45,8 @@
- class sp_instr (sp_head.{cc,h})
This is the base class for "instructions", that is, what is generated
- by the parser. It turns out that we only need 4 different sub classes:
+ by the parser. It turns out that we only need a minimum of 5 different
+ sub classes:
- sp_instr_stmt
Execute a statement. This is the "call-out" any normal SQL statement,
like a SELECT, INSERT etc. It contains the Lex structure for the
@@ -58,6 +59,10 @@
Jump if condition is not true. It turns out that the negative test is
most convenient when generating the code for the flow control
constructs.
+ - sp_instr_freturn
+ Return a value from a FUNCTION and exit.
+ For condition HANDLERs some special instructions are also needed, see
+ that section below.
- class sp_rcontext (sp_rcontext.h)
This is the runtime context in the THD structure.
@@ -120,7 +125,7 @@
with an anonymous variable bound to the value to be tested.
- - An example
+ - A simple example
Parsing the procedure:
@@ -258,8 +263,8 @@
the actual database name.
- It's possible to write procedures that work on a particular database
by calling USE, without having to use fully qualified table names
- everywhere (which doesn't help if you want to call other, "general",
- procedures anyway).
+ everywhere (which doesn't help if you want to call other, "general",
+ procedures anyway).
- Evaluating Items
@@ -315,7 +320,7 @@
This makes things a lot more complicated compared to CALL:
- We can't read and parse the FUNCTION from the mysql.proc table at the
point of invokation; the server requires that all tables used are
- opened and locked at the beginning of the query execution.
+ opened and locked at the beginning of the query execution.
One "obvious" solution would be to simply push "mysql.proc" to the list
of tables used by the query, but this implies a "join" with this table
if the query is a select, so it doesn't work (and we can't exclude this
@@ -341,9 +346,9 @@
handled by the functions
void sp_add_fun_to_lex(LEX *lex, LEX_STRING fun);
- void sp_merge_funs(LEX *dst, LEX *src);
- int sp_cache_functions(THD *thd, LEX *lex);
- void sp_clear_function_cache(THD *thd);
+ void sp_merge_funs(LEX *dst, LEX *src);
+ int sp_cache_functions(THD *thd, LEX *lex);
+ void sp_clear_function_cache(THD *thd);
in sp.cc.
@@ -361,82 +366,231 @@
analogous to other DROP statements in MySQL.
+ - Condition and Handlers
+
+ Condition names are lexical entities and are kept in the parser context
+ just like variables. But, condition are just "aliases" for SQLSTATE
+ strings, or mysqld error codes (which is a non-standard extension in
+ MySQL), and are only used during parsing.
+
+ Handlers comes in three types, CONTINUE, EXIT and UNDO. The latter is
+ like an EXIT handler with an implicit rollback, and is currently not
+ implemented.
+ The EXIT handler jumps to the end of its BEGIN-END block when finished.
+ The CONTINUE handler returns to the statement following that which
+ invoked the handler.
+
+ The handlers in effect at any point is part of each thread's runtime
+ state, so we need to push and pop handlers in the sp_rcontext during
+ execution. We use special instructions for this:
+ - sp_instr_hpush_jump
+ Push a handler. The instruction contains the necessary information,
+ like which conditions we handle and the location of the handler.
+ The jump takes us to the location after the handler code.
+ - sp_instr_hpop
+ Pop the handlers of the current frame (which we are just leaving).
+
+ It might seems strange to jump past the handlers like that, but there's
+ no extra cost in doing this, and for technical reasons it's easiest for
+ the parser to generate the handler instructions when they occur in the
+ source.
+
+ When an error occurs, one of the error routines is called and an error
+ message is normally sent back to the client immediately.
+ Catching a condition must be done in these error routines (there are
+ quite a few) to prevent them from doing this. We do this by calling
+ a method in the THD's sp_rcontext (if there is one). If a handler is
+ found, this is recorded in the context and the routine returns without
+ sending the error message.
+ The exectution loop (sp_head::execute()) checks for this after each
+ statement and invokes the handler that has been found. If several
+ errors or warnings occurs during one statement, only the first is
+ caught, the rest are ignored.
+
+ Invoking and returning from a handler is trivial in the EXIT case.
+ We simply jump to it, and it will have an sp_instr_jump as its last
+ instruction.
+
+ Calling and returning from a CONTINUE handler poses some special
+ problems. Since we need to return to the point after its invokation,
+ we push the return location on a stack in the sp_rcontext (this is
+ done by the exectution loop). The handler then ends with a special
+ instruction, sp_instr_hreturn, which returns to this location.
+
+ CONTINUE handlers have one additional problem: They are parsed at
+ the lexical level where they occur, so variable offsets will assume
+ that it's actually called at that level. However, a handler might be
+ invoked from a sub-block where additional local variables have been
+ declared, which will then share the location of any local variables
+ in the handler itself. So, when calling a CONTINUE handler, we need
+ to save any local variables above the handler's frame offset, and
+ restore them upon return. (This is not a problem for EXIT handlers,
+ since they will leave the block anyway.)
+ This is taken care of by the execution loop and the sp_instr_hreturn
+ instruction.
+
+ - Examples:
+
+ - EXIT handler
+ begin
+ declare x int default 0;
+
+ begin
+ declare exit handler for 'XXXXX' set x = 1;
+
+ (statement1);
+ (statement2);
+ end;
+ (statement3);
+ end
+
+ Pos. Instruction
+ 0 sp_instr_set(0, '0')
+ 1 sp_instr_hpush_jump(4, 1) # location and frame size
+ 2 sp_instr_set(0, '1')
+ 3 sp_instr_jump(6)
+ 4 sp_instr_stmt('statement1')
+ 5 sp_instr_stmt('statement2')
+ 6 sp_instr_hpop(1)
+ 7 sp_instr_stmt('statement3')
+
+ - CONTINUE handler
+ create procedure hndlr1(val int)
+ begin
+ declare x int default 0;
+ declare foo condition for 1146;
+ declare continue handler for foo set x = 1;
+
+ insert into t3 values ("hndlr1", val); # Non-existing table?
+ if x>0 then
+ insert into t1 values ("hndlr1", val); # This instead then
+ end if;
+ end|
+
+ Pos. Instruction
+ 0 sp_instr_set(1, '0')
+ 1 sp_instr_hpush_jump(4, 2)
+ 2 sp_instr_set(1, '1')
+ 3 sp_instr_hreturn(2) # frame size
+ 4 sp_instr_stmt('insert ... t3 ...')
+ 5 sp_instr_jump_if_not(7, 'x>0')
+ 6 sp_instr_stmt('insert ... t1 ...')
+ 7 sp_instr_hpop(2)
+
+
- Class and function APIs
+ This is an outline of the key types. Some types and other details
+ in the actual files have been omitted for readability.
- The parser context: sp_pcontext.h
typedef enum
{
sp_param_in,
- sp_param_out,
- sp_param_inout
+ sp_param_out,
+ sp_param_inout
} sp_param_mode_t;
typedef struct
{
- Item_string *name;
- enum enum_field_types type;
- sp_param_mode_t mode;
- uint offset; // Offset in current frame
- my_bool isset;
+ LEX_STRING name;
+ enum enum_field_types type;
+ sp_param_mode_t mode;
+ uint offset; // Offset in current frame
+ my_bool isset;
} sp_pvar_t;
+ typedef struct sp_cond_type
+ {
+ enum { number, state, warning, notfound, exception } type;
+ char sqlstate[6];
+ uint mysqlerr;
+ } sp_cond_type_t;
+
class sp_pcontext
{
sp_pcontext();
- // Return the maximum frame size
- uint max_framesize();
+ // Return the maximum frame size
+ uint max_framesize();
- // Return the current frame size
- uint current_framesize();
+ // Return the current frame size
+ uint current_framesize();
- // Return the number of parameters
- uint params();
+ // Return the number of parameters
+ uint params();
- // Set the number of parameters to the current frame size
- void set_params();
+ // Set the number of parameters to the current frame size
+ void set_params();
- // Set type of the variable at offset 'i' in the frame
- void set_type(uint i, enum enum_field_types type);
+ // Set type of the variable at offset 'i' in the frame
+ void set_type(uint i, enum enum_field_types type);
- // Mark the i:th variable to "set" (i.e. having a value) with
- // 'val' true.
- void set_isset(uint i, my_bool val);
+ // Mark the i:th variable to "set" (i.e. having a value) with
+ // 'val' true.
+ void set_isset(uint i, my_bool val);
- // Push the variable 'name' to the frame.
- void push(LEX_STRING *name,
- enum enum_field_types type, sp_param_mode_t mode);
+ // Push the variable 'name' to the frame.
+ void push_var(LEX_STRING *name,
+ enum enum_field_types type, sp_param_mode_t mode);
// Pop 'num' variables from the frame.
- void pop(uint num = 1);
+ void pop_var(uint num = 1);
+
+ // Find variable by name
+ sp_pvar_t *find_pvar(LEX_STRING *name);
+
+ // Find variable by index
+ sp_pvar_t *find_pvar(uint i);
+
+ // Push label 'name' of instruction index 'ip' to the label context
+ sp_label_t *push_label(char *name, uint ip);
- // Find variable by name
- sp_pvar_t *find_pvar(LEX_STRING *name);
+ // Find label 'name' in the context
+ sp_label_t *find_label(char *name);
- // Find variable by index
- sp_pvar_t *find_pvar(uint i);
+ // Return the last pushed label
+ sp_label_t *last_label();
- // Push label 'name' of instruction index 'ip' to the label context
- sp_label_t *push_label(char *name, uint ip);
+ // Return and remove the last pushed label.
+ sp_label_t *pop_label();
- // Find label 'name' in the context
- sp_label_t *find_label(char *name);
+ // Push a condition to the context
+ void push_cond(LEX_STRING *name, sp_cond_type_t *val);
- // Return the last pushed label
- sp_label_t *last_label();
+ // Pop a 'num' condition from the context
+ void pop_cond(uint num);
- // Return and remove the last pushed label.
- sp_label_t *pop_label();
+ // Find a condition in the context
+ sp_cond_type_t *find_cond(LEX_STRING *name);
+
+ // Increase the handler count
+ void add_handler();
+
+ // Returns the handler count
+ uint handlers();
}
- The run-time context (call frame): sp_rcontext.h
+ #define SP_HANDLER_NONE 0
+ #define SP_HANDLER_EXIT 1
+ #define SP_HANDLER_CONTINUE 2
+ #define SP_HANDLER_UNDO 3
+
+ typedef struct
+ {
+ struct sp_cond_type *cond;
+ uint handler; // Location of handler
+ int type;
+ uint foffset; // Frame offset for the handlers declare level
+ } sp_handler_t;
+
class sp_rcontext
{
- // 'size' is the max size of the context
- sp_rcontext(uint size);
+ // 'fsize' is the max size of the context, 'hmax' the number of handlers
+ sp_rcontext(uint fsize, uint hmax);
// Push value (parameter) 'i' to the frame
void push_item(Item *i);
@@ -459,6 +613,38 @@
// Get the FUNCTION result
Item *get_result();
+
+ // Push handler at location 'h' for condition 'cond'. 'f' is the
+ // current variable frame size.
+ void push_handler(sp_cond_type_t *cond, uint h, int type, uint f);
+
+ // Pop 'count' handlers
+ void pop_handlers(uint count);
+
+ // Find a handler for this error. This sets the state for a found
+ // handler in the context. If called repeatedly without clearing,
+ // only the first call's state is kept.
+ int find_handler(uint sql_errno);
+
+ // Returns 1 if a handler has been found, with '*ip' and '*fp' set
+ // to the handler location and frame size respectively.
+ int found_handler(uint *ip, uint *fp);
+
+ // Clear the found handler state.
+ void clear_handler();
+
+ // Push a return address for a CONTINUE handler
+ void push_hstack(uint ip);
+
+ // Pop the CONTINUE handler return stack
+ uint pop_hstack();
+
+ // Save variables from frame index 'fp' and up.
+ void save_variables(uint fp);
+
+ // Restore saved variables from to frame index 'fp' and up.
+ void restore_variables(uint fp);
+
}
@@ -469,28 +655,33 @@
class sp_head
{
- int m_type; // TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE
+ int m_type; // TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE
- sp_head(LEX_STRING *name, LEX*);
+ sp_head();
+
+ void init(LEX_STRING *name, LEX *lex, LEX_STRING *comment, char suid);
// Store this procedure in the database. This is a wrapper around
// the function sp_create_procedure().
int create(THD *);
- // Invoke a FUNCTION
- int
- execute_function(THD *thd, Item **args, uint argcount, Item **resp);
+ // Invoke a FUNCTION
+ int
+ execute_function(THD *thd, Item **args, uint argcount, Item **resp);
- // CALL a PROCEDURE
- int
- execute_procedure(THD *thd, List<Item> *args);
+ // CALL a PROCEDURE
+ int
+ execute_procedure(THD *thd, List<Item> *args);
// Add the instruction to this procedure.
void add_instr(sp_instr *);
- // Return the number of instructions.
+ // Returns the number of instructions.
uint instructions();
+ // Returns the last instruction
+ sp_instr *last_instruction();
+
// Resets lex in 'thd' and keeps a copy of the old one.
void reset_lex(THD *);
@@ -505,6 +696,17 @@
// Update all instruction with this label in the backpatch list to
// the current position.
void backpatch(struct sp_label *);
+
+ // Returns the SP name (with optional length in '*lenp').
+ char *name(uint *lenp = 0);
+
+ // Returns the result type for a function
+ Item_result result();
+
+ // Sets various attributes
+ void sp_set_info(char *creator, uint creatorlen,
+ longlong created, longlong modified,
+ bool suid, char *comment, uint commentlen);
}
- Instructions
@@ -530,60 +732,89 @@
int execute(THD *, uint *nextp);
- // Set the statement's Lex
+ // Set the statement's Lex
void set_lex(LEX *);
- // Return the statement's Lex
- LEX *get_lex();
+ // Return the statement's Lex
+ LEX *get_lex();
}
- SET instruction:
class sp_instr_set : public sp_instr
- {
- // 'offset' is the variable's frame offset, 'val' the value,
- // and 'type' the variable type.
- sp_instr_set(uint ip,
- uint offset, Item *val, enum enum_field_types type);
+ {
+ // 'offset' is the variable's frame offset, 'val' the value,
+ // and 'type' the variable type.
+ sp_instr_set(uint ip,
+ uint offset, Item *val, enum enum_field_types type);
- int execute(THD *, uint *nextp);
- }
+ int execute(THD *, uint *nextp);
+ }
- Unconditional jump
class sp_instr_jump : public sp_instr
- {
- // No destination, must be set.
- sp_instr_jump(uint ip);
+ {
+ // No destination, must be set.
+ sp_instr_jump(uint ip);
- // 'dest' is the destination instruction index.
- sp_instr_jump(uint ip, uint dest);
+ // 'dest' is the destination instruction index.
+ sp_instr_jump(uint ip, uint dest);
- int execute(THD *, uint *nextp);
+ int execute(THD *, uint *nextp);
- // Set the destination instruction 'dest'.
- void set_destination(uint dest);
+ // Set the destination instruction 'dest'.
+ void set_destination(uint dest);
}
- Conditional jump
class sp_instr_jump_if_not : public sp_instr_jump
- {
- // Jump if 'i' evaluates to false. Destination not set yet.
- sp_instr_jump_if_not(uint ip, Item *i);
+ {
+ // Jump if 'i' evaluates to false. Destination not set yet.
+ sp_instr_jump_if_not(uint ip, Item *i);
- // Jump to 'dest' if 'i' evaluates to false.
- sp_instr_jump_if_not(uint ip, Item *i, uint dest)
+ // Jump to 'dest' if 'i' evaluates to false.
+ sp_instr_jump_if_not(uint ip, Item *i, uint dest)
- int execute(THD *, uint *nextp);
+ int execute(THD *, uint *nextp);
}
- Return a function value
- class sp_instr_return : public sp_instr
- {
- // Return the value 'val'
- sp_instr_return(uint ip, Item *val, enum enum_field_types type);
-
- int execute(THD *thd, uint *nextp);
- }
+ class sp_instr_freturn : public sp_instr
+ {
+ // Return the value 'val'
+ sp_instr_freturn(uint ip, Item *val, enum enum_field_types type);
+
+ int execute(THD *thd, uint *nextp);
+ }
+
+ - Push a handler and jump
+ class sp_instr_hpush_jump : public sp_instr_jump
+ {
+ // Push handler of type 'htype', with current frame size 'fp'
+ sp_instr_hpush_jump(uint ip, int htype, uint fp);
+
+ int execute(THD *thd, uint *nextp);
+
+ // Add condition for this handler
+ void add_condition(struct sp_cond_type *cond);
+ }
+ - Pops handlers
+ class sp_instr_hpop : public sp_instr
+ {
+ // Pop 'count' handlers
+ sp_instr_hpop(uint ip, uint count);
+
+ int execute(THD *thd, uint *nextp);
+ }
+
+ - Return from a CONTINUE handler
+ class sp_instr_hreturn : public sp_instr
+ {
+ // Return from handler, and restore variables to 'fp'.
+ sp_instr_hreturn(uint ip, uint fp);
+
+ int execute(THD *thd, uint *nextp);
+ }
- Utility functions: sp.h
@@ -602,7 +833,8 @@
// definition string ("create procedure ...").
int sp_create_procedure(THD *,
char *name, uint namelen,
- char *def, uint deflen);
+ char *def, uint deflen,
+ char *comment, uint commentlen, bool suid);
// Drop the procedure 'name' from the database.
int sp_drop_procedure(THD *, char *name, uint namelen);
@@ -614,9 +846,28 @@
// definition string ("create function ...").
int sp_create_function(THD *,
char *name, uint namelen,
- char *def, uint deflen);
+ char *def, uint deflen,
+ char *comment, uint commentlen, bool suid);
// Drop the function 'name' from the database.
int sp_drop_function(THD *, char *name, uint namelen);
+
+ - The cache: sp_cache.h
+
+ class sp_cache
+ {
+ sp_cache();
+
+ void init();
+
+ void cleanup();
+
+ void insert(sp_head *sp);
+
+ sp_head *lookup(char *name, uint namelen);
+
+ void remove(sp_head *sp);
+ }
+
--
diff --git a/Docs/sp-implemented.txt b/Docs/sp-implemented.txt
index 41e7c4b2923..5ce09ae3af4 100644
--- a/Docs/sp-implemented.txt
+++ b/Docs/sp-implemented.txt
@@ -1,4 +1,4 @@
-Stored Procedures implemented 2003-03-07:
+Stored Procedures implemented 2003-09-16:
Summary of Not Yet Implemented:
@@ -7,13 +7,12 @@ Summary of Not Yet Implemented:
- External languages
- Access control
- Routine characteristics (mostly used for external languages)
- - Prepared SP caching; SPs are fetched and reparsed at each call
- SQL-99 COMMIT (related to BEGIN/END)
- DECLARE CURSOR ...
- FOR-loops (as it requires cursors)
- CASCADE/RESTRICT for ALTER and DROP
- ALTER/DROP METHOD (as it implies User Defined Types)
- - CONDITIONs, HANDLERs, SIGNAL and RESIGNAL (will probably not be implemented)
+ - SIGNAL and RESIGNAL, and UNDO handlers
Summary of what's implemented:
@@ -24,7 +23,8 @@ Summary of what's implemented:
- BEGIN/END, SET, CASE, IF, LOOP, WHILE, REPEAT, ITERATE, LEAVE
- SELECT INTO local variables
- "Non-query" FUNCTIONs only
-
+ - Prepared SP caching
+ - CONDITIONs and HANDLERs
List of what's implemented:
@@ -75,7 +75,17 @@ List of what's implemented:
query (unlike a PROCEDURE, which is called as a statement). The table
locking scheme used makes it difficult to allow "subqueries" during
FUNCTION invokation.
-
+ - SPs are cached, but with a separate cache for each thread (THD).
+ There are still quite a few non-reentrant constructs in the lexical
+ context which makes sharing prepared SPs impossible. And, even when
+ this is resolved, it's not necessarily the case that it will be faster
+ than a cache per thread. A global cache requires locks, which might
+ become a buttleneck. (It would save memory though.)
+ - CONDITIONs and HANDLERs are implemented, but not the SIGNAL and
+ RESIGNAL statements. (It's unclear if these can be implemented.)
+ The semantics of CONDITIONs is expanded to allow catching MySQL error
+ codes as well. UNDO handlers are not implemented (since we don't have
+ SQL-99 style transaction control yet).
Closed questions: