Skip to content

Commit eea1b22

Browse files
committed
feat: Add handling for updating stored columns on indexes
1 parent 3c0a13d commit eea1b22

7 files changed

Lines changed: 204 additions & 19 deletions

File tree

src/main/java/com/google/cloud/solutions/spannerddl/diff/DdlDiff.java

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import java.util.Objects;
5454
import java.util.Set;
5555
import java.util.TreeMap;
56+
import java.util.function.Function;
5657
import java.util.stream.Collectors;
5758
import org.slf4j.Logger;
5859
import org.slf4j.LoggerFactory;
@@ -126,12 +127,23 @@ public List<String> generateDifferenceStatements(Map<String, Boolean> options)
126127

127128
if (!indexDifferences.entriesDiffering().isEmpty()
128129
&& !options.get(ALLOW_RECREATE_INDEXES_OPT)) {
129-
throw new DdlDiffException(
130-
"At least one Index differs, and "
131-
+ ALLOW_RECREATE_INDEXES_OPT
132-
+ " is not set.\n"
133-
+ "Indexes: "
134-
+ Joiner.on(", ").join(indexDifferences.entriesDiffering().keySet()));
130+
131+
long numChangedIndexes =
132+
indexDifferences.entriesDiffering().values().stream()
133+
// Check if the indexes only difference is the STORING clause
134+
.map((diff) -> DdlDiff.checkIndexDiffOnlyStoring(diff))
135+
// Filter out those who have only changed Storing clauses
136+
.filter((v) -> !v)
137+
.count();
138+
139+
if (numChangedIndexes > 0) {
140+
throw new DdlDiffException(
141+
"At least one Index differs, and "
142+
+ ALLOW_RECREATE_INDEXES_OPT
143+
+ " is not set.\n"
144+
+ "Indexes: "
145+
+ Joiner.on(", ").join(indexDifferences.entriesDiffering().keySet()));
146+
}
135147
}
136148

137149
if (!constraintDifferences.entriesDiffering().isEmpty()
@@ -171,9 +183,13 @@ public List<String> generateDifferenceStatements(Map<String, Boolean> options)
171183
}
172184

173185
// Drop modified indexes that need to be re-created...
174-
for (String indexName : indexDifferences.entriesDiffering().keySet()) {
175-
LOG.info("Dropping changed index for re-creation: {}", indexName);
176-
output.add("DROP INDEX " + indexName);
186+
for (ValueDifference<ASTcreate_index_statement> difference :
187+
indexDifferences.entriesDiffering().values()) {
188+
if (!checkIndexDiffOnlyStoring(difference)) {
189+
LOG.info(
190+
"Dropping changed index for re-creation: {}", difference.leftValue().getIndexName());
191+
output.add("DROP INDEX " + difference.leftValue().getIndexName());
192+
}
177193
}
178194

179195
// Drop deleted constraints
@@ -256,8 +272,36 @@ public List<String> generateDifferenceStatements(Map<String, Boolean> options)
256272
// Re-create modified indexes...
257273
for (ValueDifference<ASTcreate_index_statement> difference :
258274
indexDifferences.entriesDiffering().values()) {
259-
LOG.info("Re-creating changed index: {}", difference.leftValue().getIndexName());
260-
output.add(difference.rightValue().toStringOptionalExistClause(false));
275+
276+
if (checkIndexDiffOnlyStoring(difference)) {
277+
LOG.info("Updating STORING clause on index: {}", difference.leftValue().getIndexName());
278+
Map<String, String> originalStoredCols =
279+
difference.leftValue().getStoredColumnNames().stream()
280+
.collect(Collectors.toMap(Function.identity(), Function.identity()));
281+
Map<String, String> newStoredCols =
282+
difference.rightValue().getStoredColumnNames().stream()
283+
.collect(Collectors.toMap(Function.identity(), Function.identity()));
284+
285+
MapDifference<String, String> colDiff = Maps.difference(originalStoredCols, newStoredCols);
286+
287+
for (String deletedCol : colDiff.entriesOnlyOnLeft().values()) {
288+
output.add(
289+
"ALTER INDEX "
290+
+ difference.leftValue().getIndexName()
291+
+ " DROP STORED COLUMN "
292+
+ deletedCol);
293+
}
294+
for (String deletedCol : colDiff.entriesOnlyOnRight().values()) {
295+
output.add(
296+
"ALTER INDEX "
297+
+ difference.leftValue().getIndexName()
298+
+ " ADD STORED COLUMN "
299+
+ deletedCol);
300+
}
301+
} else {
302+
LOG.info("Re-creating changed index: {}", difference.leftValue().getIndexName());
303+
output.add(difference.rightValue().toStringOptionalExistClause(false));
304+
}
261305
}
262306

263307
// Create new constraints.
@@ -329,6 +373,16 @@ public List<String> generateDifferenceStatements(Map<String, Boolean> options)
329373
return output.build();
330374
}
331375

