Skip to content
This repository was archived by the owner on Apr 7, 2026. It is now read-only.

Commit cd9b0a7

Browse files
committed
chore: normalize query param keys to lowercase in KeyRecipe
1 parent f707be4 commit cd9b0a7

File tree

2 files changed

+62
-3
lines changed

2 files changed

+62
-3
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/KeyRecipe.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
import java.time.format.ResolverStyle;
3131
import java.util.ArrayList;
3232
import java.util.Base64;
33+
import java.util.HashMap;
3334
import java.util.List;
35+
import java.util.Locale;
36+
import java.util.Map;
3437
import java.util.concurrent.ThreadLocalRandom;
3538
import java.util.function.BiFunction;
3639
import java.util.stream.Collectors;
@@ -780,10 +783,12 @@ public TargetRange keySetToTargetRange(KeySet in) {
780783
}
781784

782785
public TargetRange queryParamsToTargetRange(Struct in) {
786+
final Map<String, Value> lowercaseFields = new HashMap<>();
787+
for (Map.Entry<String, Value> entry : in.getFieldsMap().entrySet()) {
788+
lowercaseFields.put(entry.getKey().toLowerCase(Locale.ROOT), entry.getValue());
789+
}
783790
return encodeKeyInternal(
784-
(index, identifier) -> {
785-
return in.getFieldsMap().get(identifier);
786-
},
791+
(index, identifier) -> lowercaseFields.get(identifier.toLowerCase(Locale.ROOT)),
787792
KeyType.FULL_KEY);
788793
}
789794

google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/KeyRecipeTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.cloud.spanner.spi.v1;
1818

1919
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertFalse;
2021
import static org.junit.Assert.assertTrue;
2122

2223
import com.google.protobuf.ByteString;
@@ -76,6 +77,59 @@ public void queryParamsUsesConstantValue() throws Exception {
7677
assertTrue(target.limit.isEmpty());
7778
}
7879

80+
@Test
81+
public void queryParamsCaseInsensitiveFallback() throws Exception {
82+
com.google.spanner.v1.KeyRecipe recipeProto =
83+
createRecipe(
84+
"part { tag: 1 }\n"
85+
+ "part {\n"
86+
+ " order: ASCENDING\n"
87+
+ " null_order: NULLS_FIRST\n"
88+
+ " type { code: STRING }\n"
89+
+ " identifier: \"id\"\n"
90+
+ "}\n");
91+
92+
Struct params =
93+
parseStruct(
94+
"fields {\n" + " key: \"Id\"\n" + " value { string_value: \"foo\" }\n" + "}\n");
95+
96+
KeyRecipe recipe = KeyRecipe.create(recipeProto);
97+
TargetRange target = recipe.queryParamsToTargetRange(params);
98+
assertEquals(expectedKey("foo"), target.start);
99+
assertTrue(target.limit.isEmpty());
100+
}
101+
102+
@Test
103+
public void queryParamsCaseInsensitiveDuplicateUsesLastValue() throws Exception {
104+
com.google.spanner.v1.KeyRecipe recipeProto =
105+
createRecipe(
106+
"part { tag: 1 }\n"
107+
+ "part {\n"
108+
+ " order: ASCENDING\n"
109+
+ " null_order: NULLS_FIRST\n"
110+
+ " type { code: STRING }\n"
111+
+ " identifier: \"ID\"\n"
112+
+ "}\n");
113+
114+
// Both "Id" and "id" normalize to "id"; the last one ("id"→"bar") wins.
115+
Struct params =
116+
parseStruct(
117+
"fields {\n"
118+
+ " key: \"Id\"\n"
119+
+ " value { string_value: \"foo\" }\n"
120+
+ "}\n"
121+
+ "fields {\n"
122+
+ " key: \"id\"\n"
123+
+ " value { string_value: \"bar\" }\n"
124+
+ "}\n");
125+
126+
KeyRecipe recipe = KeyRecipe.create(recipeProto);
127+
TargetRange target = recipe.queryParamsToTargetRange(params);
128+
assertEquals(expectedKey("bar"), target.start);
129+
assertFalse(target.approximate);
130+
assertTrue(target.limit.isEmpty());
131+
}
132+
79133
private static com.google.spanner.v1.KeyRecipe createRecipe(String text)
80134
throws TextFormat.ParseException {
81135
com.google.spanner.v1.KeyRecipe.Builder builder = com.google.spanner.v1.KeyRecipe.newBuilder();

0 commit comments

Comments
 (0)