Skip to content
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

4464 fix generated default column value using in PostgreSQL and Oracle for char/clob data types #5202

Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import liquibase.changelog.DatabaseChangeLog;
import liquibase.command.CommandResults;
import liquibase.command.CommandScope;
import liquibase.command.core.GenerateChangelogCommandStep;
import liquibase.command.core.SnapshotCommandStep;
import liquibase.command.core.UpdateCommandStep;
import liquibase.command.core.helpers.DbUrlConnectionArgumentsCommandStep;
Expand All @@ -15,12 +16,15 @@
import liquibase.dbtest.AbstractIntegrationTest;
import liquibase.exception.DatabaseException;
import liquibase.exception.ValidationFailedException;
import liquibase.executor.ExecutorService;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.sql.visitor.AbstractSqlVisitor;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.core.Index;
import org.junit.Assert;
import org.junit.Test;

import java.io.ByteArrayOutputStream;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Date;
Expand Down Expand Up @@ -213,4 +217,22 @@ public void verifyIndexIsCreatedWhenAssociatedWithPropertyIsSetAsForeignKey() th
}

}

@Test
public void testChangeLogGenerationForTableWithGeneratedColumn() throws Exception {
assumeNotNull(getDatabase());
clearDatabase();
String textToTest = "GENERATED ALWAYS AS (QTY * PRICE)";

Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", getDatabase()).execute(new RawSqlStatement(
String.format("CREATE TABLE GENERATED_COLUMN_TEST(QTY INT, PRICE INT, TOTALVALUE INT %s)", textToTest)));

ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CommandScope(GenerateChangelogCommandStep.COMMAND_NAME)
.addArgumentValue(DbUrlConnectionArgumentsCommandStep.DATABASE_ARG, getDatabase())
.setOutput(baos)
.execute();

assertTrue(baos.toString().contains(textToTest));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,27 @@ public void testGeneratedColumn() throws Exception {

}

@Test
filipelautert marked this conversation as resolved.
Show resolved Hide resolved
public void testGeneratedClobColumn() throws Exception {
assumeNotNull(getDatabase());
filipelautert marked this conversation as resolved.
Show resolved Hide resolved
assumeTrue(getDatabase().getDatabaseMajorVersion() >= 12);
clearDatabase();
String textToTest = "GENERATED ALWAYS AS ((surname || ', '::text) || forename) STORED";

Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", getDatabase())
.execute(new RawSqlStatement(String.format(
"CREATE TABLE generated_text_test (fullname text %s, surname text, forename text)",
textToTest)));

ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CommandScope(GenerateChangelogCommandStep.COMMAND_NAME)
.addArgumentValue(DbUrlConnectionArgumentsCommandStep.DATABASE_ARG, getDatabase())
.setOutput(baos)
.execute();

assertTrue(baos.toString().contains(textToTest));
}

