liquibase.statement.ExecutablePreparedStatementBase Maven / Gradle / Ivy
package liquibase.statement;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import liquibase.change.ColumnConfig;
import liquibase.changelog.ChangeSet;
import liquibase.database.Database;
import liquibase.database.PreparedStatementFactory;
import liquibase.exception.DatabaseException;
import liquibase.logging.LogFactory;
import liquibase.logging.Logger;
import liquibase.resource.ResourceAccessor;
import liquibase.resource.UtfBomAwareReader;
import liquibase.util.JdbcUtils;
import liquibase.util.StreamUtil;
import liquibase.util.StringUtils;
import liquibase.util.file.FilenameUtils;
public abstract class ExecutablePreparedStatementBase implements ExecutablePreparedStatement {
private Logger log = LogFactory.getLogger();
protected Database database;
private String catalogName;
private String schemaName;
private String tableName;
private List columns;
private ChangeSet changeSet;
private Set closeables;
private ResourceAccessor resourceAccessor;
protected ExecutablePreparedStatementBase(Database database, String catalogName, String schemaName, String tableName, List columns, ChangeSet changeSet, ResourceAccessor resourceAccessor) {
this.database = database;
this.changeSet = changeSet;
this.catalogName = catalogName;
this.schemaName = schemaName;
this.tableName = tableName;
this.columns = columns;
this.changeSet = changeSet;
this.closeables = new HashSet();
this.resourceAccessor = resourceAccessor;
}
@Override
public void execute(PreparedStatementFactory factory) throws DatabaseException {
// build the sql statement
List cols = new ArrayList(getColumns().size());
String sql = generateSql(cols);
log.info("Prepared statement: "+sql);
log.debug("Number of columns = "+cols.size());
// create prepared statement
PreparedStatement stmt = factory.create(sql);
try {
// attach params
int i = 1; // index starts from 1
for(ColumnConfig col : cols) {
log.debug("Applying column parameter = "+i+" for column "+col.getName());
applyColumnParameter(stmt, i, col);
i++;
}
// trigger execution
stmt.execute();
} catch(SQLException e) {
throw new DatabaseException(e);
} finally {
for (Closeable closeable : closeables) {
StreamUtil.closeQuietly(closeable);
}
JdbcUtils.closeStatement(stmt);
}
}
protected abstract String generateSql(List cols);
private void applyColumnParameter(PreparedStatement stmt, int i, ColumnConfig col) throws SQLException, DatabaseException {
if(col.getValue() != null) {
log.debug("value is string = "+col.getValue());
stmt.setString(i, col.getValue());
} else if(col.getValueBoolean() != null) {
log.debug("value is boolean = "+col.getValueBoolean());
stmt.setBoolean(i, col.getValueBoolean());
} else if(col.getValueNumeric() != null) {
log.debug("value is numeric = "+col.getValueNumeric());
Number number = col.getValueNumeric();
if(number instanceof ColumnConfig.ValueNumeric) {
ColumnConfig.ValueNumeric valueNumeric = (ColumnConfig.ValueNumeric) number;
number = valueNumeric.getDelegate();
}
if(number instanceof Long) {
stmt.setLong(i, number.longValue());
} else if(number instanceof Integer) {
stmt.setInt(i, number.intValue());
} else if(number instanceof Double) {
stmt.setDouble(i, number.doubleValue());
} else if(number instanceof Float) {
stmt.setFloat(i, number.floatValue());
} else if(number instanceof BigDecimal) {
stmt.setBigDecimal(i, (BigDecimal)number);
} else if(number instanceof BigInteger) {
stmt.setInt(i, number.intValue());
} else {
// TODO: Consider throwing an exception here
}
} else if(col.getValueDate() != null) {
log.debug("value is date = "+col.getValueDate());
if (col.getValueDate() instanceof Timestamp) {
stmt.setTimestamp(i, (Timestamp) col.getValueDate());
} else {
stmt.setDate(i, new java.sql.Date(col.getValueDate().getTime()));
}
} else if (col.getValueBlobFile() != null) {
log.debug("value is blob = "+col.getValueBlobFile());
try {
LOBContent lob = toBinaryStream(col.getValueBlobFile());
if (lob.length <= Integer.MAX_VALUE) {
stmt.setBinaryStream(i, lob.content, (int) lob.length);
} else {
stmt.setBinaryStream(i, lob.content, lob.length);
}
} catch (IOException e) {
throw new DatabaseException(e.getMessage(), e); // wrap
}
} else if(col.getValueClobFile() != null) {
try {
log.debug("value is clob = "+col.getValueClobFile());
LOBContent lob = toCharacterStream(col.getValueClobFile(), col.getEncoding());
if (lob.length <= Integer.MAX_VALUE) {
stmt.setCharacterStream(i, lob.content, (int) lob.length);
} else {
stmt.setCharacterStream(i, lob.content, lob.length);
}
} catch (IOException e) {
throw new DatabaseException(e.getMessage(), e); // wrap
}
} else {
// NULL values might intentionally be set into a change, we must also add them to the prepared statement
log.debug("value is explicit null");
stmt.setNull(i, java.sql.Types.NULL);
}
}
private class LOBContent {
private final T content;
private final long length;
LOBContent(T content, long length) {
this.content = content;
this.length = length;
}
}
@SuppressWarnings("resource")
private LOBContent toBinaryStream(String valueLobFile) throws DatabaseException, IOException
{
InputStream in = getResourceAsStream(valueLobFile);
if (in == null) {
throw new DatabaseException("BLOB resource not found: " + valueLobFile);
}
try {
if (in instanceof FileInputStream) {
InputStream bufferedInput = createStream(in);
return new LOBContent(bufferedInput, ((FileInputStream) in).getChannel().size());
}
in = createStream(in);
final int IN_MEMORY_THRESHOLD = 100000;
if (in.markSupported()) {
in.mark(IN_MEMORY_THRESHOLD);
}
long length = StreamUtil.getContentLength(in);
if (in.markSupported() && length <= IN_MEMORY_THRESHOLD) {
in.reset();
} else {
StreamUtil.closeQuietly(in);
in = getResourceAsStream(valueLobFile);
in = createStream(in);
}
return new LOBContent(in, length);
} finally {
if (in != null) {
closeables.add(in);
}
}
}
private InputStream createStream(InputStream in) {
return (in instanceof BufferedInputStream) ? in : new BufferedInputStream(in);
}
private LOBContent toCharacterStream(String valueLobFile, String encoding) throws IOException, DatabaseException
{
InputStream in = getResourceAsStream(valueLobFile);
if (in == null) {
throw new DatabaseException("CLOB resource not found: " + valueLobFile);
}
final int IN_MEMORY_THRESHOLD = 100000;
Reader reader = null;
try {
reader = createReader(in, encoding);
if (reader.markSupported()) {
reader.mark(IN_MEMORY_THRESHOLD);
}
long length = StreamUtil.getContentLength(reader);
if (reader.markSupported() && length <= IN_MEMORY_THRESHOLD) {
reader.reset();
} else {
StreamUtil.closeQuietly(reader);
in = getResourceAsStream(valueLobFile);
reader = createReader(in, encoding);
}
return new LOBContent(reader, length);
}
finally {
if (reader != null) {
closeables.add(reader);
}
if (in != null) {
closeables.add(in);
}
}
}
@SuppressWarnings("resource")
private Reader createReader(InputStream in, String encoding) throws UnsupportedEncodingException {
return new BufferedReader(
StringUtils.trimToNull(encoding) == null
? new UtfBomAwareReader(in)
: new UtfBomAwareReader(in, encoding));
}
private InputStream getResourceAsStream(String valueLobFile) throws IOException {
String fileName = getFileName(valueLobFile);
Set streams = this.resourceAccessor.getResourcesAsStream(fileName);
if (streams == null || streams.size() == 0) {
return null;
}
if (streams.size() > 1) {
for (InputStream stream : streams) {
stream.close();
}
throw new IOException(streams.size()+ " matched "+valueLobFile);
}
return streams.iterator().next();
}
private String getFileName(String fileName) {
// Most of this method were copy-pasted from XMLChangeLogSAXHandler#handleIncludedChangeLog()
String relativeBaseFileName = changeSet.getChangeLog().getPhysicalFilePath();
// workaround for FilenameUtils.normalize() returning null for relative paths like ../conf/liquibase.xml
String tempFile = FilenameUtils.concat(FilenameUtils.getFullPath(relativeBaseFileName), fileName);
if (tempFile != null) {
fileName = tempFile;
} else {
fileName = FilenameUtils.getFullPath(relativeBaseFileName) + fileName;
}
return fileName;
}
/**
* Gets absolute and normalized path for path.
* If path is relative, absolute path is calculated relative to change log file.
*
* @param path Absolute or relative path.
* @return Absolute and normalized path.
*/
public String getAbsolutePath(String path) {
String p = path;
File f = new File(p);
if (!f.isAbsolute()) {
String basePath = FilenameUtils.getFullPath(changeSet.getChangeLog().getPhysicalFilePath());
p = FilenameUtils.normalize(basePath + p);
}
return p;
}
@Override
public boolean skipOnUnsupported() {
return false;
}
public String getCatalogName() {
return catalogName;
}
public String getSchemaName() {
return schemaName;
}
public String getTableName() {
return tableName;
}
public List getColumns() {
return columns;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy