/*
* 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.ddl;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import net.sf.jailer.ExecutionContext;
import net.sf.jailer.JailerVersion;
import net.sf.jailer.configuration.Configuration;
import net.sf.jailer.configuration.DBMS;
import net.sf.jailer.database.SQLDialect;
import net.sf.jailer.database.Session;
import net.sf.jailer.database.TemporaryTableManager;
import net.sf.jailer.database.WorkingTableScope;
import net.sf.jailer.datamodel.Column;
import net.sf.jailer.datamodel.DataModel;
import net.sf.jailer.datamodel.RowIdSupport;
import net.sf.jailer.util.PrintUtil;
import net.sf.jailer.util.Quoting;
import net.sf.jailer.util.SqlScriptExecutor;
import net.sf.jailer.util.SqlUtil;
/**
* Creates the DDL for the working-tables.
*
* @author Ralf Wisser
*/
public class DDLCreator {
/**
* The execution context.
*/
private final ExecutionContext executionContext;
/**
* Constructor.
*
* @param executionContext the command line arguments
*/
public DDLCreator(ExecutionContext executionContext) {
this.executionContext = executionContext;
}
/**
* Creates the DDL for the working-tables.
*/
public boolean createDDL(DataSource dataSource, DBMS dbms, WorkingTableScope temporaryTableScope, String workingTableSchema) throws SQLException, FileNotFoundException, IOException {
Session session = null;
if (dataSource != null) {
session = new Session(dataSource, dbms);
}
try {
return createDDL(new DataModel(executionContext), session, temporaryTableScope, workingTableSchema);
} finally {
if (session != null) {
try { session.shutDown(); } catch (Exception e) { /* ignore */ }
}
}
}
/**
* Creates the DDL for the working-tables.
*/
public void createDDL(Session localSession, WorkingTableScope temporaryTableScope, String workingTableSchema) throws FileNotFoundException, IOException, SQLException {
createDDL(new DataModel(executionContext), localSession, temporaryTableScope, workingTableSchema);
}
/**
* Creates the DDL for the working-tables.
*/
public boolean createDDL(DataModel datamodel, Session session, WorkingTableScope temporaryTableScope, String workingTableSchema) throws FileNotFoundException, IOException, SQLException {
RowIdSupport rowIdSupport = new RowIdSupport(datamodel, targetDBMS(session), executionContext);
return createDDL(datamodel, session, temporaryTableScope, rowIdSupport, workingTableSchema);
}
/**
* Creates the DDL for the working-tables.
*/
public boolean createDDL(DataModel datamodel, Session session, WorkingTableScope temporaryTableScope, RowIdSupport rowIdSupport, String workingTableSchema) throws FileNotFoundException, IOException, SQLException {
try {
return createDDL(datamodel, session, temporaryTableScope, 0, rowIdSupport, workingTableSchema);
} catch (SQLException e) {
}
// reconnect and retry with another index type
session.reconnect();
try {
return createDDL(datamodel, session, temporaryTableScope, 1, rowIdSupport, workingTableSchema);
} catch (SQLException e) {
}
// reconnect and retry with another index type
session.reconnect();
return createDDL(datamodel, session, temporaryTableScope, 2, rowIdSupport, workingTableSchema);
}
/**
* Creates the DDL for the working-tables.
*/
private boolean createDDL(DataModel dataModel, Session session, WorkingTableScope temporaryTableScope, int indexType, RowIdSupport rowIdSupport, String workingTableSchema) throws FileNotFoundException, IOException, SQLException {
String template = "script" + File.separator + "ddl-template.sql";
String contraint = pkColumnConstraint(session);
Map<String, String> typeReplacement = targetDBMS(session).getTypeReplacement();
String universalPrimaryKey = rowIdSupport.getUniversalPrimaryKey().toSQL(null, contraint, typeReplacement);
Map<String, String> arguments = new HashMap<String, String>();
arguments.put("upk", universalPrimaryKey);
arguments.put("upk-hash", "" + ((universalPrimaryKey + targetDBMS(session).getTableProperties()).hashCode()));
arguments.put("pre", rowIdSupport.getUniversalPrimaryKey().toSQL("PRE_", contraint, typeReplacement));
arguments.put("from", rowIdSupport.getUniversalPrimaryKey().toSQL("FROM_", contraint, typeReplacement));
arguments.put("to", rowIdSupport.getUniversalPrimaryKey().toSQL("TO_", contraint, typeReplacement));
arguments.put("version", JailerVersion.VERSION);
arguments.put("constraint", contraint);
TemporaryTableManager tableManager = null;
if (temporaryTableScope == WorkingTableScope.SESSION_LOCAL) {
tableManager = targetDBMS(session).getSessionTemporaryTableManager();
}
if (temporaryTableScope == WorkingTableScope.TRANSACTION_LOCAL) {
tableManager = targetDBMS(session).getTransactionTemporaryTableManager();
}
String tableName = SQLDialect.CONFIG_TABLE_;
arguments.put("config-dml-reference", tableName);
String schema = workingTableSchema != null? (session == null? workingTableSchema : new Quoting(session).requote(workingTableSchema)) + "." : "";
arguments.put("schema", schema);
arguments.put("index-schema", supportsSchemasInIndexDefinitions(session)? schema : "");
if (tableManager != null) {
arguments.put("table-suffix", "_T");
arguments.put("drop-table", tableManager.getDropTablePrefix());
arguments.put("create-table", tableManager.getCreateTablePrefix());
arguments.put("create-table-suffix", tableManager.getCreateTableSuffix());
arguments.put("create-index", tableManager.getCreateIndexPrefix());
arguments.put("create-index-suffix", tableManager.getCreateIndexSuffix());
arguments.put("index-table-prefix", tableManager.getIndexTablePrefix());
} else {
arguments.put("table-suffix", "");
arguments.put("drop-table", "DROP TABLE ");
arguments.put("create-table", "CREATE TABLE ");
arguments.put("create-table-suffix", targetDBMS(session).getTableProperties());
arguments.put("create-index", "CREATE INDEX ");
arguments.put("create-index-suffix", "");
arguments.put("index-table-prefix", "");
}
Map<String, List<String>> listArguments = new HashMap<String, List<String>>();
if (indexType == 0) {
// full index
listArguments.put("column-list", Collections.singletonList(", " + rowIdSupport.getUniversalPrimaryKey().columnList(null)));
listArguments.put("column-list-from", Collections.singletonList(", " + rowIdSupport.getUniversalPrimaryKey().columnList("FROM_")));
listArguments.put("column-list-to", Collections.singletonList(", " + rowIdSupport.getUniversalPrimaryKey().columnList("TO_")));
} else if (indexType == 1) {
// single column indexes
List<String> cl = new ArrayList<String>();
List<String> clFrom = new ArrayList<String>();
List<String> clTo = new ArrayList<String>();
for (Column c : rowIdSupport.getUniversalPrimaryKey().getColumns()) {
cl.add(", " + c.name);
clFrom.add(", FROM_" + c.name);
clTo.add(", FROM_" + c.name);
}
listArguments.put("column-list", cl);
listArguments.put("column-list-from", clFrom);
listArguments.put("column-list-to", clTo);
} else {
// minimal index
listArguments.put("column-list", Collections.singletonList(""));
listArguments.put("column-list-from", Collections.singletonList(""));
listArguments.put("column-list-to", Collections.singletonList(""));
}
String ddl = new PrintUtil().applyTemplate(template, arguments, listArguments);
if (session != null) {
File tmp = Configuration.getInstance().createTempFile();
PrintWriter pw = new PrintWriter(tmp);
pw.println(ddl);
pw.close();
new SqlScriptExecutor(session, 1).executeScript(tmp.getCanonicalPath());
tmp.delete();
} else {
System.out.println(ddl);
}
return true;
}
private boolean supportsSchemasInIndexDefinitions(Session session) {
Boolean result = targetDBMS(session).getSupportsSchemasInIndexDefinitions();
if (result == null) {
if (session == null) {
return true;
}
try {
result = session.getMetaData().supportsSchemasInDataManipulation();
} catch (SQLException e) {
return false;
}
}
return result;
}
private String pkColumnConstraint(Session session) {
String nullableContraint = targetDBMS(session).getNullableContraint();
if (nullableContraint != null) {
return " " + nullableContraint;
}
return "";
}
private DBMS targetDBMS(Session session) {
if (session == null) {
if (executionContext.getTargetDBMS() != null) {
return executionContext.getTargetDBMS();
}
return DBMS.forDBMS(null); // default
}
return session.dbms;
}
/**
* Checks whether working-tables schema is up-to-date.
* @param useRowId
* @param workingTableSchema
*
* @return <code>true</code> if working-tables schema is up-to-date
*/
public boolean isUptodate(DataSource dataSource, DBMS dbms, boolean useRowId, String workingTableSchema) {
try {
if (dataSource != null) {
final Session session = new Session(dataSource, dbms);
try {
return isUptodate(session, useRowId, workingTableSchema);
} finally {
session.shutDown();
}
}
} catch (Exception e) {
}
return false;
}
/**
* Checks whether working-tables schema is up-to-date.
* @param useRowId
* @param workingTableSchema
*
* @return <code>true</code> if working-tables schema is up-to-date
*/
public boolean isUptodate(final Session session, boolean useRowId, String workingTableSchema) {
try {
try {
final boolean[] uptodate = new boolean[] { false };
final DataModel datamodel = new DataModel(executionContext);
final Map<String, String> typeReplacement = targetDBMS(session).getTypeReplacement();
final RowIdSupport rowIdSupport = new RowIdSupport(datamodel, targetDBMS(session), useRowId);
final String schema = workingTableSchema == null ? "" : new Quoting(session).requote(workingTableSchema) + ".";
session.executeQuery("Select jvalue from " + schema + SQLDialect.CONFIG_TABLE_ + " where jversion='" + JailerVersion.VERSION + "' and jkey='upk'",
new Session.ResultSetReader() {
public void readCurrentRow(ResultSet resultSet) throws SQLException {
String contraint = pkColumnConstraint(session);
String universalPrimaryKey = rowIdSupport.getUniversalPrimaryKey().toSQL(null, contraint, typeReplacement);
String h = "" + (universalPrimaryKey + targetDBMS(session).getTableProperties()).hashCode();
uptodate[0] = resultSet.getString(1).equals(h);
}
public void close() {
}
});
// look for jailer tables
for (String table : SqlUtil.JAILER_MH_TABLES) {
session.executeQuery("Select * from " + schema + table + " Where 1=0", new Session.ResultSetReader() {
public void readCurrentRow(ResultSet resultSet) throws SQLException {
}
public void close() {
}
});
}
return uptodate[0];
} catch (Exception e) {
return false;
}
} catch (Exception e) {
return false;
}
}
/**
* Checks whether working-tables schema is present.
*
* @return <code>true</code> if working-tables schema is present
*/
public boolean isPresent(Session session) {
try {
try {
final boolean[] uptodate = new boolean[] { false };
session.executeQuery("Select jvalue from " + SQLDialect.CONFIG_TABLE_ + " where jkey='upk'",
new Session.ResultSetReader() {
public void readCurrentRow(ResultSet resultSet) throws SQLException {
uptodate[0] = true;
}
public void close() {
}
});
return uptodate[0];
} catch (Exception e) {
return false;
}
} catch (Exception e) {
return false;
}
}
/**
* Checks for conflicts of existing tables and working-tables.
*
* @return name of table in conflict or <code>null</code>
*/
public String getTableInConflict(DataSource dataSource, DBMS dbms) {
try {
if (dataSource != null) {
Session session = new Session(dataSource, dbms);
session.setSilent(true);
try {
final boolean[] uptodate = new boolean[] { false };
session.executeQuery("Select jvalue from " + SQLDialect.CONFIG_TABLE_
+ " where jkey='magic' and jvalue='837065098274756382534403654245288'", new Session.ResultSetReader() {
public void readCurrentRow(ResultSet resultSet) throws SQLException {
uptodate[0] = true;
}
public void close() {
}
});
if (uptodate[0]) {
session.shutDown();
return null;
}
} catch (Exception e) {
// fall through
}
// look for jailer tables
for (String table : SqlUtil.JAILER_TABLES) {
try {
session.executeQuery("Select * from " + table + " Where 1=0", new Session.ResultSetReader() {
public void readCurrentRow(ResultSet resultSet) throws SQLException {
}
public void close() {
}
});
session.shutDown();
return table;
} catch (Exception e) {
// fall through
}
}
session.shutDown();
}
return null;
} catch (Exception e) {
return null;
}
}
}