Skip to content

Commit c9ddfcc

Browse files
committed
Cloud Spanner: Check duplicate columns in Mutation
close #2257 * Adds `checkDuplicateColumns`, executed before a mutation is built, to ensure each column name is only used once. Note that column names are case-insensitive according to the [DDL docs](https://cloud.google.com/spanner/docs/data-definition-language#create_table). * Tests duplicate columns with the same case and different cases. * Removes duplicate column check in `asMap` because all `Mutation`s are now guaranteed not to have duplicate columns. Removes corresponding test code too.
1 parent 95f0dd7 commit c9ddfcc

2 files changed

Lines changed: 31 additions & 15 deletions

File tree

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Mutation.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424

2525
import java.io.Serializable;
2626
import java.util.Collections;
27+
import java.util.HashSet;
2728
import java.util.LinkedHashMap;
2829
import java.util.List;
2930
import java.util.Map;
3031
import java.util.Objects;
32+
import java.util.Set;
3133
import javax.annotation.Nullable;
3234

3335
/**
@@ -188,9 +190,17 @@ public ValueBinder<WriteBuilder> set(String columnName) {
188190
return binder;
189191
}
190192

193+
/**
194+
* Returns a newly created {@code Mutation} based on the contents of the {@code Builder}.
195+
*
196+
* @throws IllegalStateException if any duplicate columns are present. Duplicate detection is
197+
* case-insensitive.
198+
*/
191199
public Mutation build() {
192200
checkBindingInProgress(false);
193-
return new Mutation(table, operation, columns.build(), values.build(), null);
201+
ImmutableList<String> columnNames = columns.build();
202+
checkDuplicateColumns(columnNames);
203+
return new Mutation(table, operation, columnNames, values.build(), null);
194204
}
195205

196206
private void checkBindingInProgress(boolean expectInProgress) {
@@ -200,6 +210,17 @@ private void checkBindingInProgress(boolean expectInProgress) {
200210
throw new IllegalStateException("Incomplete binding for column " + currentColumn);
201211
}
202212
}
213+
214+
private void checkDuplicateColumns(ImmutableList<String> columnNames) {
215+
Set<String> columnNameSet = new HashSet<>();
216+
for (String columnName : columns.build()) {
217+
columnName = columnName.toLowerCase();
218+
if (columnNameSet.contains(columnName)) {
219+
throw new IllegalStateException("Duplicate column: " + columnName);
220+
}
221+
columnNameSet.add(columnName);
222+
}
223+
}
203224
}
204225

205226
/** Returns the name of the table that this mutation will affect. */
@@ -247,9 +268,6 @@ public Map<String, Value> asMap() {
247268
LinkedHashMap<String, Value> map = new LinkedHashMap<>();
248269
for (int i = 0; i < columns.size(); ++i) {
249270
Value existing = map.put(columns.get(i), values.get(i));
250-
if (existing != null) {
251-
throw new IllegalStateException("Duplicate column: " + columns.get(i));
252-
}
253271
}
254272
return Collections.unmodifiableMap(map);
255273
}

google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,16 @@ public void replace() {
124124

125125
@Test
126126
public void duplicateColumn() {
127-
// The current implementation does not repeat validation performed by the server; duplicates
128-
// are permitted but will later cause an operation failure.
127+
expectedException.expect(IllegalStateException.class);
128+
expectedException.expectMessage("Duplicate column");
129129
Mutation m = Mutation.newInsertBuilder("T1").set("C1").to(true).set("C1").to(false).build();
130-
assertThat(m.getOperation()).isEqualTo(Mutation.Op.INSERT);
131-
assertThat(m.getColumns()).containsExactly("C1", "C1").inOrder();
132-
assertThat(m.getValues()).containsExactly(Value.bool(true), Value.bool(false)).inOrder();
133-
assertThat(m.toString()).isEqualTo("insert(T1{C1=true,C1=false})");
130+
}
131+
132+
@Test
133+
public void duplicateColumnCaseInsensitive() {
134+
expectedException.expect(IllegalStateException.class);
135+
expectedException.expectMessage("Duplicate column");
136+
Mutation m = Mutation.newInsertBuilder("T1").set("C1").to(true).set("c1").to(false).build();
134137
}
135138

136139
@Test
@@ -141,11 +144,6 @@ public void asMap() {
141144
m = Mutation.newInsertBuilder("T").set("C1").to(true).set("C2").to(1234).build();
142145
assertThat(m.asMap())
143146
.isEqualTo(ImmutableMap.of("C1", Value.bool(true), "C2", Value.int64(1234)));
144-
145-
m = Mutation.newInsertBuilder("T").set("C1").to(true).set("C1").to(false).build();
146-
expectedException.expect(IllegalStateException.class);
147-
expectedException.expectMessage("Duplicate column");
148-
m.asMap();
149147
}
150148

151149
@Test

0 commit comments

Comments
 (0)