Liquibase for JVM languages

Hi,

I am looking for ways to invoke the Liquibase JAR without specifying XML changelog files. Instead of XML, I want to be able to pass some equivalent data structure (some nested class instances maybe). So, my questions are:

  1. Is it possible to pass some data structure instead of XML file as changelog? I am prepared to construct the data structure programmatically and can do the glue-work on my own if required.

  2. Is there any hook through which I can receive data structure (instead of XML) while generating changelog (schema reverse-engineering) or a diff.

Any pointers to these will be much appreciated. I am trying to build a Clojure-wrapper for Liquibase.

Regards,
Shantanu

One of the goals of the liquibase 2.0 refactoring was to support exactly this sort of thing.  There is someone currently working on a groovy based DSL as well.

Take a look at http://liquibase.jira.com/wiki/display/CONTRIB/ChangeLogParser as a starting point.  The general idea would be that you can create a new ChangeLogParser implementation the same as XMLSaxChangeLogParser and FormattedChangeLogParser.  The job of the parser is to take a input stream and convert it into a DatabaseChangeLog object. Everything before and everything after is exactly the same no matter what the changelog format.

You can also implement ChangeLogSerializer which takes is what the diff tool uses to output differences as changelog entries.  Then generateChangeLog et al would create files in your format.  This would allow you to have generateChangeLog and similar tools output your format instead of XML. 

Let me know if you have any questions

Nathan

I notice that the DatabaseChangeLog constructor looks like this:

public DatabaseChangeLog(String physicalFilePath) {
  …blah…
}

I want to know what’s the rationale behind taking filepath as argument in a DatabaseChangeLog. What if I want to define a changelog entirely in code, and not use an external file at all? Will doing so (i.e. pass a dummy file name that does not exist) have any unwanted effect?

It’s not required on it’s own, but either physicalFilePath or logicalFilePath is required.  The main use is that each changeset is identified by a combination of the “id”, “author” and “logicalFilePath” and if a logicalFilePath isn’t set, it falls back to the physicalFilePath.  It’s also used in a few of the changelog parsers for displaying the file where errors occured.

I committed a change to add a default constructor, but you will want to at least call setLogicalFilePath()

Nathan

Thanks Nathan, it has helpful. At the risk of sounding repetitive, my next question is – why does the ChangeSet constructor require a physical filename? It would be greatly helpful if you could document the parameters that are hard to understand at the first glance, such as “contextList”, “dbmsList” etc:

ChangeSet(
  String id,
  String author,
  boolean alwaysRun,
  boolean runOnChange,
  String filePath,
  String physicalFilePath,
  String contextList,
  String dbmsList,
  boolean runInTransaction)

If you can, please consider documenting (just one liners will do) the various interface method signatures too - though they are somewhat guessable.

Oops! noticed this page later: http://www.liquibase.org/manual/changeset

The plan is to document it all well, it’s just been lower priority than getting it coded in the first place :slight_smile:

You are right that ChangeSet should not take a physical file path.  It needs the logical path for the reason listed above, but should not include a physical file path.  I removed it from the constructor and the object.

I added documentation to the ChangeSet object attributes as a start.  More will come, if you have particular questions feel free to ask, it helps me focus my documentation efforts.  It does line up well with the changeSet user documentation, but javadoc is needed as well.

Nathan

One small suggestion for classes that contain properties such as ColumnConfig, ConstraintsConfig etc. – can you consider implementing toString(), hashCode and equals() methods? It would be very helpful for diagnostics and for using them inside maps. One implementation idea could be to extend them from a base class that implements toString() by looking up property values by reflection, and hashCode() by toString().hashCode(), and similarly for equals().

Regards,
Shantanu

My next question is about constructing PreconditionContainer instances programmatically, which seems less straightforward than the rest. Could you please explain (preferably with example/s) how to do that?

Sorry for the noise, but I thought to set just a gentle reminder for this one.  :slight_smile:

Regards,
Shantanu

Sorry, it got pushed into my “longer response required, will take more time” queue.

The idea of the PreconditionContainer is that its job is to be the facade to whatever preconditions are defined.  You construct it with just a call to new PreconditionContainer().  Preconditions themselves are created by a call to PreconditionFactory.getInstance().create(name) which creates the correct precondition object for the given name.  You can then set attributes on that precondition (attributes vary by the actual precondtion) and then call precondtionContainer.addNestedPrecondition(precondition) passing the newly created precondition.

The addNestedPrecondition() method exists for all the PrecondtionLogic “preconditions” which are basically the and/or/not groupings of normal preconditions.

If you look at XMLChangeLogSAXHandler we have preconditionLogicStack which is a stack to allow arbitrary nesting of logical preconditoins.  The bottom of the stack is the precondtion container and as we create precondions based on the XML, if it is a PrecondtionLogic precondition, we add it to the top of the stack, and if it is a normal precondtion, we add it to the top precondition.  As we leave the etc. XML tags we pop the top precondition off the stack since we are done with it until at the end the stack is emply.

I’m not sure how much of that stack logic should be moved into the Precondtion liqubase API and what is XML-handling specific and should be left out.

Any suggestions on how it could be improved?

Nathan

Good, thanks for the update. Let me know if you run into anything else. I hear 0.2 is nearly out, congratulations!


Nathan

Nathan, thanks for the detailed reply. I have been able to get pre-conditions working for me. However, I am running into StackOverflowError with PreconditionLogic types (and, or, not) – I am creating few simple preconditions and wrapping them up in a AndPrecondition and then adding this AndPrecondition object to PreconditionContainer.


I will experiment a bit more and if I still have the issue I will post to a new discussion thread. Meanwhile, if you can give some clue please let me know.


Regards,

Shantanu

Precondition StackOverflowError issue is resolved. It was my oversight in code.


Regards,

Shantanu

Yes, I just pushed a 0.2 release of Clj-Liquibase with pre-condition feature and few more things. Thanks so much for all your support.


Regards,

Shantanu