Skip to content

Commit

Permalink
fix: allow unquoted text values in arrays (#706)
Browse files Browse the repository at this point in the history
PostgreSQL allows (text) values in array literals to be unquoted. Quotes
are only required if the literal contains commas.
  • Loading branch information
olavloite authored Mar 6, 2023
1 parent c043c46 commit b09f540
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,7 @@ public ArrayParser(
case TEXT:
this.item =
stringArrayToList(
new String(item, StandardCharsets.UTF_8),
elementOid,
this.isStringEquivalent,
this.sessionState,
false);
new String(item, StandardCharsets.UTF_8), elementOid, this.sessionState, false);
break;
case BINARY:
this.item = binaryArrayToList(item, false);
Expand Down Expand Up @@ -138,14 +134,12 @@ private List<?> toList(Value value, Code arrayElementType) {
public static List<?> stringArrayToList(
@Nullable String value,
int elementOid,
boolean isStringEquivalent,
SessionState sessionState,
boolean convertToValidSpannerElements) {
if (value == null) {
return null;
}
List<String> values =
SimpleParser.readArrayLiteral(value, isStringEquivalent, elementOid == Oid.BYTEA);
List<String> values = SimpleParser.readArrayLiteral(value, elementOid == Oid.BYTEA);
ArrayList<Object> result = new ArrayList<>(values.size());
for (String element : values) {
if (element == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,7 @@ public static boolean isCommand(String command, String query) {
return new SimpleParser(query).peekKeyword(command);
}

public static List<String> readArrayLiteral(
String expression, boolean mustBeQuoted, boolean returnRawHexValue) {
public static List<String> readArrayLiteral(String expression, boolean returnRawHexValue) {
List<String> result = new ArrayList<>();
SimpleParser parser = new SimpleParser(expression);
if (!parser.eatToken("{")) {
Expand All @@ -288,7 +287,7 @@ public static List<String> readArrayLiteral(
break;
} else if (parser.eatKeyword("null")) {
result.add(null);
} else if (mustBeQuoted || parser.peekToken("\"")) {
} else if (parser.peekToken("\"")) {
QuotedString quotedString = parser.readQuotedString('"', true);
if (quotedString == null) {
throw PGExceptionFactory.newPGException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,49 +170,39 @@ static Value getSpannerValue(SessionState sessionState, Type type, String record
switch (type.getArrayElementType().getCode()) {
case STRING:
return Value.stringArray(
cast(
ArrayParser.stringArrayToList(
recordValue, Oid.TEXT, false, sessionState, true)));
cast(ArrayParser.stringArrayToList(recordValue, Oid.TEXT, sessionState, true)));
case PG_JSONB:
return Value.pgJsonbArray(
cast(
ArrayParser.stringArrayToList(
recordValue, Oid.JSONB, false, sessionState, true)));
ArrayParser.stringArrayToList(recordValue, Oid.JSONB, sessionState, true)));
case BOOL:
return Value.boolArray(
cast(
ArrayParser.stringArrayToList(
recordValue, Oid.BOOL, false, sessionState, true)));
cast(ArrayParser.stringArrayToList(recordValue, Oid.BOOL, sessionState, true)));
case INT64:
return Value.int64Array(
cast(
ArrayParser.stringArrayToList(
recordValue, Oid.INT8, false, sessionState, true)));
cast(ArrayParser.stringArrayToList(recordValue, Oid.INT8, sessionState, true)));
case FLOAT64:
return Value.float64Array(
cast(
ArrayParser.stringArrayToList(
recordValue, Oid.FLOAT8, false, sessionState, true)));
recordValue, Oid.FLOAT8, sessionState, true)));
case PG_NUMERIC:
return Value.pgNumericArray(
cast(
ArrayParser.stringArrayToList(
recordValue, Oid.NUMERIC, false, sessionState, true)));
recordValue, Oid.NUMERIC, sessionState, true)));
case BYTES:
return Value.bytesArray(
cast(
ArrayParser.stringArrayToList(
recordValue, Oid.BYTEA, false, sessionState, true)));
ArrayParser.stringArrayToList(recordValue, Oid.BYTEA, sessionState, true)));
case DATE:
return Value.dateArray(
cast(
ArrayParser.stringArrayToList(
recordValue, Oid.DATE, false, sessionState, true)));
cast(ArrayParser.stringArrayToList(recordValue, Oid.DATE, sessionState, true)));
case TIMESTAMP:
return Value.timestampArray(
cast(
ArrayParser.stringArrayToList(
recordValue, Oid.TIMESTAMPTZ, false, sessionState, true)));
recordValue, Oid.TIMESTAMPTZ, sessionState, true)));
}
default:
SpannerException spannerException =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,19 @@ public void testTimestamp() {
Type.timestamp(),
Oid.TIMESTAMPTZ)
.item);
assertEquals(
Arrays.asList(
Timestamp.parseTimestamp("2023-02-13T19:15:00.123456Z"),
null,
Timestamp.parseTimestamp("2000-01-01T00:00:00Z")),
new ArrayParser(
"{2023-02-13T19:15:00.123456+00:00,null,2000-01-01T00:00:00+00}"
.getBytes(StandardCharsets.UTF_8),
FormatCode.TEXT,
mock(SessionState.class),
Type.timestamp(),
Oid.TIMESTAMPTZ)
.item);
}

