All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.umlg.sqlg.test.JDBC Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
package org.umlg.sqlg.test;

import org.junit.Assert;

import java.sql.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;

/**
 * Date: 2014/07/19
 * Time: 11:20 AM
 */
public class JDBC {

    /**
     * Helper class whose equals() method returns
     * true for all strings on this format: SQL061021105830900
     */
    public static class GeneratedId {
        public boolean equals(Object o) {
            // unless JSR169, use String.matches...
            if (JDBC.vmSupportsJDBC3()) {
                return o instanceof String &&
                        ((String) o).matches("SQL[0-9]{15}");
            } else {
                String tmpstr = (String) o;
                boolean b = true;
                if (!(o instanceof String))
                    b = false;
                if (!(tmpstr.startsWith("SQL")))
                    b = false;
                if (tmpstr.length() != 18)
                    b = false;
                for (int i = 3; i < 18; i++) {
                    if (Character.isDigit(tmpstr.charAt(i)))
                        continue;
                    else {
                        b = false;
                        break;
                    }
                }
                return b;
            }
        }

        public String toString() {
            return "xxxxGENERATED-IDxxxx";
        }
    }

    /**
     * Constant to pass to DatabaseMetaData.getTables() to fetch
     * just tables.
     */
    public static final String[] GET_TABLES_TABLE = new String[]{"TABLE"};
    /**
     * Constant to pass to DatabaseMetaData.getTables() to fetch
     * just views.
     */
    public static final String[] GET_TABLES_VIEW = new String[]{"VIEW"};
    /**
     * Constant to pass to DatabaseMetaData.getTables() to fetch
     * just synonyms.
     */
    public static final String[] GET_TABLES_SYNONYM =
            new String[]{"SYNONYM"};

    /**
     * Types.SQLXML value without having to compile with JDBC4.
     */
    public static final int SQLXML = 2009;

    /**
     * Tell if we are allowed to use DriverManager to create database
     * connections.
     */
    private static final boolean HAVE_DRIVER
            = haveClass("java.sql.Driver");

    /**
     * Does the Savepoint class exist, indicates
     * JDBC 3 (or JSR 169).
     */
    private static final boolean HAVE_SAVEPOINT
            = haveClass("java.sql.Savepoint");

    /**
     * Does the java.sql.SQLXML class exist, indicates JDBC 4.
     */
    private static final boolean HAVE_SQLXML
            = haveClass("java.sql.SQLXML");

    /**
     * Can we load a specific class, use this to determine JDBC level.
     *
     * @param className Class to attempt load on.
     * @return true if class can be loaded, false otherwise.
     */
    static boolean haveClass(String className) {
        try {
            Class.forName(className);
            return true;
        } catch (Throwable e) {
            return false;
        }
    }

