DBSetup

Download from http://www.nyangau.org/dbsetup/dbsetup.zip.

What does it do

I often make tools available that interact with databases, and when I do, I typically include examples to show them off. This means I need to supply SQL to set up tables, indexes and initial content. Unfortunately, SQL dialects vary quite a bit, and I don't really want to supply multiple SQL files, one per database vendor.

DBSetup is a tool which takes a database description file and generates SQL to create and/or drop types, tables and indexes.

The input file is XML, and a file DBSetup.xsd is provided declaring what a valid database description file looks like. If the database description file validates, there is a very good chance DBSetup can process it. The main exception is that tables must be defined before foreign keys which reference them.

It has very limited heuristics for dealing with differences in primitive data type. At present, it knows that datetime isn't supported on Oracle, and rewrites this as timestamp.

It understands the concept of simple user define type names. This is really a form of type alias rather than "object types". With Sybase it generates sp_addtypes for them, but with other database types it substitutes the user defined type name with what they are an alias for.

It understands tables. You can specify a Sybase specific lock attribute, so you can specify datarows or similar on table creation. You can specify a MySQL specific engine attribute, so you can specify InnoDB or similar on table creation. These are ignored on other database types.

It understands primary keys and foreign keys. It will name a primary constraint as PK_<tablename>, but allows you to override this. This is important because Oracle has a 30 character limit. There is no default constraint name for foreign keys, you must supply one.

It allows you to define indexes. These can be marked as unique and/or clustered. The clustered attribute only makes it into the Sybase DDL at present.

It also provides a mechanism for initializing table content. This is quite crude - you include a set of rows, and DDL is generate that simply does an INSERT statement for each of them.

As soon as you look further, at stored procedures, things start to become very vendor specific, so no attempt to support these is made.

I imagine I'll enhance this tool from time to time, as I need new SQL features supporting, or as I encounter more database types.

Usage

Usage :-

$ ./DBSetup -help
usage: DBSetup [flags]
flags: -fn file.xml  table specifications (default: DBSetup.xml)
       -dbtype type  one of MySQL (default), Oracle or Sybase
       -create       generate DDL to create tables, indexes and initial content
       -drop         generate DDL to delete indexes and tables

To create Oracle SQL to create the ORDERS database :-

$ ./DBSetup -fn orders.xml -dbtype Oracle -create > orders_create.sql

Database description file

Here is a simple example, which reflects the example ORDERS database delivered in Tabulator :-

<dbsetup>

  <table name="ORDERS">
    <column name="id" type="int"/>
    <column name="customer" type="varchar(100)"/>
    <primaryKey constraint="PK_ORDERS">
      <columnRef name="id"/>
    </primaryKey>
    <index name="ORDERS_id" unique="true">
      <columnRef name="id"/>
    </index>
    <row>1,'Wile Coyote'</row>
    <row>2,'Roadrunner'</row>
  </table>

  <table name="ITEMS">
    <column name="id" type="int"/>
    <column name="order_id" type="int"/>
    <column name="description" type="varchar(200)"/>
    <primaryKey constraint="PK_ITEMS">
      <columnRef name="id"/>
    </primaryKey>
    <foreignKey constraint="FK_ORDERS" table="ORDERS">
      <columnRef name="order_id"/>
    </foreignKey>
    <index name="ITEMS_id">
      <columnRef name="id"/>
    </index>
    <index name="ITEMS_order_id">
      <columnRef name="order_id"/>
    </index>
    <row>10,1,'Acme explosives'</row>
    <row>11,1,'Acme rocket sledge'</row>
    <row>12,1,'Medical insurance'</row>
    <row>20,2,'Bird seed'</row>
  </table>

</dbsetup>

Editing the database description XML in an editor which can validate against an XSD is a good idea, because the XSD includes constraint checks, such as "all primaryKey and foreignKey constraints must be unique", and "all index columnRef names must refer to columns in the table".

Issue: At this time timestamp literals are a problem in <row> elements because they must be preceded by TIMESTAMP when using Oracle.