376+
/** Verify that different indexes are only different in STORING clause. */
377+
private static boolean checkIndexDiffOnlyStoring(
378+
ValueDifference<ASTcreate_index_statement> indexDifference) {
379+
380+
return indexDifference
381+
.leftValue()
382+
.getDefinitionWithoutStoring()
383+
.equals(indexDifference.rightValue().getDefinitionWithoutStoring());
384+
}
385+
332386
@VisibleForTesting
333387
static List<String> generateAlterTableStatements(
334388
ASTcreate_table_statement left, ASTcreate_table_statement right, Map<String, Boolean> options)

src/main/java/com/google/cloud/solutions/spannerddl/parser/ASTcreate_index_statement.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121

2222
import com.google.cloud.solutions.spannerddl.diff.AstTreeUtils;
2323
import com.google.common.base.Joiner;
24+
import java.util.Collections;
25+
import java.util.List;
26+
import java.util.stream.Collectors;
2427

2528
public class ASTcreate_index_statement extends SimpleNode
2629
implements Comparable<ASTcreate_index_statement> {
@@ -65,6 +68,31 @@ public String toStringOptionalExistClause(boolean includeExists) {
6568
interleave);
6669
}
6770

71+
/**
72+
* Gets the Index definition without the Stored column clause (and any EXISTS clause).
73+
*
74+
* <p>Used for comparing indexes for compatible changes.
75+
*/
76+
public String getDefinitionWithoutStoring() {
77+
validateChildren();
78+
ASTindex_interleave_clause interleave =
79+
getOptionalChildByType(children, ASTindex_interleave_clause.class);
80+
81+
return Joiner.on(" ")
82+
.skipNulls()
83+
.join(
84+
"CREATE",
85+
getOptionalChildByType(children, ASTunique_index.class),
86+
getOptionalChildByType(children, ASTnull_filtered.class),
87+
"INDEX",
88+
getIndexName(),
89+
"ON",
90+
getChildByType(children, ASTtable.class),
91+
getChildByType(children, ASTcolumns.class),
92+
(interleave != null ? "," : null),
93+
interleave);
94+
}
95+
6896
private void validateChildren() {
6997
for (Node child : children) {
7098
switch (child.getId()) {
@@ -83,6 +111,14 @@ private void validateChildren() {
83111
}
84112
}
85113

114+
public List<String> getStoredColumnNames() {
115+
ASTstored_column_list cols = getOptionalChildByType(children, ASTstored_column_list.class);
116+
if (cols == null) {
117+
return Collections.emptyList();
118+
}
119+
return cols.getStoredColumns().stream().map(Object::toString).collect(Collectors.toList());
120+
}
121+
86122
@Override
87123
public int compareTo(ASTcreate_index_statement other) {
88124
return this.getIndexName().compareTo(other.getIndexName());

src/main/java/com/google/cloud/solutions/spannerddl/parser/ASTstored_column_list.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.google.cloud.solutions.spannerddl.diff.AstTreeUtils;
2020
import com.google.common.base.Joiner;
21+
import java.util.List;
2122

2223
public class ASTstored_column_list extends SimpleNode {
2324

@@ -31,8 +32,10 @@ public ASTstored_column_list(DdlParser p, int id) {
3132

3233
@Override
3334
public String toString() {
34-
return "STORING ( "
35-
+ Joiner.on(", ").join(AstTreeUtils.getChildrenAssertType(children, ASTstored_column.class))
36-
+ " )";
35+
return "STORING ( " + Joiner.on(", ").join(getStoredColumns()) + " )";
36+
}
37+
38+
public List<ASTstored_column> getStoredColumns() {
39+
return AstTreeUtils.getChildrenAssertType(children, ASTstored_column.class);
3740
}
3841
}

src/test/java/com/google/cloud/solutions/spannerddl/diff/DdlDiffTest.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,53 @@ public void diffCreateIndexDifferentExists() throws DdlDiffException {
440440
.isEmpty();
441441
}
442442

443+
@Test
444+
public void diffCreateIndexOnlyStoring() throws DdlDiffException {
445+
assertThat(
446+
DdlDiff.build(
447+
"CREATE INDEX myindex ON mytable ( col1 ) STORING (col2, col3);",
448+
"CREATE INDEX myindex ON mytable ( col1 ) STORING (col3, col4);")
449+
.generateDifferenceStatements(
450+
ImmutableMap.of(
451+
ALLOW_RECREATE_INDEXES_OPT,
452+
false,
453+
ALLOW_DROP_STATEMENTS_OPT,
454+
false,
455+
ALLOW_RECREATE_CONSTRAINTS_OPT,
456+
false)))
457+
.containsExactly(
458+
"ALTER INDEX myindex DROP STORED COLUMN col2",
459+
"ALTER INDEX myindex ADD STORED COLUMN col4");
460+
}
461+
462+
@Test
463+
public void diffCreateIndexNotOnlyStoringRecreates() throws DdlDiffException {
464+
assertThat(
465+
DdlDiff.build(
466+
"CREATE UNIQUE INDEX myindex ON mytable ( col1 ) STORING (col2, col3);",
467+
"CREATE INDEX myindex ON mytable ( col1 ) STORING (col3, col4);")
468+
.generateDifferenceStatements(
469+
ImmutableMap.of(
470+
ALLOW_RECREATE_INDEXES_OPT,
471+
true,
472+
ALLOW_DROP_STATEMENTS_OPT,
473+
false,
474+
ALLOW_RECREATE_CONSTRAINTS_OPT,
475+
false)))
476+
.containsExactly(
477+
"DROP INDEX myindex",
478+
"CREATE INDEX myindex ON mytable ( col1 ASC ) STORING ( col3, col4 )");
479+
}
480+
481+
@Test
482+
public void diffCreateIndexNotOnlyStoringThrows() throws DdlDiffException {
483+
getDiffCheckDdlDiffException(
484+
"CREATE UNIQUE INDEX myindex ON mytable ( col1 ) STORING (col2, col3);",
485+
"CREATE INDEX myindex ON mytable ( col1 ) STORING (col3, col4);",
486+
false,
487+
"At least one Index differs, and allowRecreateIndexes is not set");
488+
}
489+
443490
private static void getDiffCheckDdlDiffException(
444491
String originalDdl, String newDdl, boolean allowDropStatements, String exceptionContains) {
445492
try {
@@ -458,7 +505,9 @@ private static List<String> getDiff(
458505
ALLOW_RECREATE_CONSTRAINTS_OPT,
459506
true,
460507
ALLOW_DROP_STATEMENTS_OPT,
461-
allowDropStatements));
508+
allowDropStatements,
509+
ALLOW_RECREATE_INDEXES_OPT,
510+
false));
462511
}
463512

464513
@Test

src/test/resources/expectedDdlDiff.txt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ ALTER TABLE test1 ADD CONSTRAINT fk_in_table FOREIGN KEY ( col2 ) REFERENCES oth
250250
== TEST 47 Modifying index with IF NOT EXIST
251251

252252
DROP INDEX test4
253-
CREATE INDEX test4 ON test1 ( col1 ASC ) STORING ( col2 )
253+
CREATE UNIQUE NULL_FILTERED INDEX test4 ON test1 ( col1 ASC ) STORING ( col2 )
254254

255255
== TEST 48 change streams create modify delete in correct order wrt tables
256256

@@ -276,7 +276,22 @@ ALTER CHANGE STREAM USER_CHANGES SET OPTIONS (retention_period='7d')
276276

277277
ALTER CHANGE STREAM USER_CHANGES SET OPTIONS (retention_period=NULL)
278278

279-
==
279+
== TEST 52 Modifying index STORING clause only - add
280+
281+
ALTER INDEX test4 ADD STORED COLUMN col4
282+
ALTER INDEX test5 ADD STORED COLUMN col2
283+
284+
== TEST 53 Modifying index STORING clause only - remove
280285

286+
ALTER INDEX test4 DROP STORED COLUMN col3
287+
ALTER INDEX test5 DROP STORED COLUMN col2
288+
ALTER INDEX test5 DROP STORED COLUMN col3
281289

290+
== TEST 54 Modifying index STORING clause only - add/remove
291+
292+
ALTER INDEX test4 DROP STORED COLUMN col2
293+
ALTER INDEX test4 ADD STORED COLUMN col4
294+
ALTER INDEX test4 ADD STORED COLUMN col5
295+
296+
==
282297

src/test/resources/newDdl.txt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ primary key (col1);
425425

426426
== TEST 47 Modifying index with IF NOT EXIST
427427

428-
Create index IF NOT EXISTS test4 on test1 ( col1 ) STORING ( col2 )
428+
Create UNIQUE NULL_filtered index IF NOT EXISTS test4 on test1 ( col1 ) STORING ( col2 )
429429

430430
== TEST 48 change streams create modify delete in correct order wrt tables
431431

@@ -455,4 +455,18 @@ CREATE CHANGE STREAM USER_CHANGES
455455
CREATE CHANGE STREAM USER_CHANGES
456456
FOR USER;
457457

458+
== TEST 52 Modifying index STORING clause only - add
459+
460+
CREATE UNIQUE NULL_FILTERED INDEX test4 ON test1 ( col1 ASC ) STORING ( col2, col3, col4 );
461+
CREATE UNIQUE NULL_FILTERED INDEX test5 ON test1 ( col1 ASC ) STORING ( col2 );
462+
463+
== TEST 53 Modifying index STORING clause only - remove
464+
465+
CREATE UNIQUE NULL_FILTERED INDEX test4 ON test1 ( col1 ASC ) STORING ( col2 );
466+
CREATE UNIQUE NULL_FILTERED INDEX test5 ON test1 ( col1 ASC );
467+
468+
== TEST 54 Modifying index STORING clause only - add/remove
469+
470+
CREATE UNIQUE NULL_FILTERED INDEX test4 ON test1 ( col1 ASC ) STORING ( col3, col5, col4 );
471+
458472
==

src/test/resources/originalDdl.txt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ primary key (col1);
424424

425425
== TEST 47 Modifying index with IF NOT EXIST
426426

427-
Create index IF NOT EXISTS test4 on test1 ( col1 )
427+
Create index IF NOT EXISTS test4 on test1 ( col1 ) STORING ( col2 )
428428

429429
== TEST 48 change streams create modify delete in correct order wrt tables
430430

@@ -453,6 +453,20 @@ CREATE CHANGE STREAM USER_CHANGES
453453
CREATE CHANGE STREAM USER_CHANGES
454454
FOR USER OPTIONS (retention_period='7d');
455455

456+
== TEST 52 Modifying index STORING clause only - add
457+
458+
CREATE UNIQUE NULL_FILTERED INDEX test4 ON test1 ( col1 ASC ) STORING ( col2, col3 );
459+
CREATE UNIQUE NULL_FILTERED INDEX test5 ON test1 ( col1 ASC );
460+
461+
== TEST 53 Modifying index STORING clause only - remove
462+
463+
CREATE UNIQUE NULL_FILTERED INDEX test4 ON test1 ( col1 ASC ) STORING ( col2, col3 );
464+
CREATE UNIQUE NULL_FILTERED INDEX test5 ON test1 ( col1 ASC ) STORING ( col2, col3 );
465+
466+
== TEST 54 Modifying index STORING clause only - add/remove
467+
468+
CREATE UNIQUE NULL_FILTERED INDEX test4 ON test1 ( col1 ASC ) STORING ( col2, col3 );
469+
456470
==
457471

458472

0 commit comments

Comments
 (0)