/* * Copyright 2007 - 2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.sf.jailer.database; import java.io.IOException; import java.io.OutputStreamWriter; import java.sql.ResultSet; import java.sql.SQLException; import net.sf.jailer.ExecutionContext; import net.sf.jailer.configuration.DBMS; import net.sf.jailer.database.Session.AbstractResultSetReader; import net.sf.jailer.database.Session.ResultSetReader; import net.sf.jailer.datamodel.Column; import net.sf.jailer.datamodel.Table; import net.sf.jailer.subsetting.TransformerFactory; import net.sf.jailer.util.CellContentConverter; import net.sf.jailer.util.Quoting; import net.sf.jailer.util.SqlUtil; /** * A {@link ResultSetReader} that writes the read rows as SQL-DELETE-statements * into the deletion-script. * * @author Ralf Wisser */ public class DeletionTransformer extends AbstractResultSetReader { /** * The table to read from. */ private final Table table; /** * The file to write to. */ private final OutputStreamWriter scriptFileWriter; /** * For building compact delete-statements. */ private StatementBuilder deleteStatementBuilder; /** * Current session; */ private final Session session; /** * Configuration of the target DBMS. */ private final DBMS targetDBMSConfiguration; /** * For quoting of column names. */ private final Quoting quoting; /** * The execution context. */ private final ExecutionContext executionContext; /** * Factory. */ public static class Factory implements TransformerFactory { private final int maxBodySize; private final OutputStreamWriter scriptFileWriter; private final Session session; private final DBMS targetDBMSConfiguration; /** * The execution context. */ private final ExecutionContext executionContext; /** * Constructor. * * @param table the table to read from * @param scriptFileWriter the file to write to * @param maxBodySize maximum length of SQL values list (for generated deletes) * @param targetDBMSConfiguration configuration of the target DBMS */ public Factory(OutputStreamWriter scriptFileWriter, int maxBodySize, Session session, DBMS targetDBMSConfiguration, ExecutionContext executionContext) { this.executionContext = executionContext; this.maxBodySize = maxBodySize; this.scriptFileWriter = scriptFileWriter; this.session = session; this.targetDBMSConfiguration = targetDBMSConfiguration; } /** * Creates transformer (as {@link ResultSetReader} which * transforms rows of a given table into an external representation. * * @param table the table * @return a transformer */ @Override public ResultSetReader create(Table table) throws SQLException { return new DeletionTransformer(table, scriptFileWriter, maxBodySize, session, targetDBMSConfiguration, executionContext); } }; /** * Constructor. * * @param table the table to read from * @param scriptFileWriter the file to write to * @param maxBodySize maximum length of SQL values list (for generated deletes) * @param session the session * @param targetDBMSConfiguration configuration of the target DBMS * @param executionContext */ private DeletionTransformer(Table table, OutputStreamWriter scriptFileWriter, int maxBodySize, Session session, DBMS targetDBMSConfiguration, ExecutionContext executionContext) throws SQLException { this.executionContext = executionContext; this.table = table; this.scriptFileWriter = scriptFileWriter; deleteStatementBuilder = new StatementBuilder(maxBodySize); this.quoting = new Quoting(session); if (targetDBMSConfiguration != null && targetDBMSConfiguration != session.dbms) { if (targetDBMSConfiguration.getIdentifierQuoteString() != null) { this.quoting.setIdentifierQuoteString(targetDBMSConfiguration.getIdentifierQuoteString()); } } this.session = session; this.targetDBMSConfiguration = targetDBMSConfiguration; if (table.primaryKey.getColumns().isEmpty()) { throw new RuntimeException("Unable to delete from table \"" + table.getName() + "\".\n" + "No primary key."); } } /** * Reads result-set and writes into export-script. * * @param resultSet the result set */ public void readCurrentRow(ResultSet resultSet) throws SQLException { try { final SQLDialect currentDialect = targetDBMSConfiguration.getSqlDialect(); CellContentConverter cellContentConverter = getCellContentConverter(resultSet, session, targetDBMSConfiguration); if (DBMS.SYBASE.equals(targetDBMSConfiguration) || (currentDialect != null && !currentDialect.isSupportsInClauseForDeletes())) { String deleteHead = "Delete from " + qualifiedTableName(table) + " Where ("; boolean firstTime = true; String item = ""; for (Column pkColumn: table.primaryKey.getColumns()) { item += (firstTime? "" : " and ") + quoting.requote(pkColumn.name) + "=" + cellContentConverter.toSql(cellContentConverter.getObject(resultSet, quoting.unquote(pkColumn.name))); firstTime = false; } if (!deleteStatementBuilder.isAppendable(deleteHead, item)) { writeToScriptFile(deleteStatementBuilder.build()); } deleteStatementBuilder.append(deleteHead, item, ") or (", ");\n"); } else { String deleteHead; String item; if (table.primaryKey.getColumns().size() == 1) { deleteHead = "Delete from " + qualifiedTableName(table) + " Where " + quoting.requote(table.primaryKey.getColumns().get(0).name) + " in ("; item = cellContentConverter.toSql(cellContentConverter.getObject(resultSet, quoting.unquote(table.primaryKey.getColumns().get(0).name))); } else { deleteHead = "Delete from " + qualifiedTableName(table) + " Where ("; item = "("; boolean firstTime = true; for (Column pkColumn: table.primaryKey.getColumns()) { deleteHead += (firstTime? "" : ", ") + quoting.requote(pkColumn.name); item += (firstTime? "" : ", ") + cellContentConverter.toSql(cellContentConverter.getObject(resultSet, quoting.unquote(pkColumn.name))); firstTime = false; } item += ")"; deleteHead += ") in ("; if (currentDialect.isNeedsValuesKeywordForDeletes()) { deleteHead += "values "; } } if (!deleteStatementBuilder.isAppendable(deleteHead, item)) { writeToScriptFile(deleteStatementBuilder.build()); } deleteStatementBuilder.append(deleteHead, item, ", ", ");\n"); } } catch (IOException e) { throw new RuntimeException(e); } } /** * Gets qualified table name. * * @param t the table * @return qualified name of t */ private String qualifiedTableName(Table t) { String schema = t.getOriginalSchema(""); String mappedSchema = executionContext.getSourceSchemaMapping().get(schema); if (mappedSchema != null) { schema = mappedSchema; } if (schema.length() == 0) { return quoting.requote(t.getUnqualifiedName()); } return quoting.requote(schema) + "." + quoting.requote(t.getUnqualifiedName()); } /** * Writes into script. */ private void writeToScriptFile(String content) throws IOException { synchronized (scriptFileWriter) { if (DBMS.ORACLE.equals(targetDBMSConfiguration)) { scriptFileWriter.write(SqlUtil.splitDMLStatement(content, 2400)); } else { scriptFileWriter.write(content); } } } /** * Finalizes reading. */ public void close() { try { writeToScriptFile(deleteStatementBuilder.build()); } catch (IOException e) { throw new RuntimeException(e); } } }