From 2e7fa17fc1018f4325051cb2f9af109d6d6777cb Mon Sep 17 00:00:00 2001 From: Tina Park Date: Mon, 19 Oct 2026 17:57:34 +0000 Subject: [PATCH] Add IMPORT/EXPORT support for expression indexes addition: Add IMPORT/EXPORT support for expression indexes --- .../teleport/spanner/ddl/IndexColumn.java | 21 ++++++++++++++++++- .../spanner/ddl/InformationSchemaScanner.java | 10 ++++++--- .../spanner/AvroSchemaToDdlConverterTest.java | 7 +++++++ .../ddl/InformationSchemaScannerTest.java | 4 ++-- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/v1/src/main/java/com/google/cloud/teleport/spanner/ddl/IndexColumn.java b/v1/src/main/java/com/google/cloud/teleport/spanner/ddl/IndexColumn.java index cf2811bc509..0be1aed7d61 100644 --- a/v1/src/main/java/com/google/cloud/teleport/spanner/ddl/IndexColumn.java +++ b/v1/src/main/java/com/google/cloud/teleport/spanner/ddl/IndexColumn.java @@ -40,6 +40,9 @@ public abstract class IndexColumn implements Serializable { @Nullable public abstract NullsOrder nullsOrder(); + @Nullable + public abstract String expression(); + public static IndexColumn create(String name, Order order, Dialect dialect) { return new AutoValue_IndexColumn.Builder().dialect(dialect).name(name).order(order).autoBuild(); } @@ -79,7 +82,12 @@ public enum NullsOrder { } public void prettyPrint(Appendable appendable) throws IOException { - appendable.append(quoteIdentifier(name(), dialect())).append(" ").append(order().title); + if (expression() == null) { + appendable.append(quoteIdentifier(name(), dialect())); + } else { + appendable.append("(").append(expression()).append(")"); + } + appendable.append(" ").append(order().title); if (nullsOrder() != null) { appendable.append(" NULLS ").append(nullsOrder().title); } @@ -112,6 +120,8 @@ public abstract static class Builder { abstract Builder nullsOrder(NullsOrder nullsOrder); + abstract Builder expression(String expression); + abstract IndexColumn autoBuild(); } @@ -222,6 +232,15 @@ public IndexColumnsBuilder nullsLast() { return this; } + public IndexColumnsBuilder expression(String expression) { + if (indexColumnBuilder == null) { + throw new IllegalArgumentException( + "Builder is missing. Call create method to initiate a builder first."); + } + indexColumnBuilder.expression(expression); + return this; + } + public ImmutableList build() { return columns.build(); } diff --git a/v1/src/main/java/com/google/cloud/teleport/spanner/ddl/InformationSchemaScanner.java b/v1/src/main/java/com/google/cloud/teleport/spanner/ddl/InformationSchemaScanner.java index e9f6a70bb6d..495ba0dd287 100644 --- a/v1/src/main/java/com/google/cloud/teleport/spanner/ddl/InformationSchemaScanner.java +++ b/v1/src/main/java/com/google/cloud/teleport/spanner/ddl/InformationSchemaScanner.java @@ -564,6 +564,7 @@ private void listIndexColumns( String indexLocalName = resultSet.getString(4); String indexType = resultSet.getString(5); String spannerType = resultSet.getString(6); + String expression = resultSet.getString(7); if (indexLocalName.equals("PRIMARY_KEY")) { IndexColumn.IndexColumnsBuilder pkBuilder = @@ -599,7 +600,10 @@ private void listIndexColumns( } IndexColumn.IndexColumnsBuilder indexColumnsBuilder = indexBuilder.columns().create().name(columnName); - boolean isStoring = resultSet.isNull(7); + if (expression != null) { + indexColumnsBuilder.expression(expression); + } + boolean isStoring = resultSet.isNull(8); if (isStoring) { indexColumnsBuilder.storing(); } else if (ordering == null) { @@ -631,7 +635,7 @@ Statement listIndexColumnsSQL() { case GOOGLE_STANDARD_SQL: return Statement.of( "SELECT t.table_schema, t.table_name, t.column_name, t.column_ordering, t.index_name," - + " t.index_type, t.spanner_type, t.ordinal_position " + + " t.index_type, t.spanner_type, t.expression, t.ordinal_position " + "FROM information_schema.index_columns AS t " + " WHERE t.table_schema NOT IN" + " ('INFORMATION_SCHEMA', 'SPANNER_SYS')" @@ -639,7 +643,7 @@ Statement listIndexColumnsSQL() { case POSTGRESQL: return Statement.of( "SELECT t.table_schema, t.table_name, t.column_name, t.column_ordering, t.index_name," - + " t.index_type, t.spanner_type, t.ordinal_position " + + " t.index_type, t.spanner_type, t.expression, t.ordinal_position " + "FROM information_schema.index_columns AS t " + "WHERE t.table_schema NOT IN " + "('information_schema', 'spanner_sys', 'pg_catalog') " diff --git a/v1/src/test/java/com/google/cloud/teleport/spanner/AvroSchemaToDdlConverterTest.java b/v1/src/test/java/com/google/cloud/teleport/spanner/AvroSchemaToDdlConverterTest.java index 582019e6fbd..0fd57d81f7d 100644 --- a/v1/src/test/java/com/google/cloud/teleport/spanner/AvroSchemaToDdlConverterTest.java +++ b/v1/src/test/java/com/google/cloud/teleport/spanner/AvroSchemaToDdlConverterTest.java @@ -240,6 +240,8 @@ public void simple() { + " \"spannerIndex_0\" : " + " \"CREATE INDEX `UsersByFirstName` ON `Users` (`first_name`)\"," + " \"spannerIndex_1\" : " + + " \"CREATE INDEX `UsersExpressionIndex` ON `Users` (concat(`first_name`, `last_name`))\"," + + " \"spannerIndex_2\" : " + " \"CREATE VECTOR INDEX `VI` ON `Users` (`Embeddings`) WHERE Embeddings IS NOT NULL OPTIONS (distance_type=\'COSINE\')\"," + " \"spannerForeignKey_0\" : " + " \"ALTER TABLE `Users` ADD CONSTRAINT `fk` FOREIGN KEY (`first_name`) " @@ -311,6 +313,7 @@ public void simple() { + " CONSTRAINT `ck` CHECK(`first_name` != 'last_name')," + " ) PRIMARY KEY (`id` ASC, `gen_id` ASC, `last_name` DESC)" + " CREATE INDEX `UsersByFirstName` ON `Users` (`first_name`)" + + " CREATE INDEX `UsersExpressionIndex` ON `Users` (concat(`first_name`, `last_name`))" + " CREATE VECTOR INDEX `VI` ON `Users` (`Embeddings`) WHERE Embeddings IS NOT NULL" + " OPTIONS (distance_type=\'COSINE\')" + " ALTER TABLE `Users` ADD CONSTRAINT `fk`" @@ -536,6 +539,9 @@ public void pgSimple() { + " : \"\\\"last_name\\\" ASC\", \"spannerIndex_0\" :" + " \"CREATE INDEX \\\"UsersByFirstName\\\" ON \\\"Users\\\" (\\\"first_name\\\")\", " + " \"spannerIndex_1\" : " + + " \"CREATE INDEX \\\"UsersExpressionIndex\\\" ON \\\"Users\\\" (concat(" + + "\\\"first_name\\\", \\\"last_name\\\"))\", " + + " \"spannerIndex_2\" : " + " \"CREATE INDEX \\\"VI\\\" ON \\\"Users\\\" USING ScaNN (\\\"Embeddings\\\") " + "WHERE \\\"Embeddings\\\" IS NOT NULL OPTIONS (distance_type=\'COSINE\')\"," + " \"spannerForeignKey_0\" : \"ALTER TABLE \\\"Users\\\" ADD CONSTRAINT \\\"fk\\\"" @@ -602,6 +608,7 @@ public void pgSimple() { + " PRIMARY KEY (\"id\", \"gen_id\", \"last_name\")" + " )" + " CREATE INDEX \"UsersByFirstName\" ON \"Users\" (\"first_name\")" + + " CREATE INDEX \"UsersExpressionIndex\" ON \"Users\" (concat(\"first_name\", \"last_name\"))" + " CREATE INDEX \"VI\" ON \"Users\" USING ScaNN (\"Embeddings\") WHERE \"Embeddings\" IS NOT NULL OPTIONS (distance_type=\'COSINE\')" + " ALTER TABLE \"Users\" ADD CONSTRAINT \"fk\" FOREIGN KEY (\"first_name\")" + " REFERENCES \"AllowedNames\" (\"first_name\")" diff --git a/v1/src/test/java/com/google/cloud/teleport/spanner/ddl/InformationSchemaScannerTest.java b/v1/src/test/java/com/google/cloud/teleport/spanner/ddl/InformationSchemaScannerTest.java index 7a649db2011..117d2f39d84 100644 --- a/v1/src/test/java/com/google/cloud/teleport/spanner/ddl/InformationSchemaScannerTest.java +++ b/v1/src/test/java/com/google/cloud/teleport/spanner/ddl/InformationSchemaScannerTest.java @@ -185,7 +185,7 @@ public void testListIndexColumnsSQL() { googleSQLInfoScanner.listIndexColumnsSQL().getSql(), equalToCompressingWhiteSpace( "SELECT t.table_schema, t.table_name, t.column_name, t.column_ordering, t.index_name, " - + "t.index_type, t.spanner_type, t.ordinal_position " + + "t.index_type, t.spanner_type, t.expression, t.ordinal_position " + "FROM information_schema.index_columns AS t " + " WHERE t.table_schema NOT IN" + " ('INFORMATION_SCHEMA', 'SPANNER_SYS')" @@ -195,7 +195,7 @@ public void testListIndexColumnsSQL() { postgresSQLInfoScanner.listIndexColumnsSQL().getSql(), equalToCompressingWhiteSpace( "SELECT t.table_schema, t.table_name, t.column_name, t.column_ordering, t.index_name, " - + "t.index_type, t.spanner_type, t.ordinal_position " + + "t.index_type, t.spanner_type, t.expression, t.ordinal_position " + "FROM information_schema.index_columns AS t " + "WHERE t.table_schema NOT IN " + "('information_schema', 'spanner_sys', 'pg_catalog') "