    /**
     * Return true if the virtual machine environment
     * supports JDBC4 or later. JDBC 4 is a superset
     * of JDBC 3 and of JSR169.
     * 
* This method returns true in a JDBC 4 environment * and false in a JDBC 3 or JSR 169 environment. */ public static boolean vmSupportsJDBC4() { return HAVE_DRIVER && HAVE_SQLXML; } /** * Return true if the virtual machine environment * supports JDBC3 or later. JDBC 3 is a super-set of JSR169 * and a subset of JDBC 4. *
* This method will return true in a JDBC 3 or JDBC 4 * environment, but false in a JSR169 environment. */ public static boolean vmSupportsJDBC3() { return HAVE_DRIVER && HAVE_SAVEPOINT; } /** * Return true if the virtual machine environment * supports JSR169. JSR169 is a subset of JDBC 3 * and hence a subset of JDBC 4 as well. *
* This method returns true only in a JSR 169 * environment. */ public static boolean vmSupportsJSR169() { return !HAVE_DRIVER && HAVE_SAVEPOINT; } /** * Rollback and close a connection for cleanup. * Test code that is expecting Connection.close to succeed * normally should just call conn.close(). *

*

* If conn is not-null and isClosed() returns false * then both rollback and close will be called. * If both methods throw exceptions * then they will be chained together and thrown. * * @throws java.sql.SQLException Error closing connection. */ public static void cleanup(Connection conn) throws SQLException { if (conn == null) return; if (conn.isClosed()) return; SQLException sqle = null; try { conn.rollback(); } catch (SQLException e) { sqle = e; } try { conn.close(); } catch (SQLException e) { if (sqle == null) sqle = e; else sqle.setNextException(e); throw sqle; } } /** * Drop a database schema by dropping all objects in it * and then executing DROP SCHEMA. If the schema is * APP it is cleaned but DROP SCHEMA is not executed. *

* TODO: Handle dependencies by looping in some intelligent * way until everything can be dropped. * * @param dmd DatabaseMetaData object for database * @param schema Name of the schema * @throws java.sql.SQLException database error */ public static void dropSchema(DatabaseMetaData dmd, String schema) throws SQLException { Connection conn = dmd.getConnection(); Assert.assertFalse(conn.getAutoCommit()); Statement s = dmd.getConnection().createStatement(); // Functions - not supported by JDBC meta data until JDBC 4 // Need to use the CHAR() function on A.ALIASTYPE // so that the compare will work in any schema. PreparedStatement psf = conn.prepareStatement( "SELECT ALIAS FROM SYS.SYSALIASES A, SYS.SYSSCHEMAS S" + " WHERE A.SCHEMAID = S.SCHEMAID " + " AND CHAR(A.ALIASTYPE) = ? " + " AND S.SCHEMANAME = ?"); psf.setString(1, "F"); psf.setString(2, schema); ResultSet rs = psf.executeQuery(); dropUsingDMD(s, rs, schema, "ALIAS", "FUNCTION"); // Procedures rs = dmd.getProcedures((String) null, schema, (String) null); dropUsingDMD(s, rs, schema, "PROCEDURE_NAME", "PROCEDURE"); // Views rs = dmd.getTables((String) null, schema, (String) null, GET_TABLES_VIEW); dropUsingDMD(s, rs, schema, "TABLE_NAME", "VIEW"); // Tables rs = dmd.getTables((String) null, schema, (String) null, GET_TABLES_TABLE); dropUsingDMD(s, rs, schema, "TABLE_NAME", "TABLE"); // At this point there may be tables left due to // foreign key constraints leading to a dependency loop. // Drop any constraints that remain and then drop the tables. // If there are no tables then this should be a quick no-op. ResultSet table_rs = dmd.getTables((String) null, schema, (String) null, GET_TABLES_TABLE); while (table_rs.next()) { String tablename = table_rs.getString("TABLE_NAME"); rs = dmd.getExportedKeys((String) null, schema, tablename); while (rs.next()) { short keyPosition = rs.getShort("KEY_SEQ"); if (keyPosition != 1) continue; String fkName = rs.getString("FK_NAME"); // No name, probably can't happen but couldn't drop it anyway. if (fkName == null) continue; String fkSchema = rs.getString("FKTABLE_SCHEM"); String fkTable = rs.getString("FKTABLE_NAME"); String ddl = "ALTER TABLE " + JDBC.escape(fkSchema, fkTable) + " DROP FOREIGN KEY " + JDBC.escape(fkName); s.executeUpdate(ddl); } rs.close(); } table_rs.close(); conn.commit(); // Tables (again) rs = dmd.getTables((String) null, schema, (String) null, GET_TABLES_TABLE); dropUsingDMD(s, rs, schema, "TABLE_NAME", "TABLE"); // drop UDTs psf.setString(1, "A"); psf.setString(2, schema); rs = psf.executeQuery(); dropUsingDMD(s, rs, schema, "ALIAS", "TYPE"); psf.close(); // Synonyms - need work around for DERBY-1790 where // passing a table type of SYNONYM fails. rs = dmd.getTables((String) null, schema, (String) null, GET_TABLES_SYNONYM); dropUsingDMD(s, rs, schema, "TABLE_NAME", "SYNONYM"); // sequences if (sysSequencesExists(conn)) { psf = conn.prepareStatement ( "SELECT SEQUENCENAME FROM SYS.SYSSEQUENCES A, SYS.SYSSCHEMAS S" + " WHERE A.SCHEMAID = S.SCHEMAID " + " AND S.SCHEMANAME = ?"); psf.setString(1, schema); rs = psf.executeQuery(); dropUsingDMD(s, rs, schema, "SEQUENCENAME", "SEQUENCE"); psf.close(); } // Finally drop the schema if it is not APP if (!schema.equals("APP")) { s.executeUpdate("DROP SCHEMA " + JDBC.escape(schema) + " RESTRICT"); } conn.commit(); s.close(); } /** * Return true if the SYSSEQUENCES table exists. */ private static boolean sysSequencesExists(Connection conn) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement ( "select count(*) from sys.systables t, sys.sysschemas s\n" + "where t.schemaid = s.schemaid\n" + "and ( cast(s.schemaname as varchar(128)))= 'SYS'\n" + "and ( cast(t.tablename as varchar(128))) = 'SYSSEQUENCES'"); rs = ps.executeQuery(); rs.next(); return (rs.getInt(1) > 0); } finally { if (rs != null) { rs.close(); } if (ps != null) { ps.close(); } } } /** * DROP a set of objects based upon a ResultSet from a * DatabaseMetaData call. *

* TODO: Handle errors to ensure all objects are dropped, * probably requires interaction with its caller. * * @param s Statement object used to executeRegularQuery the DROP commands. * @param rs DatabaseMetaData ResultSet * @param schema Schema the objects are contained in * @param mdColumn The column name used to extract the object's * name from rs * @param dropType The keyword to use after DROP in the SQL statement * @throws java.sql.SQLException database errors. */ private static void dropUsingDMD( Statement s, ResultSet rs, String schema, String mdColumn, String dropType) throws SQLException { String dropLeadIn = "DROP " + dropType + " "; // First collect the set of DROP SQL statements. ArrayList ddl = new ArrayList(); while (rs.next()) { String objectName = rs.getString(mdColumn); String raw = dropLeadIn + JDBC.escape(schema, objectName); if ("TYPE".equals(dropType) || "SEQUENCE".equals(dropType)) { raw = raw + " restrict "; } ddl.add(raw); } rs.close(); if (ddl.isEmpty()) return; // Execute them as a complete batch, hoping they will all succeed. s.clearBatch(); int batchCount = 0; for (Iterator i = ddl.iterator(); i.hasNext(); ) { Object sql = i.next(); if (sql != null) { s.addBatch(sql.toString()); batchCount++; } } int[] results; boolean hadError; try { results = s.executeBatch(); Assert.assertNotNull(results); Assert.assertEquals("Incorrect result length from executeBatch", batchCount, results.length); hadError = false; } catch (BatchUpdateException batchException) { results = batchException.getUpdateCounts(); Assert.assertNotNull(results); Assert.assertTrue("Too many results in BatchUpdateException", results.length <= batchCount); hadError = true; } // Remove any statements from the list that succeeded. boolean didDrop = false; for (int i = 0; i < results.length; i++) { int result = results[i]; if (result == Statement.EXECUTE_FAILED) hadError = true; else if (result == Statement.SUCCESS_NO_INFO || result >= 0) { didDrop = true; ddl.set(i, null); } else Assert.fail("Negative executeBatch status"); } s.clearBatch(); if (didDrop) { // Commit any work we did do. s.getConnection().commit(); } // If we had failures drop them as individual statements // until there are none left or none succeed. We need to // do this because the batch processing stops at the first // error. This copes with the simple case where there // are objects of the same type that depend on each other // and a different drop order will allow all or most // to be dropped. if (hadError) { do { hadError = false; didDrop = false; for (ListIterator i = ddl.listIterator(); i.hasNext(); ) { Object sql = i.next(); if (sql != null) { try { s.executeUpdate(sql.toString()); i.set(null); didDrop = true; } catch (SQLException e) { hadError = true; } } } if (didDrop) s.getConnection().commit(); } while (hadError && didDrop); } } /** * Does the work of assertDrainResults() as described * above. If the received row count is non-negative, * this method also asserts that the number of rows * in the result set matches the received row count. *

* The ResultSet is closed by this method. * * @param rs Result set to drain. * @param expectedRows If non-negative, indicates how * many rows we expected to see in the result set. * @return the number of rows seen. * @throws java.sql.SQLException */ public static int assertDrainResults(ResultSet rs, int expectedRows) throws SQLException { ResultSetMetaData rsmd = rs.getMetaData(); int rows = 0; while (rs.next()) { for (int col = 1; col <= rsmd.getColumnCount(); col++) { String s = rs.getString(col); Assert.assertEquals(s == null, rs.wasNull()); if (rs.wasNull()) assertResultColumnNullable(rsmd, col); } rows++; } rs.close(); if (expectedRows >= 0) Assert.assertEquals("Unexpected row count:", expectedRows, rows); return rows; } /** * Assert that a column is nullable in its ResultSetMetaData. * Used when a utility method checking the contents of a * ResultSet sees a NULL value. If the value is NULL then * the column's definition in ResultSetMetaData must allow NULLs * (or not disallow NULLS). * * @param rsmd Metadata of the ResultSet * @param col Position of column just fetched that was NULL. * @throws java.sql.SQLException Error accessing meta data */ private static void assertResultColumnNullable(ResultSetMetaData rsmd, int col) throws SQLException { Assert.assertFalse(rsmd.isNullable(col) == ResultSetMetaData.columnNoNulls); } /** * Escape a non-qualified name so that it is suitable * for use in a SQL query executed by JDBC. */ public static String escape(String name) { StringBuffer buffer = new StringBuffer(name.length() + 2); buffer.append('"'); for (int i = 0; i < name.length(); i++) { char c = name.charAt(i); // escape double quote characters with an extra double quote if (c == '"') buffer.append('"'); buffer.append(c); } buffer.append('"'); return buffer.toString(); } /** * Escape a schama-qualified name so that it is suitable * for use in a SQL query executed by JDBC. */ public static String escape(String schema, String name) { return escape(schema) + "." + escape(name); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy