Skip to content

Commit b09f540

Browse files
authored
fix: allow unquoted text values in arrays (#706)
PostgreSQL allows (text) values in array literals to be unquoted. Quotes are only required if the literal contains commas.
1 parent c043c46 commit b09f540

File tree

5 files changed

+54
-53
lines changed

5 files changed

+54
-53
lines changed

src/main/java/com/google/cloud/spanner/pgadapter/parsers/ArrayParser.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,7 @@ public ArrayParser(
8989
case TEXT:
9090
this.item =
9191
stringArrayToList(
92-
new String(item, StandardCharsets.UTF_8),
93-
elementOid,
94-
this.isStringEquivalent,
95-
this.sessionState,
96-
false);
92+
new String(item, StandardCharsets.UTF_8), elementOid, this.sessionState, false);
9793
break;
9894
case BINARY:
9995
this.item = binaryArrayToList(item, false);
@@ -138,14 +134,12 @@ private List<?> toList(Value value, Code arrayElementType) {
138134
public static List<?> stringArrayToList(
139135
@Nullable String value,
140136
int elementOid,
141-
boolean isStringEquivalent,
142137
SessionState sessionState,
143138
boolean convertToValidSpannerElements) {
144139
if (value == null) {
145140
return null;
146141
}
147-
List<String> values =
148-
SimpleParser.readArrayLiteral(value, isStringEquivalent, elementOid == Oid.BYTEA);
142+
List<String> values = SimpleParser.readArrayLiteral(value, elementOid == Oid.BYTEA);
149143
ArrayList<Object> result = new ArrayList<>(values.size());
150144
for (String element : values) {
151145
if (element == null) {

src/main/java/com/google/cloud/spanner/pgadapter/statements/SimpleParser.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,7 @@ public static boolean isCommand(String command, String query) {
275275
return new SimpleParser(query).peekKeyword(command);
276276
}
277277

278-
public static List<String> readArrayLiteral(
279-
String expression, boolean mustBeQuoted, boolean returnRawHexValue) {
278+
public static List<String> readArrayLiteral(String expression, boolean returnRawHexValue) {
280279
List<String> result = new ArrayList<>();
281280
SimpleParser parser = new SimpleParser(expression);
282281
if (!parser.eatToken("{")) {
@@ -288,7 +287,7 @@ public static List<String> readArrayLiteral(
288287
break;
289288
} else if (parser.eatKeyword("null")) {
290289
result.add(null);
291-
} else if (mustBeQuoted || parser.peekToken("\"")) {
290+
} else if (parser.peekToken("\"")) {
292291
QuotedString quotedString = parser.readQuotedString('"', true);
293292
if (quotedString == null) {
294293
throw PGExceptionFactory.newPGException(

src/main/java/com/google/cloud/spanner/pgadapter/utils/CsvCopyParser.java

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -170,49 +170,39 @@ static Value getSpannerValue(SessionState sessionState, Type type, String record
170170
switch (type.getArrayElementType().getCode()) {
171171
case STRING:
172172
return Value.stringArray(
173-
cast(
174-
ArrayParser.stringArrayToList(
175-
recordValue, Oid.TEXT, false, sessionState, true)));
173+
cast(ArrayParser.stringArrayToList(recordValue, Oid.TEXT, sessionState, true)));
176174
case PG_JSONB:
177175
return Value.pgJsonbArray(
178176
cast(
179-
ArrayParser.stringArrayToList(
180-
recordValue, Oid.JSONB, false, sessionState, true)));
177+
ArrayParser.stringArrayToList(recordValue, Oid.JSONB, sessionState, true)));
181178
case BOOL:
182179
return Value.boolArray(
183-
cast(
184-
ArrayParser.stringArrayToList(
185-
recordValue, Oid.BOOL, false, sessionState, true)));
180+
cast(ArrayParser.stringArrayToList(recordValue, Oid.BOOL, sessionState, true)));
186181
case INT64:
187182
return Value.int64Array(
188-
cast(
189-
ArrayParser.stringArrayToList(
190-
recordValue, Oid.INT8, false, sessionState, true)));
183+
cast(ArrayParser.stringArrayToList(recordValue, Oid.INT8, sessionState, true)));
191184
case FLOAT64:
192185
return Value.float64Array(
193186
cast(
194187
ArrayParser.stringArrayToList(
195-
recordValue, Oid.FLOAT8, false, sessionState, true)));
188+
recordValue, Oid.FLOAT8, sessionState, true)));
196189
case PG_NUMERIC:
197190
return Value.pgNumericArray(
198191
cast(
199192
ArrayParser.stringArrayToList(
200-
recordValue, Oid.NUMERIC, false, sessionState, true)));
193+
recordValue, Oid.NUMERIC, sessionState, true)));
201194
case BYTES:
202195
return Value.bytesArray(
203196
cast(
204-
ArrayParser.stringArrayToList(
205-
recordValue, Oid.BYTEA, false, sessionState, true)));
197+
ArrayParser.stringArrayToList(recordValue, Oid.BYTEA, sessionState, true)));
206198
case DATE:
207199
return Value.dateArray(
208-
cast(
209-
ArrayParser.stringArrayToList(
210-
recordValue, Oid.DATE, false, sessionState, true)));
200+
cast(ArrayParser.stringArrayToList(recordValue, Oid.DATE, sessionState, true)));
211201
case TIMESTAMP:
212202
return Value.timestampArray(
213203
cast(
214204
ArrayParser.stringArrayToList(
215-
recordValue, Oid.TIMESTAMPTZ, false, sessionState, true)));
205+
recordValue, Oid.TIMESTAMPTZ, sessionState, true)));
216206
}
217207
default:
218208
SpannerException spannerException =

