HenPlus

JDBC SQL-shell

Table of Contents
Overview
Getting Started
Getting Connected
Multiple Open Sessions
Using Variables
Setting Properties
Commandline Completion
Per Project Configuration
Plugins
Aliases
Table Dumps
Tree View

FAQ
Download

History

Know this ? You need a small little tool for your everyday work and you wonder why nobody did this before -- and then you write it yourself. HenPlus is the result of such an effort. Its name reflects the original intent: it started as a platform independent replacement for the Oracle SQLPlus tool. I wrote the first version of HenPlus in 1997 (before Oracle was available for Linux) since I had to develop an application that connected to an Oracle database -- but the only SQLPlus I could use in the environment back then was running on some Redmond operation system that lacked (and still lacks) the environment to develop serious software.
Since then I've rewritten this tool, added command line completion and history -- but it still remained small and useful. It is freely distributable in source and binary form because it is protected by the GNU Public License.

Overview

HenPlus is a SQL shell written in Java that works for any database that offers JDBC support. So basically any database. Why do we need this ? Any database comes with some shell, but all of them have missing features (and several shells are simply unusable). And if you work with several databases at once (if you are a developer, then you do this all the time), switching between these tools is tedious.

This is where HenPlus steps in. It supports:

Getting started

Download

You can download source and binary packages of HenPlus at the
HenPlus SourceForge Download Page.

If you are using Debian, you can install HenPlus by adding the following line to your /etc/apt/sources.list

  deb http://osdn.dl.sourceforge.net/henplus ./
After that, run
  apt-get update
then
  apt-get install henplus

Compilation

If you have downloaded HenPlus as a binary RPM or debian package, you can skip this part.

Ok, you are still here, so you have to compile it first. First you need an additional library. HenPlus uses the features of the GNU-readline library and therefore needs the JNI java wrapper library >= java-readline 0.7.3.

To build HenPlus the ant build tool (Version >= 1.4) is required. To compile HenPlus, make sure that the libreadline-java.jar is in the classpath. By default, the java-readline package installs this in /usr/share/java/libreadline-java.jar. Compilation needs to be done with JDK >= 1.3, but the resulting jar file works with old JDK 1.2.2 (the Runtime.addShutdownHook() method is used in compilation).

Now, just type

$ ant jar
If you are root, then you can install it with:
$ ant install
which will install henplus in
/usr/share/henplus/henplus.jarThe jar-file containing the HenPlus classes. You can add additional jar files in this directory. All of them are added to the classpath in the henplus shellscript (use this for JDBC-drivers).
/usr/bin/henplusshellscript to start henplus
If you want another installation base (default: /usr), you provide this with the parameter 'prefix':
$ ant -Dprefix=/usr/local install
(For package providers: the build.xml provides as well the DESTDIR parameter)

I haven't compiled this on Windows, but it shouldn't be a big deal .. if you manage to compile the java-readline. If you did it, just post your experience, so that we can include it in this documentation.

Running

You can start HenPlus with the henplus shell script with or without an jdbc-url on the command line.
$ henplus jdbc:mysql://localhost/foobar

Make sure, command line editing is enabled
If the first line, henplus writes reads:
no readline found (no JavaReadline in java.library.path). Using simple stdin.
.. then, the JNI-part of the readline library could not be found, so command line editing is disabled because henplus then reads from stdin as fallback. This happens if the LD_LIBRARY_PATH does not point to the JNI library; edit the /usr/bin/henplus shellscript so that the LD_LIBRARY_PATH contains the directory where libJavaReadline.so resides.
Getting started
The important commands you need to know to get it running are help and connect. The help command gives an overview, what commands are supported:
Hen*Plus> help
 help | ?                                 : provides help for commands
 about | version | license                : about HenPlus
 exit | quit                              : exits HenPlus
 echo | prompt                            : echo argument
 list-plugins | plug-in | plug-out        : handle Plugins
 list-drivers | register | unregister     : handle JDBC drivers
 list-aliases | alias | unalias           : handle Aliases
 load | start | @ | @@                    : load file and execute commands
 connect | disconnect | rename-session | switch | sessions: manage sessions
 status                                   : show status of this connection
 tables | views | rehash                  : list available user objects
 describe <tablename>                     : describe a database object
 tree-view <tablename>                    : tree representation of connected tables
 dump-out | dump-in | verify-dump | dump-conditional: handle table dumps
 system | !                               : execute system commands
 set-var | unset-var                      : set/unset variables
 set-property | reset-property            : set global HenPlus properties
 set-session-property | reset-session-property: set SQL-connection specific properties
