How to support a new database or a new programming language
SPWrapper code can be arranged into two layers.
On the top we have the ant tasks and the code generators, these classes
knows how to build source code but don't know very much of the underlaying database.
On the bottom layer there are introspectors: these classes knows all the
database related details about datatypes, driver classes, etc.
For instance while running SqlJaTor, the translator detects input parameters,
looks for a suitable introspector wich in turn execute sql code and detects
output parameter types and driver details.
With this information the translator is finally able to assemble java code.
This page explains how to add support for a new database and explains how to write a new translator to generate source files in other languages.
Introspectors
Since version 0.7 there are two types of introspectors: hardcoded introspectors and fallback introspectors. Hardcoded introspectors are java classes that extends the net.sourceforge.spwrapper.Introspector class, are hard to be build, but can be used from both the stored procedure and the sql statement code generators.
Fallback introspector is a class that can be configured with text files and enables SqlJaTor to quickly work with a new db
This tutorial covers only the FallbackIntrospector configuration,
because it can be done quickly and can even be used to fix bugs with less tested databases.
If you are interested into Hardcoded instropectors look at net.sourceforge.spwrapper.oracle.OracleIntrospector source code.
Fallback Introspectors
The net.sourceforge.spwrapper.fallback.FallbackIntrospector
is instantiated many times with various configuration files, one for each
database. Actually at SPWrapper startup the FallbackIntrospector is
registered three times: one for Postgresql, one for Mysql and then
for Sql Server.
The good new is that at startup it is possible to register your
own configuration without recompiling SPWrapper. Simply add this option to
your java vm
-Dnet.sourceforge.spwrapper.custom-introspector=/my/new/configuration.properties
This option takes the precedence over other standard introspector, so you will also be
able to override default spwrapper behaviours.
Obviously the file configuration.properties must be placed
into the directory my/new into your current CLASSPATH
The configuration files
The version 0.9 introduced new configuration files, extending what was possible to achieve with previous releases.
This table descibes these files; one note:
these files can have wathever name you decide.
configuration.properties |
Use this file to specify driver related information such as driver class, etc. In this file you can also specify if the other files have to be read and what are their actual names and classpaths. |
language-binding.properties |
Language by language you must provide the parameters needed to correctly generate the source code. there must be one file for each supported language. |
aliases.properties |
This file provides basic type mappings. Suppose your database
reports int4 as integer type, this type is not SQL standard,
therefore FallbackIntrospector does not know what to do with it.
In this file you can renamet int4 to
INTEGER wich is understood and managed. |
mappings.xml |
If your database supports very speific datatype such as
internet addresses, geometric shapes, etc. you can write in this
file the source code to be used as template to handle these types.
Actually all the code generated by SPWrapper is based on these templates! |
The first configuration file must be provided for each database, usually this file is named as the database. For instance for Oracle the configuration file will be named oracle.properties
This file must contain these keys:
supported.driver.classes | Comma separated list of java.sql.Driver classes contained into your jdbc driver.
The introspector instantiates all of these classes and uses them to check if the opened
connections is supported by this introspector. This field is mandatory.
This field is used only at compile time and does not influences the generated java code. |
supported.connection.classes | Comma separated list of connection classes provided by the jdbc driver. Used at compile time to check if the opened connection is supported by this Introspector. This field is mandatory. |
driver.name | This field is mandatory and contains a descriptive text, just type in the name of your database |
language-supported[0-9]+ | For each supported language you should provide one key for each supported language. So if you are willing to support five languages you should write five key numbering them language-supported1 language-supported2 ... language-supported5. the value of this key must be the language name, in lowercase |
lang-class-language | For each supported language a Binding class should be chosen. The binding class contains all the language details needed by the translator to assemble the target source file. Currently there are two available classes net.sourceforge.spwrapper.JBinding and net.sourceforge.spwrapper.python.PyBinding |
lang-conf-language | Each binding is configurable through another property file. These properties must contain the name of these files |
hack.getProcedures.ProcedureType | Used to fix a mysql specific bug, procedures and functions are not differentiated. Can be "true" or "false" defaults to false. |
example
#defines the configuration for the OracleIntrospector
#
# Copyright (C) 2007 Giovanni Bricconi
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
supported.driver.classes=oracle.jdbc.OracleDriver,oracle.jdbc.driver.OracleDriver
supported.connection.classes=oracle.jdbc.OracleConnectionWrapper,oracle.jdbc.driver.T4CConnection
#Driver name for error messages, language independent
driver.name=OracleDriver
#language bindings
language-supported1=java
lang-class-java=net.sourceforge.spwrapper.JBinding
lang-conf-java=/net/sourceforge/spwrapper/oracle/java.properties
language-supported2=python
lang-class-python=net.sourceforge.spwrapper.python.PyBinding
lang-conf-python=/net/sourceforge/spwrapper/oracle/python.properties
Language binding configuration
Each language binding must be properly configured, and each language binding has a different set of configuration needed. Let's look at Java configuration and later to Python configuration.
Java binding
language | The language name, such as "python" or "java". Mandatory. |
additional.imports | Comma separated list of packages to be imported into the generated java files. Usually the packages that contains the driver specific callable & prepared statement classes, plus the result set class. Do not add the import trailing ".*" characters. This field is mandatory. |
driver.specific.mappings | This optional field allows you to specify an xml file with higly specific datatype support, this file can contains java code fragments tha spwrapper and sqljator will assemble into java code. Always specify the property file name with the '/' style as
shown in the example. |
type.alias | This optional field allow you to specify a properties file that contains database specific type aliases. Type aliases allows you to map specific datatypes such as int4 to SQL standard types such as INTEGER . Always specify the property file name with the '/' style as shown in the example. |
forname.class | This is the class name that should be used to register the database jdbc driver. Each stub generated by sqljator or spwrapper will contain a Class.forName
to register the jdbc driver in case it is not already been loaded. You can provide only one class name. This field is mandatory. |
callable.statement.class | This field must contain your driver specific callable statement class. This class or interface usually allows access to method specific to your database only. If your driver contains many callable statement classes try to understand what is the base class/interface among them. This field is mandatory. |
prepared.statement.class | Similar to the previous field, just concerning the prepared statement base class or interface. This field is mandatory. |
result.set.class | Similar to the previous two fields, just concerning the rsult set base class or interface. This field is mandatory. |
example
# java bindings for oracle
#
# Copyright (C) 2007 Giovanni Bricconi
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
language=java
additional.imports=oracle.jdbc, oracle.sql,net.sourceforge.spwrapper.oracle
driver.specific.mappings=/net/sourceforge/spwrapper/oracle/OracleMappings.xml
#currently not needed
#type.alias=
forname.class=oracle.jdbc.OracleDriver
callable.statement.class=oracle.jdbc.OracleCallableStatement
prepared.statement.class=oracle.jdbc.OraclePreparedStatement
result.set.class=oracle.jdbc.OracleResultSet
Python binding
language | The language name, such as "python" or "java". Mandatory. |
additional.imports | Comma separated list of Python modules to import at the begging of the generated python sources. |
driver.specific.mappings | Same of the Java binding, optional an should point to an xml file tha contains python fragments that will be assembled to create the python source. Overrides the default framgents, and can introduce database specific types. |
type.alias | Same as in Java bindings, points to a file that contains an equivalence map between SQL standard datatypes and database specific type names. |
module.name | Python database driver module name (dbapi2) |
parameter.style | Python database parameter style used by this driver. Can be format,named,numeric,pyformat,qmark |
example
# python bindings for oracle
#
# Copyright (C) 2007 Giovanni Bricconi
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
language=python
additional.imports=cx_Oracle
#driver.specific.mappings=/net/sourceforge/spwrapper/oracle/OracleMappings.xml
#currently not needed
type.alias=/net/sourceforge/spwrapper/oracle/python-alias.properties
module.name=cx_Oracle
parameter.style=named
mappings.xml
The exact name of this file must be reported into the language binding configuration files. Simply call it MysqlMappings,xml, SqlServerMappings.xml or wathever else is appropriate.
To discover what can be written into this file we will analyze the OracleMappings.xml file provided
with spwrapper sources.
<?xml version="1.0" encoding="UTF-8"?>
<!--
OracleMappings.xml - bridge between sql data types and java code
Copyright (C) 2006 Giovanni Bricconi
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-->
<!DOCTYPE SPWrapperMappings [
<!ENTITY stdInVarDecl "<![CDATA[protected boolean {1}_setNull=false;
public void set_{1}_null(boolean b) '{'{1}_setNull = b; }
protected {3} {1};
public void set_{1}({3} in) '{'{1} = in; }
]]>" >
<!ENTITY stdOutVarDecl "<![CDATA[protected boolean {1}_wasNull=false;
public boolean was_{1}_null() '{' return {1}_wasNull; }
protected {3} {1};
public {3} get_{1}() '{' return {1}; }
]]>">
<!ENTITY stdInOutVarDecl "<![CDATA[protected boolean {1}_setNull=false;
public void set_{1}_null(boolean b) '{' {1}_setNull = b; }
protected {3} {1};
public void set_{1}({3} in) '{' {1} = in; }
protected boolean {1}_wasNull=false;
public boolean was_{1}_null() '{' return {1}_wasNull; }
public {3} get_{1}() '{' return {1}; }
]]>" >
]>
<Mappings>
<Mapping sqlType="OBJECT" composite="true">
<!-- JavaType>java.sql.Struct</JavaType -->
<JavaType>java.lang.Object</JavaType>
<InVarDecl>&stdInVarDecl;<![CDATA[
protected String {1}_sqlType{4};
public void set_{1}_sqlType(String in) '{' {1}_sqlType=in;}
]]></InVarDecl>
<OutVarDecl>&stdOutVarDecl;<![CDATA[
protected String {1}_sqlType{4};
public void set_{1}_sqlType(String in) '{' {1}_sqlType=in;}
public java.sql.Struct get_{1}_Struct() '{'
return (java.sql.Struct) {1};
}
]]></OutVarDecl>
<InOutVarDecl>&stdInOutVarDecl;<![CDATA[
protected String {1}_sqlType{4};
public void set_{1}_sqlType(String in) '{' {1}_sqlType=in;}
public java.sql.Struct get_{1}_Struct() '{'
return (java.sql.Struct) {1};
}
]]>
</InOutVarDecl>
<Getter>
<![CDATA[
{1}=statement.getObject({2});//({3})statement.getObject({2});
{1}_wasNull=statement.wasNull();
]]>
</Getter>
<Setter>
<![CDATA[
if ({1}_setNull) statement.setNull({2},Types.STRUCT,{1}_sqlType);
else statement.setObject({2},{1});
]]>
</Setter>
<Register>
<![CDATA[
(narrowedStatement).registerOutParameter({2},OracleTypes.STRUCT,
{1}_sqlType);
]]>
</Register>
</Mapping>
...
</Mappings>
After the usual xml declaration some useful entities are declared, their purpose will be
explained later. Until then consider them as macro code that will be used to avoid cut
and paste.
The root element of this file is always Mappings , it can contain only
Mapping elements. Each mapping defines a database type.
Mapping
The Mapping element has one mandatory attribute, sqlType ; it specifies
the new database specific data type.
Other Mapping attributes are composite, createCallback, quickCall . Composite
must be set to true if the SQL type is structured, say has sub fields such as a C struct.
CreateCallback must be set to true if you whant that a callback method is created and invoked
each time a colum of this type is fetched out the result set, it is meant for BLOB like types.
QuickCall must be set to false if it is ambiguous or not possible to pass this argument
as java function call argument; an example is Oracle's RefCursor type. None of these attributes
is mandatory.
Mapping has 7 children elements:
- JavaType - the name of the target java class/scalar type
- InVarDecl
- OutVarDecl
- InOutVarDecl
- Getter
- Setter
- Register
JavaType
This element is very simple, has no attirbute and should contains only the java type to wich you are mapping
the current SQL type. It is a good idea to use fully qualified names (my.package.Class instead of Class)
InVarDecl, OutVarDecl and InOutVarDecl
These elements contains the java template to be used when the current type is used as input, output or as
input-output argument. I decided not to use XSL templates because of their complexity and verbosity,
the standard java MessageFormat was good enough for this task.
Remember to write '{' if you want to be an open brace in your java code, otherwise there will be a exception.
The variables that will be used inside the templates are:
- {0} - the original SQL name of the parameter, can be empty if concering the functions return
parameter. Probably useless.
- {1} - the java/python argument name, as detected or specified by the user.
- {2} - a number, the argument position in prepared statements/callable statement
- {3} - the java/python type, actually taken form the JavaType element
- {4} - the sql composite type name, useful only with composite types like structs
- {5} - dbapi module name
Now that you know the meaning of the parameter we can examine what you should place in the
source templates
Within InVarDecl you should provide a member variable of the correct type, the corresponding setter, and a boolean
varible and its setter to allow the user to pass NULL values as input parameters. Conventionally the boolean
variable should be called {1}_setNull and its setter set_{1}_null() . As you can guess
if we are dealing with the parameter "salary" these snippets will be transated into salaty_setNull
and set_salary_null()
Within OutVarDecl you should declare the member variable and a getter, to allow the stub user to retrieve the
actual value. Again you should add a boolean to detect NULL output values. Again the boolean variables
should be named {1}_wasNull and its getter was_{1}_null()
The InOutVarDecl combines both needs, and is used only when the variable is used for input and output purposes.
Now the purpose of the XML entities can be explained, if those code snippet are correct for your datatype you can
consider using them in this way:
<InVarDecl>&stdInVarDecl;</InVarDecl>
<OutVarDecl>&stdOutVarDecl;</OutVarDecl>
<InOutVarDecl>&stdInOutVarDecl;</InOutVarDecl>
Getter, Setter and Register
The getter is the code fragment used to read the parameter value out of the result set/callable statement. It should
also detect the _wasNull value. Use statement as callable statement or result set variable, it will
correctly replaced by the translator. Yow can also use narrowedStatement if you need driver
specific functions.
The Setter obviously is the getter counterpart, should pass in the value or set it to null according to user request
Finally the Register element is needed to register this parameter as output parameter, with the correct type
Note for Python programmers
A lot of parameter described above have no meaning for Python programs. Usually you should only provide the sqlType for the Mapping element. Then the InVarDecl, OutVarDecl, InOutVarDecl and Getter. Refer to StandardMappings.xml in the net.sourceforge.spwrapper.python package.
This is an excerpt
<!DOCTYPE SPWrapperMappings [
<!ENTITY stdInVarDecl "<![CDATA[
def set_{1}(self,_arg):
self.{1}=_arg
]]>" >
...
<Mapping sqlType="VARCHAR">
<JavaType>notNeeded</JavaType>
<InVarDecl>&stdInVarDecl;</InVarDecl>
<OutVarDecl>&stdOutVarDecl;</OutVarDecl>
<InOutVarDecl>&stdInOutVarDecl;</InOutVarDecl>
<Getter>&stdGet;</Getter>
<Setter>notNeeded</Setter>
<Register>notNeeded</Register>
</Mapping>
aliases.properties
This is a very simple property file, the key value should be your database type, and its value should be the standard SQL type to which you whant to map it. This is the list of the available target types for Java
SQL type | Target java type | Python Available |
CHAR | java.lang.String | yes |
VARCHAR | java.lang.String | yes |
LONGVARCHAR | String and Stream | yes |
NUMERIC | BigDecimal | yes |
DECIMAL | BigDecimal | yes |
BIT | boolean | yes |
TINYINT | byte | yes |
SMALLINT | short | yes |
INTEGER | int | yes |
BIGINT | long | yes |
REAL | float | yes |
FLOAT | double | yes |
BINARY | byte[] | yes |
VARBINARY | byte[] | yes |
LONGVARBINARY | byte[] or stream, probably buggy, to be changed in next release | yes |
DATE | java.sql.Date | yes |
TIME | java.sql.Time | yes |
TIMESTAMP | java.sql.Timestamp | yes |
BLOB | java.sql.Blob | no, not tested |
CLOB | java.sql.Clob | no, not tested |
BOOLEAN | boolean | yes |
OBJECT | java.lang.Object | yes |
If you do not find the suitable type in this table you have to add your own mappings.xml file. Python users should look into net.sourceforge.spwrapper.python.StandarMappings.xml.
File Example:
# aliases.properties - aliases to support postgresql types
#
# Copyright (C) 2007 Giovanni Bricconi
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# This file contains a set of aliases to map
# database specific types to sql generic types
# Each row shoul contain an original type (case
# sensitive) and a destination type, case sensitive too
# This file should be used only for types that
# do not require special code handling.
# The other types should stay in files
# like net.sourceforge.spwrapper.oracle.OracleMappings.xml
int8=BIGINT
serial8=BIGINT
float8=DOUBLE
int4=INTEGER
int=INTEGER
float4=REAL
int2=SMALLINT
serial4=INTEGER
timestamp=TIMESTAMP
text=LONGVARCHAR
bpchar=CHAR
timestamptz=TIMESTAMP
timetz=TIME
|