package net.sourceforge.mayfly.acceptance; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; /** * To make this work: <pre> - Install postgres server and start it running - As a database superuser (perhaps "postgres"), run createuser --createdb --no-adduser -P mayflytest and supply a password of mayflytest. createdb mayflytest - Also edit pg_hba.conf to have a line such as: "host all all 127.0.0.1/32 trust" (which basically means that connections from localhost don't need to authenticate themselves; I'm not sure how this relates to passwords). </pre> */ public class PostgresDialect extends Dialect { @Override public Connection openConnection() throws Exception { Class.forName("org.postgresql.Driver"); Connection bootstrapConnection = DriverManager.getConnection("jdbc:postgresql:", "mayflytest", "mayflytest"); try { SqlTestCase.execute("DROP DATABASE test", bootstrapConnection); } catch (SQLException databaseDoesNotExist) { } SqlTestCase.execute("CREATE DATABASE test", bootstrapConnection); bootstrapConnection.close(); return openAdditionalConnection(); } @Override public Connection openAdditionalConnection() throws SQLException { return DriverManager.getConnection("jdbc:postgresql:test", "mayflytest", "mayflytest"); } @Override public void shutdown(Connection connection) throws Exception { connection.close(); Connection teardownConnection = DriverManager.getConnection("jdbc:postgresql:", "mayflytest", "mayflytest"); // The connection.close() above is needed for this to work, but // doesn't complete immediately. So we need the retries. executeWithRetries("DROP DATABASE test", teardownConnection); teardownConnection.close(); } private void executeWithRetries(String sql, Connection connection) throws Exception { int tries = 0; while (true) { try { SqlTestCase.execute(sql, connection); break; } catch (SQLException e) { if (tries == 10) { throw e; } ++tries; Thread.sleep(100); } } } @Override public boolean fromIsOptional() { return true; } @Override public boolean canHaveLimitWithoutOrderBy() { // The postgres manual warns that the results may not be // meaningful, but postgres doesn't throw an error. return true; } @Override public boolean isReservedWord(String word) { return "offset".equalsIgnoreCase(word); } @Override public boolean canOrderByExpression(boolean isAggregate) { return true; } @Override public boolean whereCanReferToColumnAlias() { return false; } // True for postgres 8.0.7, false for postgres 8.1.4 // public boolean canHaveHavingWithoutGroupBy() { // return true; // } // Seems to be false for postgres 8.1.4, true for 8.2.5 // public boolean aggregateAsteriskIsForCountOnly() { // // I didn't really look into just what postgres // // does for this case. // return false; // } /* * As of Postgres 8.1.8, this is true. */ // public boolean errorIfUpdateToAggregate(boolean rowsPresent) { // /* Some versions of Postgres apparently can crash - CVE-2006-5540 */ // // if (rowsPresent) { // // false for 8.1.4. Probably true for some future version. // return false; // } // else { // /* This one is already true, I guess, although the message // is "ctid is NULL" which doesn't really make it clear to // me that Posgres is winning on purpose rather than by accident. // */ // return true; // } // } @Override public boolean nullSortsLower() { return false; } @Override public boolean detectsSyntaxErrorsInPrepareStatement() { return false; } @Override public boolean backslashInAStringIsAnEscape() { /* * "our long-term plan to transition to SQL-standard * string literal rules, wherein backslash is * not a special character." * http://www.postgresql.org/docs/techdocs.50 */ return true; } @Override public boolean trailingSpacesConsultedInComparisons() { return true; } @Override public boolean schemasMissing() { // Haven't really looked too much at what postgres has // for schemas. "create schema authorization mayflytest" // seemed to get somewhere but "set schema" didn't work was // about as far as I got. return true; } @Override public boolean numberOfValuesMustMatchNumberOfColumns() { return false; } @Override public boolean canInsertNoValues() { /* The hibernate dialect makes it look like the postgres syntax is "insert into foo default values" which actually seems fairly sensible. Verify this. */ return false; } @Override public boolean disallowNullsInExpressions() { return false; } @Override public boolean disallowNullOnRightHandSideOfIn() { return false; } @Override public boolean haveTinyint() { return false; } @Override public boolean expressionsAreTypeLong() { return false; } @Override public String binaryTypeName() { return "bytea"; } @Override public boolean blobTypeWorks() { /* The error I'm getting is: Bad value for type int: \001\003\377\220 I guess this is just a postgres bug (why would the type be "int" when we declare it as bytea?). This is postgres 8.1.8-1.fc6 as shipped in Fedora. */ return false; } @Override public boolean canGetBytesOnNumber() { return true; } @Override public boolean canMixStringAndInteger() { return true; } @Override public boolean canSetStringOnDecimalColumn() { return false; } @Override public boolean haveDropTableFooIfExists() { return false; } @Override public boolean haveDropTableIfExistsFoo() { return false; } @Override public boolean haveModifyColumn() { return false; } @Override public boolean canDropLastColumn() { return true; } @Override public boolean haveDropForeignKey() { return false; } @Override public boolean defaultValueCanBeExpression() { return true; } @Override public boolean allowDateInTimestampColumn() { return true; } @Override public boolean allowTimestampInDateColumn() { return true; } @Override public boolean haveSequencySerial() { return true; } @Override public String identityType() { return "serial primary key"; } /** * According to discussion on postgres mailing lists, they plan on * adding sql200x syntax only when they can give it sql200x semantics. */ @Override public boolean haveSql2003AutoIncrement() { return false; } @Override public boolean allowHexForBinary() { /* Postgres does have the x'00' syntax but it just seems to be for BIT VARYING(x) which doesn't seem to behave quite like BYTEA (or BLOB/BINARY in other databases). */ return false; } @Override public String lastIdentityValueQuery(String table, String column) { return new StringBuffer().append("select currval('") .append(table) .append('_') .append(column) .append("_seq')") .toString(); } @Override public boolean autoCommitMustBeOffToCallRollback() { return false; } @Override public boolean allowOrderByOnDelete() { return false; } @Override public boolean metaDataProblemWithUppercaseTableName() { return true; } @Override public String productName() { return "PostgreSQL"; } @Override public boolean deleteAllRowsIsSmartAboutForeignKeys() { return true; } @Override public boolean callJavaMethodAsStoredProcedure() { return false; } @Override public boolean haveDropIndexOn() { return false; } }