From 796d870bebf4440a7d9714b4200784656a6ea61b Mon Sep 17 00:00:00 2001 From: "par.winzell" Date: Tue, 9 Dec 2008 23:15:58 +0000 Subject: [PATCH] HSQL support: - HSQL largely follows SQL92, thus to preserve case we have to enclose identifiers such as column and table names in quotes. - HSQL has a method for auto-generating primary key values, but its syntax is different from MySQL and PostgreSQL -- just when we got to celebrate that those two giants had agreed on a convenient alias. - HSQL can't handle uniqueness as a column-level constraint. It can certainly handle unique columns, it just seems to feel that's a table level constraint and thus to be specified as a prologue during table creation. Thus we override createTableIfMissing to strip uniqueness from column definitions and append those instead to the table-level unique constraints. We might want to switch to this approach for all the databases. - We override expandDefinition() to magically translate a default value of IDENTITY into a HSQL-specific column mark-up that triggers the primary key value auto-generation. This is for Depot's benefit, and I would not be surprised if this method was soon moved to that project. git-svn-id: https://samskivert.googlecode.com/svn/trunk@2501 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- .../com/samskivert/jdbc/HsqldbLiaison.java | 135 +++++++++++++++--- 1 file changed, 113 insertions(+), 22 deletions(-) diff --git a/src/java/com/samskivert/jdbc/HsqldbLiaison.java b/src/java/com/samskivert/jdbc/HsqldbLiaison.java index b535ab8b..c8e6e99a 100644 --- a/src/java/com/samskivert/jdbc/HsqldbLiaison.java +++ b/src/java/com/samskivert/jdbc/HsqldbLiaison.java @@ -20,11 +20,23 @@ package com.samskivert.jdbc; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; + import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; import java.sql.SQLException; +import com.samskivert.jdbc.ColumnDefinition; +import com.samskivert.util.ArrayUtil; + /** - * Handles liaison for HSQLDB. NOTE: incomplete and doesn't work yet! + * Handles liaison for HSQLDB. */ public class HsqldbLiaison extends BaseLiaison { @@ -37,50 +49,50 @@ public class HsqldbLiaison extends BaseLiaison // from DatabaseLiaison public String columnSQL (String column) { - return column; + return "\"" + column + "\""; } // from DatabaseLiaison public String tableSQL (String table) { - return table; + return "\"" + table + "\""; } // from DatabaseLiaison public String indexSQL (String index) { - return index; + return "\"" + index + "\""; } // from DatabaseLiaison - public void createGenerator (Connection conn, String tableName, String columnName, int initValue) + public void createGenerator (Connection conn, String tableName, + String columnName, int initValue) throws SQLException { - // TODO: is there any way we can set the initial AUTO_INCREMENT value? + // HSQL's IDENTITY() does not create any database entities } // from DatabaseLiaison public void deleteGenerator (Connection conn, String tableName, String columnName) throws SQLException { - // AUTO_INCREMENT does not create any database entities that we need to delete + // HSQL's IDENTITY() does not create any database entities that we need to delete } // from DatabaseLiaison public int lastInsertedId (Connection conn, String table, String column) throws SQLException { - throw new UnsupportedOperationException(); -// // MySQL does not keep track of per-table-and-column insertion data, so we are pretty much -// // going on blind faith here that we're fetching the right ID. In the overwhelming number -// // of cases that will be so, but it's still not pretty. -// Statement stmt = null; -// try { -// stmt = conn.createStatement(); -// ResultSet rs = stmt.executeQuery("select LAST_INSERT_ID()"); -// return rs.next() ? rs.getInt(1) : -1; -// } finally { -// JDBCUtil.close(stmt); -// } + // HSQL does not keep track of per-table-and-column insertion data, so we are pretty much + // going on blind faith here that we're fetching the right ID. In the overwhelming number + // of cases that will be so, but it's still not pretty. + Statement stmt = null; + try { + stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("call IDENTITY()"); + return rs.next() ? rs.getInt(1) : -1; + } finally { + JDBCUtil.close(stmt); + } } // from DatabaseLiaison @@ -92,8 +104,87 @@ public class HsqldbLiaison extends BaseLiaison // from DatabaseLiaison public boolean isDuplicateRowException (SQLException sqe) { - throw new UnsupportedOperationException(); -// String msg = sqe.getMessage(); // TODO: not sure -// return (msg != null && msg.indexOf("Duplicate entry") != -1); + // Violation of unique constraint SYS_PK_51: duplicate value(s) for column(s) FOO + String msg = sqe.getMessage(); + return (msg != null && msg.indexOf("duplicate value(s)") != -1); + } + + // BaseLiaison's implementation of table creation accepts unique constraints both as + // part of the column definition and as a separate argument, and merrily passes this + // duality onto the database. Postgres and MySQL both handle this fine but HSQL seems + // to simply not allow uniqueness in the column definitions. So, for HSQL, we transfer + // uniqueness from the ColumnDefinitions to the uniqueConstraintColumns before we pass + // it in to the super implementation. + // + // TODO: Consider making this the general MO instead of a subclass override. In fact + // it may be that uniqueness should be removed from ColumnDefinition. + @Override // from DatabaseLiaison + public boolean createTableIfMissing ( + Connection conn, String table, String[] columns, ColumnDefinition[] definitions, + String[][] uniqueConstraintColumns, String[] primaryKeyColumns) + throws SQLException + { + Preconditions.checkArgument(columns.length == definitions.length, + "Column name and definition number mismatch"); + + // make a set of unique constraints already provided + Set> uColSet = Sets.newHashSet(); + if (uniqueConstraintColumns != null) { + for (String[] uCols : uniqueConstraintColumns) { + uColSet.add(Arrays.asList(uCols)); + } + } + + // go through the columns and find any that are unique; these we replace with a + // non-unique variant, and instead add a new entry to the table unique constraint + ColumnDefinition[] newDefinitions = new ColumnDefinition[definitions.length]; + for (int ii = 0; ii < definitions.length; ii ++) { + ColumnDefinition def = definitions[ii]; + if (def.unique) { + // let's be nice and not mutate the caller's object + newDefinitions[ii] = new ColumnDefinition( + def.type, def.nullable, false, def.defaultValue); + // if a uniqueness constraint for this column was not in the + // uniqueConstraintColumns parameter, add such an entry + if (!uColSet.contains(Sets.newHashSet(columns[ii]))) { + String[] newConstraint = new String[] { columns[ii] }; + uniqueConstraintColumns = (uniqueConstraintColumns == null) ? + new String[][] { newConstraint } : + ArrayUtil.append(uniqueConstraintColumns, newConstraint); + } + } else { + newDefinitions[ii] = def; + } + }; + + // now call the real implementation with our modified data + return super.createTableIfMissing( + conn, table, columns, newDefinitions, uniqueConstraintColumns, primaryKeyColumns); + } + + @Override // from DatabaseLiaison + protected String expandDefinition ( + String type, boolean nullable, boolean unique, String defaultValue) + { + StringBuilder builder = new StringBuilder(type); + + // append the default value if one was specified + if (defaultValue != null) { + if ("IDENTITY".equals(defaultValue)) { + // this is a blatant hack, we need this method to join Depot's SQLBuilder + builder.append(" GENERATED BY DEFAULT AS IDENTITY (START WITH 1)"); + } else { + builder.append(" DEFAULT ").append(defaultValue); + } + } + + if (!nullable) { + builder.append(" NOT NULL"); + } + if (unique) { + throw new IllegalArgumentException("HSQL can't deal with column uniqueness here"); + } + + return builder.toString(); } }