src/test/java/com/google/cloud/spanner/pgadapter/parsers/ArrayParserTest.java

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,19 @@ public void testTimestamp() {
189189
Type.timestamp(),
190190
Oid.TIMESTAMPTZ)
191191
.item);
192+
assertEquals(
193+
Arrays.asList(
194+
Timestamp.parseTimestamp("2023-02-13T19:15:00.123456Z"),
195+
null,
196+
Timestamp.parseTimestamp("2000-01-01T00:00:00Z")),
197+
new ArrayParser(
198+
"{2023-02-13T19:15:00.123456+00:00,null,2000-01-01T00:00:00+00}"
199+
.getBytes(StandardCharsets.UTF_8),
200+
FormatCode.TEXT,
201+
mock(SessionState.class),
202+
Type.timestamp(),
203+
Oid.TIMESTAMPTZ)
204+
.item);
192205
}
193206

194207
@Test
@@ -202,6 +215,15 @@ public void testDate() {
202215
Type.date(),
203216
Oid.DATE)
204217
.item);
218+
assertEquals(
219+
Arrays.asList(Date.parseDate("2023-02-13"), null, Date.parseDate("2000-01-01")),
220+
new ArrayParser(
221+
"{2023-02-13,null,2000-01-01}".getBytes(StandardCharsets.UTF_8),
222+
FormatCode.TEXT,
223+
mock(SessionState.class),
224+
Type.date(),
225+
Oid.DATE)
226+
.item);
205227
}
206228

207229
@Test
@@ -451,16 +473,13 @@ public void testNullBinaryArray() {
451473
@Test
452474
public void testNullTextArray() {
453475
assertNull(
454-
ArrayParser.stringArrayToList(
455-
null, Oid.UNSPECIFIED, false, mock(SessionState.class), false));
476+
ArrayParser.stringArrayToList(null, Oid.UNSPECIFIED, mock(SessionState.class), false));
456477
assertNull(
457-
ArrayParser.stringArrayToList(
458-
null, Oid.UNSPECIFIED, false, mock(SessionState.class), true));
478+
ArrayParser.stringArrayToList(null, Oid.UNSPECIFIED, mock(SessionState.class), true));
459479
assertNull(
460-
ArrayParser.stringArrayToList(
461-
null, Oid.UNSPECIFIED, true, mock(SessionState.class), false));
480+
ArrayParser.stringArrayToList(null, Oid.UNSPECIFIED, mock(SessionState.class), false));
462481
assertNull(
463-
ArrayParser.stringArrayToList(null, Oid.UNSPECIFIED, true, mock(SessionState.class), true));
482+
ArrayParser.stringArrayToList(null, Oid.UNSPECIFIED, mock(SessionState.class), true));
464483
}
465484

466485
static ResultSet createArrayResultSet(Type arrayElementType, Value value) {

src/test/java/com/google/cloud/spanner/pgadapter/statements/SimpleParserTest.java

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -475,28 +475,27 @@ public void testParserTableOrIndexName() {
475475

476476
@Test
477477
public void testReadArrayLiteral() {
478-
assertEquals(ImmutableList.of(), SimpleParser.readArrayLiteral("{}", true, true));
479-
assertEquals(ImmutableList.of("foo"), SimpleParser.readArrayLiteral("{\"foo\"}", true, true));
478+
assertEquals(ImmutableList.of(), SimpleParser.readArrayLiteral("{}", true));
479+
assertEquals(ImmutableList.of("foo"), SimpleParser.readArrayLiteral("{\"foo\"}", true));
480480
assertEquals(
481-
ImmutableList.of("foo", "bar"),
482-
SimpleParser.readArrayLiteral("{\"foo\", \"bar\"}", true, true));
481+
ImmutableList.of("foo", "bar"), SimpleParser.readArrayLiteral("{\"foo\", \"bar\"}", true));
483482
assertEquals(
484483
Arrays.asList("foo", "bar", null),
485-
SimpleParser.readArrayLiteral("{\"foo\", \"bar\", null}", true, true));
486-
assertEquals(ImmutableList.of("1", "2"), SimpleParser.readArrayLiteral("{1, 2}", false, true));
487-
assertEquals(
488-
ImmutableList.of("1", "2"), SimpleParser.readArrayLiteral("{\"1\", \"2\"}", false, true));
484+
SimpleParser.readArrayLiteral("{\"foo\", \"bar\", null}", true));
485+
assertEquals(ImmutableList.of("1", "2"), SimpleParser.readArrayLiteral("{1, 2}", true));
486+
assertEquals(ImmutableList.of("1", "2"), SimpleParser.readArrayLiteral("{\"1\", \"2\"}", true));
489487
assertEquals(
490488
ImmutableList.of("{\"foo\": \"bar\"}"),
491-
SimpleParser.readArrayLiteral("{\"{\\\"foo\\\": \\\"bar\\\"}\"}", true, true));
489+
SimpleParser.readArrayLiteral("{\"{\\\"foo\\\": \\\"bar\\\"}\"}", true));
492490

493-
assertThrows(PGException.class, () -> SimpleParser.readArrayLiteral("1, 2", false, true));
494-
assertThrows(PGException.class, () -> SimpleParser.readArrayLiteral("{1, 2", false, true));
495-
assertThrows(PGException.class, () -> SimpleParser.readArrayLiteral("1, 2}", false, true));
496-
assertThrows(
497-
PGException.class, () -> SimpleParser.readArrayLiteral("{1, 2} extra token", false, true));
491+
assertThrows(PGException.class, () -> SimpleParser.readArrayLiteral("1, 2", true));
492+
assertThrows(PGException.class, () -> SimpleParser.readArrayLiteral("{1, 2", true));
493+
assertThrows(PGException.class, () -> SimpleParser.readArrayLiteral("1, 2}", true));
498494
assertThrows(
499-
PGException.class,
500-
() -> SimpleParser.readArrayLiteral("{foo, bar}", /* mustBeQuoted = */ true, true));
495+
PGException.class, () -> SimpleParser.readArrayLiteral("{1, 2} extra token", true));
496+
497+
assertEquals(ImmutableList.of("foo", "bar"), SimpleParser.readArrayLiteral("{foo, bar}", true));
498+
assertEquals(
499+
ImmutableList.of("foo 1", "bar 2"), SimpleParser.readArrayLiteral("{foo 1, bar 2}", true));
501500
}
502501
}

0 commit comments

Comments
 (0)