Using generated assets

Often you want a database (or a schema) with a particular user to access it, and then to use the generated SQL to populate it.

MySQL

I tested using MySQL 5.1.

Simply start it up, create a database, create a user, and as that user run the script to create the tables :-

$ sudo /etc/init.d/mysqld start
$ mysql --user root --password
mysql> create database mydb ;
mysql> use mydb ;
mysql> create user 'myuser'@'localhost' identified by 'myuserpassword' ;
mysql> grant all on mydb.* to 'myuser'@'localhost' ;
mysql> exit
$ mysql --user myuser --password=myuserpassword
mysql> use mydb ;
mysql> source /somepath/somegenerated.sql ;
mysql> exit
$

Later Linuxes tend to have MariaDB, and use systemd, so instead start it with :-

$ sudo systemctl start mariadb

Oracle

Here I use Oracle XE 10.2.0.

Start up Oracle, create a user and grant access to the database and permission to create tables in it. Then as that user, run the generated script.

$ sudo /etc/init.d/oracle-xe start
$ su - oracle
$ export ORACLE_HOME=/usr/lib/oracle/xe/app/oracle/product/10.2.0/server
$ export ORACLE_SID=XE
$ cd $ORACLE_HOME/bin
$ ./sqlplus / as sysdba
SQL> create user myuser identified by myuserpassword ;
SQL> grant connect, resource to myuser ;
SQL> exit
$ ./sqlplus
myuser
myuserpassword
SQL> @/somepath/somegenerated.sql ;
SQL> exit
$

With Oracle XE 11.2.0, you use

$ export ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe

Sybase

Here I use the free Sybase ASE 16, developer edition. I had earlier created a dataserver called myserver with a user data directory of /home/sybase/myserver.

Start it :-

$ su - sybase
$ . /opt/sap/SYBASE.sh
$ myserver/ASE-16_0/install/RUN_myserver

The script to run a Sybase dataserver doesn't seem to be like a normal init-script, so we leave it running, and open another window.

When creating the database, we need to first allocate somewhere for it to put its data and logs. In development and test databases, rather than set up log management, we can simply let Sybase try to do it on every checkpoint :-

$ su - sybase
$ . /opt/sap/SYBASE.sh
$ isql -S MYSERVERHOST:MYSERVERPORT -U sa -P sapassword
1> disk init name="mydb_data",physname="/home/sybase/myserver/data/mydb_data.dat",size="1G"
2> go
1> disk init name="mydb_log",physname="/home/sybase/myserver/data/mydb_log.dat",size="100M"
2> go
1> create database mydb on mydb_data="1G" log on mydb_log="100M"
2> go
1> sp_dboption mydb,"trunc log on chkpt",true
2> go
1> ^D
$

We can then run the generated script to create the tables :-

$ isql -S MYSERVERHOST:MYSERVERPORT -U sa -P sapassword -D mydb -i /somepath/somegenerated.sql
$

In Sybase, the ability to login is one thing, and its another for that login to be present as a user of a database :-

$ isql -S MYSERVERHOST:MYSERVERPORT -U sa -P sapassword
1> create login myuser with password myuserpassword default database mydb
2> go
1> use mydb
2> go
1> sp_adduser myuser,myuser
2> go
1> ^D
$

The trickiest bit is that isql doesn't provide an easy way to grant access to all tables in a database, but with a little shell, we can :-

$ cat /somepath/somegenerated.sql |\
  sed -n 's/CREATE TABLE \(.*\)/grant all on \1 to myuser\ngo/p' |\
  isql -S MYSERVERHOST:MYSERVERPORT -U sa -P sapassword -D mydb
$

Finally, because its not controlled by an init-script, this is how to shut it down :-

$ isql -S MYSERVERHOST:MYSERVERPORT -U sa -P sapassword
1> shutdown
2> go
$

Copying

Feel free to copy, its public domain. Caveat Emptor.


The documentation is written and maintained by the DBSetup author, Andy Key
andy.z.key@googlemail.com