config read from [/home/hzeller/.henplus]

You exit the HenPlus shell by typing the exit or quit command or by just typing EOF-Character (CTRL-D).

Just explore the commands by typing help [commandname] and learn what the built-in commands are all about. Start with the connect command

Hen*Plus> help connect
(more details on connection to the database in the
Getting connected section).
Semicolon as Separator
Usually there is one command per line. However, you can have multiple commands on one line if you separate them with a semicolon:
echo "*** The build-in help ***" ; help

The SQL commands however (you guess it: 'select', 'update', 'create' .. ) are not complete after the newline; you always have to close them with a semicolon -- so it is possible to write statements on multiple lines:
oracle:localhost> create table foobar (
                    id number(10) primary key,
                    text varchar(127)
                  );
ok. (70 msec)
oracle:localhost>

Some commands are not even complete, if there is a semicolon -- these are 'create procedure' and 'create trigger'. These commands contain some more complex SQL-operations that are each separated by a semicolon. The whole command is then completed with a single slash at the beginning of a new line (this syntax is the same that SQLPlus supports):
oracle:localhost> create or replace trigger foobar_autoinc
                  before insert on foobar
                  for each row
                  begin
                    select foobar_seq.nextval into :new.id from dual;
                  end foobar_autoinc;
                  /
ok. (320 msec)
oracle:localhost>

SQL-Comments
That should be enough to start working with HenPlus. But especially if you are running scripts (with the load-command) there is one additional piece of information you might need to know: the types of comments that are ignored. AFAIK, the SQL standard defines only an ANSI-endline comment, that starts with two dashes; this is supported by HenPlus:
  select * from foobar; -- this is a comment

However, other non-standard types of comments have come to use in several SQL-shells so HenPlus ignores these as well. One style are the C/C++/Java style comments, that comment out a range between /* some comment */.
/*
   This is a longer comment that goes
   across several lines
 */
 select * from foobar;

