Skip to content

Commit

Permalink
fix: support null parameters
Browse files Browse the repository at this point in the history
- Adds more integration tests using the native PG JDBC driver.
- Adds support for null values in query parameters.

The PG JDBC driver sends DATE/TIMESTAMP parameters with type code
Oid.UNSPECIFIED. Untyped NULL values are currently not supported by the
Spangres backend, and also not by the Java client library / JDBC driver.
A patch for the Java client library has been submitted here: googleapis/java-spanner#1680
  • Loading branch information
olavloite committed Feb 11, 2022
1 parent 56da0cb commit e2cc831
Show file tree
Hide file tree
Showing 14 changed files with 487 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,21 @@ public class ArrayParser extends Parser<List> {
boolean isStringEquivalent;

public ArrayParser(ResultSet item, int position) throws SQLException {
this.item = Arrays.asList((Object[]) item.getArray(position).getArray());
this.arrayType = item.getArray(position).getBaseType();
if (this.arrayType == Types.ARRAY) {
throw new IllegalArgumentException(
"Spanner does not support embedded Arrays."
+ " If you are seeing this, something went wrong!");
if (item != null) {
this.item = Arrays.asList((Object[]) item.getArray(position).getArray());
this.arrayType = item.getArray(position).getBaseType();
if (this.arrayType == Types.ARRAY) {
throw new IllegalArgumentException(
"Spanner does not support embedded Arrays."
+ " If you are seeing this, something went wrong!");
}
this.isStringEquivalent = stringEquivalence(this.arrayType);
}
this.isStringEquivalent = stringEquivalence(this.arrayType);
}

@Override
public List getItem() {
return this.item;
public int getSqlType() {
return Types.ARRAY;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.nio.charset.StandardCharsets;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.postgresql.util.PGbytea;

/**
Expand All @@ -34,23 +35,30 @@ public BinaryParser(Object item) {
}

public BinaryParser(byte[] item, FormatCode formatCode) {
switch (formatCode) {
case TEXT:
try {
this.item = PGbytea.toBytes(item);
if (item != null) {
switch (formatCode) {
case TEXT:
try {
this.item = PGbytea.toBytes(item);
break;
} catch (SQLException e) {
throw new IllegalArgumentException(
"Invalid binary value: " + new String(item, StandardCharsets.UTF_8), e);
}
case BINARY:
this.item = item;
break;
} catch (SQLException e) {
throw new IllegalArgumentException(
"Invalid binary value: " + new String(item, StandardCharsets.UTF_8), e);
}
case BINARY:
this.item = item;
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
}
}
}

@Override
public int getSqlType() {
return Types.BINARY;
}

@Override
protected String stringParse() {
return PGbytea.toPGString(this.item);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,26 @@

package com.google.cloud.spanner.pgadapter.parsers;

import com.google.common.collect.ImmutableSet;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Locale;
import java.util.Set;
import org.postgresql.util.ByteConverter;

/**
* Parse specified data to boolean. For most cases it is simply translating from chars 't'/'f' to
* bit, or simply returning the bit representation.
*/
public class BooleanParser extends Parser<Boolean> {

private static String TRUE_KEY = "t";
private static String FALSE_KEY = "f";
private static final String TRUE_VALUE = "t";
private static final String FALSE_VALUE = "f";
// See https://www.postgresql.org/docs/current/datatype-boolean.html
private static final Set<String> TRUE_VALUES =
ImmutableSet.of("t", "tr", "tru", "true", "y", "ye", "yes", "on", "1");
private static final Set<String> FALSE_VALUES =
ImmutableSet.of("f", "fa", "fal", "fals", "false", "n", "no", "of", "off");

public BooleanParser(ResultSet item, int position) throws SQLException {
this.item = item.getBoolean(position);
Expand All @@ -36,27 +44,35 @@ public BooleanParser(Object item) {
}

public BooleanParser(byte[] item, FormatCode formatCode) {
switch (formatCode) {
case TEXT:
String stringValue = new String(item, UTF8);
this.item = stringValue.equals(TRUE_KEY);
break;
case BINARY:
this.item = ByteConverter.bool(item, 0);
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
if (item != null) {
switch (formatCode) {
case TEXT:
String stringValue = new String(item, UTF8).toLowerCase(Locale.ENGLISH);
if (TRUE_VALUES.contains(stringValue)) {
this.item = true;
} else if (FALSE_VALUES.contains(stringValue)) {
this.item = false;
} else {
throw new IllegalArgumentException(stringValue + " is not a valid boolean value");
}
break;
case BINARY:
this.item = ByteConverter.bool(item, 0);
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
}
}
}

@Override
public Boolean getItem() {
return this.item;
public int getSqlType() {
return Types.BOOLEAN;
}

@Override
protected String stringParse() {
return this.item ? TRUE_KEY : FALSE_KEY;
return this.item ? TRUE_VALUE : FALSE_VALUE;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.google.common.base.Preconditions;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
Expand All @@ -34,18 +35,20 @@ public DateParser(Object item) {
}

public DateParser(byte[] item, FormatCode formatCode) {
switch (formatCode) {
case TEXT:
String stringValue = new String(item, UTF8);
this.item = java.sql.Date.valueOf(stringValue);
break;
case BINARY:
long days = ByteConverter.int4(item, 0) + PG_EPOCH_DAYS;
this.validateRange(days);
this.item = java.sql.Date.valueOf(LocalDate.ofEpochDay(days));
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
if (item != null) {
switch (formatCode) {
case TEXT:
String stringValue = new String(item, UTF8);
this.item = java.sql.Date.valueOf(stringValue);
break;
case BINARY:
long days = ByteConverter.int4(item, 0) + PG_EPOCH_DAYS;
this.validateRange(days);
this.item = java.sql.Date.valueOf(LocalDate.ofEpochDay(days));
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
}
}
}

Expand Down Expand Up @@ -76,6 +79,11 @@ public static boolean isDate(String value) {
return false;
}

@Override
public int getSqlType() {
return Types.DATE;
}

@Override
protected String stringParse() {
return item.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.postgresql.util.ByteConverter;

/** Translate from wire protocol to double. */
Expand All @@ -30,21 +31,23 @@ public DoubleParser(Object item) {
}

public DoubleParser(byte[] item, FormatCode formatCode) {
switch (formatCode) {
case TEXT:
this.item = Double.valueOf(new String(item));
break;
case BINARY:
this.item = ByteConverter.float8(item, 0);
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
if (item != null) {
switch (formatCode) {
case TEXT:
this.item = Double.valueOf(new String(item));
break;
case BINARY:
this.item = ByteConverter.float8(item, 0);
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
}
}
}

@Override
public Double getItem() {
return this.item;
public int getSqlType() {
return Types.DOUBLE;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.postgresql.util.ByteConverter;

/** Translate from wire protocol to int. */
Expand All @@ -30,21 +31,23 @@ public IntegerParser(Object item) {
}

public IntegerParser(byte[] item, FormatCode formatCode) {
switch (formatCode) {
case TEXT:
this.item = Integer.valueOf(new String(item));
break;
case BINARY:
this.item = ByteConverter.int4(item, 0);
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
if (item != null) {
switch (formatCode) {
case TEXT:
this.item = Integer.valueOf(new String(item));
break;
case BINARY:
this.item = ByteConverter.int4(item, 0);
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
}
}
}

@Override
public Integer getItem() {
return this.item;
public int getSqlType() {
return Types.INTEGER;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.postgresql.util.ByteConverter;

/** Translate from wire protocol to long. */
Expand All @@ -30,21 +31,23 @@ public LongParser(Object item) {
}

public LongParser(byte[] item, FormatCode formatCode) {
switch (formatCode) {
case TEXT:
this.item = Long.valueOf(new String(item));
break;
case BINARY:
this.item = ByteConverter.int8(item, 0);
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
if (item != null) {
switch (formatCode) {
case TEXT:
this.item = Long.valueOf(new String(item));
break;
case BINARY:
this.item = ByteConverter.int8(item, 0);
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
}
}
}

@Override
public Long getItem() {
return this.item;
public int getSqlType() {
return Types.BIGINT;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.nio.charset.StandardCharsets;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.postgresql.util.ByteConverter;

/** Translate from wire protocol to {@link Number}. */
Expand All @@ -32,26 +33,28 @@ public NumericParser(Object item) {
}

public NumericParser(byte[] item, FormatCode formatCode) {
switch (formatCode) {
case TEXT:
String stringValue = new String(item);
if (stringValue.equalsIgnoreCase("NaN")) {
this.item = Double.NaN;
} else {
this.item = new BigDecimal(new String(item));
}
break;
case BINARY:
this.item = ByteConverter.numeric(item, 0, item.length);
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
if (item != null) {
switch (formatCode) {
case TEXT:
String stringValue = new String(item);
if (stringValue.equalsIgnoreCase("NaN")) {
this.item = Double.NaN;
} else {
this.item = new BigDecimal(new String(item));
}
break;
case BINARY:
this.item = ByteConverter.numeric(item, 0, item.length);
break;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
}
}
}

@Override
public Number getItem() {
return this.item;
public int getSqlType() {
return Types.NUMERIC;
}

@Override
Expand Down
Loading

0 comments on commit e2cc831

Please sign in to comment.