Coverage Report - liquibase.database.core.PostgresDatabase
 
Classes in this File Line Coverage Branch Coverage Complexity
PostgresDatabase
25%
21/81
20%
17/82
2.704
 
 1  
 package liquibase.database.core;
 2  
 
 3  
 import liquibase.database.AbstractDatabase;
 4  
 import liquibase.database.DatabaseConnection;
 5  
 import liquibase.exception.DatabaseException;
 6  
 import liquibase.executor.ExecutorService;
 7  
 import liquibase.logging.LogFactory;
 8  
 import liquibase.statement.core.RawSqlStatement;
 9  
 import liquibase.util.StringUtils;
 10  
 
 11  
 import java.util.ArrayList;
 12  
 import java.util.HashSet;
 13  
 import java.util.List;
 14  
 import java.util.Set;
 15  
 
 16  
 /**
 17  
  * Encapsulates PostgreSQL database support.
 18  
  */
 19  
 public class PostgresDatabase extends AbstractDatabase {
 20  
     public static final String PRODUCT_NAME = "PostgreSQL";
 21  
 
 22  9
     private Set<String> systemTablesAndViews = new HashSet<String>();
 23  
 
 24  
     private String defaultDatabaseSchemaName;
 25  
 
 26  9
     public PostgresDatabase() {
 27  
         // systemTablesAndViews.add("pg_logdir_ls");
 28  
         // systemTablesAndViews.add("administrable_role_authorizations");
 29  
         // systemTablesAndViews.add("applicable_roles");
 30  
         // systemTablesAndViews.add("attributes");
 31  
         // systemTablesAndViews.add("check_constraint_routine_usage");
 32  
         // systemTablesAndViews.add("check_constraints");
 33  
         // systemTablesAndViews.add("column_domain_usage");
 34  
         // systemTablesAndViews.add("column_privileges");
 35  
         // systemTablesAndViews.add("column_udt_usage");
 36  
         // systemTablesAndViews.add("columns");
 37  
         // systemTablesAndViews.add("constraint_column_usage");
 38  
         // systemTablesAndViews.add("constraint_table_usage");
 39  
         // systemTablesAndViews.add("data_type_privileges");
 40  
         // systemTablesAndViews.add("domain_constraints");
 41  
         // systemTablesAndViews.add("domain_udt_usage");
 42  
         // systemTablesAndViews.add("domains");
 43  
         // systemTablesAndViews.add("element_types");
 44  
         // systemTablesAndViews.add("enabled_roles");
 45  
         // systemTablesAndViews.add("key_column_usage");
 46  
         // systemTablesAndViews.add("parameters");
 47  
         // systemTablesAndViews.add("referential_constraints");
 48  
         // systemTablesAndViews.add("role_column_grants");
 49  
         // systemTablesAndViews.add("role_routine_grants");
 50  
         // systemTablesAndViews.add("role_table_grants");
 51  
         // systemTablesAndViews.add("role_usage_grants");
 52  
         // systemTablesAndViews.add("routine_privileges");
 53  
         // systemTablesAndViews.add("routines");
 54  
         // systemTablesAndViews.add("schemata");
 55  
         // systemTablesAndViews.add("sequences");
 56  
         // systemTablesAndViews.add("sql_features");
 57  
         // systemTablesAndViews.add("sql_implementation_info");
 58  
         // systemTablesAndViews.add("sql_languages");
 59  
         // systemTablesAndViews.add("sql_packages");
 60  
         // systemTablesAndViews.add("sql_parts");
 61  
         // systemTablesAndViews.add("sql_sizing");
 62  
         // systemTablesAndViews.add("sql_sizing_profiles");
 63  
         // systemTablesAndViews.add("table_constraints");
 64  
         // systemTablesAndViews.add("table_privileges");
 65  
         // systemTablesAndViews.add("tables");
 66  
         // systemTablesAndViews.add("triggers");
 67  
         // systemTablesAndViews.add("usage_privileges");
 68  
         // systemTablesAndViews.add("view_column_usage");
 69  
         // systemTablesAndViews.add("view_routine_usage");
 70  
         // systemTablesAndViews.add("view_table_usage");
 71  
         // systemTablesAndViews.add("views");
 72  
         // systemTablesAndViews.add("information_schema_catalog_name");
 73  
         // systemTablesAndViews.add("triggered_update_columns");
 74  
         // systemTablesAndViews.add("book_pkey");
 75  9
     }
 76  
 
 77  
     public String getTypeName() {
 78  55
         return "postgresql";
 79  
     }
 80  
 
 81  
     @Override
 82  
     public Set<String> getSystemTablesAndViews() {
 83  0
         return systemTablesAndViews;
 84  
     }
 85  
 
 86  
     public int getPriority() {
 87  0
         return PRIORITY_DEFAULT;
 88  
     }
 89  
 
 90  
     public boolean supportsInitiallyDeferrableColumns() {
 91  1
         return true;
 92  
     }
 93  
 
 94  
     public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
 95  1
         return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
 96  
     }
 97  
 
 98  
     public String getDefaultDriver(String url) {
 99  0
         if (url.startsWith("jdbc:postgresql:")) {
 100  0
             return "org.postgresql.Driver";
 101  
         }
 102  0
         return null;
 103  
     }
 104  
 
 105  
     @Override
 106  
     public boolean supportsSequences() {
 107  18
         return true;
 108  
     }
 109  
 
 110  
     public String getCurrentDateTimeFunction() {
 111  1
         if (currentDateTimeFunction != null) {
 112  0
             return currentDateTimeFunction;
 113  
         }
 114  
 
 115  1
         return "NOW()";
 116  
     }
 117  
 
 118  
     @Override
 119  
     protected String getDefaultDatabaseSchemaName() throws DatabaseException {
 120  
 
 121  0
         if (defaultDatabaseSchemaName == null) {
 122  
             try {
 123  0
                 List<String> searchPaths = getSearchPaths();
 124  0
                 if (searchPaths != null && searchPaths.size() > 0) {
 125  0
                     for (String searchPath : searchPaths) {
 126  0
                         if (searchPath != null && searchPath.length() > 0) {
 127  0
                             defaultDatabaseSchemaName = searchPath;
 128  
 
 129  0
                             if (defaultDatabaseSchemaName.equals("$user")
 130  
                                     && getConnection().getConnectionUserName() != null) {
 131  0
                                 if (!schemaExists(getConnection().getConnectionUserName())) {
 132  0
                                     defaultDatabaseSchemaName = null;
 133  
                                 } else {
 134  0
                                     defaultDatabaseSchemaName = getConnection().getConnectionUserName();
 135  
                                 }
 136  
                             }
 137  
 
 138  0
                             if (defaultDatabaseSchemaName != null)
 139  0
                                 break;
 140  
                         }
 141  
                     }
 142  
                 }
 143  0
             } catch (Exception e) {
 144  
                 // TODO: throw?
 145  0
                 e.printStackTrace();
 146  0
                 LogFactory.getLogger().severe("Failed to get default catalog name from postgres", e);
 147  0
             }
 148  
         }
 149  
 
 150  0
         return defaultDatabaseSchemaName;
 151  
     }
 152  
 
 153  
     @Override
 154  
     public String getDefaultCatalogName() throws DatabaseException {
 155  0
         return "public";
 156  
     }
 157  
 
 158  
     @Override
 159  
     public String getDatabaseChangeLogTableName() {
 160  0
         return super.getDatabaseChangeLogTableName().toLowerCase();
 161  
     }
 162  
 
 163  
     @Override
 164  
     public String getDatabaseChangeLogLockTableName() {
 165  0
         return super.getDatabaseChangeLogLockTableName().toLowerCase();
 166  
     }
 167  
 
 168  
     // public void dropDatabaseObjects(String schema) throws DatabaseException {
 169  
     // try {
 170  
     // if (schema == null) {
 171  
     // schema = getConnectionUsername();
 172  
     // }
 173  
     // new Executor(this).execute(new RawSqlStatement("DROP OWNED BY " + schema));
 174  
     //
 175  
     // getConnection().commit();
 176  
     //
 177  
     // changeLogTableExists = false;
 178  
     // changeLogLockTableExists = false;
 179  
     // changeLogCreateAttempted = false;
 180  
     // changeLogLockCreateAttempted = false;
 181  
     //
 182  
     // } catch (SQLException e) {
 183  
     // throw new DatabaseException(e);
 184  
     // }
 185  
     // }
 186  
 
 187  
     @Override
 188  
     public boolean isSystemTable(String catalogName, String schemaName, String tableName) {
 189  0
         return super.isSystemTable(catalogName, schemaName, tableName) || "pg_catalog".equals(schemaName)
 190  
                 || "pg_toast".equals(schemaName) || tableName.endsWith("_seq") || tableName.endsWith("_key")
 191  
                 || tableName.endsWith("_pkey") || tableName.startsWith("idx_") || tableName.startsWith("pk_");
 192  
     }
 193  
 
 194  
     public boolean supportsTablespaces() {
 195  0
         return true;
 196  
     }
 197  
 
 198  
     @Override
 199  
     public String getAutoIncrementClause() {
 200  0
         return "";
 201  
     }
 202  
 
 203  
     @Override
 204  
     public String convertRequestedSchemaToSchema(String requestedSchema) throws DatabaseException {
 205  0
         if (requestedSchema == null)
 206  0
             requestedSchema = getDefaultSchemaName();
 207  
 
 208  0
         if (requestedSchema == null) {
 209  
             // Return the catalog name instead..
 210  0
             return getDefaultCatalogName();
 211  
         } else {
 212  0
             String schema = StringUtils.trimToNull(requestedSchema);
 213  0
             return (schema != null) ? schema.toLowerCase() : schema;
 214  
         }
 215  
     }
 216  
 
 217  
     @Override
 218  
     public String convertRequestedSchemaToCatalog(String requestedSchema) throws DatabaseException {
 219  0
         return super.convertRequestedSchemaToCatalog(requestedSchema);
 220  
     }
 221  
 
 222  
     @Override
 223  
     public String escapeDatabaseObject(String objectName) {
 224  14
         if (objectName == null) {
 225  6
             return null;
 226  
         }
 227  8
         if (objectName.contains("-") || hasMixedCase(objectName) || startsWithNumeric(objectName)
 228  
                 || isReservedWord(objectName)) {
 229  4
             return "\"" + objectName + "\"";
 230  
         } else {
 231  4
             return super.escapeDatabaseObject(objectName);
 232  
         }
 233  
 
 234  
     }
 235  
 
 236  
     /*
 237  
      * Check if given string has case problems according to postgresql documentation. If there are at least one
 238  
      * characters with upper case while all other are in lower case (or vice versa) this string should be escaped.
 239  
      * 
 240  
      * Note: This may make postgres support more case sensitive than normally is, but needs to be left in for backwards
 241  
      * compatibility. Method is public so a subclass extension can override it to always return false.
 242  
      */
 243  
     protected boolean hasMixedCase(String tableName) {
 244  8
         return tableName.matches(".*[A-Z].*") && tableName.matches(".*[a-z].*");
 245  
 
 246  
     }
 247  
 
 248  
     /*
 249  
      * Check if given string starts with numeric values that may cause problems and should be escaped.
 250  
      */
 251  
     private boolean startsWithNumeric(String tableName) {
 252  5
         return tableName.matches("^[0-9].*");
 253  
 
 254  
     }
 255  
 
 256  
     @Override
 257  
     public boolean isReservedWord(String tableName) {
 258  21
         for (int i = 0; i != this.reservedWords.length; i++)
 259  17
             if (this.reservedWords[i].toLowerCase().equalsIgnoreCase(tableName))
 260  1
                 return true;
 261  4
         return false;
 262  
     }
 263  
 
 264  
     /*
 265  
      * Reserved words from postgresql documentation
 266  
      */
 267  9
     private String[] reservedWords = new String[] { "USER", "LIKE", "GROUP", "DATE"
 268  
     // "ALL", "ANALYSE", "ANALYZE", "AND", "ANY", "ARRAY", "AS", "ASC", "ASYMMETRIC", "AUTHORIZATION", "BETWEEN",
 269  
     // "BINARY", "BOTH", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "CONSTRAINT", "CORRESPONDING", "CREATE", "CROSS",
 270  
     // "CURRENT_DATE", "CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "DEFAULT", "DEFERRABLE",
 271  
     // "DESC", "DISTINCT", "DO", "ELSE", "END", "EXCEPT", "FALSE", "FOR", "FOREIGN", "FREEZE", "FROM", "FULL", "GRANT",
 272  
     // "GROUP", "HAVING",
 273  
     // "ILIKE", "IN", "INITIALLY", "INNER", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "LEADING", "LEFT", "LIKE",
 274  
     // "LIMIT", "LOCALTIME", "LOCALTIMESTAMP", "NATURAL", "NEW", "NOT", "NOTNULL", "NULL", "OFF", "OFFSET", "OLD", "ON",
 275  
     // "ONLY", "OPEN", "OR", "ORDER", "OUTER", "OVERLAPS", "PLACING", "PRIMARY", "REFERENCES", "RETURNING", "RIGHT",
 276  
     // "SELECT", "SESSION_USER", "SIMILAR", "SOME", "SYMMETRIC", "TABLE", "THEN", "TO", "TRAILING", "TRUE", "UNION",
 277  
     // "UNIQUE", "USER", "USING", "VERBOSE", "WHEN", "WHERE"
 278  
     };
 279  
 
 280  
     /*
 281  
      * Get the current search paths
 282  
      */
 283  
     private List<String> getSearchPaths() {
 284  0
         List<String> searchPaths = null;
 285  
 
 286  
         try {
 287  0
             DatabaseConnection con = getConnection();
 288  
 
 289  0
             if (con != null) {
 290  0
                 String searchPathResult = (String) ExecutorService.getInstance().getExecutor(this)
 291  
                         .queryForObject(new RawSqlStatement("SHOW search_path"), String.class);
 292  
 
 293  0
                 if (searchPathResult != null) {
 294  0
                     String dirtySearchPaths[] = searchPathResult.split("\\,");
 295  0
                     searchPaths = new ArrayList<String>();
 296  0
                     for (String searchPath : dirtySearchPaths) {
 297  0
                         searchPath = searchPath.trim();
 298  
 
 299  
                         // Ensure there is consistency ..
 300  0
                         if (searchPath.equals("\"$user\"")) {
 301  0
                             searchPath = "$user";
 302  
                         }
 303  
 
 304  0
                         searchPaths.add(searchPath);
 305  
                     }
 306  
                 }
 307  
 
 308  
             }
 309  0
         } catch (Exception e) {
 310  
             // TODO: Something?
 311  0
             e.printStackTrace();
 312  0
             LogFactory.getLogger().severe("Failed to get default catalog name from postgres", e);
 313  0
         }
 314  
 
 315  0
         return searchPaths;
 316  
     }
 317  
 
 318  
     private boolean catalogExists(String catalogName) throws DatabaseException {
 319  0
         if (catalogName != null) {
 320  0
             return runExistsQuery("select count(*) from information_schema.schemata where catalog_name='" + catalogName
 321  
                     + "'");
 322  
         } else {
 323  0
             return false;
 324  
         }
 325  
     }
 326  
 
 327  
     private boolean schemaExists(String schemaName) throws DatabaseException {
 328  0
         return schemaName != null
 329  
                 && runExistsQuery("select count(*) from information_schema.schemata where schema_name='" + schemaName
 330  
                         + "'");
 331  
     }
 332  
 
 333  
     private boolean runExistsQuery(String query) throws DatabaseException {
 334  0
         Long count = ExecutorService.getInstance().getExecutor(this).queryForLong(new RawSqlStatement(query));
 335  
 
 336  0
         return count != null && count > 0;
 337  
     }
 338  
 
 339  
     @Override
 340  
     public String escapeIndexName(String schemaName, String indexName) {
 341  0
         return indexName;
 342  
     }
 343  
 }