@Test
public void validateUserCanOnlyAccessTablesFromSchemasAllowedToRead() throws Exception {
assumeNotNull(this.getDatabase());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public class ColumnSnapshotGenerator extends JdbcSnapshotGenerator {
private static final Pattern POSTGRES_NUMBER_VALUE_PATTERN = Pattern.compile(POSTGRES_NUMBER_VALUE_REGEX);

private static final String MYSQL_DEFAULT_GENERATED = "DEFAULT_GENERATED";
private static final String GENERATED_ALWAYS_AS = "GENERATED ALWAYS AS ";
private static final String YES_VALUE = "YES";
private static final String IS_GENERATED_COLUMN = "IS_GENERATEDCOLUMN";

private final ColumnAutoIncrementService columnAutoIncrementService = new ColumnAutoIncrementService();

Expand Down Expand Up @@ -522,7 +525,7 @@ protected Object readDefaultValue(CachedRow columnMetadataResultSet, Column colu
if ((database instanceof OracleDatabase) && (columnMetadataResultSet.get(COLUMN_DEF_COL) == null)) {
columnMetadataResultSet.set(COLUMN_DEF_COL, columnMetadataResultSet.get("DATA_DEFAULT"));

if ((columnMetadataResultSet.get(COLUMN_DEF_COL) != null) && "NULL".equalsIgnoreCase((String)
if ((columnMetadataResultSet.get(COLUMN_DEF_COL) != null) && StringUtil.equalsWordNull((String)
columnMetadataResultSet.get(COLUMN_DEF_COL))) {
columnMetadataResultSet.set(COLUMN_DEF_COL, null);
}
Expand All @@ -533,10 +536,10 @@ protected Object readDefaultValue(CachedRow columnMetadataResultSet, Column colu
return new DatabaseFunction((String) columnDef);
}

if ("YES".equals(columnMetadataResultSet.get("VIRTUAL_COLUMN"))) {
if (YES_VALUE.equals(columnMetadataResultSet.get("VIRTUAL_COLUMN"))) {
Object virtColumnDef = columnMetadataResultSet.get(COLUMN_DEF_COL);
if ((virtColumnDef != null) && !"null".equals(virtColumnDef)) {
columnMetadataResultSet.set(COLUMN_DEF_COL, "GENERATED ALWAYS AS (" + virtColumnDef + ")");
if ((virtColumnDef != null) && !StringUtil.equalsWordNull(virtColumnDef.toString())) {
columnMetadataResultSet.set(COLUMN_DEF_COL, GENERATED_ALWAYS_AS + "(" + virtColumnDef + ")");
}
}

Expand All @@ -560,7 +563,7 @@ protected Object readDefaultValue(CachedRow columnMetadataResultSet, Column colu

if ((database instanceof AbstractDb2Database)
&& ((columnMetadataResultSet.get(COLUMN_DEF_COL) != null)
&& "NULL".equalsIgnoreCase((String) columnMetadataResultSet.get(COLUMN_DEF_COL)))) {
&& StringUtil.equalsWordNull((String) columnMetadataResultSet.get(COLUMN_DEF_COL)))) {
columnMetadataResultSet.set(COLUMN_DEF_COL, null);
}

Expand All @@ -581,9 +584,9 @@ protected Object readDefaultValue(CachedRow columnMetadataResultSet, Column colu

columnMetadataResultSet.set(COLUMN_DEF_COL, defaultValue);

if ("YES".equals(columnMetadataResultSet.get("IS_GENERATEDCOLUMN"))) {
if (YES_VALUE.equals(columnMetadataResultSet.get(IS_GENERATED_COLUMN))) {
Object virtColumnDef = columnMetadataResultSet.get(COLUMN_DEF_COL);
if ((virtColumnDef != null) && !"null".equals(virtColumnDef)) {
if ((virtColumnDef != null) && !StringUtil.equalsWordNull(virtColumnDef.toString())) {
columnMetadataResultSet.set(COLUMN_DEF_COL, "COMPUTE (" + virtColumnDef + ")");
}
}
Expand Down Expand Up @@ -630,12 +633,14 @@ private void readDefaultValueForPostgresDatabase(CachedRow columnMetadataResultS
columnMetadataResultSet.set(COLUMN_DEF_COL, defaultValue);
}

if ("YES".equals(columnMetadataResultSet.get("IS_GENERATEDCOLUMN"))) {
if (YES_VALUE.equals(columnMetadataResultSet.get(IS_GENERATED_COLUMN))) {
Object virtColumnDef = columnMetadataResultSet.get(COLUMN_DEF_COL);
if ((virtColumnDef != null) && !"null".equals(virtColumnDef)) {
if (virtColumnDef != null && !StringUtil.equalsWordNull(virtColumnDef.toString()) &&
!String.valueOf(virtColumnDef).startsWith(GENERATED_ALWAYS_AS) // to avoid duplication
) {
// Column type added on PG 12 and until PG 15 only STORED mode is supported and jdbc metadata just say "YES" or "NO"
// VIRTUAL support is yet to be implemented, so we need to come back here if that happens and see what needs to be changed
columnMetadataResultSet.set(COLUMN_DEF_COL, "GENERATED ALWAYS AS " + virtColumnDef + " STORED");
columnMetadataResultSet.set(COLUMN_DEF_COL, GENERATED_ALWAYS_AS + virtColumnDef + " STORED");
}
}
}
Expand Down
13 changes: 9 additions & 4 deletions liquibase-standard/src/main/java/liquibase/util/SqlUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@

import java.math.BigDecimal;
import java.sql.Types;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static java.util.Locale.ENGLISH;
import static java.util.Locale.US;

public abstract class SqlUtil {
Expand Down Expand Up @@ -114,6 +112,13 @@ public static Object parseValue(Database database, Object val, DataType type) {
}
}

if ((database instanceof PostgresDatabase || database instanceof OracleDatabase) &&
(liquibaseDataType instanceof CharType || liquibaseDataType instanceof ClobType) &&
stringVal.toUpperCase(ENGLISH).startsWith("GENERATED ALWAYS AS ")
) {
return new DatabaseFunction(stringVal);
}

boolean strippedSingleQuotes = false;
if (stringVal.startsWith("'") && stringVal.endsWith("'")) {
stringVal = stringVal.substring(1, stringVal.length() - 1);
Expand Down