| /* |
| * Copyright (c) 2007-2013 Eike Stepper (Berlin, Germany) and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Eike Stepper - initial API and implementation |
| */ |
| package org.eclipse.net4j.db; |
| |
| import org.eclipse.net4j.db.ddl.IDBField; |
| import org.eclipse.net4j.db.ddl.IDBNamedElement; |
| import org.eclipse.net4j.db.ddl.IDBSchema; |
| import org.eclipse.net4j.db.ddl.IDBTable; |
| import org.eclipse.net4j.internal.db.DBConnection; |
| import org.eclipse.net4j.internal.db.DBDatabase; |
| import org.eclipse.net4j.internal.db.DataSourceConnectionProvider; |
| import org.eclipse.net4j.internal.db.bundle.OM; |
| import org.eclipse.net4j.internal.db.ddl.DBIndex; |
| import org.eclipse.net4j.internal.db.ddl.DBNamedElement; |
| import org.eclipse.net4j.spi.db.DBAdapter; |
| import org.eclipse.net4j.util.ReflectUtil; |
| import org.eclipse.net4j.util.StringUtil; |
| import org.eclipse.net4j.util.io.ExtendedDataInput; |
| import org.eclipse.net4j.util.io.ExtendedDataOutput; |
| import org.eclipse.net4j.util.om.OMPlatform; |
| import org.eclipse.net4j.util.om.monitor.OMMonitor; |
| import org.eclipse.net4j.util.om.monitor.OMMonitor.Async; |
| import org.eclipse.net4j.util.om.trace.ContextTracer; |
| import org.eclipse.net4j.util.security.IUserAware; |
| |
| import javax.sql.DataSource; |
| |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.sql.Blob; |
| import java.sql.Clob; |
| import java.sql.Connection; |
| import java.sql.DatabaseMetaData; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * A utility class with various static factory and convenience methods. |
| * |
| * @author Eike Stepper |
| */ |
| @SuppressWarnings("resource") |
| public final class DBUtil |
| { |
| /** |
| * @since 4.2 |
| */ |
| public static final int MAX_BATCH_SIZE = Integer |
| .parseInt(OMPlatform.INSTANCE.getProperty("org.eclipse.net4j.db.MAX_BATCH_SIZE", "2000")); |
| |
| /** |
| * A system property to enable noisy close, i.e. exception catch in close methods are thrown as {@link DBException} exception. |
| * |
| * @since 4.4 |
| */ |
| public static final String PROP_ENABLE_NOISY_CLOSE = "org.eclipse.net4j.db.close.noisy"; |
| |
| private static final boolean isNoisyCloseEnabled = Boolean |
| .valueOf(OMPlatform.INSTANCE.getProperty(PROP_ENABLE_NOISY_CLOSE, Boolean.FALSE.toString())); |
| |
| private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_SQL, DBUtil.class); |
| |
| private DBUtil() |
| { |
| } |
| |
| /** |
| * @deprecated This method exists only to create a "resource leak" warning, so that DBUtil can safely SuppressWarnings("resource"). |
| */ |
| @Deprecated |
| @SuppressWarnings("unused") |
| private static void avoidResourceLeakWarning() throws SQLException |
| { |
| Connection connection = new DBConnection(null, null); |
| Statement statement = connection.createStatement(); |
| close(statement); |
| } |
| |
| /** |
| * For debugging purposes ONLY! |
| * |
| * @deprecated Should only be used when debugging. |
| * @since 3.0 |
| */ |
| @Deprecated |
| public static void sqlDump(Connection conn, String sql) |
| { |
| ResultSet rs = null; |
| |
| try |
| { |
| TRACER.format("Dumping output of {0}", sql); //$NON-NLS-1$ |
| rs = conn.createStatement().executeQuery(sql); |
| int numCol = rs.getMetaData().getColumnCount(); |
| |
| StringBuilder row = new StringBuilder(); |
| for (int c = 1; c <= numCol; c++) |
| { |
| row.append(String.format("%10s | ", rs.getMetaData().getColumnLabel(c))); //$NON-NLS-1$ |
| } |
| |
| TRACER.trace(row.toString()); |
| |
| row = new StringBuilder(); |
| for (int c = 1; c <= numCol; c++) |
| { |
| row.append("-----------+--"); //$NON-NLS-1$ |
| } |
| |
| TRACER.trace(row.toString()); |
| |
| while (rs.next()) |
| { |
| row = new StringBuilder(); |
| for (int c = 1; c <= numCol; c++) |
| { |
| row.append(String.format("%10s | ", rs.getString(c))); //$NON-NLS-1$ |
| } |
| |
| TRACER.trace(row.toString()); |
| } |
| |
| row = new StringBuilder(); |
| for (int c = 1; c <= numCol; c++) |
| { |
| row.append("-----------+-"); //$NON-NLS-1$ |
| } |
| |
| TRACER.trace(row.toString()); |
| } |
| catch (SQLException ex) |
| { |
| // Do nothing |
| } |
| finally |
| { |
| close(rs); |
| } |
| } |
| |
| /** |
| * For debugging purposes ONLY! |
| * |
| * @deprecated Should only be used when debugging. |
| * @since 3.0 |
| */ |
| @Deprecated |
| public static void sqlDump(IDBConnectionProvider connectionProvider, String sql) |
| { |
| Connection connection = connectionProvider.getConnection(); |
| |
| try |
| { |
| sqlDump(connection, sql); |
| } |
| finally |
| { |
| close(connection); |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static String dumpToString(IDBNamedElement namedElement) |
| { |
| return ((DBNamedElement)namedElement).dumpToString(); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static void dump(IDBNamedElement namedElement) |
| { |
| ((DBNamedElement)namedElement).dump(); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static void dump(IDBNamedElement namedElement, Writer writer) throws IOException |
| { |
| ((DBNamedElement)namedElement).dump(writer); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static IDBDatabase openDatabase(IDBAdapter adapter, IDBConnectionProvider connectionProvider, |
| String schemaName) |
| { |
| return openDatabase(adapter, connectionProvider, schemaName, false); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static IDBDatabase openDatabase(IDBAdapter adapter, IDBConnectionProvider connectionProvider, |
| String schemaName, boolean fixNullableIndexColumns) |
| { |
| return new DBDatabase((DBAdapter)adapter, connectionProvider, schemaName, fixNullableIndexColumns); |
| } |
| |
| public static IDBSchema createSchema(String name) |
| { |
| return new org.eclipse.net4j.internal.db.ddl.DBSchema(name); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static void readSchema(IDBAdapter adapter, Connection connection, IDBSchema schema) |
| { |
| adapter.readSchema(connection, schema); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static IDBSchema readSchema(IDBAdapter adapter, Connection connection, String name) |
| { |
| return readSchema(adapter, connection, name, false); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static IDBSchema readSchema(IDBAdapter adapter, Connection connection, String name, |
| boolean fixNullableIndexColumns) |
| { |
| IDBSchema schema = new org.eclipse.net4j.internal.db.ddl.DBSchema(name); |
| |
| if (fixNullableIndexColumns) |
| { |
| DBIndex.FIX_NULLABLE_INDEX_COLUMNS.set(true); |
| } |
| |
| try |
| { |
| readSchema(adapter, connection, schema); |
| } |
| finally |
| { |
| if (fixNullableIndexColumns) |
| { |
| try |
| { |
| Set<IDBField> nullableIndexFields = DBIndex.NULLABLE_INDEX_FIELDS.get(); |
| if (nullableIndexFields != null && !nullableIndexFields.isEmpty()) |
| { |
| fixNullableIndexFields(adapter, connection, nullableIndexFields); |
| } |
| } |
| finally |
| { |
| DBIndex.NULLABLE_INDEX_FIELDS.remove(); |
| DBIndex.FIX_NULLABLE_INDEX_COLUMNS.remove(); |
| } |
| } |
| } |
| |
| return schema; |
| } |
| |
| private static void fixNullableIndexFields(IDBAdapter adapter, Connection connection, |
| Set<IDBField> nullableIndexFields) |
| { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("The internal schema migration has fixed the following nullable index columns:"); |
| builder.append(StringUtil.NL); |
| |
| boolean autoCommit = false; |
| Statement statement = null; |
| |
| try |
| { |
| autoCommit = setAutoCommit(connection, false); |
| statement = connection.createStatement(); |
| |
| for (IDBField field : nullableIndexFields) |
| { |
| field.setNotNull(true); |
| |
| String sql = adapter.sqlModifyField(field); |
| statement.execute(sql); |
| |
| builder.append("- "); |
| builder.append(sql); |
| builder.append(StringUtil.NL); |
| } |
| |
| connection.commit(); |
| OM.LOG.info(builder.toString()); |
| } |
| catch (SQLException ex) |
| { |
| OM.LOG.error(ex); |
| rollbackSilently(connection); |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(statement); |
| setAutoCommit(connection, autoCommit); |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static IDBSchema copySchema(IDBSchema source) |
| { |
| return new org.eclipse.net4j.internal.db.ddl.DBSchema(source); |
| } |
| |
| public static DataSource createDataSource(Map<Object, Object> properties) |
| { |
| return createDataSource(properties, null); |
| } |
| |
| public static DataSource createDataSource(Map<Object, Object> properties, String namespace) |
| { |
| return createDataSource(properties, namespace, "class"); //$NON-NLS-1$ |
| } |
| |
| public static DataSource createDataSource(Map<Object, Object> properties, String namespace, String driverClassKey) |
| { |
| try |
| { |
| return (DataSource)ReflectUtil.instantiate(properties, namespace, driverClassKey, OM.class.getClassLoader()); |
| } |
| catch (Exception ex) |
| { |
| throw new DBException(ex); |
| } |
| } |
| |
| public static IDBConnectionProvider createConnectionProvider(DataSource dataSource) |
| { |
| return createConnectionProvider(dataSource, null); |
| } |
| |
| /** |
| * @since 4.3 |
| */ |
| public static IDBConnectionProvider2 createConnectionProvider(DataSource dataSource, String user) |
| { |
| return new DataSourceConnectionProvider(dataSource, user); |
| } |
| |
| /** |
| * Retrieves an {@link IDBAdapter adapter} from the {@link IDBAdapter#REGISTRY adapter registry}. |
| * <p> |
| * If Eclipse is running adapters are automatically created from descriptors that are contributed to the extension point <code>org.eclipse.net4j.db.dbAdapters</code>. |
| * <p> |
| * In standalone scenarios the needed adapter instances must be registered with the {@link IDBAdapter#REGISTRY adapter registry} manually. |
| */ |
| public static IDBAdapter getDBAdapter(String adapterName) |
| { |
| return IDBAdapter.REGISTRY.get(adapterName); |
| } |
| |
| public static Exception close(ResultSet resultSet) |
| { |
| if (resultSet != null) |
| { |
| try |
| { |
| Statement statement = resultSet.getStatement(); |
| if (statement != null && statement.getMaxRows() != 0) |
| { |
| statement.setMaxRows(0); |
| } |
| } |
| catch (Exception ignore) |
| { |
| //$FALL-THROUGH$ |
| } |
| |
| try |
| { |
| resultSet.close(); |
| } |
| catch (Exception ex) |
| { |
| if (isNoisyCloseEnabled) |
| { |
| throw new DBException(ex); |
| } |
| |
| OM.LOG.error(ex); |
| return ex; |
| } |
| } |
| |
| return null; |
| } |
| |
| public static Exception close(Statement statement) |
| { |
| if (statement != null) |
| { |
| try |
| { |
| statement.close(); |
| } |
| catch (Exception ex) |
| { |
| if (isNoisyCloseEnabled) |
| { |
| throw new DBException(ex); |
| } |
| |
| OM.LOG.error(ex); |
| return ex; |
| } |
| } |
| |
| return null; |
| } |
| |
| public static Exception close(Connection connection) |
| { |
| if (connection != null) |
| { |
| try |
| { |
| // Only for connections with autoCommit = false, we try a rollback |
| // first to clear any open transactions. |
| if (!connection.getAutoCommit()) |
| { |
| rollbackSilently(connection); |
| } |
| |
| connection.close(); |
| } |
| catch (Exception ex) |
| { |
| if (isNoisyCloseEnabled) |
| { |
| throw new DBException(ex); |
| } |
| |
| OM.LOG.error(ex); |
| return ex; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static boolean setAutoCommit(Connection connection, boolean autoCommit) |
| { |
| try |
| { |
| if (connection.getAutoCommit() != autoCommit) |
| { |
| connection.setAutoCommit(autoCommit); |
| return !autoCommit; |
| } |
| |
| return autoCommit; |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static Exception rollbackSilently(Connection connection) |
| { |
| try |
| { |
| if (!connection.getAutoCommit()) |
| { |
| connection.rollback(); |
| } |
| |
| return null; |
| } |
| catch (Exception ex) |
| { |
| OM.LOG.error(ex); |
| return ex; |
| } |
| } |
| |
| /** |
| * @since 3.0 |
| * @deprecated As of 4.2 use {@link #getAllSchemaNames(Connection)}. |
| */ |
| @Deprecated |
| public static List<String> getAllSchemaTableNames(Connection connection) |
| { |
| return getAllSchemaNames(connection); |
| } |
| |
| /** |
| * @since 3.0 |
| * @deprecated As of 4.2 use {@link #getAllSchemaNames(DatabaseMetaData)}. |
| */ |
| @Deprecated |
| public static List<String> getAllSchemaTableNames(DatabaseMetaData metaData) |
| { |
| return new ArrayList<String>(getAllSchemaNames(metaData)); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static List<String> getAllSchemaNames(Connection connection) |
| { |
| try |
| { |
| DatabaseMetaData metaData = connection.getMetaData(); |
| return new ArrayList<String>(getAllSchemaNames(metaData)); |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static Set<String> getAllSchemaNames(DatabaseMetaData metaData) |
| { |
| ResultSet schemas = null; |
| |
| try |
| { |
| Set<String> names = new HashSet<String>(); |
| schemas = metaData.getSchemas(); |
| while (schemas.next()) |
| { |
| String name = schemas.getString(1); |
| names.add(name); |
| } |
| |
| return names; |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(schemas); |
| } |
| } |
| |
| public static List<String> getAllTableNames(Connection connection, String dbName) |
| { |
| ResultSet tables = null; |
| |
| try |
| { |
| List<String> names = new ArrayList<String>(); |
| DatabaseMetaData metaData = connection.getMetaData(); |
| if (dbName != null) |
| { |
| dbName = dbName.toUpperCase(); |
| Set<String> schemaNames = getAllSchemaNames(metaData); |
| if (!schemaNames.contains(dbName)) |
| { |
| dbName = null; |
| } |
| } |
| |
| if (dbName == null && connection instanceof IUserAware) |
| { |
| dbName = ((IUserAware)connection).getUserID(); |
| } |
| |
| tables = metaData.getTables(null, dbName, null, new String[] { "TABLE" }); //$NON-NLS-1$ |
| while (tables.next()) |
| { |
| String name = tables.getString(3); |
| names.add(name); |
| } |
| |
| return names; |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(tables); |
| } |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| public static List<Exception> dropAllTables(Connection connection, String dbName) |
| { |
| Statement statement = null; |
| |
| try |
| { |
| statement = connection.createStatement(); |
| List<Exception> exceptions = new ArrayList<Exception>(); |
| |
| for (String tableName : getAllTableNames(connection, dbName)) |
| { |
| String sql = "DROP TABLE " + tableName; //$NON-NLS-1$ |
| trace(sql); |
| |
| try |
| { |
| statement.execute(sql); |
| } |
| catch (SQLException ex) |
| { |
| exceptions.add(ex); |
| } |
| } |
| |
| return exceptions; |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(statement); |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static int asInt(Object value) |
| { |
| if (value instanceof Number) |
| { |
| return ((Number)value).intValue(); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static long asLong(Object value) |
| { |
| if (value instanceof Number) |
| { |
| return ((Number)value).longValue(); |
| } |
| |
| return 0L; |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public static int selectMinimumInt(Connection connection, IDBField field, String... where) throws DBException |
| { |
| Number number = getFunctionResult(connection, field, "MIN", where); //$NON-NLS-1$ |
| return asInt(number); |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public static long selectMinimumLong(Connection connection, IDBField field, String... where) throws DBException |
| { |
| Number number = getFunctionResult(connection, field, "MIN", where); //$NON-NLS-1$ |
| return asLong(number); |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public static int selectMaximumInt(Connection connection, IDBField field, String... where) throws DBException |
| { |
| Number number = getFunctionResult(connection, field, "MAX", where); //$NON-NLS-1$ |
| return asInt(number); |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public static long selectMaximumLong(Connection connection, IDBField field, String... where) throws DBException |
| { |
| Number number = getFunctionResult(connection, field, "MAX", where); //$NON-NLS-1$ |
| return asLong(number); |
| } |
| |
| private static Number getFunctionResult(Connection connection, IDBField field, String function, String... where) |
| throws DBException |
| { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("SELECT "); //$NON-NLS-1$ |
| builder.append(function); |
| builder.append("("); //$NON-NLS-1$ |
| builder.append(field); |
| builder.append(") FROM "); //$NON-NLS-1$ |
| builder.append(field.getTable()); |
| |
| for (int i = 0; i < where.length; i++) |
| { |
| if (i == 0) |
| { |
| builder.append(" WHERE "); //$NON-NLS-1$ |
| } |
| else |
| { |
| builder.append(" AND "); //$NON-NLS-1$ |
| } |
| |
| builder.append("("); //$NON-NLS-1$ |
| builder.append(where[i]); |
| builder.append(")"); //$NON-NLS-1$ |
| } |
| |
| String sql = trace(builder.toString()); |
| Statement statement = null; |
| ResultSet resultSet = null; |
| |
| try |
| { |
| statement = connection.createStatement(); |
| |
| try |
| { |
| resultSet = statement.executeQuery(sql); |
| if (!resultSet.next()) |
| { |
| return null; |
| } |
| |
| return (Number)resultSet.getObject(1); |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(resultSet); |
| } |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(statement); |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static <T> T execute(IDBConnectionProvider connectionProvider, RunnableWithConnection<T> runnable) |
| { |
| Connection connection = null; |
| |
| try |
| { |
| connection = connectionProvider.getConnection(); |
| |
| T result = runnable.run(connection); |
| connection.commit(); |
| return result; |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(connection); |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static void execute(Connection connection, CharSequence sql) |
| { |
| String string = sql.toString(); |
| if (TRACER.isEnabled()) |
| { |
| TRACER.trace(string); |
| } |
| |
| Statement statement = null; |
| |
| try |
| { |
| statement = connection.createStatement(); |
| statement.execute(string); |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex.getMessage() + " --> " + sql, ex); |
| } |
| finally |
| { |
| close(statement); |
| } |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| public static void executeBatch(PreparedStatement stmt, int counter) |
| { |
| executeBatch(stmt, counter, true); |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| public static void executeBatch(PreparedStatement stmt, int counter, boolean checkExactlyOne) |
| { |
| try |
| { |
| int[] results = stmt.executeBatch(); |
| if (results.length != counter) |
| { |
| throw new DBException("Statement has " + results.length + " results (expected: " + counter + ")"); |
| } |
| |
| for (int i = 0; i < results.length; i++) |
| { |
| int result = results[i]; |
| if (result != Statement.SUCCESS_NO_INFO) |
| { |
| if (result < 0) |
| { |
| throw new DBException("Result " + i + " is not successful: " + result); |
| } |
| |
| if (checkExactlyOne && result != 1) |
| { |
| throw new DBException("Result " + i + " did not affect exactly one row: " + result); |
| } |
| } |
| } |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| } |
| |
| public static int update(Connection connection, String sql) |
| { |
| trace(sql); |
| Statement statement = null; |
| |
| try |
| { |
| statement = connection.createStatement(); |
| return statement.executeUpdate(sql); |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(statement); |
| } |
| } |
| |
| /** |
| * Execute update on the given prepared statement and handle common cases of return values. |
| * |
| * @param stmt |
| * the prepared statement |
| * @param exactlyOne |
| * if <code>true</code>, the update count is checked to be <code>1</code>. Else the update result is only |
| * checked so that the update was successful (i.e. result code != Statement.EXECUTE_FAILED). |
| * @return the update count / execution result as returned by {@link PreparedStatement#executeUpdate()}. Can be used |
| * by the caller to perform more advanced checks. |
| * @throws SQLException |
| * if {@link PreparedStatement#executeUpdate()} throws it. |
| * @throws IllegalStateException |
| * if the check indicated by <code>excatlyOne</code> indicates an error. |
| * @since 4.0 |
| */ |
| public static int update(PreparedStatement stmt, boolean exactlyOne) throws SQLException |
| { |
| if (isTracerEnabled()) |
| { |
| trace(stmt.toString()); |
| } |
| |
| int result = stmt.executeUpdate(); |
| |
| // basic check of update result |
| if (exactlyOne && result != 1) |
| { |
| throw new IllegalStateException(stmt.toString() + " returned Update count " + result + " (expected: 1)"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| if (result == Statement.EXECUTE_FAILED) |
| { |
| throw new IllegalStateException(stmt.toString() + " returned EXECUTE_FAILED"); //$NON-NLS-1$ |
| } |
| |
| return result; |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| public static int clearTable(Connection connection, IDBTable table) |
| { |
| return clearTable(connection, table.getName()); |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| public static int clearTable(Connection connection, String tableName) |
| { |
| String sql = "DELETE FROM " + tableName; |
| return update(connection, sql); |
| } |
| |
| public static int select(Connection connection, IDBRowHandler rowHandler, String where, IDBField... fields) |
| throws DBException |
| { |
| IDBTable table = fields[0].getTable(); |
| for (int i = 1; i < fields.length; i++) |
| { |
| if (fields[i].getTable() != table) |
| { |
| throw new IllegalArgumentException("Multiple tables not allowed: " + Arrays.asList(fields)); //$NON-NLS-1$ |
| } |
| } |
| |
| StringBuilder builder = new StringBuilder(); |
| builder.append("SELECT "); //$NON-NLS-1$ |
| for (int i = 0; i < fields.length; i++) |
| { |
| if (i > 0) |
| { |
| builder.append(", "); //$NON-NLS-1$ |
| } |
| |
| builder.append(fields[i]); |
| } |
| |
| builder.append(" FROM "); //$NON-NLS-1$ |
| builder.append(table); |
| if (where != null) |
| { |
| builder.append(" WHERE "); //$NON-NLS-1$ |
| builder.append(where); |
| } |
| |
| String sql = trace(builder.toString()); |
| Statement statement = null; |
| ResultSet resultSet = null; |
| |
| try |
| { |
| statement = connection.createStatement(); |
| |
| try |
| { |
| int rows = 0; |
| boolean proceed = true; |
| Object[] values = new Object[fields.length]; |
| resultSet = statement.executeQuery(sql); |
| while (proceed && resultSet.next()) |
| { |
| for (int i = 0; i < fields.length; i++) |
| { |
| values[i] = resultSet.getObject(i + 1); |
| if (values[i] instanceof Blob) |
| { |
| Blob blob = (Blob)values[i]; |
| long length = blob.length(); |
| if (length > Integer.MAX_VALUE) |
| { |
| throw new IllegalStateException("byte[] too long: " + length); //$NON-NLS-1$ |
| } |
| |
| values[i] = blob.getBytes(1, (int)length); |
| } |
| else if (values[i] instanceof Clob) |
| { |
| Clob clob = (Clob)values[i]; |
| long length = clob.length(); |
| if (length > Integer.MAX_VALUE) |
| { |
| throw new IllegalStateException("String too long: " + length); //$NON-NLS-1$ |
| } |
| |
| values[i] = clob.getSubString(1, (int)length); |
| } |
| } |
| |
| proceed = rowHandler.handle(rows++, values); |
| } |
| |
| return rows; |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(resultSet); |
| } |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(statement); |
| } |
| } |
| |
| public static int select(Connection connection, IDBRowHandler rowHandler, IDBField... fields) throws DBException |
| { |
| return select(connection, rowHandler, null, fields); |
| } |
| |
| public static Object[] select(Connection connection, String where, IDBField... fields) throws DBException |
| { |
| final Object[][] result = new Object[1][]; |
| IDBRowHandler rowHandler = new IDBRowHandler() |
| { |
| public boolean handle(int row, Object... values) |
| { |
| result[0] = values; |
| return false; |
| } |
| }; |
| |
| select(connection, rowHandler, where, fields); |
| return result[0]; |
| } |
| |
| /** |
| * Returns the number of rows contained in the given result set. |
| * <p> |
| * The {@link ResultSet#getStatement() statement} of the result set must have been created with |
| * {@link ResultSet#TYPE_SCROLL_INSENSITIVE TYPE_SCROLL_INSENSITIVE}. |
| * |
| * @since 4.0 |
| */ |
| public static int getRowCount(ResultSet resultSet) throws DBException |
| { |
| reset(resultSet); |
| |
| try |
| { |
| resultSet.last(); |
| return resultSet.getRow(); |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| reset(resultSet); |
| } |
| } |
| |
| private static void reset(ResultSet resultSet) throws DBException |
| { |
| try |
| { |
| resultSet.beforeFirst(); |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public static void serializeTable(ExtendedDataOutput out, Connection connection, IDBTable table, String tableAlias, |
| String sqlSuffix) throws DBException, IOException |
| { |
| serializeTable(out, connection, table, tableAlias, sqlSuffix, null); |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| public static void serializeTable(ExtendedDataOutput out, Connection connection, IDBTable table, String tableAlias, |
| String sqlSuffix, SerializeRowHandler handler) throws DBException, IOException |
| { |
| IDBField[] fields = table.getFields(); |
| |
| StringBuilder builder = new StringBuilder(); |
| builder.append("SELECT "); //$NON-NLS-1$ |
| for (int i = 0; i < fields.length; i++) |
| { |
| if (i > 0) |
| { |
| builder.append(", "); //$NON-NLS-1$ |
| } |
| |
| if (tableAlias != null) |
| { |
| builder.append(tableAlias); |
| builder.append("."); //$NON-NLS-1$ |
| } |
| |
| builder.append(fields[i]); |
| } |
| |
| builder.append(" FROM "); //$NON-NLS-1$ |
| builder.append(table); |
| if (tableAlias != null) |
| { |
| builder.append(" "); //$NON-NLS-1$ |
| builder.append(tableAlias); |
| } |
| |
| if (sqlSuffix != null) |
| { |
| builder.append(sqlSuffix); |
| } |
| |
| String sql = trace(builder.toString()); |
| Statement statement = null; |
| ResultSet resultSet = null; |
| boolean successful = false; |
| |
| try |
| { |
| statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); |
| |
| try |
| { |
| resultSet = statement.executeQuery(sql); |
| |
| // Write resultSet size for progress monitoring |
| int size = getRowCount(resultSet); |
| out.writeInt(size); |
| if (size == 0) |
| { |
| return; |
| } |
| |
| Object[] values = handler != null ? new Object[fields.length] : null; |
| |
| while (resultSet.next()) |
| { |
| for (int i = 0; i < fields.length; i++) |
| { |
| IDBField field = fields[i]; |
| DBType type = field.getType(); |
| boolean canBeNull = !field.isNotNull(); |
| |
| Object value = type.writeValueWithResult(out, resultSet, i + 1, canBeNull); |
| if (values != null) |
| { |
| values[i] = value; |
| } |
| } |
| |
| if (handler != null) |
| { |
| handler.handleRow(out, connection, fields, values); |
| } |
| } |
| |
| successful = true; |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(resultSet); |
| } |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| try |
| { |
| if (handler != null) |
| { |
| handler.done(successful); |
| } |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(statement); |
| } |
| } |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| public static void deserializeTable(ExtendedDataInput in, Connection connection, IDBTable table, OMMonitor monitor) |
| throws IOException |
| { |
| deserializeTable(in, connection, table, monitor, null); |
| } |
| |
| /** |
| * @since 4.1 |
| */ |
| public static void deserializeTable(ExtendedDataInput in, Connection connection, IDBTable table, OMMonitor monitor, |
| DeserializeRowHandler handler) throws IOException |
| { |
| int size = in.readInt(); |
| if (size == 0) |
| { |
| return; |
| } |
| |
| IDBField[] fields = table.getFields(); |
| |
| StringBuilder builder = new StringBuilder(); |
| StringBuilder params = new StringBuilder(); |
| |
| builder.append("INSERT INTO "); //$NON-NLS-1$ |
| builder.append(table); |
| builder.append("("); //$NON-NLS-1$ |
| |
| for (int i = 0; i < fields.length; i++) |
| { |
| if (i > 0) |
| { |
| builder.append(", "); //$NON-NLS-1$ |
| params.append(", "); //$NON-NLS-1$ |
| } |
| |
| builder.append(fields[i]); |
| params.append("?"); //$NON-NLS-1$ |
| } |
| |
| builder.append(") VALUES ("); //$NON-NLS-1$ |
| builder.append(params.toString()); |
| builder.append(")"); //$NON-NLS-1$ |
| |
| String sql = trace(builder.toString()); |
| PreparedStatement statement = null; |
| boolean successful = false; |
| |
| monitor.begin(1 + 2 * size); |
| |
| try |
| { |
| statement = connection.prepareStatement(sql); |
| monitor.worked(); |
| |
| Object[] values = handler != null ? new Object[fields.length] : null; |
| |
| int batchSize = 0; |
| for (int row = 0; row < size; row++) |
| { |
| for (int i = 0; i < fields.length; i++) |
| { |
| IDBField field = fields[i]; |
| DBType type = field.getType(); |
| boolean canBeNull = !field.isNotNull(); |
| Object value = type.readValueWithResult(in, statement, i + 1, canBeNull); |
| if (values != null) |
| { |
| values[i] = value; |
| } |
| } |
| |
| statement.addBatch(); |
| if (handler != null) |
| { |
| handler.handleRow(in, connection, fields, values); |
| } |
| |
| monitor.worked(); |
| |
| if (++batchSize == MAX_BATCH_SIZE) |
| { |
| executeBatch(statement, batchSize, monitor); |
| batchSize = 0; |
| } |
| } |
| |
| if (batchSize != 0) |
| { |
| executeBatch(statement, batchSize, monitor); |
| } |
| |
| successful = true; |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| try |
| { |
| if (handler != null) |
| { |
| handler.done(successful); |
| } |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| finally |
| { |
| close(statement); |
| monitor.done(); |
| } |
| } |
| } |
| |
| private static void executeBatch(PreparedStatement statement, int batchSize, OMMonitor monitor) throws SQLException |
| { |
| Async async = monitor.forkAsync(batchSize); |
| |
| try |
| { |
| statement.executeBatch(); |
| } |
| finally |
| { |
| async.stop(); |
| } |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public static String trace(String sql) |
| { |
| if (isTracerEnabled()) |
| { |
| TRACER.trace(sql); |
| } |
| |
| return sql; |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public static boolean isTracerEnabled() |
| { |
| return TRACER.isEnabled(); |
| } |
| |
| /** |
| * @since 4.2 |
| * @author Eike Stepper |
| */ |
| public interface RunnableWithConnection<T> |
| { |
| public T run(Connection connection) throws SQLException; |
| } |
| |
| /** |
| * Call-back interface with a {@link #done(boolean) method} that is called <i>after</i> |
| * a number of table rows have been handled by one of the subtypes of this interface. |
| * |
| * @since 4.1 |
| * @author Eike Stepper |
| */ |
| public interface RowHandler |
| { |
| public void done(boolean successful) throws SQLException, IOException; |
| } |
| |
| /** |
| * A {@link RowHandler row handler} with a {@link #handleRow(ExtendedDataOutput, Connection, IDBField[], Object[]) method} |
| * that is called once per row serialized within {@link DBUtil#serializeTable(ExtendedDataOutput, Connection, IDBTable, String, String, SerializeRowHandler) DBUtil.serializeTable()}. |
| * |
| * @since 4.1 |
| * @author Eike Stepper |
| */ |
| public interface SerializeRowHandler extends RowHandler |
| { |
| public void handleRow(ExtendedDataOutput out, Connection connection, IDBField[] fields, Object[] values) |
| throws SQLException, IOException; |
| } |
| |
| /** |
| * A {@link RowHandler row handler} with a {@link #handleRow(ExtendedDataInput, Connection, IDBField[], Object[]) method} |
| * that is called once per row deserialized within {@link DBUtil#deserializeTable(ExtendedDataInput, Connection, IDBTable, OMMonitor, DeserializeRowHandler) DBUtil.deserializeTable()}. |
| * |
| * @since 4.1 |
| * @author Eike Stepper |
| */ |
| public interface DeserializeRowHandler extends RowHandler |
| { |
| public void handleRow(ExtendedDataInput in, Connection connection, IDBField[] fields, Object[] values) |
| throws SQLException, IOException; |
| } |
| } |