Skip to content

Commit 5dfe226

Browse files
authored
fix: (Spanner) include schema name in table name (#2510)
Spanner databases contain both system schemas and a user schema. The current Spring Data Spanner implementation assumes that all table names in a database are unique, but this is not true if a user creates a table that is equal to the name of a system table in one of the system schemas. Additionally, Spanner could introduce support for user-defined named schemas in the future. This would increase the probability that a single database contains multiple tables with the same name, but in different schemas. This change fixes any problems that might occur if a database contains multiple tables with the same name in different schemas. The change does not attempt to implement full support for using multiple different schemas with Spring Cloud Data Spanner.
1 parent 42dd96a commit 5dfe226

File tree

2 files changed

+128
-4
lines changed

2 files changed

+128
-4
lines changed

spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/admin/SpannerDatabaseAdminTemplate.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@
2020
import com.google.cloud.spanner.DatabaseAdminClient;
2121
import com.google.cloud.spanner.DatabaseClient;
2222
import com.google.cloud.spanner.DatabaseId;
23+
import com.google.cloud.spanner.Dialect;
2324
import com.google.cloud.spanner.ResultSet;
2425
import com.google.cloud.spanner.Statement;
2526
import com.google.cloud.spanner.Struct;
2627
import com.google.cloud.spring.data.spanner.core.mapping.SpannerDataException;
2728
import java.util.HashMap;
2829
import java.util.HashSet;
2930
import java.util.Map;
31+
import java.util.Objects;
3032
import java.util.Set;
3133
import java.util.concurrent.ExecutionException;
3234
import java.util.function.Supplier;
@@ -40,13 +42,17 @@
4042
*/
4143
public class SpannerDatabaseAdminTemplate {
4244

45+
private static final String TABLE_SCHEMA_COL_NAME = "table_schema";
46+
4347
private static final String TABLE_NAME_COL_NAME = "table_name";
4448

4549
private static final String PARENT_TABLE_NAME_COL_NAME = "parent_table_name";
4650

4751
private static final Statement TABLE_AND_PARENT_QUERY =
4852
Statement.of(
4953
"SELECT t."
54+
+ TABLE_SCHEMA_COL_NAME
55+
+ ", t."
5056
+ TABLE_NAME_COL_NAME
5157
+ ", t."
5258
+ PARENT_TABLE_NAME_COL_NAME
@@ -151,14 +157,36 @@ public Map<String, String> getChildParentTablesMap() {
151157
while (results.next()) {
152158
Struct row = results.getCurrentRowAsStruct();
153159
relationships.put(
154-
row.getString(TABLE_NAME_COL_NAME),
160+
getQualifiedTableName(
161+
row.getString(TABLE_SCHEMA_COL_NAME),
162+
row.getString(TABLE_NAME_COL_NAME)),
155163
row.isNull(PARENT_TABLE_NAME_COL_NAME)
156164
? null
157-
: row.getString(PARENT_TABLE_NAME_COL_NAME));
165+
: getQualifiedTableName(
166+
// Parent/child tables must be in the same schema, and so there is no separate
167+
// schema name column for the parent table.
168+
row.getString(TABLE_SCHEMA_COL_NAME),
169+
row.getString(PARENT_TABLE_NAME_COL_NAME)));
158170
}
159171
return relationships;
160172
}
161173
}
174+
175+
private String getQualifiedTableName(String schema, String table) {
176+
if (schema == null || Objects.equals(schema, getDefaultSchemaName())) {
177+
return table;
178+
}
179+
return schema + "." + table;
180+
}
181+
182+
private String getDefaultSchemaName() {
183+
// TODO: Get the default schema directly from the dialect when this is supported in the Spanner
184+
// Java client.
185+
if (databaseClientProvider.get().getDialect() == Dialect.POSTGRESQL) {
186+
return "public";
187+
}
188+
return "";
189+
}
162190

163191
/**
164192
* Return true if the given table names are interleaved as ancestor and descendant. These may be

spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/admin/SpannerDatabaseAdminTemplateTests.java

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,39 +76,104 @@ void setup() {
7676

7777
@Test
7878
void getTableRelationshipsTest() {
79+
when(this.mockDatabasePage.getValues())
80+
.thenReturn(
81+
List.of(new Database(this.databaseId, State.READY, this.databaseAdminClient)));
7982
ReadContext readContext = mock(ReadContext.class);
8083

8184
Struct s1 =
8285
Struct.newBuilder()
86+
.set("table_schema")
87+
.to("")
8388
.set("table_name")
8489
.to(Value.string("grandpa"))
8590
.set("parent_table_name")
8691
.to(Value.string(null))
8792
.build();
8893
Struct s2 =
8994
Struct.newBuilder()
95+
.set("table_schema")
96+
.to("")
9097
.set("table_name")
9198
.to(Value.string("parent_a"))
9299
.set("parent_table_name")
93100
.to(Value.string("grandpa"))
94101
.build();
95102
Struct s3 =
96103
Struct.newBuilder()
104+
.set("table_schema")
105+
.to("")
97106
.set("table_name")
98107
.to(Value.string("parent_b"))
99108
.set("parent_table_name")
100109
.to(Value.string("grandpa"))
101110
.build();
102111
Struct s4 =
103112
Struct.newBuilder()
113+
.set("table_schema")
114+
.to("")
104115
.set("table_name")
105116
.to(Value.string("child"))
106117
.set("parent_table_name")
107118
.to(Value.string("parent_a"))
108119
.build();
120+
Struct s5 =
121+
Struct.newBuilder()
122+
.set("table_schema")
123+
.to("INFORMATION_SCHEMA")
124+
.set("table_name")
125+
.to(Value.string("TABLES"))
126+
.set("parent_table_name")
127+
.to(Value.string(null))
128+
.build();
129+
Struct s6 =
130+
Struct.newBuilder()
131+
.set("table_schema")
132+
.to("")
133+
.set("table_name")
134+
.to(Value.string("TABLES"))
135+
.set("parent_table_name")
136+
.to(Value.string(null))
137+
.build();
138+
Struct s7 =
139+
Struct.newBuilder()
140+
.set("table_schema")
141+
.to("")
142+
.set("table_name")
143+
.to(Value.string("CHILD_TABLES"))
144+
.set("parent_table_name")
145+
.to(Value.string("TABLES"))
146+
.build();
147+
Struct s8 =
148+
Struct.newBuilder()
149+
.set("table_schema")
150+
.to("my_schema")
151+
.set("table_name")
152+
.to(Value.string("grandpa"))
153+
.set("parent_table_name")
154+
.to(Value.string(null))
155+
.build();
156+
Struct s9 =
157+
Struct.newBuilder()
158+
.set("table_schema")
159+
.to("my_schema")
160+
.set("table_name")
161+
.to(Value.string("dad"))
162+
.set("parent_table_name")
163+
.to(Value.string("grandpa"))
164+
.build();
165+
Struct s10 =
166+
Struct.newBuilder()
167+
.set("table_schema")
168+
.to("my_schema")
169+
.set("table_name")
170+
.to(Value.string("child"))
171+
.set("parent_table_name")
172+
.to(Value.string("dad"))
173+
.build();
109174

110175
MockResults mockResults = new MockResults();
111-
mockResults.structs = Arrays.asList(s1, s2, s3, s4);
176+
mockResults.structs = Arrays.asList(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10);
112177
ResultSet results = mock(ResultSet.class);
113178
when(results.next()).thenAnswer(invocation -> mockResults.next());
114179
when(results.getCurrentRowAsStruct()).thenAnswer(invocation -> mockResults.getCurrent());
@@ -118,9 +183,27 @@ void getTableRelationshipsTest() {
118183
Map<String, Set<String>> relationships =
119184
this.spannerDatabaseAdminTemplate.getParentChildTablesMap();
120185

121-
assertThat(relationships).hasSize(2);
186+
assertThat(this.spannerDatabaseAdminTemplate.tableExists("some-random-table")).isFalse();
187+
assertThat(this.spannerDatabaseAdminTemplate.tableExists("grandpa")).isTrue();
188+
assertThat(this.spannerDatabaseAdminTemplate.tableExists("parent_a")).isTrue();
189+
assertThat(this.spannerDatabaseAdminTemplate.tableExists("parent_b")).isTrue();
190+
assertThat(this.spannerDatabaseAdminTemplate.tableExists("child")).isTrue();
191+
assertThat(this.spannerDatabaseAdminTemplate.tableExists("child")).isTrue();
192+
assertThat(this.spannerDatabaseAdminTemplate.tableExists("INFORMATION_SCHEMA.TABLES")).isTrue();
193+
assertThat(this.spannerDatabaseAdminTemplate.tableExists("TABLES")).isTrue();
194+
assertThat(this.spannerDatabaseAdminTemplate.tableExists("CHILD_TABLES")).isTrue();
195+
assertThat(this.spannerDatabaseAdminTemplate.tableExists("my_schema.grandpa")).isTrue();
196+
assertThat(this.spannerDatabaseAdminTemplate.tableExists("my_schema.dad")).isTrue();
197+
assertThat(this.spannerDatabaseAdminTemplate.tableExists("my_schema.child")).isTrue();
198+
199+
assertThat(relationships).hasSize(5);
122200
assertThat(relationships.get("grandpa")).containsExactlyInAnyOrder("parent_a", "parent_b");
123201
assertThat(relationships.get("parent_a")).containsExactlyInAnyOrder("child");
202+
assertThat(relationships.get("child")).isNull();
203+
assertThat(relationships.get("TABLES")).containsExactlyInAnyOrder("CHILD_TABLES");
204+
assertThat(relationships.get("INFORMATION_SCHEMA.TABLES")).isNull();
205+
assertThat(relationships.get("my_schema.grandpa")).containsExactlyInAnyOrder("my_schema.dad");
206+
assertThat(relationships.get("my_schema.dad")).containsExactlyInAnyOrder("my_schema.child");
124207

125208
assertThat(this.spannerDatabaseAdminTemplate.isInterleaved("grandpa", "child"))
126209
.as("verify grand-child relationship")
@@ -140,6 +223,19 @@ void getTableRelationshipsTest() {
140223
assertThat(this.spannerDatabaseAdminTemplate.isInterleaved("parent_b", "child"))
141224
.as("verify not parent-child relationship")
142225
.isFalse();
226+
227+
assertThat(this.spannerDatabaseAdminTemplate
228+
.isInterleaved("my_schema.grandpa", "my_schema.dad"))
229+
.as("verify my-schema grand-child relationship")
230+
.isTrue();
231+
assertThat(this.spannerDatabaseAdminTemplate
232+
.isInterleaved("my_schema.grandpa", "my_schema.child"))
233+
.as("verify my-schema grand-child relationship")
234+
.isTrue();
235+
assertThat(this.spannerDatabaseAdminTemplate
236+
.isInterleaved("my_schema.grandpa", "child"))
237+
.as("verify not my-schema grand-child relationship")
238+
.isFalse();
143239
}
144240

145241
@Test

0 commit comments

Comments
 (0)