Another style of comments, allowed for instance in the MySQL-Shell is the UNIX-shell like '#' endline comment; however, this character is only allowed as first character to be a comment -- otherwise using the Hash-Symbol in normal SQL-Statements (e.g. column names), would not work.
# this is a comment.
 select * from foobar;
 create table foo (id# number); -- the hash here is not a comment
One beta tester requested an additional type of comment: Two semicolons at the beginning of a line to comment out the whole line. The problem with semicolon is, that it is as well used as separator between commands and as such may of course occur twice in a row. Therefore, two semicolons are only regarded as comment, if they are the first on a line:
;; This line is commented out
   ;; this one as well, since there are only whitespaces on the left
select * from foobar ;; echo "this echo is executed as usual"

To make a long story short -- the supported types of comments are

Not supported is the C++/Java like endline comment that starts with two slashes //. The reason is, that many JDBC-URLs contain these two slashes, and we don't want to comment these out, right ? ;-)

If you encounter some other reasonable style of comments you want that it is supported by HenPlus, please let me know.

Switching off comment removal... The comment removal is done in HenPlus, since some JDBC-Drivers have problems to remove comments or do not remove the same set of comment types given here. However, sometimes it is necessary not to remove comments, since some datases use comments to convey hinting in statements. In Oracle, for instance, you give hints to the query optimizer in the form

    select /*+ index(foo,foo_a_idx) */ a from foo where ...
For this reason, the property comments-remove is provided; it allows to switch off comment removal, so the string is sent to the database as-is. The command is set-property comments-remove off.

Getting connected

Getting connected to a database is simple: you need the JDBC driver from your database vendor, put it in the classpath, register the driver -- that's it. HenPlus provides the commands register, unregister and list-drivers to manage the drivers (you know it already: the help command tells you more details). Some of the common drivers are already registered by default .. however, they still need to be in the CLASSPATH still.
Hen*Plus> list-drivers
loaded drivers are marked with '*' (otherwise not found in CLASSPATH)
------------+---------------------------------+---------------------------------------+
    for     |          driver class           |              sample url               |
------------+---------------------------------+---------------------------------------+
   Adabas   | de.sag.jdbc.adabasd.ADriver     | jdbc:adabasd://localhost:7200/work    |
   DB2      | COM.ibm.db2.jdbc.net.DB2Driver  | jdbc:db2://localhost:6789/foobar      |
 * MySQL    | org.gjt.mm.mysql.Driver         | jdbc:mysql://localhost/foobar         |
 * Oracle   | oracle.jdbc.driver.OracleDriver | jdbc:oracle:thin:@localhost:1521:ORCL |
 * Postgres | org.postgresql.Driver           | jdbc:postgresql://localhost/foobar    |
   SAP-DB   | com.sap.dbtech.jdbc.DriverSapDB | jdbc:sapdb://localhost/foobar         |
------------+---------------------------------+---------------------------------------+

With the list-drivers command you can see what driver classes are registered and loaded. If you cannot connect to some JDBC URL, check first, if the appropriate driver is actually loaded. If it is not loaded, then it is probably not found in the CLASSPATH. You can explicitly copy your frequently used drivers into the installation directory (/usr/share/henplus/) -- all the jar/zip files found there are added to the CLASSPATH on startup in the henplus shellscript.

Drivers once registered are remembered by HenPlus, so that they are loaded automatically on next startup. For registering and unregistering drivers, see the online help:
Hen*Plus> help register
Hen*Plus> help unregister

When the driver is loaded, you can connect to the database using the JDBC-URL:
Hen*Plus> connect jdbc:oracle:thin:@localhost:1521:ORCL
HenPlus II connecting
 url 'jdbc:oracle:thin:@localhost:1521:ORCL'
 driver version 1.0
============ authorization required ===
Username: henman
Password: 
 Oracle - Oracle8i Enterprise Edition Release 8.1.7.0.1 - Production
JServer Release 8.1.7.0.1 - Production
 read committed *
 serializable
henman@oracle:localhost>
This will then ask for the username and the password and you are connected. Since it is not possible to set the terminal to non-echo mode while typing the password, a thread constantly redraws the prompt (This is after a hack
found here; thanks so Alec Noronha for the link. If the the redrawing causes trouble with your installation, please let me know).

Typing JDBC-URLs is tedious ? That's right, so HenPlus remembers all the connection URLs you were connected to and provides it in the context sensitive commandline completion for the connect command. So next time you connect, you just type
Hen*Plus> connect <TAB>
jdbc:oracle:thin:@localhost:1521:ORCL  jdbc:oracle:thin:@database.my.net:1521:BLUE
Hen*Plus> connect jdbc:oracle:thin:@d <TAB>
Hen*Plus> connect jdbc:oracle:thin:@database.my.net:1521:BLUE
.. and connecting with long URLs is a piece of cake.

On connection, the prompt changes to a string that reflects the current connection. By default, this prompt is automatically extracted from the JDBC-URL, but you can provide another name as second parameter in the connection command (see help for the 'connect' command). Or just rename the session:
henman@oracle:localhost> rename-session hello
hello>

You can disconnect with the disconnect command or by simply pressing CTRL-D.

Multiple Open Sessions

You can be connected to multiple databases at once; just issue the connect command multiple times. You can list the sessions you are connected to the sessions command. Of course, the shell provides only access to one session at a time so you can switch between the sessions with the 'switch' command.
Hen*Plus> connect jdbc:oracle:thin:@localhost:1521:ORCL
[... enter user/password ...]
henman@oracle:localhost> connect jdbc:mysql://localhost/test
mysql:localhost> sessions
current session is marked with '*'
------------------------------+--------+---------------------------------------+
           session            |  user  |                 url                   |
------------------------------+--------+---------------------------------------+
    henman@oracle:localhost   | henman | jdbc:oracle:thin:@localhost:1521:ORCL |
  * mysql:localhost           | [NULL] | jdbc:mysql://localhost/test           |
------------------------------+--------+---------------------------------------+
mysql:localhost> switch henman@oracle:localhost
henman@oracle:localhost>
Of course, the switch command provides command line completion for the other sessions names. If you only have two sessions, then its even easier: just type switch without parameter (so in the example above, this would have been sufficient).

Using Variables

You can use Variables, that can be used as text replacement everywhere. The replacement works similar to shellscripts, however setting must be done explicitly with the set command instead of a simple assignment:
henman@oracle:localhost> set-var tabname footab
henman@oracle:localhost> select count(*) from ${tabname};

The command for setting variables has changed since Version 0.9 of HenPlus. Previously, this command was 'set'; it changed to 'set-var', since several Databases understand 'set' as a build in-command that otherwise would be shadowed by HenPlus' set.

Variables can be expanded with or without curly braces: $FOO and ${FOO} is the same.

If you don't want a variable to be expanded, double the dollar-sign: $$FOO. It does not help to embed it in single-quoted strings '$FOO'.

Unlike the shell, variables that are not set, are not expanded to an empty string, but left as they are. So an unset variable $FOOBAR expands to .. $FOOBAR. This is, because some strange scripts (esp. Oracle scripts) contain names with dollar characters and thus would behave strange if the behaviour would be the same as in shellscripts.

All variable settings can be shown with the set-var command without any parameters. The settings are stored, so that they are available on next startup.

Setting Properties

There are some global properties, that can be modified by the set-property command. Connection session specific properties are handled with the set-session-property command. Corresponding reset-* commands reset the property to its default.

The usage of these commands is simple. If you just type the set-* command, then the list of supported properties with short description is shown. With the property name given as single parameter, the detailed help for that property is given. With an additional parameter the property is actually set to that value:
sa@hsqldb> select * from FOO ;
---+---+---+
 X | Y | Z |
---+---+---+
 1 | 2 | 3 |
---+---+---+
1 row in result (first row: 4 msec; total: 7 msec)
sa@hsqldb> set-property                      -- no parameters: show list
-----------------------+-------+-------------------------------------------------------+
         Name          | Value |                      Description                      |
-----------------------+-------+-------------------------------------------------------+
 column-delimiter      | |     | modify column separator in query results              |
 comments-remove       | on    | switches the removal of SQL-comments                  |
 echo-commands         | off   | echo commands prior to execution.                     |
 sql-result-limit      | 2000  | set the maximum number of rows printed                |
 sql-result-showfooter | on    | switches if footer in selected tables should be shown |
 sql-result-showheader | on    | switches if header in selected tables should be shown |
-----------------------+-------+-------------------------------------------------------+
sa@hsqldb> set-property column-delimiter -- property name parameter: show detailed description
DESCRIPTION
        Set another string that is used to separate columns in
        SQL result sets. Usually this is a pipe-symbol '|', but
        maybe you want to have an empty string ?
sa@hsqldb> set-property column-delimiter "****"       -- property name and value: set property
sa@hsqldb> select * from FOO ;
------+------+------+
 X **** Y **** Z ****
------+------+------+
 1 **** 2 **** 3 ****
------+------+------+
1 row in result (3 msec)
sa@hsqldb> reset-property column-delimiter

Global properties are stored in your project preferences, so that you don't have to retype them on next startup.

For supported properties of the SQL-connection session just type set-session-property. At present, the properties auto-commit and isolation-level are supported. Unlike the global properties, this set of properties is specific per session and is not stored.

Commandline Completion

HenPlus provides a commandline completion for virtually everything: tables in select statements, column-names in where-clauses, variable-names in set/unset command, variable names after typing '$'. Just try it ..

Per Project Configuration

HenPlus stores the commandline history, the connection-URLs and the variable settings persistently in the filesystem. This is written to a directory ~/.henplus, that is created in your home directory.

If you have multiple projects you work on, all this information about any of these projects is collected in the ~/.henplus directory. Since a per project separation of this information is desirable, HenPlus supports per project storage of this information.

To store only information you use in some project, just create an empty .henplus directory in the directory you usually start the HenPlus utility -- then HenPlus will use this directory to store its configuration. More, if it does not find the .henplus directory within the current working dir, it goes up the directory tree until it finds the .henplus directory. If it still does not find the directory, it falls back to the ~/.henplus in your home directory.

If you type 'help', then HenPlus tells you in the last line, where it has loaded its initial configuration from.

Plugins

Need some command that creates some fancy statistics or report from your database tables ? Or need a command that you are missing from some other database tool ? Or want to pop up some Swing-window to do database record editing ?

HenPlus does not limit you to the features already provided. It is very simple to write plug-ins, that can be added and removed at run time. See the help for plug-in, plug-out and list-plugins for details. Basically, you just need to write a class that implements the henplus.Command interface. A plugin will register one (or a set of) new commands that behave just like internal commands. They are, for instance, available in the help command.

There is one cool plugin shipped with version 0.9.4, provided by Martin Grotzke. It resides in the henplus.jar, so if you have installed henplus, it is already there, but you have to plug-in this command yourself; after plugging it in, HenPlus will remember this for future starts.
Hen*Plus> plug-in henplus.plugins.tablediff.TableDiffCommand
adding commands: tablediff
Hen*Plus> list-plugins
loaded plugins are marked with '*'
----------------------------------------------+-----------+
                 plugin class                 | commands  |
----------------------------------------------+-----------+
 * henplus.plugins.tablediff.TableDiffCommand | tablediff |
----------------------------------------------+-----------+
What does this command do ? By providing two session names and a list of tables, you get the meta difference for corresponding tables in both sessions. So you get whether columns have been added/removed or datatypes that are different for columns with the same name. This is particularly useful if you have multiple installations of database schemas and wonder if they match (e.g. for test/productive environments); a way to automatically create 'alter table'-scripts is planned.

(TODO: This feature needs a way to load the classes from an URLClassloader, so its possible to dynamically reload the classes when they changed. Anyone?)

Aliases

For certain repeating tasks its tedious to write the same command over and over again. Therefore it is possible to store aliases.
henman@oracle:localhost> alias ls tables
This would alias the 'tables' command so that it can be called by simply typing 'ls'.

Parameters given to aliases are appended to the original command, so that you can define aliases for commands that need parameters:
henman@oracle:localhost> alias size select count(*) from
henman@oracle:localhost> size footab
execute alias: select count(*) from footab
----------+
 count(*) |
----------+
        9 |
----------+
1 row in result (first row: 3 msec; total: 3 msec)
Note, that even TAB-completion (in this case: of the tablename) works: the alias command peeks into the original command that is executed when you type 'size'.

All aliases can be shown with list-aliases; they are stored, so that they are available on next startup.

Database independent table dumps

You can dump out tables in a database independent format. See the online help for dump-out and dump-in. You even can dump only selected values of a certain table; see dump-conditional for this.

henman@oracle:localhost> dump-out mytables.dump.gz student addresses;
.. dumps out the tables student and addresses to the file mytables.dump.gz. The file is gzipped on-the-fly due to the .gz-suffix.

The format that is written must be database independent, thus it is not possible to store them as simple 'INSERT INTO..' statements, as the different databases have different assumption how some data types should be parsed :-( Thus the dump format is a canonical text format that resembles the original insert-statement arguments; it is easily parseable for the human eye and external tools:


(tabledump 'student'
   (file-encoding 'UTF-8')
   (dump-version 1 1)
   (henplus-version '0.9.1')
   (database-info 'MySQL - 3.23.47')
   (meta ('name',   'sex',    'student_id')
         ('STRING', 'STRING', 'INTEGER'   ))
   (data ('Megan','F',1)
         ('Joseph','M',2)
         ('Kyle','M',3)
         ('Mac Donald\'s','M',4))
   (rows 4))

XML ?

One could argue, that XML would be an idea here, since it is hype and should therefore be used everywhere :-) ... But seriously, I decided against it because it blows up the file size (and database export tend not to be small) and is not very human readable due to the 'noise'. The normal use-case of dumps like this is to Both use-cases are addressed with this format: each data record is on a single line and is almost compatible with the typical set-syntax of SQL. You can simply construct an insert-Statement with this with minimal editing required (like insert into student ((name, sex, student_id) values ('Joseph','M',2);).

If you want XML export/import, just go ahead write a plugin that does this -- this would be great for many tools, as long as humans don't have to deal with it.

Tree View of connected tables

Know this ? To just find out the datastructure of foreign-key connected tables in your database you have to describe manually through all tables. This is quite tedious. Thus, HenPlus provides a tree view of your tables. Cyclic references are resolved by printing the recursive entity in parenthesis. See the example for some bugtracker database:
henman@oracle:localhost> tree-view BT_TRACKERUSER

BT_TRACKERUSER
|-- BT_BUGHISTORY
|   |-- BT_BUG
|   |   `-- (BT_BUGHISTORY)
|   `-- BT_BUGCOMMENTATTACHMENT
`-- BT_USERPERMISSION
265 msec

Quotes

Kolja F. after installing HenPlus for a colleague (German)
"Henplus ist wirklich dermassen geil. Der Macker, der mit sqlplus arbeiten musste, hätte vor Freude fast geheult."

FAQ

This will eventually be the FAQ. But for now, there is only one question..

Q: When I am connected to a postgreSQL database, sometimes the connection seems to stop working. No select works.
A: This is primarily not a problem with HenPlus, but in general with Postgres when it encounters an error in some SQL-statement. Since everything in Postgres is done within a transaction, a transaction is regarded invalid on some error; any subsequent commands are ignored, until you finish the transaction with 'commit' or 'rollback'. This might be annoying if you run SQL-scripts -- if there is only one error, all subsequent commands are ignored. One solution might be to switch on autocommit (HenPlus command: set-session-property auto-commit on).


Thanks Rebecca for proof reading.
$Id: HenPlus.html,v 1.21 2004/02/01 20:57:26 hzeller Exp $ © Henner Zeller <H.Zeller@acm.org>