TIP:		350
Title:		Tcl Database Connectivity - Corrigenda
Version:	$Revision: 1.1 $
Author:		Kevin B. Kenny <kennykb@acm.org>
State:		Draft
Type:		Informative
Created:	18-Apr-2009
Post-History:
Vote:		Pending
Obsoletes:	308

~ Abstract

This TIP defines a common database access interface for Tcl scripts. It is an
update to [308] to take into account experience gained since that TIP was
written. Note that this TIP does ''not'' repeat the contents of that one,
which is mostly correct apart from the changes described in this document.

~ Summary of Changes

Implementation experience on Tcl Database Connectivity [308] has exposed
several issues with its specification that require editorial corrections. In
brief:

   1. The error codes returned from TDBC drivers are detailed in such a way as
      to make them more usable in the '''try''' command.

   1. The '''starttransaction''' method on a database connection is renamed,
      '''begintransaction'''

   1. The '''execute''' method on a statement, and all of the methods that
      invoke it ('''allrows''' and '''foreach''' on database connections)
      changes its behaviour in the case where a bound variable in its SQL code
      refers to a Tcl variable that is an array, or a read trace on the
      associated variable fails.

   1. The order of arguments on the '''foreach''' methods on database
      connections, statements and result sets is changed.

   1. The ''statementClass'' and ''resultSetClass'' instance variables, and
      the ''init'' method of connections, statements and result sets, are
      deprecated; a new initialization API is provided.

   1. A Tcl command, '''tdbc::mapSqlState''', and a C function,
      '''Tdbc_MapSqlState''' are provided for the convenience of driver
      writers.

~ Introduction

The actual implementation of TDBC and three database drivers for it has
revealed a handful of mistakes in the TDBC specification [308].  The purpose
of this TIP is to correct those errors and promulgate a specification that
matches TDBC as implemented.

~ Specification

~~ Error Codes

Whenever a TDBC driver reports an error in interacting with an underlying
database, it SHOULD set the interpreter error code to a list of at least four
elements. The first element should be the constant string '''TDBC'''. The
second should be an 'error class' chosen from the list below.  The third
should be the (usually five-character) SQL state that the database reported,
or the constant string '''HY000''' if the SQL state cannot be determined.  (In
the latter case, the error class should be '''GENERAL_ERROR'''.)  The fourth
element should be the name of the TDBC driver that reported the error. Any
elements beyond the fourth SHOULD give further details (for example an error
code returned by a native API), and are driver dependent.

The permissible values for the error class are as follows.  Note that each one
corresponds to the first two characters of a five-character 'SQL state' that
is common to most SQL database API's; the SQL state corresponding to the class
is also given.

| SQL State
| Prefix     Error Class
| --------------------------------------------------------
|    00      UNQUALIFIED_SUCCESSFUL_COMPLETION
|    01      WARNING
|    02      NO_DATA
|    07      DYNAMIC_SQL_ERROR
|    08      CONNECTION_EXCEPTION
|    09      TRIGGERED_ACTION_EXCEPTION
|    0A      FEATURE_NOT_SUPPORTED
|    0B      INVALID_TRANSACTION_INITIATION
|    0D      INVALID_TARGET_TYPE_SPECIFICATION
|    0F      LOCATOR_EXCEPTION
|    0K      INVALID_RESIGNAL_STATEMENT
|    0L      INVALID_GRANTOR
|    0P      INVALID_ROLE_SPECIFICATION
|    0W      INVALID_STATEMENT_UN_TRIGGER
|    20      CASE_NOT_FOUND_FOR_CASE_STATEMENT
|    21      CARDINALITY_VIOLATION
|    22      DATA_EXCEPTION
|    23      CONSTRAINT_VIOLATION
|    24      INVALID_CURSOR_STATE
|    25      INVALID_TRANSACTION_STATE
|    26      INVALID_SQL_STATEMENT_IDENTIFIER
|    27      TRIGGERED_DATA_CHANGE_VIOLATION
|    28      INVALID_AUTHORIZATION_SPECIFICATION
|    2B      DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST
|    2C      INVALID_CHARACTER_SET_NAME
|    2D      INVALID_TRANSACTION_TERMINATION
|    2E      INVALID_CONNECTION_NAME
|    2F      SQL_ROUTINE_EXCEPTION
|    33      INVALID_SQL_DESCRIPTOR_NAME
|    34      INVALID_CURSOR_NAME
|    35      INVALID_CONDITION_NUMBER
|    36      CURSOR_SENSITIVITY_EXCEPTION
|    37      SYNTAX_ERROR_OR_ACCESS_VIOLATION
|    38      EXTERNAL_ROUTINE_EXCEPTION
|    39      EXTERNAL_ROUTINE_INVOCATION_EXCEPTION
|    3B      SAVEPOINT_EXCEPTION
|    3C      AMBIGUOUS_CURSOR_NAME
|    3D      INVALID_CATALOG_NAME
|    3F      INVALID_SCHEMA_NAME
|    40      TRANSACTION_ROLLBACK
|    42      SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION
|    44      WITH_CHECK_OPTION_VIOLATION
|    45      UNHANDLED_USER_DEFINED_EXCEPTION
|    46      JAVA_DDL
|    51      INVALID_APPLICATION_STATE
|    53      INSUFFICIENT_RESOURCES
|    54      PROGRAM_LIMIT_EXCEEDED
|    55      OBJECT_NOT_IN_PREREQUISITE_STATE
|    56      MISCELLANEOUS_SQL_OR_PRODUCT_ERROR
|    57      RESOURCE_NOT_AVAILABLE_OR_OPERATOR_INTERVENTION
|    58      SYSTEM_ERROR
|    70      INTERRUPTED
|    F0      CONFIGURATION_FILE_ERROR
|    HY      GENERAL_ERROR
|    HZ      REMOTE_DATABASE_ACCESS_ERROR
|    IM      DRIVER_ERROR
|    P0      PGSQL_PLSQL_ERROR
|    S0      ODBC_2_0_DML_ERROR
|    S1      ODBC_2_0_GENERAL_ERROR
|    XA      TRANSACTION_ERROR
|    XX      INTERNAL_ERROR
|  anything
|   else     UNKNOWN_SQLSTATE