@Test
Expand All @@ -202,6 +215,15 @@ public void testDate() {
Type.date(),
Oid.DATE)
.item);
assertEquals(
Arrays.asList(Date.parseDate("2023-02-13"), null, Date.parseDate("2000-01-01")),
new ArrayParser(
"{2023-02-13,null,2000-01-01}".getBytes(StandardCharsets.UTF_8),
FormatCode.TEXT,
mock(SessionState.class),
Type.date(),
Oid.DATE)
.item);
}

@Test
Expand Down Expand Up @@ -451,16 +473,13 @@ public void testNullBinaryArray() {
@Test
public void testNullTextArray() {
assertNull(
ArrayParser.stringArrayToList(
null, Oid.UNSPECIFIED, false, mock(SessionState.class), false));
ArrayParser.stringArrayToList(null, Oid.UNSPECIFIED, mock(SessionState.class), false));
assertNull(
ArrayParser.stringArrayToList(
null, Oid.UNSPECIFIED, false, mock(SessionState.class), true));
ArrayParser.stringArrayToList(null, Oid.UNSPECIFIED, mock(SessionState.class), true));
assertNull(
ArrayParser.stringArrayToList(
null, Oid.UNSPECIFIED, true, mock(SessionState.class), false));
ArrayParser.stringArrayToList(null, Oid.UNSPECIFIED, mock(SessionState.class), false));
assertNull(
ArrayParser.stringArrayToList(null, Oid.UNSPECIFIED, true, mock(SessionState.class), true));
ArrayParser.stringArrayToList(null, Oid.UNSPECIFIED, mock(SessionState.class), true));
}

static ResultSet createArrayResultSet(Type arrayElementType, Value value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,28 +475,27 @@ public void testParserTableOrIndexName() {

@Test
public void testReadArrayLiteral() {
assertEquals(ImmutableList.of(), SimpleParser.readArrayLiteral("{}", true, true));
assertEquals(ImmutableList.of("foo"), SimpleParser.readArrayLiteral("{\"foo\"}", true, true));
assertEquals(ImmutableList.of(), SimpleParser.readArrayLiteral("{}", true));
assertEquals(ImmutableList.of("foo"), SimpleParser.readArrayLiteral("{\"foo\"}", true));
assertEquals(
ImmutableList.of("foo", "bar"),
SimpleParser.readArrayLiteral("{\"foo\", \"bar\"}", true, true));
ImmutableList.of("foo", "bar"), SimpleParser.readArrayLiteral("{\"foo\", \"bar\"}", true));
assertEquals(
Arrays.asList("foo", "bar", null),
SimpleParser.readArrayLiteral("{\"foo\", \"bar\", null}", true, true));
assertEquals(ImmutableList.of("1", "2"), SimpleParser.readArrayLiteral("{1, 2}", false, true));
assertEquals(
ImmutableList.of("1", "2"), SimpleParser.readArrayLiteral("{\"1\", \"2\"}", false, true));
SimpleParser.readArrayLiteral("{\"foo\", \"bar\", null}", true));
assertEquals(ImmutableList.of("1", "2"), SimpleParser.readArrayLiteral("{1, 2}", true));
assertEquals(ImmutableList.of("1", "2"), SimpleParser.readArrayLiteral("{\"1\", \"2\"}", true));
assertEquals(
ImmutableList.of("{\"foo\": \"bar\"}"),
SimpleParser.readArrayLiteral("{\"{\\\"foo\\\": \\\"bar\\\"}\"}", true, true));
SimpleParser.readArrayLiteral("{\"{\\\"foo\\\": \\\"bar\\\"}\"}", true));

assertThrows(PGException.class, () -> SimpleParser.readArrayLiteral("1, 2", false, true));
assertThrows(PGException.class, () -> SimpleParser.readArrayLiteral("{1, 2", false, true));
assertThrows(PGException.class, () -> SimpleParser.readArrayLiteral("1, 2}", false, true));
assertThrows(
PGException.class, () -> SimpleParser.readArrayLiteral("{1, 2} extra token", false, true));
assertThrows(PGException.class, () -> SimpleParser.readArrayLiteral("1, 2", true));
assertThrows(PGException.class, () -> SimpleParser.readArrayLiteral("{1, 2", true));
assertThrows(PGException.class, () -> SimpleParser.readArrayLiteral("1, 2}", true));
assertThrows(
PGException.class,
() -> SimpleParser.readArrayLiteral("{foo, bar}", /* mustBeQuoted = */ true, true));
PGException.class, () -> SimpleParser.readArrayLiteral("{1, 2} extra token", true));

assertEquals(ImmutableList.of("foo", "bar"), SimpleParser.readArrayLiteral("{foo, bar}", true));
assertEquals(
ImmutableList.of("foo 1", "bar 2"), SimpleParser.readArrayLiteral("{foo 1, bar 2}", true));
}
}

0 comments on commit b09f540

Please sign in to comment.