Working with liquibase I've found that it's possible to define properties, and use them as defaultValue for columns.
Everything is ok if you use pre-made functions, but how to do when you have some custom SQL that produces some data, to be used in the same manner?
I tried to do something like this:
<property name="myFunc" value="SELECT CURRENT_DATE + time '08:00:00' + (random() * interval '3 hours')::time" dbms="postgresql"/>
...
<column name="selected_pickup_date" type="datetime" defaultValueComputed="${myFunc}" remarks="Selected from customer during order creation">
<constraints nullable="false"/>
</column>
But it didn't work! The error code showed that liquibase was just placing my SQL string as the column's default value.
SQL wasn't parsed as I expected.
How to proceed to get the query result used as default value?
After many attempts, I understood that Liquibase doesn't parse SQL in properties, probably it only executes functions and places their result in the property value, and the placeholder ${xxx} is replaced with the function's output.
Once a new row is added to the table, and the column with defaultValue is empty, the property value is read and used in place of NULL.
I tried many solutions and luckily the last one worked:
Take the SQL and create a new function in a sql file, in my case it was simple:
DROP FUNCTION IF EXISTS fixed_date_rand_time;
CREATE FUNCTION fixed_date_rand_time() RETURNS timestamp
AS $$ SELECT CURRENT_DATE + time '08:00:00' + (random() * interval '3 hours')::time $$
LANGUAGE SQL;
Import the sql before other changelogs
<include relativeToChangelogFile="true" file="func.sql" />
Now..you can create the property! This time calling the function instead of the SQL string
<property name="myFunc" value="fixed_date_rand_time()" dbms="postgresql"/>
...
<changeSet id="20201026174848-1" author="jhipster">
<createTable tableName="my_table">
<column name="selected_pickup_date" type="datetime" defaultValueComputed="${myFunc}" remarks="Selected from customer during order creation">
<constraints nullable="true"/>
</column>
</createTable>
</changeSet>
...
Et VoilĂ ! It works now!
The very-nice thing is that properties can be used with context too, allowing you to bypass the new function when outside of local test environment:
<property name="myFunc" value="fixed_date_rand_time()" dbms="postgresql" context="test"/>
<property name="myFunc" value="NULL" dbms="postgresql" context="!test"/>
Not perfect, it works only with nullable columns, but there must a solution.
Finally the database is loaded with correct values automatically, taking away the hassle of updating dates everyday, to have everything work as expected.
I hope that this solution can help somebody, who maybe has the will to refine it a bit :-)
My Liquibase version is 3.10, I cannot update to the latest due to other dependencies... maybe the problem has been addressed there.
I don't exclude that the feature has been disabled intentionally in favour of the pro version.
Have a nice day
Related
I'm currently developing a prototype for a Spring based plugin system. The idea is that plugins can use JPA entities and a liquibase changelog to maintain the database structure. In order to separate the tables created by plugins from the tables of the core system the plugins should be forced to use a prefix for table name.
For JPA/Hibernate that can be easily archived by using a naming strategy. But I've found no way to archive that for the liquibase changeset.
For example the plugin defines a changelog like follows
<changeSet id="2015-03-17-00-01" author="foo">
<createTable tableName="fooentity">
<column name="id" type="INT">
<constraints primaryKey="true" nullable="false" />
</column>
<column name="name" type="VARCHAR(100)">
<constraints nullable="false" />
</column>
</createTable>
</changeSet>
The table should be created with name "plugin_fooentity". The plugin itself should not know anything about the prefix since the prefix is given by the plugin/core system.
Would be great if someone can give me a hint for a possible solution.
Maybe you can use modifySql for this?
You would have to copy this to all changesets that you define but it should be possible.
It has a subtag called regExpReplace which you could use to define a general term like create table (\w*?) .* and replace this with create table plugin_$1.
For me it worked like this:
Using the modifySql statement
I created 2 properties:
<property name="table.prefix" value="TBL_"/>
<property name="schema.name" value="PUBLIC"/>
Then added the following statements:
<modifySql>
<regExpReplace replace="CREATE\ TABLE\ ${schema.name}.([\w]*)\ (.*)" with="CREATE TABLE ${schema.name}.${table.prefix}$1 $2"/>
</modifySql>
<modifySql>
<regExpReplace replace="CREATE\ UNIQUE\ INDEX\ ${schema.name}.([\w]*)\ ON\ PUBLIC.([\w]*)\((.*)\)" with="CREATE UNIQUE INDEX ${schema.name}.$1 ON ${schema.name}.${table.prefix}$2($3)"/>
</modifySql>
<modifySql>
<regExpReplace replace="CREATE\ INDEX\ ${schema.name}.([\w]*)\ ON\ ${schema.name}.([\w]*)\((.*)\)" with="CREATE INDEX ${schema.name}.$1 ON ${schema.name}.${table.prefix}$2($3)"/>
</modifySql>
How do you set the default value of a date column to be "now" in UTC format? I think the answer involves the defaultValueComputed attribute on the column element.
The documentation states:
defaultValueComputed A value that is returned from a function or
procedure call. This attribute will contain the function to call.
What langauge is the function referred to supposed to be written in? Java? Is the function supposed to be the database vendor -specific date function I want to use? Is there any more documentation I can read on this topic?
Maybe this topic in the liquibase forum will help?
I think defaultValueComputed will take a database specific function to express "now". In mySQL it would be CURRENT_TIMESTAMP so it could look like this:
<createTable tableName="D_UserSession">
<column name="ts" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
</createTable>
(Copied from the forum post.)
This should be:
<property name="now" value="now()" dbms="mysql,h2"/>
<property name="now" value="current_timestamp" dbms="postgresql"/>
<property name="now" value="sysdate" dbms="oracle"/>
<property name="now" value="getdate()" dbms="mssql"/>
<changeSet author="me" id="sample_usage_demo">
<addColumn schemaName= "dbo" tableName="demo_table" >
<column name="demo_column" type="datetime" defaultValueDate="${now}">
<constraints nullable="false" />
</column>
</addColumn>
</changeSet>
In MySQL, to use a DATETIME column with fractions of second like DATETIME(6) (microseconds precision), use default value of NOW(6) (caution: CURRENT_TIMESTAMP(6) for some reason produces an error with me using liquibase 3.5.3):
<column name="created_at" type="DATETIME(6)" defaultValueComputed="NOW(6)" >
<constraints nullable="false" />
</column>
Note that the value will be stored internally in UTC, but read using the server's timezone settings (##global.time_zone, ##session.time_zone).
This works with SQlite:
<column name="last_updated_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="false"/>
</column>
Adding '$now' didn't work for me. I am using SQlite as the DB.
This worked for me:
<property name="now" value="UNIX_TIMESTAMP()" dbms="mysql"/>
<column name="ts" type="timestamp" valueDate="${now}"/>
I found it thanks to this answer: https://stackoverflow.com/a/9100388/3107952
You should probably use timestamp with time zone as that will keep the timestamps in UTC as opposed to local server time, which might be problem if you have a multi-region setup.
You can readmore about the timezone part on this Stack Overflow post.
databaseChangeLog:
- changeSet:
id: 007
author: joe
changes:
- addColumn:
tableName: my_table
columns:
- column:
name: created_at
type: timestamp with time zone
- addDefaultValue:
columnName: created_at
defaultValueComputed: now()
tableName: my_table
Due to the fact, that the requested timezone UTC is not mentioned in all of the answers here, I'd like to state another set of solutions for different database vendors.
Especially, the current answers here does not state the correct solution for Oracle.
<changeSet logicalFilePath="my_changeset.xml"
id="1"
author="me"
dbms="mariadb,h2">
<addColumn tableName="MY_TABLE">
<column name="MY_ZONED_DATE_TIME_COLUMN"
type="timestamp(6)"
defaultValueComputed="now()">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
<changeSet logicalFilePath="my_changeset.xml"
id="1"
author="me"
dbms="postgresql">
<addColumn tableName="MY_TABLE">
<column name="MY_ZONED_DATE_TIME_COLUMN"
type="timestamp(6)"
defaultValueComputed="timezone('UTC', now())">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
<changeSet logicalFilePath="my_changeset.xml"
id="1"
author="me"
dbms="oracle">
<addColumn tableName="MY_TABLE">
<column name="MY_ZONED_DATE_TIME_COLUMN"
type="timestamp(6)"
defaultValueComputed="sys_extract_utc(systimestamp)">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
(One may use properties, like showed in Dominika's answer, but we had some really bad experiences with properties in liquibase.)
Summary:
now() is fine for MariaDB, MySql and H2v
now() is not completely fine for H2, I just got a correct result starting the h2 database with UTC like jdbc:h2:mem:./my_database;TIME ZONE=UTC (h2database in version 2.x needed). Then now() is working for sure.
I'm not sure about current_timestamp on PostgreSQL, but timezone('UTC', now()) works. :)
In Oracle, sysdate (mentioned in a few other answers here) is not enough, see as well "How to get UTC value for SYSDATE on Oracle", but sys_extract_utc(systimestamp) does the trick.
I used function the database vendor.
For Oracle it`s a sysdate:
<column name="create_date" type="DATETIME" valueDate="sysdate" defaultValueComputed="sysdate" />
As liquibase is common changelog for any database, to make it generic you should not depend on any specific database like oracle, postegres, mysql instead it should be generic enough to work for any/every database.
Below is how it should be implemented :
<column name="time" type="${type.datetime}" defaultValueComputed="${column.datetime.defaultValue}"/>
This should work for all databases, for oracle, it inserts SYSTIMESTAMP as DATA_DEFAULT.
I am pretty new to NHibernate and I'm trying to create a mapping file to extend a data model project. The particular table I am mapping is called AttributeDef in the following image, the column ControlType actually relates to a lookup in the table called Code (yes, I know - there should be an FK constraint but this sort of thing is quite common in this project so please ignore the obvious howlers and focus on the question). In most cases tables which reference Code also have a column which contains the ID from the table CodeSet as the key in Code is, almost inevitably, a composite key, but not in this case presumably because the original author figured "Hey they're all from the same codeset so what's the point?".
Now if there was a column in AttributeDef which contained the CodeSet value then the mapping wouldn't be much of a problem. The mapping for the Code entity looks like this:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Activus.DataModel" namespace="Activus.DataModel">
<class name="Code" table="Code" mutable="false">
<composite-id name="CompositeCodeId" class="CompositeCodeId">
<key-property name="CodeId" column="CodeId"/>
<key-property name="CodeSet" column="CodeSet"/>
</composite-id>
<property name="Description" column="Description" type="string" length="100" not-null="true"/>
<property name="ExternalRef" column ="ExternalRef" type ="string" length ="256" not-null ="true"/>
<property name="InternalRef" column ="InternalRef" type ="string" not-null ="false"/>
<many-to-one name="CodeSet" class="CodeSet" column="CodeSet" not-null="true" insert="false" update="false"/>
</class>
</hibernate-mapping>
Therefore if there was a column in AttributeDef for the CodeSet value (notionally called FormControlCodeSet in this example) then in my AttributeDef mapping file I would include
<many-to-one name="ControlType" class="Code" not-null="false">
<column name="ControlType" />
<column name="FormControlCodeSet" />
</many-to-one>
And all should be well. The problem is that to add that column to AttributeDef would be very invasive as I would then have to make a LOT of other changes to accommodate this and that would increase the risk factor of the change I'm making to a point which might well be unacceptable (from the client's point of view given their time frame).
So, much as it's a horrible, horrible thing to contemplate, Is it possible to substitute the line
<column name="FormControlCodeSet" />
With a (whisper it) hard coded value? That value hasn't changed in a decade and isn't likely to anytime soon but it would get us past this change and would highlight the need to scope out and implement the inclusion of the extra column. I recognise how dreadful this is but unfortunately a lot of this database isn't really that well suited to ORM despite it's being shoe-horned in anyway.
You do not have to whisper requirements, when working with NHiberante. Because cooperation with legacy DB (i.e. fixed DB schema) is pretty standard, NHibernate does support many different settings.
One of these is a pair of (substitutional) settings: "column" vs "formula". The first takes the column as is, the second could do whatever we need. Take column, columns, pass a constant. So:
<many-to-one name="ControlType" class="Code" not-null="false">
<column name="ControlType" />
<!-- let's replace this -->
<!--<column name="FormControlCodeSet" />-->
<!-- 1) with a constant number 123-->
<formula>123</formula>
<!-- 2) with a constant string 'constantString' -->
<formula>'constantString'</formula>
</many-to-one>
Not sure if the FormControlCodeSet constant should be int or string, but as shown above, either option is possible.
I'm very new to NHibernate so this may be fairly trivial, but searching is leaving me confused.
I have an AddOnAmount table as follows:
AddOnID | AddOnTypeID | Period | Amount
where AddOnTypeID is a FK. The rows have a unique constraint on AddOnTypeID and Period.
The mapping looks like this:
<id name="Id" column="AddOnId" type="long">
<generator class="native" />
</id>
<many-to-one name="AddOnType" column="AddOnTypeID" class="AddOnTypeStatic" not-null="true" />
<property name="Period" />
etc.
The AddOnTypeStatic class/table just has an Id, which is the numerical value stored on the table, and a descriptive Name.
I'm trying to write a query that will search by AddOnTypeId and Period, so I can validate the existence (or not) of a row before attempting to add a duplicate from my front end, but I'm not sure how to do that as the AddOnAmountStatic class has a subclass whereas the table has just an Id.
So far I've written:
public AddOnAmountStatic FindByAddOnTypeAndPeriod(long addOnType, string period)
{
return FindOne(CreateCriteria()
.Add(Restrictions.Eq("AddOnTypeId", addOnType))
.Add(Restrictions.Eq("Period", period))
.SetCacheable(true));
}
which does not work, as AddOnTypeId isn't a property of AddOnAmountStatic. Not sure how to access the property of the subclass in this context.
My mapping works, as I've been using it so far with no problems.
Solved my problem - it was simple but thought I'd add the solution here in case it helps anyone else.
I'd been thinking of constructing the query from the table's perspective (i.e., with the AddOnTypeID), whereas what I should have done is look at it from the entity's perspective. In other words, I just needed to pass in an AddOnTypeStatic object.
What I did was take my AddOnTypeID parameter, check it exists through NHibernate (returning either an AddOnTypeStatic object or null) then passed that through to the original query. Final query is simply
return FindOne(CreateCriteria()
.Add(Restrictions.Eq("AddOnType", addOnType))
.(Restrictions.Eq("Period", period))
.SetCacheable(true));
Hope this helps another newbie!
As per the StackOverflow question 'NHibernate and sql timestamp columns as version', I use the following mapping:
<version name="RowNumber" generated="always" unsaved-value="null" type="BinaryBlob">
<column name="RowNumber" not-null="false" sql-type="timestamp" />
</version>
<property name="CreateDate" column="CreateDate" type="DateTime" update="false" insert="false" />
(Other properties after this last).
But when I run my ASP.MVC app I get:
[Path]\Clients.hbm.xml(7,90): XML validation error: The element 'urn:nhibernate-mapping-2.2:version' cannot contain child element 'urn:nhibernate-mapping-2.2:column' because the parent element's content model is empty.
But as far as I can see 2.2 is the latest version of the mapping, so how can anyone put a column element inside the version element?
Sorry if this is really basic,
In case anyone else has this problem:
It works as Ayende Rahien specifies in this blog on NHibernate - but only (AFAIK) on version 2.1.n; I was using 2.0.n. I also think you need the object's field/property to be byte[], not System.Linq.Binary as that type has no default constructor (but I am not sure about this - I seemed to have to do this)
Example (excuse the names):
<version name="RowKludge" type="BinaryBlob" generated="always" unsaved-value="null" >
<column name="RowNumber"
not-null="false"
sql-type="timestamp"/>
</version>
A SQL server 'timestamp' is not your regular timestamp, hence the requirement that the type should be a binary blob.
Note that if you do migrate you will need to change the NHibernate configuration in Web/App config - most tutorials currently available seem to be for v.2.0 (or earlier) - you need an uptodate reference or tutorial for 2.1
A quick look in the documentation reveals that your mapping is not correct. It should be something like this:
<version name="RowNumber" column="RowNumber"
generated="always" unsaved-value="null"
type="Timestamp" />
Best Regards,
Oliver Hanappi