liquibase.snapshot.jvm.IndexSnapshotGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of liquibase-core Show documentation
Show all versions of liquibase-core Show documentation
Liquibase is a tool for managing and executing database changes.
The newest version!
package liquibase.snapshot.jvm;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.Database;
import liquibase.database.core.*;
import liquibase.diff.compare.DatabaseObjectComparatorFactory;
import liquibase.exception.DatabaseException;
import liquibase.snapshot.CachedRow;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.snapshot.InvalidExampleException;
import liquibase.snapshot.JdbcDatabaseSnapshot;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.*;
import liquibase.util.ObjectUtil;
import liquibase.util.StringUtil;
import java.sql.DatabaseMetaData;
import java.util.*;
/**
* Analyses the properties of a database index and creates an object representation ("snapshot").
*/
public class IndexSnapshotGenerator extends JdbcSnapshotGenerator {
public IndexSnapshotGenerator() {
super(Index.class, new Class[]{Table.class, View.class, ForeignKey.class, UniqueConstraint.class});
}
@Override
protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException {
if (!snapshot.getSnapshotControl().shouldInclude(Index.class) || !snapshot.getDatabase().supports(Index.class)) {
return;
}
if (foundObject instanceof Table || foundObject instanceof View) {
if (foundObject instanceof View && !addToViews(snapshot.getDatabase())) {
return;
}
Relation relation = (Relation) foundObject;
Database database = snapshot.getDatabase();
Schema schema;
schema = relation.getSchema();
List rs = null;
JdbcDatabaseSnapshot.CachingDatabaseMetaData databaseMetaData = null;
try {
databaseMetaData = ((JdbcDatabaseSnapshot) snapshot).getMetaDataFromCache();
rs = databaseMetaData.getIndexInfo(((AbstractJdbcDatabase) database).getJdbcCatalogName(schema), ((AbstractJdbcDatabase) database).getJdbcSchemaName(schema), relation.getName(), null);
Map foundIndexes = new HashMap<>();
for (CachedRow row : rs) {
String indexName = row.getString("INDEX_NAME");
if (indexName == null) {
continue;
}
if ((database instanceof AbstractDb2Database) && "SYSIBM".equals(row.getString("INDEX_QUALIFIER"))) {
continue;
}
Index index = foundIndexes.get(indexName);
if (index == null) {
index = new Index();
index.setName(indexName);
index.setRelation(relation);
short type = row.getShort("TYPE");
if (type == DatabaseMetaData.tableIndexClustered) {
index.setClustered(true);
} else if (database instanceof MSSQLDatabase) {
index.setClustered(false);
}
foundIndexes.put(indexName, index);
}
String ascOrDesc;
if (database instanceof Db2zDatabase) {
ascOrDesc = row.getString("ORDER");
} else {
ascOrDesc = row.getString("ASC_OR_DESC");
}
Boolean descending = "D".equals(ascOrDesc) ? Boolean.TRUE : ("A".equals(ascOrDesc) ? Boolean
.FALSE : null);
if (database instanceof MSSQLDatabase) {
Boolean included = row.getBoolean("IS_INCLUDED_COLUMN");
Column c = new Column(row.getString("COLUMN_NAME")).setComputed(false).setDescending(descending).setRelation(index.getRelation()).setIncluded(included);
index.addColumn(c);
}else {
index.addColumn(new Column(row.getString("COLUMN_NAME")).setComputed(false).setDescending(descending).setRelation(index.getRelation()));
}
}
//add clustered indexes first, than all others in case there is a clustered and non-clustered version of the same index. Prefer the clustered version
List stillToAdd = new ArrayList<>();
for (Index exampleIndex : foundIndexes.values()) {
if ((exampleIndex.getClustered() != null) && exampleIndex.getClustered()) {
relation.getIndexes().add(exampleIndex);
} else {
stillToAdd.add(exampleIndex);
}
}
for (Index exampleIndex : stillToAdd) {
boolean alreadyAddedSimilar = false;
for (Index index : relation.getIndexes()) {
if (DatabaseObjectComparatorFactory.getInstance().isSameObject(index, exampleIndex, null, database)) {
alreadyAddedSimilar = true;
}
}
if (!alreadyAddedSimilar) {
relation.getIndexes().add(exampleIndex);
}
}
} catch (Exception e) {
throw new DatabaseException(e);
}
}
if ((foundObject instanceof UniqueConstraint) && (((UniqueConstraint) foundObject).getBackingIndex() == null)
&& !(snapshot.getDatabase() instanceof DB2Database) && !(snapshot.getDatabase() instanceof DerbyDatabase)) {
Index exampleIndex = new Index().setRelation(((UniqueConstraint) foundObject).getRelation());
exampleIndex.getColumns().addAll(((UniqueConstraint) foundObject).getColumns());
((UniqueConstraint) foundObject).setBackingIndex(exampleIndex);
}
if ((foundObject instanceof ForeignKey) && (((ForeignKey) foundObject).getBackingIndex() == null)) {
Index exampleIndex = new Index().setRelation(((ForeignKey) foundObject).getForeignKeyTable());
exampleIndex.getColumns().addAll(((ForeignKey) foundObject).getForeignKeyColumns());
((ForeignKey) foundObject).setBackingIndex(exampleIndex);
}
}
@Override
@SuppressWarnings("java:S5411")
protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException {
Database database = snapshot.getDatabase();
Relation exampleIndex = ((Index) example).getRelation();
String tableName = null;
Schema schema = null;
if (exampleIndex != null) {
tableName = exampleIndex.getName();
schema = exampleIndex.getSchema();
}
if (schema == null) {
schema = new Schema(database.getDefaultCatalogName(), database.getDefaultSchemaName());
}
for (int i = 0; i < ((Index) example).getColumns().size(); i++) {
((Index) example).getColumns().set(i, ((Index) example).getColumns().get(i));
}
String exampleName = example.getName();
if (exampleName != null) {
exampleName = database.correctObjectName(exampleName, Index.class);
}
Map foundIndexes = new LinkedHashMap <>();
JdbcDatabaseSnapshot.CachingDatabaseMetaData databaseMetaData = null;
List rs = null;
try {
databaseMetaData = ((JdbcDatabaseSnapshot) snapshot).getMetaDataFromCache();
rs = databaseMetaData.getIndexInfo(((AbstractJdbcDatabase) database).getJdbcCatalogName(schema), ((AbstractJdbcDatabase) database).getJdbcSchemaName(schema), tableName, exampleName);
for (CachedRow row : rs) {
String rawIndexName = row.getString("INDEX_NAME");
String indexName = cleanNameFromDatabase(rawIndexName, database);
String correctedIndexName = database.correctObjectName(indexName, Index.class);
if (indexName == null) {
continue;
}
if ((exampleName != null) && !exampleName.equals(correctedIndexName)) {
continue;
}
/*
* TODO Informix generates indexnames with a leading blank if no name given.
* An identifier with a leading blank is not allowed.
* So here is it replaced.
*/
if ((database instanceof InformixDatabase) && indexName.startsWith(" ")) {
continue; // suppress creation of generated_index records
}
short type = row.getShort("TYPE");
Boolean nonUnique = row.getBoolean("NON_UNIQUE");
if (nonUnique == null) {
nonUnique = true;
}
String columnName = cleanNameFromDatabase(row.getString("COLUMN_NAME"), database);
short position = row.getShort("ORDINAL_POSITION");
String definition = StringUtil.trimToNull(row.getString("FILTER_CONDITION"));
if (definition != null) {
if (!(database instanceof OracleDatabase)) { //TODO: this replaceAll code has been there for a long time but we don't know why. Investigate when it is ever needed and modify it to be smarter
definition = definition.replaceAll("\"", "");
}
}
if ((columnName == null) && (definition == null)) {
//nothing to index, not sure why these come through sometimes
continue;
}
if (!(database instanceof H2Database) && type == DatabaseMetaData.tableIndexStatistic) {
continue;
}
/*
* In Oracle database, ALL_IND_COLUMNS/ALL_IND_EXPRESSIONS (the views from which we bulk-fetch the
* column definitions for a given index) can show a strange behaviour if an index column consists of
* a regular table column, but its sort order is DESC(ending). In this case, we get something like
* this (example values):
* ALL_IND_COLUMNS.COLUMN_NAME=SYS_NC00006$
* ALL_IND_EXPRESSIONS.COLUMN_EXPRESSIONS="COLUMN1FORDESC"
* Note that the quote characters (") are part of the actual column value!
* Our strategy here is: If the expression would be a valid Oracle identifier, but not a valid Oracle
* function name, then we assume it is the name of a regular column.
*/
if ((database instanceof OracleDatabase) && (definition != null) && (columnName != null)) {
String potentialColumnExpression = definition.replaceFirst("^\"?(.*?)\"?$", "$1");
OracleDatabase oracle = (OracleDatabase) database;
if (oracle.isValidOracleIdentifier(potentialColumnExpression, Index.class)
&& (!oracle.isFunction(potentialColumnExpression))) {
columnName = potentialColumnExpression;
definition = null;
}
}
// Have we already seen/found this index? If not, let's read its properties!
Index returnIndex = foundIndexes.get(correctedIndexName);
if (returnIndex == null) {
returnIndex = new Index();
Relation relation = new Table();
if ("V".equals(row.getString("INTERNAL_OBJECT_TYPE"))) {
relation = new View();
}
returnIndex.setRelation(relation.setName(row.getString("TABLE_NAME")).setSchema(schema));
returnIndex.setName(indexName);
returnIndex.setUnique(!nonUnique);
String tablespaceName = row.getString("TABLESPACE_NAME");
if ((tablespaceName != null) && database.supportsTablespaces()) {
returnIndex.setTablespace(tablespaceName);
}
if (type == DatabaseMetaData.tableIndexClustered) {
returnIndex.setClustered(true);
} else if (database instanceof MSSQLDatabase) {
returnIndex.setClustered(false);
}
if (database instanceof MSSQLDatabase) {
Boolean recompute = (Boolean) row.get("NO_RECOMPUTE");
if (recompute != null) {
recompute = !recompute;
}
returnIndex.setAttribute("padIndex", row.get("IS_PADDED"));
returnIndex.setAttribute("fillFactor", row.get("FILL_FACTOR"));
returnIndex.setAttribute("ignoreDuplicateKeys", row.get("IGNORE_DUP_KEY"));
returnIndex.setAttribute("recomputeStatistics", recompute);
returnIndex.setAttribute("incrementalStatistics", row.get("IS_INCREMENTAL"));
returnIndex.setAttribute("allowRowLocks", row.get("ALLOW_ROW_LOCKS"));
returnIndex.setAttribute("allowPageLocks", row.get("ALLOW_PAGE_LOCKS"));
}
foundIndexes.put(correctedIndexName, returnIndex);
}
if ((database instanceof MSSQLDatabase) && ObjectUtil.defaultIfNull((Boolean) row.get("IS_INCLUDED_COLUMN"), false)) {
List includedColumns = returnIndex.getAttribute("includedColumns", List.class);
if (includedColumns == null) {
includedColumns = new ArrayList<>();
returnIndex.setAttribute("includedColumns", includedColumns);
}
includedColumns.add(columnName);
} else {
if (position != 0) { //if really a column, position is 1-based.
for (int i = returnIndex.getColumns().size(); i < position; i++) {
returnIndex.getColumns().add(null);
}
// Is this column a simple column (definition == null)
// or is it a computed expression (definition != null)
if (definition == null || database instanceof PostgresDatabase || database instanceof MSSQLDatabase) {
String ascOrDesc;
if (database instanceof Db2zDatabase) {
ascOrDesc = row.getString("ORDER");
} else {
ascOrDesc = row.getString("ASC_OR_DESC");
}
Boolean descending = "D".equals(ascOrDesc) ? Boolean.TRUE : ("A".equals(ascOrDesc) ?
Boolean.FALSE : null);
returnIndex.getColumns().set(position - 1, new Column(columnName)
.setDescending(descending).setRelation(returnIndex.getRelation()));
} else {
returnIndex.getColumns().set(position - 1, new Column()
.setRelation(returnIndex.getRelation()).setName(definition, true));
}
}
}
}
} catch (Exception e) {
throw new DatabaseException(e);
}
if (exampleName != null) {
return foundIndexes.get(exampleName);
} else {
//prefer clustered version of the index
List nonClusteredIndexes = new ArrayList<>();
for (Index index : foundIndexes.values()) {
if (DatabaseObjectComparatorFactory.getInstance().isSameObject(index.getRelation(), exampleIndex, snapshot.getSchemaComparisons(), database)) {
boolean actuallyMatches = false;
if (database.isCaseSensitive()) {
if (index.getColumnNames().equals(((Index) example).getColumnNames())) {
actuallyMatches = true;
}
} else {
if (index.getColumnNames().equalsIgnoreCase(((Index) example).getColumnNames())) {
actuallyMatches = true;
}
}
if (actuallyMatches) {
if ((index.getClustered() != null) && index.getClustered()) {
return finalizeIndex(schema, tableName, index, snapshot);
} else {
nonClusteredIndexes.add(index);
}
}
}
}
if (!nonClusteredIndexes.isEmpty()) {
return finalizeIndex(schema, tableName, nonClusteredIndexes.get(0), snapshot); // why 0?
}
return null;
}
}
protected Index finalizeIndex(Schema schema, String tableName, Index index, DatabaseSnapshot snapshot) {
if ((index.isUnique() == null) || !index.isUnique()) {
List columns = index.getColumns();
PrimaryKey tablePK = new PrimaryKey(null, schema.getCatalogName(), schema.getName(), tableName, columns.toArray(new Column[index.getColumns().size()]));
if (snapshot.get(tablePK) != null) { //actually is unique since it's the PK
index.setUnique(true);
}
}
return index;
}
protected boolean addToViews(Database database) {
return database instanceof MSSQLDatabase;
}
}