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
test: use liquibase test harness #71
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we not going to maintain the maven and gradle build separately?
This is only a draft to discuss with the people from Liquibase on how to get the test harness to work. This PR is green, but I expected it to fail as it should include additional tests that should fail. |
cc @voulg |
|
||
public class CloudSpanner extends AbstractJdbcDatabase { | ||
public class CloudSpanner extends AbstractJdbcDatabase implements ICloudSpanner { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding this interface makes it a lot easier to create proxies of the Cloud Spanner database. This can be very useful when a change type only needs very small adjustments from the standard generator. Instead of copy-pasting the code from the standard generator into the Spanner specific generator, a proxy object can be used to only override specific behavior at that moment. See the new AddColumnGeneratorSpanner
for an example, where the only change is that we need it to generate ALTER TABLE ADD COLUMN columnName
instead of ALTER TABLE ADD columnName
.
if (statement instanceof UpdateStatement) { | ||
UpdateStatement updateStatement = (UpdateStatement) statement; | ||
if (updateStatement.getWhereClause() == null) { | ||
updateStatement.setWhereClause("TRUE"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Automatically add a WHERE TRUE
clause to update statements that fill the initial value of a column.
@@ -36,7 +37,7 @@ | |||
|
|||
@Override | |||
public boolean supports(Database database) { | |||
return (database instanceof CloudSpanner); | |||
return (database instanceof ICloudSpanner); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking against ICloudSpanner
(the interface) instead of CloudSpanner
ensures that changes that generate a proxy for the database are still considered Cloud Spanner databases.
|
||
@Override | ||
protected String generateSingleColumnSQL(AddColumnStatement statement, Database database) { | ||
InvocationHandler handler = (proxy, method, args) -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generate a proxy that will add the COLUMN
keyword in front of the column name when it is needed specifically for this change.
@mescanne Would you mind taking a second look? This should now work for both Gradle and Maven. Note that the Liquibase harness test suite can (currently) only be executed in IntelliJ, and that you must use Java 8 as the execution environment. @voulg and @kristyldatical Please feel free to add any comments / questions you might have as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thorough and great to be integrated with Liquibase's test harness. Some comments in there I'd be interested in your take on.
ICloudSpanner databaseWithColumnKeyword = (ICloudSpanner) Proxy.newProxyInstance(ICloudSpanner.class.getClassLoader(), | ||
new Class[] { ICloudSpanner.class }, | ||
handler); | ||
return super.generateSingleColumnSQL(statement, databaseWithColumnKeyword); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the proxied Database is only scoped to generateSingleColumnSQL. I believe this addition of COLUMN is only needed for the emulator but not real Spanner -- I did start looking into changing the emulator itself, which I think was acceptable.
We can use the proxy in the meantime, but I'd have a strong preference to use it in the long term.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the proxied Database is only scoped to
generateSingleColumnSQL
.
Correct, but the generateMultipleColumns
method will just call generateSingleColumnSQL
multiple times, so it covers that scenario as well.
I agree that the preference would be to not need the additional COLUMN
keyword, as it is indeed only needed for the emulator. Once that is fixed, we can remove this.
@@ -16,6 +16,7 @@ | |||
import liquibase.database.Database; | |||
import liquibase.exception.ValidationErrors; | |||
import liquibase.ext.spanner.CloudSpanner; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove the import of liquibase.ext.spanner.CloudSpanner as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, done.
|
||
-- To run Liquibase Harness test suite: | ||
-- 1. Start Cloud Spanner emulator with `docker run -p 9010:9010 -p 9020:9020 gcr.io/cloud-spanner-emulator/emulator` | ||
-- 2. Execute the script below to initialize a database on projects/my-project/instances/my-instance/databases/my-database |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the emulator automatically created my-instance and my-database? That was always a challenge using the emulator for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yeah, that's a good point. I currently have a locally built version of the JDBC driver that does that :-) So I forgot to put in the documentation that you need to do that manually for the time being. Once this PR has been merged and a new version has been released, it will no longer be necessary.
@@ -52,7 +52,7 @@ | |||
<java.version>1.8</java.version> | |||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||
|
|||
<liquibase.version>3.8.9</liquibase.version> | |||
<liquibase.version>4.3.1</liquibase.version> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gradle build uses 4.2.0. Is it worth maintaining Gradle separately from Maven? We can look to drop Maven -- it would be better from a CI perspective to use a single build system.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I would also prefer switching to just one. The only thing I (or someone else) has to configure is the release process to Maven central in the Gradle setup. That is currently only setup in the pom.
I'll look into that in a separate PR.
database.escapeColumnNameList( | ||
StringUtils.join(statement.getPrimaryKeyConstraint().getColumns(), ", "))); | ||
buffer.append(")"); | ||
if (statement.getPrimaryKeyConstraint() != null && statement.getPrimaryKeyConstraint().getColumns() != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pure style (and I'm not a Google style expert) question. Wouldn't this be preferred -->
// If no PK constraint, leave it as it is and let Spanner itself throw the error
if (statement.getPrimaryKeyConstraint() == null || statement.getPrimaryKeyConstraint().getColumns() == null) {
return res;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The (open source) style guide does not explicitly state so, but I agree that in this case it's certainly more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good - still want to get the emulator fixed, and the new JDBC auto-create-instance&database looks good, too.
Adds the standard Liquibase test harness suite tests to the Spanner Liquibase integration. The tests can be executed from the IDE (IntelliJ), and works both when the project has been imported as a Maven and as a Gradle project.
All tests that cover features that are supported by Cloud Spanner succeed (on the emulator). For this, a couple of functional changes have been made to the Spanner Liquibase integration:
AddColumnGenerator
has been added to ensure that the statement includes theCOLUMN
keyword (i.e. ALTER TABLE Foo ADD COLUMN Bar ...`). This is currently only required by the emulator, but as it is to be expected that many users will use the emulator to test their applications, it is important that the integration works as much as possible with the emulator.AddForeignKeyConstraintGenerator
has been added to ensure that anyOnUpdate
andOnDelete
specifications are ignored, as this is not supported by Cloud Spanner. This is also in line with the Liquibase behavior for other databases that do not support these keywords.CreateTableGenerator
for Spanner. An array-out-of-bounds exception could be thrown if a table without primary keys was being generated. Now it throws an understandable exception, as Cloud Spanner does not support tables without a primary key.This change also touches a large number of files as it adds the
ICloudSpanner
marker interface. Using a marker interface instead of a class for checking whether the database in use is Cloud Spanner is better, as it allows us to generate proxy objects when only one small feature of the default Liquibase implementation needs to be adjusted for a specific change. This strategy is applied to the newAddColumnGenerator
to add theCOLUMN
keyword to the generated SQL.