liquibase.database.core.PostgresDatabase Maven / Gradle / Ivy
package liquibase.database.core;
import liquibase.CatalogAndSchema;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.DatabaseConnection;
import liquibase.database.ObjectQuotingStrategy;
import liquibase.database.OfflineConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.ValidationErrors;
import liquibase.logging.Logger;
import liquibase.structure.DatabaseObject;
import liquibase.exception.DatabaseException;
import liquibase.executor.ExecutorService;
import liquibase.logging.LogFactory;
import liquibase.statement.SqlStatement;
import liquibase.statement.core.RawCallStatement;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.core.Table;
import liquibase.util.JdbcUtils;
import liquibase.util.StringUtils;
import java.math.BigInteger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.*;
/**
* Encapsulates PostgreSQL database support.
*/
public class PostgresDatabase extends AbstractJdbcDatabase {
public static final String PRODUCT_NAME = "PostgreSQL";
private Set systemTablesAndViews = new HashSet();
private Set reservedWords = new HashSet();
public PostgresDatabase() {
super.setCurrentDateTimeFunction("NOW()");
//got list from http://www.postgresql.org/docs/9.1/static/sql-keywords-appendix.html?
reservedWords.addAll(Arrays.asList("ALL","ANALYSE", "AND", "ANY","ARRAY","AS", "ASC","ASYMMETRIC", "AUTHORIZATION", "BINARY", "BOTH","CASE","CAST","CHECK", "COLLATE","COLLATION", "COLUMN","CONCURRENTLY", "CONSTRAINT", "CREATE", "CURRENT_CATALOG", "CURRENT_DATE", "CURRENT_ROLE", "CURRENT_SCHEMA", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "DEFAULT", "DEFERRABLE", "DESC", "DISTINCT", "DO",
"ELSE", "END", "EXCEPT", "FALSE", "FETCH", "FOR", "FOREIGN", "FROM", "FULL", "GRANT", "GROUP", "HAVING", "ILIKE", "IN", "INITIALLY", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "LEADING", "LEFT", "LIKE", "LIMIT", "LITERAL", "LOCALTIME", "LOCALTIMESTAMP", "NOT", "NULL", "OFFSET", "ON", "ONLY", "OR", "ORDER", "OUTER", "OVER", "OVERLAPS",
"PLACING", "PRIMARY", "REFERENCES", "RETURNING", "RIGHT", "SELECT", "SESSION_USER", "SIMILAR", "SOME", "SYMMETRIC", "TABLE", "THEN", "TO", "TRAILING", "TRUE", "UNION", "UNIQUE", "USER", "USING", "VARIADIC", "VERBOSE", "WHEN", "WHERE", "WINDOW", "WITH"));
super.sequenceNextValueFunction = "nextval('%s')";
super.sequenceCurrentValueFunction = "currval('%s')";
super.unmodifiableDataTypes.addAll(Arrays.asList("bool", "int4", "int8", "float4", "float8", "bigserial", "serial", "bytea", "timestamptz", "text"));
super.unquotedObjectsAreUppercased=false;
}
@Override
public String getShortName() {
return "postgresql";
}
@Override
protected String getDefaultDatabaseProductName() {
return "PostgreSQL";
}
@Override
public Integer getDefaultPort() {
return 5432;
}
@Override
public Set getSystemViews() {
return systemTablesAndViews;
}
@Override
public int getPriority() {
return PRIORITY_DEFAULT;
}
@Override
public boolean supportsInitiallyDeferrableColumns() {
return true;
}
@Override
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
}
@Override
public String getDefaultDriver(String url) {
if (url.startsWith("jdbc:postgresql:")) {
return "org.postgresql.Driver";
}
return null;
}
@Override
public boolean supportsCatalogInObjectName(Class extends DatabaseObject> type) {
return false;
}
@Override
public boolean supportsSequences() {
return true;
}
@Override
public String getDatabaseChangeLogTableName() {
return super.getDatabaseChangeLogTableName().toLowerCase();
}
@Override
public String getDatabaseChangeLogLockTableName() {
return super.getDatabaseChangeLogLockTableName().toLowerCase();
}
@Override
public void setConnection(DatabaseConnection conn) {
super.setConnection(conn);
Logger log = LogFactory.getInstance().getLog();
if (conn instanceof JdbcConnection) {
Statement statement = null;
ResultSet resultSet = null;
try {
statement = ((JdbcConnection) conn).createStatement();
resultSet = statement.executeQuery("select setting from pg_settings where name = 'edb_redwood_date'");
if (resultSet.next()) {
String setting = resultSet.getString(1);
if (setting != null && setting.equals("on")) {
log.warning("EnterpriseDB "+conn.getURL()+" does not store DATE columns. Auto-converts them to TIMESTAMPs. (edb_redwood_date=true)");
}
}
} catch (Exception e) {
log.info("Cannot check pg_settings", e);
} finally {
JdbcUtils.close(resultSet, statement);
}
}
}
// public void dropDatabaseObjects(String schema) throws DatabaseException {
// try {
// if (schema == null) {
// schema = getConnectionUsername();
// }
// new Executor(this).execute(new RawSqlStatement("DROP OWNED BY " + schema));
//
// getConnection().commit();
//
// changeLogTableExists = false;
// changeLogLockTableExists = false;
// changeLogCreateAttempted = false;
// changeLogLockCreateAttempted = false;
//
// } catch (SQLException e) {
// throw new DatabaseException(e);
// }
// }
@Override
public boolean isSystemObject(DatabaseObject example) {
if (example instanceof Table) {
if (example.getSchema() != null) {
if ("pg_catalog".equals(example.getSchema().getName())
|| "pg_toast".equals(example.getSchema().getName())) {
return true;
}
}
}
return super.isSystemObject(example);
}
@Override
public boolean supportsTablespaces() {
return true;
}
@Override
public String getAutoIncrementClause() {
return "";
}
@Override
public boolean generateAutoIncrementStartWith(BigInteger startWith) {
return false;
}
@Override
public boolean generateAutoIncrementBy(BigInteger incrementBy) {
return false;
}
@Override
public String escapeObjectName(String objectName, Class extends DatabaseObject> objectType) {
if (quotingStrategy == ObjectQuotingStrategy.LEGACY && hasMixedCase(objectName)) {
return "\"" + objectName + "\"";
} else {
return super.escapeObjectName(objectName, objectType);
}
}
@Override
public String correctObjectName(String objectName, Class extends DatabaseObject> objectType) {
if (objectName == null || quotingStrategy != ObjectQuotingStrategy.LEGACY) {
return super.correctObjectName(objectName, objectType);
}
if (objectName.contains("-") || hasMixedCase(objectName) || startsWithNumeric(objectName) || isReservedWord(objectName)) {
return objectName;
} else {
return objectName.toLowerCase();
}
}
/*
* Check if given string has case problems according to postgresql documentation.
* If there are at least one characters with upper case while all other are in lower case (or vice versa) this string should be escaped.
*
* Note: This may make postgres support more case sensitive than normally is, but needs to be left in for backwards compatibility.
* Method is public so a subclass extension can override it to always return false.
*/
protected boolean hasMixedCase(String tableName) {
if (tableName == null) {
return false;
}
return StringUtils.hasUpperCase(tableName) && StringUtils.hasLowerCase(tableName);
}
@Override
public boolean isReservedWord(String tableName) {
return reservedWords.contains(tableName.toUpperCase());
}
/*
* Get the current search paths
*/
private List getSearchPaths() {
List searchPaths = null;
try {
DatabaseConnection con = getConnection();
if (con != null) {
String searchPathResult = (String) ExecutorService.getInstance().getExecutor(this).queryForObject(new RawSqlStatement("SHOW search_path"), String.class);
if (searchPathResult != null) {
String dirtySearchPaths[] = searchPathResult.split("\\,");
searchPaths = new ArrayList();
for (String searchPath : dirtySearchPaths) {
searchPath = searchPath.trim();
// Ensure there is consistency ..
if (searchPath.equals("\"$user\"")) {
searchPath = "$user";
}
searchPaths.add(searchPath);
}
}
}
} catch (Exception e) {
// TODO: Something?
e.printStackTrace();
LogFactory.getLogger().severe("Failed to get default catalog name from postgres", e);
}
return searchPaths;
}
@Override
protected SqlStatement getConnectionSchemaNameCallStatement() {
return new RawCallStatement("select current_schema()");
}
private boolean catalogExists(String catalogName) throws DatabaseException {
return catalogName != null && runExistsQuery(
"select count(*) from information_schema.schemata where catalog_name='" + catalogName + "'");
}
private boolean schemaExists(String schemaName) throws DatabaseException {
return schemaName != null && runExistsQuery("select count(*) from information_schema.schemata where schema_name='" + schemaName + "'");
}
private boolean runExistsQuery(String query) throws DatabaseException {
Long count = ExecutorService.getInstance().getExecutor(this).queryForLong(new RawSqlStatement(query));
return count != null && count > 0;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy