An example why violating Liferay's database rules can get you into deep trouble.
A community member posted the following plea on the Liferay Community Slack channel. I feel that it makes a great example, so I'm including it here.
Hi
I am migrating from Liferay 7.0 to Liferay 7.3 GA6
So I followed the below steps for DB migration
1 ./opt/liferay736/bundle/tools/portal-tools-db-upgrade-client
Inside the directory, I have updated the following files as per the document
1. app-server.properties
2. portal-upgrade-database.properties
3. portal-upgrade-ext.properties
2. I executed following command in terminal ./db_upgrade.sh -j "-Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.timezone=GMT -Xmx8000m"
3. Command started execution and after some period of time, I got the following errorcom.liferay.portal.kernel.upgrade.UpgradeException: java.sql.SQLException: Error on rename of './vmeeting/#sql-799_13e3' to './vmeeting/organization_' (errno: 150 - Foreign key constraint is incorrectly formed)
I compared Liferay 7.0 DB organization_ table with Liferay 7.3 GA6 table, in Liferay 7.3 GA6 we have few new columns with composite primary keys
I came to know, the upgrade tool is trying to alter the table by adding a new primary along with an old primary key (composite primary keys)
Since MySQL doesn't allow to change primary key we are getting that error
Here are log messages
Caused by: java.sql.SQLException: Error on rename of './vmeeting/#sql-799_13e3' to ./vmeeting/organization_' (errno: 150 - Foreign key constraint is incorrectly formed) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) at com.mysql.cj.jdbc.StatementImpl.executeUpdateInternal(StatementImpl.java:1355) at com.mysql.cj.jdbc.StatementImpl.executeLargeUpdate(StatementImpl.java:2128) at com.mysql.cj.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1264) at com.zaxxer.hikari.pool.ProxyStatement.executeUpdate(ProxyStatement.java:117) at com.zaxxer.hikari.pool.HikariProxyStatement.executeUpdate(HikariProxyStatement.java) at com.liferay.portal.dao.db.BaseDB.runSQL(BaseDB.java:337) at com.liferay.portal.dao.db.BaseDB.runSQL(BaseDB.java:303) at com.liferay.portal.kernel.dao.db.BaseDBProcess.runSQL(BaseDBProcess.java:71) at com.liferay.portal.kernel.upgrade.UpgradeProcess.removePrimaryKey(UpgradeProcess.java:642) at com.liferay.portal.kernel.upgrade.UpgradeCTModel._upgradeCTModel(UpgradeCTModel.java:112) at com.liferay.portal.kernel.upgrade.UpgradeCTModel.doUpgrade(UpgradeCTModel.java:47) at com.liferay.portal.kernel.upgrade.UpgradeProcess.upgrade(UpgradeProcess.java:104) ... 15 more
Please guide me
Thanks in advance
So Rule #1, as Olaf and I are both fond of saying, is Never look inside the Liferay database.
Rule #2 often goes unsaid, but is really much more important: Never, ever, under any circumstances, make changes to any Liferay tables, columns, indexes, etc.
And if there's a Rule #3, it is probably Go back and re-read rules 1 and 2.
As soon as someone decided it was a good idea to change the Liferay database, this team was doomed.
They didn't just add a column to Liferay, they created their own fork of Liferay. As soon as they did that, they were completely on their own.
And the thing is, Liferay provides facilities that the team could have used instead of adding columns and keys to the Liferay database...
If they had additional fields, they could have used custom fields. If they had a lot of them or they wanted to build some sort of compound key mechanism, they could have used a separate ServiceBuilder entity to hold the key. I can't tell you how many times I've had a CustomUser entity with a single primary key column named userId, where the Liferay table had its thing and my CustomUser table had its own thing, but they both shared the same userId key value.
Liferay never uses foreign keys at all. ServiceBuilder does not even have a facility to define or maintain foreign keys. They are shunned by the platform.
Why? Well, as in this case, they get in the way when Liferay needs to make table changes because of new capabilities introduced in later versions. Liferay long ago learned that the value from defining a foreign key was quickly lost when trying to apply data model changes during upgrades. Additionally, since Liferay uses a search index in conjunction with a database, cascading delete capabilities of foreign keys would not be reflected in corresponding index updates, leading the database and the index to be out of sync.
As a new developer to Liferay, you're not expected to understand why Liferay doesn't use foreign keys and why they don't use cascading deletes. In fact you're not expected to understand why Liferay has made any of the decisions they have made during the implementation of the product. And there will come a time when you do understand why Liferay has made certain decisions and you may disagree, sometimes vehemently.
In either case, you are expected to understand that Liferay puts a great deal of thought and analysis into the design and architecture of every piece of Liferay and that all decisions were made for a reason.
But that's okay, the platform is extensible so you can tweak it in ways to fall in line with your requirements. But when you take on this extensive of customization, realize that you are effectively forking Liferay and will be taking on all of the responsibilities of maintaining and updating the code.
Unfortunately the community member is left in a bad predicament. They can't move forward with the upgrade because Liferay can't handle the changes they've made. If I were on their team, I'd want to start from "fixing" their 7.0 environment to undo these changes and customizations that were introduced. Create an SB project to hold the additional organization details and then build an upgrade process that populates the table from the changed organization_ table, and it ends by dropping whatever table changes that were made to the table(s). Add a service wrapper or model listener to the Organization entity to handle cascade delete concerns to the new entity. Combine that with the coding effort to look for these values from the new SB entities, and basically get to a working 7.0 environment that is standard Liferay+extensions, no more heavy customizations.
Once that is done, then approach the 7.0 -> 7.3 upgrade. Since the 7.0 would be standard, the db upgrade tool will be able to handle it.
This is really the only solution. I mean, could you manually figure out how to work around this by temporarily dropping foreign keys, reducing the custom compound key back to a regular PK, then do the upgrade and re-apply all of this stuff? Sure, that might be possible. But do you really want to jump through all of those hoops again when 7.4 comes out? And again for 7.5? or 8.0? Or whatever Liferay wants to call it? Unless you truly fix the environment, you're looking at a hard road.