The reason for structuring the error codes in this way is to make errors more
accessible to the '''try''' command [329]. For instance, a Tcl script that
wishes to detect and handle division by zero in a SQL statement might look
like:

|  try {
|      $statement foreach row {
|          # ... process the row
|      }
| } trap {TDBC DATA_EXCEPTION 22012} {
|      puts "Division by zero!"
| }

Since the previous specification [308] left the error code unspecified, this
change is not expected to impact any client code.

~~ Transaction Control

The '''begintransaction''' method was inadvertently called,
'''starttransaction''' in the TDBC specification. Therefore, the word
'''starttransaction''' should be replaced with '''begintransaction''' wherever
it appears.

This change will break no existing code; no '''starttransaction''' method has
been defined for any TDBC driver.

~~ The '''execute''' Method of a Statement - Variable Substitution

The rule that an array variable provided as a bound value to a substituent in
a SQL statement MUST result in an error has proven to be awkward to implement
in practice.  Moreover, the original specification [308] fails to indicate
what happens if a read trace on one of a statement's bound variables throws an
error.

The sentence,

 > An array variable provided to a substituent MUST result in an error.

is therefore to be replaced with:

 > An array variable provided to a substituent, or a variable in which
   substitution results in an error being reported by a read trace, MUST
   result in a NULL value being provided.

This change is expected to have minimal impact on existing code; the behaviour
being described is simply providing a NULL value for a case that was an error
before (an array where a scalar is expected) and a case that was unspecified
before (an error within a variable trace).

~~ The ''foreach'' Methods

The syntax of the ''foreach'' method of connections, statements, and result
sets in the original specification contains editorial errors.  The correct
syntax is:

 > ''dbHandle'' '''foreach''' ?'''-as''' '''lists'''|'''dicts'''?
   ?'''-columnsvariable''' ''varName''? ?--? 
   ''varName'' ''sql'' ?''dictionary''? ''script''

 > ''statement'' '''foreach''' ?'''-as''' '''lists'''|'''dicts'''?
   ?'''-columnsvariable''' ''varName''? ?--? 
   ''varName'' ?''dictionary''? ''script''

 > ''resultset'' '''foreach'''  ?'''-as''' '''lists'''|'''dicts'''?
   ?'''-columnsvariable''' ''varName''? ?--? 
   ''varName'' ''script''

This change represents an editorial correction; the reference implementation
functioned in this way even prior to the acceptance of the original
specification [308].

~~ The Constructor Patterns

The ''statementClass'' variable, and the '''init''' method, are no longer
recommended for use in the constructors of connection classes.  Instead, the
recommended pattern is that a connection class SHOULD implement a
'''statementCreate''' method that accepts the fully qualified name of the
command that is to represent the statement, the connection handle and the SQL
statement, and returns a handle to the statement object.  The usual way to do
so is with a forwarded method:

|  forward statementCreate ::driver::statement create

If the '''statementCreate''' method is not present, the default one looks for
a variable named ''statementClass'' in the connection object, and invokes its
'''create''' command.

In this way, drivers that are written to the original specification continue
to operate.

Similarly, the ''resultSetClass'' variable, and the '''init''' method, are no
longer recommended for use in the constructors of statement classes. Instead,
the statement class SHOULD implement a '''resultSetCreate''' method that
accepts the fully qualified name of the command that will represent the result
set, the statement handle, and the parameters to the '''prepare''' method.
Once again, this method will usually simply be forwarded to the appropriate
constructor:

|  forward resultSetCreate ::driver::resultSet create

Once again, backward compatibility is provided by a '''resultSetCreate'''
method in the base class.  This method looks for a ''resultSetClass'' variable
in the statement instance, and interprets it as a class name, invoking the
''create'' method in that class.

''Rationale:'' These changes eliminate several jumps among methods with
'''uplevel''' calls, and yield both simpler code and improved performance.

~~ SQL State Mapping

For the convenience of drivers that deal with database APIs that provide a
standard SQL dtate in the event of errors, a Tcl command,
'''tdbc::mapSqlState''' is provided.  This command accepts a (usually five
character) SQL state, and returns the error class that should go in the second
element of the error code.  The mapping is described in the table in the
'''Error Codes''' section above.

Similarly, A C function is provided:

 > const char * '''Tdbc_MapSqlState'''(const char *''sqlstate'');

This call looks up the given ''sqlstate'' and returns its error class
according to the table.

~ License

This file is explicitly released to the public domain and the author
explicitly disclaims all rights under copyright law.
