Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement multi-schema support #727

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Copy link
Collaborator

@filipelautert filipelautert Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@philipp-kleber-avelios Why getConnectionCatalogName() and getConnectionSchemaName() are being locked here to constants?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For getConnectionCatalogName(), the behavior is the same as before just without one less indirection (it used to call getDefaultCatalogName() internally).

For getConnectionSchemaName(), the behavior is slightly different from before:
It used to call getDefaultSchemaName() internally, but we can't do this anymore: The new getDefaultSchemaName() implementation calls super.getDefaultSchemaName() and this then calls getConnectionSchemaName(), so we'd have a call-cycle.

Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,11 @@ public abstract class HibernateDatabase extends AbstractJdbcDatabase {
protected Dialect dialect;

private boolean indexesForForeignKeys = false;
public static final String DEFAULT_SCHEMA = "HIBERNATE";
public static final String DEFAULT_CATALOG_NAME = "HIBERNATE";
public static final String DEFAULT_SCHEMA_NAME = "HIBERNATE";
public static final String HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS = "hibernate.temp.use_jdbc_metadata_defaults";

public HibernateDatabase() {
setDefaultCatalogName(DEFAULT_SCHEMA);
setDefaultSchemaName(DEFAULT_SCHEMA);
}
public HibernateDatabase() { }

public boolean requiresPassword() {
return false;
Expand Down Expand Up @@ -295,23 +293,28 @@ public boolean supportsTablespaces() {
}

@Override
protected String getConnectionCatalogName() throws DatabaseException {
return getDefaultCatalogName();
protected String getConnectionCatalogName() {
return DEFAULT_CATALOG_NAME;
}

@Override
protected String getConnectionSchemaName() {
return getDefaultSchemaName();
return DEFAULT_SCHEMA_NAME;
}

@Override
public String getDefaultSchemaName() {
return DEFAULT_SCHEMA;
final String defaultSchemaName = super.getDefaultSchemaName();
if (defaultSchemaName != null) {
return defaultSchemaName;
}

return DEFAULT_SCHEMA_NAME;
}

@Override
public String getDefaultCatalogName() {
return DEFAULT_SCHEMA;
return DEFAULT_CATALOG_NAME;
}

@Override
Expand All @@ -334,6 +337,9 @@ public boolean supportsCatalogs() {
return true;
}

@Override
public void setDefaultCatalogName(String defaultCatalogName) { }

/**
* Used by hibernate to ensure no database access is performed.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
import liquibase.database.Database;
import liquibase.exception.DatabaseException;
import liquibase.ext.hibernate.database.HibernateDatabase;
import liquibase.logging.LogFactory;
import liquibase.logging.LogService;
import liquibase.logging.Logger;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.snapshot.InvalidExampleException;
import liquibase.snapshot.SnapshotGenerator;
import liquibase.snapshot.SnapshotGeneratorChain;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Schema;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.mapping.Table;

Expand Down Expand Up @@ -107,4 +106,27 @@ protected org.hibernate.mapping.Table findHibernateTable(DatabaseObject example,
}
return null;
}

protected static boolean schemaMatchesTable(
DatabaseObject example,
Table hibernateTable
) {
if (example.getSchema().getName() != null && example.getSchema().getName().equalsIgnoreCase(hibernateTable.getSchema())) {
return true;
}

return example.getSchema().isDefault() && hibernateTable.getSchema() == null;
}

protected static boolean schemaMatchesNamespace(
Schema schema,
Namespace hibernateNamespace
) {
if (hibernateNamespace.getName().getSchema() != null && hibernateNamespace.getName().getSchema().matches(schema.getName())) {
return true;
}

return hibernateNamespace.getName().getSchema() == null && schema.isDefault();
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package liquibase.ext.hibernate.snapshot;

import liquibase.Scope;
import liquibase.exception.DatabaseException;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.snapshot.InvalidExampleException;
Expand All @@ -18,7 +19,12 @@ public SchemaSnapshotGenerator() {

@Override
protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException {
return new Schema(snapshot.getDatabase().getDefaultCatalogName(), snapshot.getDatabase().getDefaultSchemaName()).setDefault(true);
Schema schema = new Schema(example.getSchema().getCatalog(), example.getSchema().getName());
if (snapshot.getDatabase().getDefaultSchemaName() != null && snapshot.getDatabase().getDefaultSchemaName().equalsIgnoreCase(schema.getName())) {
schema.setDefault(true);
}

return schema;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) thro
Schema schema = (Schema) foundObject;
HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase();
for (org.hibernate.boot.model.relational.Namespace namespace : database.getMetadata().getDatabase().getNamespaces()) {
if (!schemaMatchesNamespace(schema, namespace)) {
continue;
}

for (org.hibernate.boot.model.relational.Sequence sequence : namespace.getSequences()) {
schema.addDatabaseObject(new Sequence()
.setName(sequence.getName().getSequenceName().getText())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
import liquibase.structure.core.Table;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.generator.Generator;
import org.hibernate.mapping.*;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SimpleValue;

import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -38,13 +41,19 @@ protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot
return example;
}

Table table = new Table().setName(hibernateTable.getName());
Scope.getCurrentScope().getLog(getClass()).info("Found table " + table.getName());
if (!schemaMatchesTable(example, hibernateTable)) {
Scope.getCurrentScope().getLog(getClass()).info("Skipping table " + hibernateTable.getName() + " for schema " + example.getSchema().getName() + ", because it is part of another one.");
return null;
}

Table table = new Table();
table.setName(hibernateTable.getName());
table.setSchema(example.getSchema());
if (hibernateTable.getComment() != null && !hibernateTable.getComment().isEmpty()) {
table.setRemarks(hibernateTable.getComment());
}

Scope.getCurrentScope().getLog(getClass()).info("Found table " + example.getSchema().getName() + "." + table.getName());
return table;
}

Expand Down Expand Up @@ -115,7 +124,6 @@ protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) thro
private void addDatabaseObjectToSchema(org.hibernate.mapping.Table join, Schema schema, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException {
Table joinTable = new Table().setName(join.getName());
joinTable.setSchema(schema);
Scope.getCurrentScope().getLog(getClass()).info("Found table " + joinTable.getName());
schema.addDatabaseObject(snapshotObject(joinTable, snapshot));
}
}
60 changes: 60 additions & 0 deletions src/test/java/com/example/multischema/auction/AuctionInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.example.multischema.auction;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import java.util.Date;

@Entity
@Table(schema = "PUBLIC")
public class AuctionInfo {
private String id;
private String description;
private Date ends;
private Float maxAmount;

@Column(length = 1000)
public String getDescription() {
return description;
}

public Date getEnds() {
return ends;
}

@Id
public String getId() {
return id;
}


public Float getMaxAmount() {
return maxAmount;
}

public void setId(String id) {
this.id = id;
}

public void setDescription(String description) {
this.description = description;
}

public void setEnds(Date ends) {
this.ends = ends;
}

public void setMaxAmount(Float maxAmount) {
this.maxAmount = maxAmount;
}

public AuctionInfo(String id, String description, Date ends, Float maxAmount) {
this.id = id;
this.description = description;
this.ends = ends;
this.maxAmount = maxAmount;
}

}
86 changes: 86 additions & 0 deletions src/test/java/com/example/multischema/auction/AuctionItem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.example.multischema.auction;

import jakarta.persistence.*;

import java.util.Date;
import java.util.List;

@Entity
@Table(schema = "PUBLIC")
public class AuctionItem extends Persistent {
private String description;
private String shortDescription;
private List<Bid> bids;
private Bid successfulBid;
private User seller;
private Date ends;
private int condition;

@OneToMany(mappedBy = "item", cascade = CascadeType.ALL)
public List<Bid> getBids() {
return bids;
}

@Column(length = 1000)
public String getDescription() {
return description;
}

@ManyToOne
public User getSeller() {
return seller;
}

@ManyToOne
public Bid getSuccessfulBid() {
return successfulBid;
}

public void setBids(List<Bid> bids) {
this.bids = bids;
}

public void setDescription(String string) {
description = string;
}

public void setSeller(User user) {
seller = user;
}

public void setSuccessfulBid(Bid bid) {
successfulBid = bid;
}

public Date getEnds() {
return ends;
}

public void setEnds(Date date) {
ends = date;
}

public int getCondition() {
return condition;
}

public void setCondition(int i) {
condition = i;
}

public String toString() {
return shortDescription + " (" + description + ": " + condition
+ "/10)";
}

@Column(length = 200)
public String getShortDescription() {
return shortDescription;
}

public void setShortDescription(String shortDescription) {
this.shortDescription = shortDescription;
}


}
34 changes: 34 additions & 0 deletions src/test/java/com/example/multischema/auction/AuditedItem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.example.multischema.auction;

import jakarta.persistence.*;
import org.hibernate.envers.Audited;

@Audited
@Entity
@Table(schema = "PUBLIC")
public class AuditedItem {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "AUDITED_ITEM_SEQ")
@SequenceGenerator(name = "AUDITED_ITEM_SEQ", sequenceName = "AUDITED_ITEM_SEQ")
private long id;
@Column(unique = true)
private String name;

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}
Loading
Loading