Refactoring (or something else) for "USE someSchema;"?

I realize there may not be an easy way to do this.

I have a modular application.  Each module has a changelog.xml that deals with its chunk of the database.  Ultimately all tables in the application should live in the same schema, whose name needs to be governed by the application.

The application has a master changelog.xml file that includes all the sub-changelogs in classpath order.

I’d like to keep it so that the module doesn’t know about the application schema.

The first thing that occurred to me was changelog parameters.  The manual makes reference to a changelog “parameters block”, but from looking at the schema (http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd) I see no such element.

I could pass the schema name in as a system property, but what should I do for testing purposes when, for example, the schema is effectively “”?  I don’t want create table statements that look like this:

CREATE TABLE .foobar

Anyhow, any ideas or best practices or hidden documentation welcome.  I’m using version 1.9.5.

Best,
Laird

There is a defaultSchema parameter that can be used to set what schema is used by the liquibase tables, and which schema is used when none are specified in the changelog file.  How you set it depends on how you run liquibase (command line vs ant vs maven etc.)  The goal of that setting is to allow what you are describing.

You had another response before, did you remove it?  I didn’t to read it all to comment before it disappeared.

Nathan

Originally posted by: Nathan
You had another response before, did you remove it?  I didn't to read it all to comment before it disappeared.

Yep; dumb pilot error I’d just as soon not have on the record.  :slight_smile:

Best,
Laird

Originally posted by: Nathan
There is a defaultSchema parameter that can be used to set what schema is used by the liquibase tables, and which schema is used when none are specified in the changelog file.  How you set it depends on how you run liquibase (command line vs ant vs maven etc.)  The goal of that setting is to allow what you are describing.

Ah, I see it now!  Great; thanks.  Is an attempt made to create it if it does not exist (I assume not)?  I see no createSchema refactoring.  I realize I could just use raw SQL but would like to retain the level of abstraction where possible.

Best,
Laird

Would you consider placing a well-known property available for interpolation called something like “defaultSchemaName” whose value is not taken from the changeLogParameters, but from the Database#getDefaultSchemaName() in effect at changelog parse time?

That, in conjunction with a simple, hypothetical PropertyPrecondition class would enable me to place schema creation logic at the top of my master changelog only if (a) a default schema is in effect and (b) if a property making that default schema name available to the change log evaluates to non-null.

In an attempt to give back, I can write the PropertyPrecondition class if you like.  Obviously I can make it a custom, but this seems like a particularly good candidate for inclusion into the codebase.

Best,
Laird

As a gesture of good faith, here’s a custom precondition class that allows you to test some bean property of the Database.  Stand back.

This code is in the public domain.

Best,
Laird

    import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor;

    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;

    import liquibase.database.Database;

    import liquibase.exception.CustomPreconditionFailedException;
    import liquibase.exception.CustomPreconditionErrorException;

    import liquibase.preconditions.CustomPrecondition;

    import liquibase.util.StringUtils;

    public class DatabasePropertyPrecondition implements CustomPrecondition {

      private String propertyName;

      private String expectedPropertyValue;

      public DatabasePropertyPrecondition() {
        super();
      }

      @Override
      public void check(final Database database) throws CustomPreconditionFailedException, CustomPreconditionErrorException {
        final String expectedPropertyValue = StringUtils.trimToNull(this.getExpectedPropertyValue());
        Object rawValue = null;
        try {
          rawValue = this.getPropertyValue(database, StringUtils.trimToNull(this.getPropertyName()));
        } catch (final IllegalAccessException illegalAccessException) {
          throw new CustomPreconditionErrorException(String.format(“There was an IllegalAccessException encountered while getting the value of the Database property named %s.”, this.getPropertyName()), illegalAccessException);
        } catch (final InvocationTargetException invocationTargetException) {
          throw new CustomPreconditionErrorException(String.format(“There was an InvocationTargetException encountered while getting the value of the Database property named %s.”, this.getPropertyName()), invocationTargetException);
        } catch (final IntrospectionException introspectionException) {
          throw new CustomPreconditionErrorException(String.format(“There was an IntrospectionException encountered while getting the value of the Database property named %s.”, this.getPropertyName()), introspectionException);
        }
        if (expectedPropertyValue == null) {
          if (rawValue != null) {
            throw new CustomPreconditionFailedException(String.format(“The Database property %s with value %s did not equal the expected property value %s.”, this.getPropertyName(), rawValue, expectedPropertyValue));
          }
        } else if (rawValue == null || !expectedPropertyValue.equals(rawValue)) {
          throw new CustomPreconditionFailedException(String.format(“The Database property %s with value %s did not equal the expected property value %s.”, this.getPropertyName(), rawValue, expectedPropertyValue));
        }
      }

      protected Object getPropertyValue(final Database database, final String propertyName) throws IllegalAccessException, IntrospectionException, InvocationTargetException {
        if (database == null || propertyName == null) {
          return null;
        }
        final BeanInfo beanInfo = Introspector.getBeanInfo(database.getClass());
        assert beanInfo != null;
        Object returnValue = null;
        final PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
        if (pds != null && pds.length > 0) {
          PropertyDescriptor pd = null;
          for (final PropertyDescriptor p : pds) {
            if (p != null && propertyName.equals(p.getName())) {
              pd = p;
              break;
            }
          }
          if (pd != null) {
            final Method readMethod = pd.getReadMethod();
            if (readMethod != null) {
              returnValue = readMethod.invoke(database);
            }
          }
        }
        return returnValue;
      }

      public String getPropertyName() {
        return this.propertyName;
      }

      public void setPropertyName(final String propertyName) {
        this.propertyName = propertyName;
      }

      public String getExpectedPropertyValue() {
        return this.expectedPropertyValue;
      }

      public void setExpectedPropertyValue(final String expectedPropertyValue) {
        this.expectedPropertyValue = expectedPropertyValue;
      }

    }

The defaultSchemaName parameter is a good idea.  I added a feature in jira for it (http://liquibase.jira.com/browse/CORE-600), although I think I can pass along “database” as a parameter and you can call ${database.defaultSchemaName} and you can also do things like ${database.booleanType}

Liquibase does not create the schema if it doesn’t exist.  You are right that we don’t have a createSchema tag at this point.  The main reason is because creating schemas gets more into manual DBA work that is different on every machine (setting tablespaces, security, etc.)

I did add a changeLogParameterDefined precondition in liquibase 2.0 that is similar to your code.  I’ll compare the two and see if there is any good ideas in yours that I missed. 

One thing to know about changeLogParameters is that they work like Ant parameters, in that once one is set it cannot be changed.  So if you want a default value for something, you can set it at the beginning of your changelog and it will use that value unless you set it earlier (such as with a -D system property etc.) without needing to do “is this already set” logic.

Nathan

Originally posted by: Nathan
I did add a changeLogParameterDefined precondition in liquibase 2.0 that is similar to your code.  I'll compare the two and see if there is any good ideas in yours that I missed. 

If you’re open to a dependency or two, you might–instead of rolling introspection logic as I did here–use the most excellent MVEL templating language/swiss army knife (mvel.codehaus.org; make sure you look at version 2.0 and later).  Seed it with a Database and a DatabaseChangeLog and let us users go hog wild.

Best,
Laird

Thanks for the suggestion, but I do have some mvel style support in the code already.  I’d rather have a limited feature set than introduce a dependency.  So far I’ve been able to keep dependencies to a minumum which I think is very helpful to end-users.

Nathan