diff --git a/.gitignore b/.gitignore index 0141aa5d4c8..23d4fe55083 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ docs/ *.pyc .flattened-pom.xml +.java-version +.vscode/ diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java index a11c573233b..d13c61aaf01 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractStructReader.java @@ -165,14 +165,15 @@ public boolean getBoolean(String columnName) { @Override public long getLong(int columnIndex) { - checkNonNullOfCodes(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnIndex); + checkNonNullOfCodes( + columnIndex, Arrays.asList(Code.ENUM, Code.PG_OID, Code.INT64), columnIndex); return getLongInternal(columnIndex); } @Override public long getLong(String columnName) { int columnIndex = getColumnIndex(columnName); - checkNonNullOfCodes(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnName); + checkNonNullOfCodes(columnIndex, Arrays.asList(Code.ENUM, Code.PG_OID, Code.INT64), columnName); return getLongInternal(columnIndex); } @@ -366,7 +367,8 @@ public List getBooleanList(String columnName) { @Override public long[] getLongArray(int columnIndex) { checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnIndex); - checkArrayElementType(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnIndex); + checkArrayElementType( + columnIndex, Arrays.asList(Code.ENUM, Code.PG_OID, Code.INT64), columnIndex); return getLongArrayInternal(columnIndex); } @@ -374,14 +376,16 @@ public long[] getLongArray(int columnIndex) { public long[] getLongArray(String columnName) { int columnIndex = getColumnIndex(columnName); checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnName); - checkArrayElementType(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnName); + checkArrayElementType( + columnIndex, Arrays.asList(Code.ENUM, Code.PG_OID, Code.INT64), columnName); return getLongArrayInternal(columnIndex); } @Override public List getLongList(int columnIndex) { checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnIndex); - checkArrayElementType(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnIndex); + checkArrayElementType( + columnIndex, Arrays.asList(Code.ENUM, Code.PG_OID, Code.INT64), columnIndex); return getLongListInternal(columnIndex); } @@ -389,7 +393,8 @@ public List getLongList(int columnIndex) { public List getLongList(String columnName) { int columnIndex = getColumnIndex(columnName); checkNonNullOfCodes(columnIndex, Collections.singletonList(Code.ARRAY), columnName); - checkArrayElementType(columnIndex, Arrays.asList(Code.ENUM, Code.INT64), columnName); + checkArrayElementType( + columnIndex, Arrays.asList(Code.ENUM, Code.PG_OID, Code.INT64), columnName); return getLongListInternal(columnIndex); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java index a6769acfadf..852b9ed61a3 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStruct.java @@ -114,6 +114,9 @@ private Object writeReplace() { case PG_JSONB: builder.set(fieldName).to(Value.pgJsonb((String) value)); break; + case PG_OID: + builder.set(fieldName).to(Value.pgOid((Long) value)); + break; case BYTES: builder .set(fieldName) @@ -158,6 +161,9 @@ private Object writeReplace() { case PG_JSONB: builder.set(fieldName).toPgJsonbArray((Iterable) value); break; + case PG_OID: + builder.set(fieldName).toPgOidArray((Iterable) value); + break; case BYTES: case PROTO: builder @@ -262,6 +268,7 @@ private static Object decodeValue(Type fieldType, com.google.protobuf.Value prot checkType(fieldType, proto, KindCase.BOOL_VALUE); return proto.getBoolValue(); case INT64: + case PG_OID: case ENUM: checkType(fieldType, proto, KindCase.STRING_VALUE); return Long.parseLong(proto.getStringValue()); @@ -319,6 +326,7 @@ private static Struct decodeStructValue(Type structType, ListValue structValue) static Object decodeArrayValue(Type elementType, ListValue listValue) { switch (elementType.getCode()) { case INT64: + case PG_OID: case ENUM: // For int64/float64/float32/enum types, use custom containers. // These avoid wrapper object creation for non-null arrays. @@ -563,6 +571,8 @@ protected Value getValueInternal(int columnIndex) { return Value.json(isNull ? null : getJsonInternal(columnIndex)); case PG_JSONB: return Value.pgJsonb(isNull ? null : getPgJsonbInternal(columnIndex)); + case PG_OID: + return Value.pgOid(isNull ? null : getLongInternal(columnIndex)); case BYTES: return Value.internalBytes(isNull ? null : getLazyBytesInternal(columnIndex)); case PROTO: @@ -598,6 +608,8 @@ protected Value getValueInternal(int columnIndex) { return Value.jsonArray(isNull ? null : getJsonListInternal(columnIndex)); case PG_JSONB: return Value.pgJsonbArray(isNull ? null : getPgJsonbListInternal(columnIndex)); + case PG_OID: + return Value.pgOidArray(isNull ? null : getLongListInternal(columnIndex)); case BYTES: return Value.bytesArray(isNull ? null : getBytesListInternal(columnIndex)); case PROTO: diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java index 0e65fa7f1ba..112ecc8120c 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Struct.java @@ -396,6 +396,7 @@ private Object getAsObject(int columnIndex) { case BOOL: return getBooleanInternal(columnIndex); case INT64: + case PG_OID: case ENUM: return getLongInternal(columnIndex); case FLOAT32: @@ -426,6 +427,7 @@ private Object getAsObject(int columnIndex) { case BOOL: return getBooleanListInternal(columnIndex); case INT64: + case PG_OID: case ENUM: return getLongListInternal(columnIndex); case FLOAT32: diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java index 5d871227f54..4d93a9bfb02 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Type.java @@ -55,6 +55,7 @@ public final class Type implements Serializable { private static final Type TYPE_STRING = new Type(Code.STRING, null, null); private static final Type TYPE_JSON = new Type(Code.JSON, null, null); private static final Type TYPE_PG_JSONB = new Type(Code.PG_JSONB, null, null); + private static final Type TYPE_PG_OID = new Type(Code.PG_OID, null, null); private static final Type TYPE_BYTES = new Type(Code.BYTES, null, null); private static final Type TYPE_TIMESTAMP = new Type(Code.TIMESTAMP, null, null); private static final Type TYPE_DATE = new Type(Code.DATE, null, null); @@ -67,6 +68,7 @@ public final class Type implements Serializable { private static final Type TYPE_ARRAY_STRING = new Type(Code.ARRAY, TYPE_STRING, null); private static final Type TYPE_ARRAY_JSON = new Type(Code.ARRAY, TYPE_JSON, null); private static final Type TYPE_ARRAY_PG_JSONB = new Type(Code.ARRAY, TYPE_PG_JSONB, null); + private static final Type TYPE_ARRAY_PG_OID = new Type(Code.ARRAY, TYPE_PG_OID, null); private static final Type TYPE_ARRAY_BYTES = new Type(Code.ARRAY, TYPE_BYTES, null); private static final Type TYPE_ARRAY_TIMESTAMP = new Type(Code.ARRAY, TYPE_TIMESTAMP, null); private static final Type TYPE_ARRAY_DATE = new Type(Code.ARRAY, TYPE_DATE, null); @@ -137,6 +139,11 @@ public static Type pgJsonb() { return TYPE_PG_JSONB; } + /** Returns the descriptor for the {@code PG_OID} type. */ + public static Type pgOid() { + return TYPE_PG_OID; + } + /** * To get the descriptor for the {@code PROTO} type. * @@ -198,6 +205,8 @@ public static Type array(Type elementType) { return TYPE_ARRAY_JSON; case PG_JSONB: return TYPE_ARRAY_PG_JSONB; + case PG_OID: + return TYPE_ARRAY_PG_OID; case BYTES: return TYPE_ARRAY_BYTES; case TIMESTAMP: @@ -280,6 +289,7 @@ public enum Code { STRING(TypeCode.STRING, "character varying"), JSON(TypeCode.JSON, "unknown"), PG_JSONB(TypeCode.JSON, "jsonb", TypeAnnotationCode.PG_JSONB), + PG_OID(TypeCode.INT64, "oid", TypeAnnotationCode.PG_OID), PROTO(TypeCode.PROTO, "proto"), ENUM(TypeCode.ENUM, "enum"), BYTES(TypeCode.BYTES, "bytea"), @@ -592,6 +602,8 @@ static Type fromProto(com.google.spanner.v1.Type proto) { return json(); case PG_JSONB: return pgJsonb(); + case PG_OID: + return pgOid(); case BYTES: return bytes(); case TIMESTAMP: diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java index 49a6c1e7bc2..c2c851d6dd8 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java @@ -254,6 +254,20 @@ public static Value pgJsonb(@Nullable String v) { return new PgJsonbImpl(v == null, v); } + /** + * Returns an {@code PG_OID} value. + * + * @param v the value, which may be null + */ + public static Value pgOid(@Nullable Long v) { + return new PgOidImpl(v == null, v == null ? 0 : v); + } + + /** Returns an {@code PG_OID} value. */ + public static Value pgOid(long v) { + return new PgOidImpl(false, v); + } + /** * Return a {@code PROTO} value for not null proto messages. * @@ -587,6 +601,40 @@ public static Value pgJsonbArray(@Nullable Iterable v) { return new PgJsonbArrayImpl(v == null, v == null ? null : immutableCopyOf(v)); } + /** + * Returns an {@code ARRAY} value. + * + * @param v the source of element values, which may be null to produce a value for which {@code + * isNull()} is {@code true} + */ + public static Value pgOidArray(@Nullable long[] v) { + return pgOidArray(v, 0, v == null ? 0 : v.length); + } + + /** + * Returns an {@code ARRAY} value that takes its elements from a region of an array. + * + * @param v the source of element values, which may be null to produce a value for which {@code + * isNull()} is {@code true} + * @param pos the start position of {@code v} to copy values from. Ignored if {@code v} is {@code + * null}. + * @param length the number of values to copy from {@code v}. Ignored if {@code v} is {@code + * null}. + */ + public static Value pgOidArray(@Nullable long[] v, int pos, int length) { + return pgOidArrayFactory.create(v, pos, length); + } + + /** + * Returns an {@code ARRAY} value. + * + * @param v the source of element values. This may be {@code null} to produce a value for which + * {@code isNull()} is {@code true}. Individual elements may also be {@code null}. + */ + public static Value pgOidArray(@Nullable Iterable v) { + return pgOidArrayFactory.create(v); + } + /** * Returns an {@code ARRAY} value. * @@ -1115,6 +1163,25 @@ Value newValue(boolean isNull, BitSet nulls, long[] values) { return new Int64ArrayImpl(isNull, nulls, values); } }; + + private static final PrimitiveArrayValueFactory pgOidArrayFactory = + new PrimitiveArrayValueFactory() { + @Override + long[] newArray(int size) { + return new long[size]; + } + + @Override + void set(long[] arr, int i, Long value) { + arr[i] = value; + } + + @Override + Value newValue(boolean isNull, BitSet nulls, long[] values) { + return new PgOidArrayImpl(isNull, nulls, values); + } + }; + private static final PrimitiveArrayValueFactory float32ArrayFactory = new PrimitiveArrayValueFactory() { @Override @@ -1822,6 +1889,41 @@ void valueToString(StringBuilder b) { } } + private static class PgOidImpl extends AbstractValue { + private final long value; + + private PgOidImpl(boolean isNull, long value) { + super(isNull, Type.pgOid()); + this.value = value; + } + + @Override + public long getInt64() { + checkNotNull(); + return value; + } + + @Override + com.google.protobuf.Value valueToProto() { + return com.google.protobuf.Value.newBuilder().setStringValue(Long.toString(value)).build(); + } + + @Override + void valueToString(StringBuilder b) { + b.append(value); + } + + @Override + boolean valueEquals(Value v) { + return ((PgOidImpl) v).value == value; + } + + @Override + int valueHash() { + return Long.valueOf(value).hashCode(); + } + } + private static class LazyBytesImpl extends AbstractObjectValue { private LazyBytesImpl(boolean isNull, LazyByteArray value) { @@ -2457,6 +2559,48 @@ void appendElement(StringBuilder b, String element) { } } + private static class PgOidArrayImpl extends PrimitiveArrayImpl { + private final long[] values; + + private PgOidArrayImpl(boolean isNull, BitSet nulls, long[] values) { + super(isNull, Type.pgOid(), nulls); + this.values = values; + } + + @Override + public List getInt64Array() { + return getArray(); + } + + @Override + boolean valueEquals(Value v) { + PgOidArrayImpl that = (PgOidArrayImpl) v; + return Arrays.equals(values, that.values); + } + + @Override + int size() { + return values.length; + } + + @Override + Long getValue(int i) { + return values[i]; + } + + @Override + com.google.protobuf.Value getValueAsProto(int i) { + return com.google.protobuf.Value.newBuilder() + .setStringValue(Long.toString(values[i])) + .build(); + } + + @Override + int arrayHash() { + return Arrays.hashCode(values); + } + } + private static class LazyBytesArrayImpl extends AbstractArrayValue { private transient AbstractLazyInitializer> bytesArray = defaultInitializer(); @@ -2790,6 +2934,8 @@ private Value getValue(int fieldIndex) { return Value.numeric(value.getBigDecimal(fieldIndex)); case PG_NUMERIC: return Value.pgNumeric(value.getString(fieldIndex)); + case PG_OID: + return Value.pgOid(value.getLong(fieldIndex)); case DATE: return Value.date(value.getDate(fieldIndex)); case TIMESTAMP: @@ -2815,6 +2961,8 @@ private Value getValue(int fieldIndex) { return Value.jsonArray(value.getJsonList(fieldIndex)); case PG_JSONB: return Value.pgJsonbArray(value.getPgJsonbList(fieldIndex)); + case PG_OID: + return Value.pgOidArray(value.getLongList(fieldIndex)); case BYTES: case PROTO: return Value.bytesArray(value.getBytesList(fieldIndex)); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java index d675686ffeb..8386bd5c213 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ValueBinder.java @@ -263,6 +263,21 @@ public R toPgJsonbArray(@Nullable Iterable values) { return handle(Value.pgJsonbArray(values)); } + /** Binds to {@code Value.pgOidArray(values)} */ + public R toPgOidArray(@Nullable long[] values) { + return handle(Value.pgOidArray(values)); + } + + /** Binds to {@code Value.pgOidArray(values, pos, length)} */ + public R toPgOidArray(@Nullable long[] values, int pos, int length) { + return handle(Value.pgOidArray(values, pos, length)); + } + + /** Binds to {@code Value.pgOidArray(values)} */ + public R toPgOidArray(@Nullable Iterable values) { + return handle(Value.pgOidArray(values)); + } + /** Binds to {@code Value.bytesArray(values)} */ public R toBytesArray(@Nullable Iterable values) { return handle(Value.bytesArray(values)); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java index 16dd51a36a3..595bbcaf26a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractStructReaderTypesTest.java @@ -286,6 +286,7 @@ public static Collection parameters() { "getJson", Collections.singletonList("getValue") }, + {Type.pgOid(), "getLongInternal", 123L, "getLong", Collections.singletonList("getValue")}, { Type.timestamp(), "getTimestampInternal", @@ -384,6 +385,20 @@ public static Collection parameters() { "getJsonList", Collections.singletonList("getValue") }, + { + Type.array(Type.pgOid()), + "getLongArrayInternal", + new long[] {1, 2}, + "getLongArray", + Arrays.asList("getLongList", "getValue") + }, + { + Type.array(Type.pgOid()), + "getLongListInternal", + Arrays.asList(3L, 4L), + "getLongList", + Arrays.asList("getLongArray", "getValue") + }, { Type.array(Type.bytes()), "getBytesListInternal", diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java index 4ab6829a327..057d85bb346 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java @@ -4490,7 +4490,10 @@ public void testGetAllTypesAsString() { col++); assertAsString("2023-01-11", resultSet, col++); assertAsString("2023-01-11T11:55:18.123456789Z", resultSet, col++); - + if (dialect == Dialect.POSTGRESQL) { + // Check PG_OID value + assertAsString("100", resultSet, col++); + } assertAsString(ImmutableList.of("true", "NULL", "false"), resultSet, col++); assertAsString( ImmutableList.of( @@ -4543,6 +4546,14 @@ public void testGetAllTypesAsString() { assertAsString( ImmutableList.of(String.format("%d", Genre.JAZZ_VALUE), "NULL"), resultSet, col++); } + if (dialect == Dialect.POSTGRESQL) { + // Check ARRAY value + assertAsString( + ImmutableList.of( + String.format("%d", Long.MAX_VALUE), String.format("%d", Long.MIN_VALUE), "NULL"), + resultSet, + col++); + } assertFalse(resultSet.next()); } } @@ -4985,187 +4996,185 @@ private ListValue getRows(Dialect dialect) { .addValues( com.google.protobuf.Value.newBuilder() .setStringValue("2023-01-11T11:55:18.123456789Z") - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addValues( - com.google.protobuf.Value.newBuilder().setBoolValue(true).build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder().setBoolValue(false).build()) - .build())) - .addValues( - com.google.protobuf.Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue(String.valueOf(Long.MAX_VALUE)) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue(String.valueOf(Long.MIN_VALUE)) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build()) - .build())) - .addValues( - com.google.protobuf.Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addValues( - com.google.protobuf.Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNumberValue(Float.MAX_VALUE) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNumberValue(Float.MIN_VALUE) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue("NaN") - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNumberValue(3.14f) - .build()) - .build())) - .addValues( - com.google.protobuf.Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addValues( - com.google.protobuf.Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNumberValue(-12345.6789d) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNumberValue(3.14d) - .build()) - .build())) - .addValues( - com.google.protobuf.Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue("6.626") - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue("-8.9123") - .build()) - .build())) - .addValues( - com.google.protobuf.Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue("test-string1") - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue("test-string2") - .build()) - .build())) - .addValues( - com.google.protobuf.Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue("{\"key\": \"value1\"}") - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue("{\"key\": \"value2\"}") - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build()) - .build())) - .addValues( - com.google.protobuf.Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue( - Base64.getEncoder() - .encodeToString( - "test-bytes1".getBytes(StandardCharsets.UTF_8))) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue( - Base64.getEncoder() - .encodeToString( - "test-bytes2".getBytes(StandardCharsets.UTF_8))) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build()) - .build())) - .addValues( - com.google.protobuf.Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue("2000-02-29") - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue("2000-01-01") - .build()) - .build())) - .addValues( - com.google.protobuf.Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue("2023-01-11T11:55:18.123456789Z") - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build()) - .addValues( - com.google.protobuf.Value.newBuilder() - .setStringValue("2023-01-12T11:55:18Z") - .build()) - .build())); + .build()); + if (dialect == Dialect.POSTGRESQL) { + // Add PG_OID value + valuesBuilder.addValues(com.google.protobuf.Value.newBuilder().setStringValue("100").build()); + } + valuesBuilder + .addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder().setBoolValue(true).build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder().setBoolValue(false).build()) + .build())) + .addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue(String.valueOf(Long.MAX_VALUE)) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue(String.valueOf(Long.MIN_VALUE)) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .build())) + .addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNumberValue(Float.MAX_VALUE) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNumberValue(Float.MIN_VALUE) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder().setStringValue("NaN").build()) + .addValues( + com.google.protobuf.Value.newBuilder().setNumberValue(3.14f).build()) + .build())) + .addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNumberValue(-12345.6789d) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder().setNumberValue(3.14d).build()) + .build())) + .addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder().setStringValue("6.626").build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue("-8.9123") + .build()) + .build())) + .addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue("test-string1") + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue("test-string2") + .build()) + .build())) + .addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue("{\"key\": \"value1\"}") + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue("{\"key\": \"value2\"}") + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .build())) + .addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue( + Base64.getEncoder() + .encodeToString( + "test-bytes1".getBytes(StandardCharsets.UTF_8))) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue( + Base64.getEncoder() + .encodeToString( + "test-bytes2".getBytes(StandardCharsets.UTF_8))) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .build())) + .addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue("2000-02-29") + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue("2000-01-01") + .build()) + .build())) + .addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue("2023-01-11T11:55:18.123456789Z") + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue("2023-01-12T11:55:18Z") + .build()) + .build())); + if (dialect == Dialect.GOOGLE_STANDARD_SQL) { // Proto columns is supported only for GOOGLE_STANDARD_SQL valuesBuilder @@ -5205,6 +5214,27 @@ private ListValue getRows(Dialect dialect) { .build()) .build())); } + if (dialect == Dialect.POSTGRESQL) { + // Add ARRAY value + valuesBuilder.addValues( + com.google.protobuf.Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue(String.valueOf(Long.MAX_VALUE)) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setStringValue(String.valueOf(Long.MIN_VALUE)) + .build()) + .addValues( + com.google.protobuf.Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .build())); + } + return valuesBuilder.build(); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java index 5e6a4ffc2ca..2051e006d81 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/GrpcResultSetTest.java @@ -787,6 +787,22 @@ public void getPgJsonb() { assertEquals("[]", resultSet.getPgJsonb(0)); } + @Test + public void getPgOid() { + consumer.onPartialResultSet( + PartialResultSet.newBuilder() + .setMetadata(makeMetadata(Type.struct(Type.StructField.of("f", Type.pgOid())))) + .addValues(Value.pgOid(Long.MIN_VALUE).toProto()) + .addValues(Value.pgOid(Long.MAX_VALUE).toProto()) + .build()); + consumer.onCompleted(); + + assertThat(resultSet.next()).isTrue(); + assertThat(resultSet.getLong(0)).isEqualTo(Long.MIN_VALUE); + assertThat(resultSet.next()).isTrue(); + assertThat(resultSet.getLong(0)).isEqualTo(Long.MAX_VALUE); + } + @Test public void getProtoMessage() { SingerInfo singerInfo1 = @@ -1010,6 +1026,21 @@ public void getPgJsonbList() { assertEquals(jsonList, resultSet.getPgJsonbList(0)); } + @Test + public void getPgOidArray() { + long[] longArray = {111, 333, 444, 0, -1, -2234, Long.MAX_VALUE, Long.MIN_VALUE}; + + consumer.onPartialResultSet( + PartialResultSet.newBuilder() + .setMetadata( + makeMetadata(Type.struct(Type.StructField.of("f", Type.array(Type.pgOid()))))) + .addValues(Value.pgOidArray(longArray).toProto()) + .build()); + consumer.onCompleted(); + assertThat(resultSet.next()).isTrue(); + assertThat(resultSet.getLongArray(0)).isEqualTo(longArray); + } + @Test public void getProtoMessageList() { SingerInfo singerInfo1 = diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java index 0cfb57d4a22..3f7687482ce 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java @@ -595,6 +595,10 @@ private Mutation.WriteBuilder appendAllTypes(Mutation.WriteBuilder builder) { .to(Value.pgJsonb("{\"key\": \"value\"}}")) .set("pgJsonbNull") .to(Value.pgJsonb(null)) + .set("pgOid") + .to(Value.pgOid(42)) + .set("pgOidNull") + .to(Value.pgOid(null)) .set("timestamp") .to(Timestamp.MAX_VALUE) .set("timestampNull") @@ -679,6 +683,12 @@ private Mutation.WriteBuilder appendAllTypes(Mutation.WriteBuilder builder) { .toPgJsonbArray(null) .set("pgJsonbArrValue") .to(Value.pgJsonbArray(ImmutableList.of("{\"key\": \"value1\"}}", "{\"key\": \"value2\"}"))) + .set("pgOidArr") + .toPgOidArray(new long[] {1, 2, 3}) + .set("pgOidArrNull") + .toPgOidArray((long[]) null) + .set("pgOidArrValue") + .to(Value.pgOidArray(ImmutableList.of(1L, 2L))) .set("timestampArr") .toTimestampArray(ImmutableList.of(Timestamp.MAX_VALUE, Timestamp.MAX_VALUE)) .set("timestampArrNull") diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java index 454bd3c70a4..3ca550caa2d 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ResultSetsTest.java @@ -110,6 +110,7 @@ public void resultSetIteration() { Type.StructField.of("stringVal", Type.string()), Type.StructField.of("jsonVal", Type.json()), Type.StructField.of("pgJsonbVal", Type.pgJsonb()), + Type.StructField.of("pgOidVal", Type.pgOid()), Type.StructField.of("byteVal", Type.bytes()), Type.StructField.of("timestamp", Type.timestamp()), Type.StructField.of("date", Type.date()), @@ -128,6 +129,7 @@ public void resultSetIteration() { Type.StructField.of("stringArray", Type.array(Type.string())), Type.StructField.of("jsonArray", Type.array(Type.json())), Type.StructField.of("pgJsonbArray", Type.array(Type.pgJsonb())), + Type.StructField.of("pgOidArray", Type.array(Type.pgOid())), Type.StructField.of( "protoMessageArray", Type.array(Type.proto(SingerInfo.getDescriptor().getFullName()))), @@ -153,6 +155,8 @@ public void resultSetIteration() { .to(Value.json(jsonVal)) .set("pgJsonbVal") .to(Value.pgJsonb(jsonVal)) + .set("pgOidVal") + .to(Value.pgOid(2)) .set("byteVal") .to(Value.bytes(ByteArray.copyFrom(byteVal))) .set("timestamp") @@ -185,6 +189,8 @@ public void resultSetIteration() { .to(Value.jsonArray(Arrays.asList(jsonArray))) .set("pgJsonbArray") .to(Value.pgJsonbArray(Arrays.asList(jsonArray))) + .set("pgOidArray") + .to(Value.pgOidArray(longArray)) .set("protoMessageArray") .to( Value.protoMessageArray( @@ -214,6 +220,8 @@ public void resultSetIteration() { .to(Value.json(jsonVal)) .set("pgJsonbVal") .to(Value.pgJsonb(jsonVal)) + .set("pgOidVal") + .to(Value.pgOid(3)) .set("byteVal") .to(Value.bytes(ByteArray.copyFrom(byteVal))) .set("timestamp") @@ -246,6 +254,8 @@ public void resultSetIteration() { .to(Value.jsonArray(Arrays.asList(jsonArray))) .set("pgJsonbArray") .to(Value.pgJsonbArray(Arrays.asList(jsonArray))) + .set("pgOidArray") + .to(Value.pgOidArray(longArray)) .set("protoMessageArray") .to( Value.protoMessageArray( @@ -309,6 +319,10 @@ public void resultSetIteration() { assertEquals(jsonVal, rs.getPgJsonb("pgJsonbVal")); assertEquals(Value.pgJsonb(jsonVal), rs.getValue("pgJsonbVal")); + assertThat(rs.getLong(columnIndex)).isEqualTo(2L); + assertThat(rs.getValue(columnIndex++)).isEqualTo(Value.pgOid(2L)); + assertThat(rs.getColumnType("pgOidVal")).isEqualTo(Type.pgOid()); + assertThat(rs.getBytes(columnIndex)).isEqualTo(ByteArray.copyFrom(byteVal)); assertThat(rs.getValue(columnIndex++)).isEqualTo(Value.bytes(ByteArray.copyFrom(byteVal))); assertThat(rs.getBytes("byteVal")).isEqualTo(ByteArray.copyFrom(byteVal)); @@ -396,6 +410,13 @@ public void resultSetIteration() { assertEquals(Arrays.asList(jsonArray), rs.getPgJsonbList(columnIndex++)); assertEquals(Arrays.asList(jsonArray), rs.getPgJsonbList("pgJsonbArray")); + assertThat(rs.getLongArray(columnIndex)).isEqualTo(longArray); + assertThat(rs.getValue(columnIndex)).isEqualTo(Value.pgOidArray(longArray)); + assertThat(rs.getLongArray("pgOidArray")).isEqualTo(longArray); + assertThat(rs.getValue("pgOidArray")).isEqualTo(Value.pgOidArray(longArray)); + assertThat(rs.getLongList(columnIndex++)).isEqualTo(Longs.asList(longArray)); + assertThat(rs.getLongList("pgOidArray")).isEqualTo(Longs.asList(longArray)); + assertThat(rs.getProtoMessageList(columnIndex, SingerInfo.getDefaultInstance())) .isEqualTo(Arrays.asList(protoMessageArray)); assertThat(rs.getValue(columnIndex++)) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java index 6eedc058c5a..aea799aa158 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TypeTest.java @@ -180,6 +180,16 @@ Type newType() { }.test(); } + @Test + public void pgOid() { + new ScalarTypeTester(Type.Code.PG_OID, TypeCode.INT64, TypeAnnotationCode.PG_OID) { + @Override + Type newType() { + return Type.pgOid(); + } + }.test(); + } + @Test public void bytes() { new ScalarTypeTester(Type.Code.BYTES, TypeCode.BYTES) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java index 204880bf7d6..23128ad52b2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java @@ -18,6 +18,7 @@ import static com.google.cloud.spanner.ValueBinderTest.DefaultValues.defaultBytesBase64; import static com.google.cloud.spanner.ValueBinderTest.DefaultValues.defaultJson; +import static com.google.cloud.spanner.ValueBinderTest.DefaultValues.defaultLongWrapper; import static com.google.cloud.spanner.ValueBinderTest.DefaultValues.defaultPgJsonb; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -48,6 +49,7 @@ public class ValueBinderTest { private static final String JSON_METHOD_NAME = "json"; private static final String PG_JSONB_METHOD_NAME = "pgJsonb"; + private static final String PG_OID_METHOD_NAME = "pgOid"; private static final String PG_NUMERIC_METHOD_NAME = "pgNumeric"; private static final String PROTO_MESSAGE_METHOD_NAME = "protoMessage"; private static final String PROTO_ENUM_METHOD_NAME = "protoEnum"; @@ -144,6 +146,9 @@ public void reflection() } else if (method.getName().equalsIgnoreCase(PG_JSONB_METHOD_NAME)) { binderMethod = ValueBinder.class.getMethod("to", Value.class); assertThat(binderMethod.invoke(binder, Value.pgJsonb(null))).isEqualTo(lastReturnValue); + } else if (method.getName().equalsIgnoreCase(PG_OID_METHOD_NAME)) { + binderMethod = ValueBinder.class.getMethod("to", Value.class); + assertThat(binderMethod.invoke(binder, Value.pgOid(null))).isEqualTo(lastReturnValue); } else if (method.getName().equalsIgnoreCase(PG_NUMERIC_METHOD_NAME)) { binderMethod = ValueBinder.class.getMethod("to", Value.class); assertThat(binderMethod.invoke(binder, Value.pgNumeric(null))) @@ -172,6 +177,11 @@ public void reflection() binderMethod = ValueBinder.class.getMethod("to", Value.class); assertThat(binderMethod.invoke(binder, Value.pgJsonb(defaultPgJsonb()))) .isEqualTo(lastReturnValue); + } else if (method.getName().equalsIgnoreCase(PG_OID_METHOD_NAME)) { + defaultObject = defaultLongWrapper(); + binderMethod = ValueBinder.class.getMethod("to", Value.class); + assertThat(binderMethod.invoke(binder, Value.pgOid(defaultLongWrapper()))) + .isEqualTo(lastReturnValue); } else if (method.getName().equalsIgnoreCase(PG_NUMERIC_METHOD_NAME)) { defaultObject = DEFAULT_PG_NUMERIC; binderMethod = ValueBinder.class.getMethod("to", Value.class); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java index 460c646807e..92b63913fdb 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java @@ -614,6 +614,27 @@ public void testPgJsonbNested() { assertEquals(json, v.getAsString()); } + @Test + public void testPgOid() { + Value v = Value.pgOid(Long.valueOf(123)); + assertThat(v.getType()).isEqualTo(Type.pgOid()); + assertThat(v.isNull()).isFalse(); + assertThat(v.getInt64()).isEqualTo(123); + assertThat(v.toString()).isEqualTo("123"); + assertEquals("123", v.getAsString()); + } + + @Test + public void testPgOidNull() { + Value v = Value.pgOid(null); + assertThat(v.getType()).isEqualTo(Type.pgOid()); + assertThat(v.isNull()).isTrue(); + assertThat(v.toString()).isEqualTo(NULL_STRING); + IllegalStateException e = assertThrows(IllegalStateException.class, v::getInt64); + assertThat(e.getMessage()).contains("null value"); + assertEquals("NULL", v.getAsString()); + } + @Test public void bytes() { ByteArray bytes = newByteArray("abc"); @@ -1190,6 +1211,67 @@ public void testPgJsonbArrayTryGetFloat64Array() { value::getFloat64Array, "Expected: ARRAY actual: ARRAY>"); } + @Test + public void testPgOidArray() { + Value v = Value.pgOidArray(new long[] {1, 2}); + assertThat(v.isNull()).isFalse(); + assertThat(v.getInt64Array()).containsExactly(1L, 2L).inOrder(); + assertThat(v.toString()).isEqualTo("[1,2]"); + assertEquals("[1,2]", v.getAsString()); + } + + @Test + public void testPgOidArrayRange() { + Value v = Value.pgOidArray(new long[] {1, 2, 3, 4, 5}, 1, 3); + assertThat(v.isNull()).isFalse(); + assertThat(v.getInt64Array()).containsExactly(2L, 3L, 4L).inOrder(); + assertThat(v.toString()).isEqualTo("[2,3,4]"); + assertEquals("[2,3,4]", v.getAsString()); + } + + @Test + public void pgOidArrayNull() { + Value v = Value.pgOidArray((long[]) null); + assertThat(v.isNull()).isTrue(); + assertThat(v.toString()).isEqualTo(NULL_STRING); + IllegalStateException e = assertThrows(IllegalStateException.class, v::getInt64Array); + assertThat(e.getMessage()).contains("null value"); + assertEquals("NULL", v.getAsString()); + } + + @Test + public void testPgOidArrayWrapper() { + Value v = Value.pgOidArray(Arrays.asList(1L, null, 3L)); + assertThat(v.isNull()).isFalse(); + assertThat(v.getInt64Array()).containsExactly(1L, null, 3L).inOrder(); + assertThat(v.toString()).isEqualTo("[1,NULL,3]"); + assertEquals("[1,NULL,3]", v.getAsString()); + } + + @Test + public void testPgOidArrayWrapperNull() { + Value v = Value.pgOidArray((Iterable) null); + assertThat(v.isNull()).isTrue(); + assertThat(v.toString()).isEqualTo(NULL_STRING); + IllegalStateException e = assertThrows(IllegalStateException.class, v::getInt64Array); + assertThat(e.getMessage()).contains("null value"); + assertEquals("NULL", v.getAsString()); + } + + @Test + public void testPgOidArrayTryGetBool() { + Value value = Value.pgOidArray(Collections.singletonList(1234L)); + IllegalStateException e = assertThrows(IllegalStateException.class, value::getBool); + assertThat(e.getMessage()).contains("Expected: BOOL actual: ARRAY>"); + } + + @Test + public void testPgOidArrayNullTryGetBool() { + Value value = Value.pgOidArray((Iterable) null); + IllegalStateException e = assertThrows(IllegalStateException.class, value::getBoolArray); + assertThat(e.getMessage()).contains("Expected: ARRAY actual: ARRAY>"); + } + @Test public void bytesArray() { ByteArray a = newByteArray("a"); @@ -2020,6 +2102,10 @@ public void testEqualsHashCode() { tester.addEqualityGroup(Value.pgNumeric("8765.4321")); tester.addEqualityGroup(Value.pgNumeric(null)); + tester.addEqualityGroup(Value.pgOid(123L), Value.pgOid(Long.valueOf(123))); + tester.addEqualityGroup(Value.pgOid(456L)); + tester.addEqualityGroup(Value.pgOid(null)); + tester.addEqualityGroup(Value.string("abc"), Value.string("abc")); tester.addEqualityGroup(Value.string("def")); tester.addEqualityGroup(Value.string(null)); @@ -2100,6 +2186,15 @@ public void testEqualsHashCode() { tester.addEqualityGroup(Value.pgNumericArray(Collections.singletonList("1.25"))); tester.addEqualityGroup(Value.pgNumericArray(null), Value.pgNumericArray(null)); + tester.addEqualityGroup( + Value.pgOidArray(Arrays.asList(1L, 2L)), + Value.pgOidArray(new long[] {1L, 2L}), + Value.pgOidArray(new long[] {0L, 1L, 2L, 3L}, 1, 2), + Value.pgOidArray(plainIterable(1L, 2L))); + tester.addEqualityGroup(Value.pgOidArray(Collections.singletonList(3L))); + tester.addEqualityGroup(Value.pgOidArray(Collections.singletonList(null))); + tester.addEqualityGroup(Value.pgOidArray((Iterable) null)); + tester.addEqualityGroup( Value.stringArray(Arrays.asList("a", "b")), Value.stringArray(Arrays.asList("a", "b"))); tester.addEqualityGroup(Value.stringArray(Collections.singletonList("c"))); @@ -2169,6 +2264,10 @@ public void testGetAsString() { assertEquals("123456789.123456789", Value.pgNumeric("123456789.123456789").getAsString()); assertEquals("NaN", Value.pgNumeric("NaN").getAsString()); + assertEquals("1", Value.pgOid(1L).getAsString()); + assertEquals(String.valueOf(Long.MAX_VALUE), Value.pgOid(Long.MAX_VALUE).getAsString()); + assertEquals(String.valueOf(Long.MIN_VALUE), Value.pgOid(Long.MIN_VALUE).getAsString()); + assertEquals(Strings.repeat("foo", 36), Value.string(Strings.repeat("foo", 36)).getAsString()); assertEquals(Strings.repeat("foo", 36), Value.json(Strings.repeat("foo", 36)).getAsString()); assertEquals(Strings.repeat("foo", 36), Value.pgJsonb(Strings.repeat("foo", 36)).getAsString()); @@ -2212,6 +2311,9 @@ public void serialization() { reserializeAndAssert(Value.pgNumeric(Value.NAN)); reserializeAndAssert(Value.pgNumeric(null)); + reserializeAndAssert(Value.pgOid(123L)); + reserializeAndAssert(Value.pgOid(null)); + reserializeAndAssert(Value.string("abc")); reserializeAndAssert(Value.string(null)); @@ -2261,6 +2363,12 @@ public void serialization() { Value.pgNumericArray(BrokenSerializationList.of("1.23", "1.24", Value.NAN))); reserializeAndAssert(Value.pgNumericArray(null)); + reserializeAndAssert( + Value.pgOidArray(BrokenSerializationList.of(Long.valueOf(1L), Long.valueOf(2L)))); + reserializeAndAssert( + Value.pgOidArray(BrokenSerializationList.of(Long.valueOf(1L), Long.valueOf(2L), null))); + reserializeAndAssert(Value.pgOidArray((Iterable) null)); + reserializeAndAssert(Value.timestamp(null)); reserializeAndAssert(Value.timestamp(Value.COMMIT_TIMESTAMP)); reserializeAndAssert(Value.timestamp(Timestamp.now())); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AllTypesMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AllTypesMockServerTest.java index 44ae730f8c1..3313fa53426 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AllTypesMockServerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AllTypesMockServerTest.java @@ -73,6 +73,7 @@ public static Object[] data() { public static final String PG_NUMERIC_VALUE = "3.14"; public static final String STRING_VALUE = "test-string"; public static final String JSON_VALUE = "{\"key1\":\"value1\", \"key2\":\"value2\"}"; + public static final long PG_OID_VALUE = 1L; public static final byte[] BYTES_VALUE = "test-bytes".getBytes(StandardCharsets.UTF_8); public static final Date DATE_VALUE = Date.fromYearMonthDay(2024, 3, 2); public static final Timestamp TIMESTAMP_VALUE = @@ -112,6 +113,8 @@ public static Object[] data() { "{\"key1\":\"value1.1\", \"key2\":\"value1.2\"}", null, "{\"key1\":\"value3.1\", \"key2\":\"value3.2\"}"); + public static final List PG_OID_ARRAY_VALUE = + Arrays.asList(100L, null, 200L, Long.MIN_VALUE, Long.MAX_VALUE); public static final List BYTES_ARRAY_VALUE = Arrays.asList(ByteArray.copyFrom("test-bytes1"), null, ByteArray.copyFrom("test-bytes2")); public static final List DATE_ARRAY_VALUE = @@ -155,12 +158,14 @@ private void setupAllTypesResultSet(Dialect dialect) { // COL8: BYTES // COL9: DATE // COL10: TIMESTAMP - // COL11-20: ARRAY<..> for the types above. + // COL11: PG_OID (added only for POSTGRESQL dialect) + // COL12-21: ARRAY<..> for the types above. // Only for GoogleSQL: - // COL21: PROTO - // COL22: ENUM - // COL23: ARRAY - // COL24: ARRAY + // COL22: PROTO + // COL23: ENUM + // COL24: ARRAY + // COL25: ARRAY + // COL26: ARRAY (added only for POSTGRESQL dialect) ListValue.Builder row1Builder = ListValue.newBuilder() .addValues(Value.newBuilder().setBoolValue(BOOL_VALUE)) @@ -178,115 +183,92 @@ private void setupAllTypesResultSet(Dialect dialect) { .addValues( Value.newBuilder().setStringValue(Base64.getEncoder().encodeToString(BYTES_VALUE))) .addValues(Value.newBuilder().setStringValue(DATE_VALUE.toString())) - .addValues(Value.newBuilder().setStringValue(TIMESTAMP_VALUE.toString())) - .addValues( - Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addAllValues( - BOOL_ARRAY_VALUE.stream() - .map( - b -> - b == null - ? Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build() - : Value.newBuilder().setBoolValue(b).build()) - .collect(Collectors.toList())) - .build())) - .addValues( - Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addAllValues( - INT64_ARRAY_VALUE.stream() - .map( - l -> - l == null - ? Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build() - : Value.newBuilder() - .setStringValue(String.valueOf(l)) - .build()) - .collect(Collectors.toList())) - .build())) - .addValues( - Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addAllValues( - FLOAT32_ARRAY_VALUE.stream() - .map( - f -> { - if (f == null) { - return Value.newBuilder() + .addValues(Value.newBuilder().setStringValue(TIMESTAMP_VALUE.toString())); + if (dialect == Dialect.POSTGRESQL) { + row1Builder.addValues( + Value.newBuilder().setStringValue(String.valueOf(PG_OID_VALUE)).build()); + } + + row1Builder + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + BOOL_ARRAY_VALUE.stream() + .map( + b -> + b == null + ? Value.newBuilder() .setNullValue(NullValue.NULL_VALUE) - .build(); - } else if (Float.isNaN(f)) { - return Value.newBuilder().setStringValue("NaN").build(); - } else { - return Value.newBuilder().setNumberValue(f).build(); - } - }) - .collect(Collectors.toList())) - .build())) - .addValues( - Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addAllValues( - FLOAT64_ARRAY_VALUE.stream() - .map( - d -> { - if (d == null) { - return Value.newBuilder() + .build() + : Value.newBuilder().setBoolValue(b).build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + INT64_ARRAY_VALUE.stream() + .map( + l -> + l == null + ? Value.newBuilder() .setNullValue(NullValue.NULL_VALUE) - .build(); - } else if (Double.isNaN(d)) { - return Value.newBuilder().setStringValue("NaN").build(); - } else { - return Value.newBuilder().setNumberValue(d).build(); - } - }) - .collect(Collectors.toList())) - .build())) - .addValues( - Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addAllValues( - dialect == Dialect.POSTGRESQL - ? PG_NUMERIC_ARRAY_VALUE.stream() - .map( - string -> - string == null - ? Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build() - : Value.newBuilder() - .setStringValue(string) - .build()) - .collect(Collectors.toList()) - : NUMERIC_ARRAY_VALUE.stream() - .map( - bigDecimal -> - bigDecimal == null - ? Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build() - : Value.newBuilder() - .setStringValue( - bigDecimal.toEngineeringString()) - .build()) - .collect(Collectors.toList())) - .build())) - .addValues( - Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addAllValues( - STRING_ARRAY_VALUE.stream() + .build() + : Value.newBuilder() + .setStringValue(String.valueOf(l)) + .build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + FLOAT32_ARRAY_VALUE.stream() + .map( + f -> { + if (f == null) { + return Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build(); + } else if (Float.isNaN(f)) { + return Value.newBuilder().setStringValue("NaN").build(); + } else { + return Value.newBuilder().setNumberValue(f).build(); + } + }) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + FLOAT64_ARRAY_VALUE.stream() + .map( + d -> { + if (d == null) { + return Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build(); + } else if (Double.isNaN(d)) { + return Value.newBuilder().setStringValue("NaN").build(); + } else { + return Value.newBuilder().setNumberValue(d).build(); + } + }) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + dialect == Dialect.POSTGRESQL + ? PG_NUMERIC_ARRAY_VALUE.stream() .map( string -> string == null @@ -294,77 +276,103 @@ private void setupAllTypesResultSet(Dialect dialect) { .setNullValue(NullValue.NULL_VALUE) .build() : Value.newBuilder().setStringValue(string).build()) - .collect(Collectors.toList())) - .build())) - .addValues( - Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addAllValues( - JSON_ARRAY_VALUE.stream() - .map( - json -> - json == null - ? Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build() - : Value.newBuilder().setStringValue(json).build()) - .collect(Collectors.toList())) - .build())) - .addValues( - Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addAllValues( - BYTES_ARRAY_VALUE.stream() + .collect(Collectors.toList()) + : NUMERIC_ARRAY_VALUE.stream() .map( - byteArray -> - byteArray == null + bigDecimal -> + bigDecimal == null ? Value.newBuilder() .setNullValue(NullValue.NULL_VALUE) .build() : Value.newBuilder() .setStringValue( - Base64.getEncoder() - .encodeToString( - byteArray.toByteArray())) + bigDecimal.toEngineeringString()) .build()) .collect(Collectors.toList())) - .build())) - .addValues( - Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addAllValues( - DATE_ARRAY_VALUE.stream() - .map( - date -> - date == null - ? Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build() - : Value.newBuilder() - .setStringValue(date.toString()) - .build()) - .collect(Collectors.toList())) - .build())) - .addValues( - Value.newBuilder() - .setListValue( - ListValue.newBuilder() - .addAllValues( - TIMESTAMP_ARRAY_VALUE.stream() - .map( - timestamp -> - timestamp == null - ? Value.newBuilder() - .setNullValue(NullValue.NULL_VALUE) - .build() - : Value.newBuilder() - .setStringValue(timestamp.toString()) - .build()) - .collect(Collectors.toList())) - .build())); + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + STRING_ARRAY_VALUE.stream() + .map( + string -> + string == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder().setStringValue(string).build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + JSON_ARRAY_VALUE.stream() + .map( + json -> + json == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder().setStringValue(json).build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + BYTES_ARRAY_VALUE.stream() + .map( + byteArray -> + byteArray == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder() + .setStringValue( + Base64.getEncoder() + .encodeToString(byteArray.toByteArray())) + .build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + DATE_ARRAY_VALUE.stream() + .map( + date -> + date == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder() + .setStringValue(date.toString()) + .build()) + .collect(Collectors.toList())) + .build())) + .addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + TIMESTAMP_ARRAY_VALUE.stream() + .map( + timestamp -> + timestamp == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder() + .setStringValue(timestamp.toString()) + .build()) + .collect(Collectors.toList())) + .build())); if (dialect == Dialect.GOOGLE_STANDARD_SQL) { // Add PROTO values. @@ -433,6 +441,27 @@ private void setupAllTypesResultSet(Dialect dialect) { .build()); } + if (dialect == Dialect.POSTGRESQL) { + // Add ARRAY values. + row1Builder.addValues( + Value.newBuilder() + .setListValue( + ListValue.newBuilder() + .addAllValues( + PG_OID_ARRAY_VALUE.stream() + .map( + l -> + l == null + ? Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build() + : Value.newBuilder() + .setStringValue(String.valueOf(l)) + .build()) + .collect(Collectors.toList())) + .build())); + } + com.google.spanner.v1.ResultSet resultSet = com.google.spanner.v1.ResultSet.newBuilder() .setMetadata( @@ -454,7 +483,7 @@ public static Statement createInsertStatement(Dialect dialect) { .mapToObj(col -> "@p" + col) .collect(Collectors.joining(", ", "", ")"))); int param = 0; - return builder + builder .bind("p" + ++param) .to(BOOL_VALUE) .bind("p" + ++param) @@ -480,7 +509,11 @@ public static Statement createInsertStatement(Dialect dialect) { .bind("p" + ++param) .to(DATE_VALUE) .bind("p" + ++param) - .to(TIMESTAMP_VALUE) + .to(TIMESTAMP_VALUE); + if (dialect == Dialect.POSTGRESQL) { + builder.bind("p" + ++param).to(PG_OID_VALUE); + } + builder .bind("p" + ++param) .toBoolArray(BOOL_ARRAY_VALUE) .bind("p" + ++param) @@ -506,8 +539,11 @@ public static Statement createInsertStatement(Dialect dialect) { .bind("p" + ++param) .toDateArray(DATE_ARRAY_VALUE) .bind("p" + ++param) - .toTimestampArray(TIMESTAMP_ARRAY_VALUE) - .build(); + .toTimestampArray(TIMESTAMP_ARRAY_VALUE); + if (dialect == Dialect.POSTGRESQL) { + builder.bind("p" + ++param).toInt64Array(PG_OID_ARRAY_VALUE); + } + return builder.build(); } @After @@ -538,6 +574,9 @@ public void testSelectAllTypes() { assertArrayEquals(BYTES_VALUE, resultSet.getBytes(++col).toByteArray()); assertEquals(DATE_VALUE, resultSet.getDate(++col)); assertEquals(TIMESTAMP_VALUE, resultSet.getTimestamp(++col)); + if (dialect == Dialect.POSTGRESQL) { + assertEquals(PG_OID_VALUE, resultSet.getLong(++col)); + } assertEquals(BOOL_ARRAY_VALUE, resultSet.getBooleanList(++col)); assertEquals(INT64_ARRAY_VALUE, resultSet.getLongList(++col)); @@ -557,7 +596,9 @@ public void testSelectAllTypes() { assertEquals(BYTES_ARRAY_VALUE, resultSet.getBytesList(++col)); assertEquals(DATE_ARRAY_VALUE, resultSet.getDateList(++col)); assertEquals(TIMESTAMP_ARRAY_VALUE, resultSet.getTimestampList(++col)); - + if (dialect == Dialect.POSTGRESQL) { + assertEquals(PG_OID_ARRAY_VALUE, resultSet.getLongList(++col)); + } assertFalse(resultSet.next()); } } @@ -572,22 +613,39 @@ public void testInsertAllTypes() { ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0); Map paramTypes = request.getParamTypesMap(); Map params = request.getParams().getFieldsMap(); - assertEquals(20, paramTypes.size()); - assertEquals(20, params.size()); + assertEquals(dialect == Dialect.POSTGRESQL ? 22 : 20, paramTypes.size()); + assertEquals(dialect == Dialect.POSTGRESQL ? 22 : 20, params.size()); // Verify param types. - ImmutableList expectedTypes = - ImmutableList.of( - TypeCode.BOOL, - TypeCode.INT64, - TypeCode.FLOAT32, - TypeCode.FLOAT64, - TypeCode.NUMERIC, - TypeCode.STRING, - TypeCode.JSON, - TypeCode.BYTES, - TypeCode.DATE, - TypeCode.TIMESTAMP); + ImmutableList expectedTypes; + if (dialect == Dialect.POSTGRESQL) { + expectedTypes = + ImmutableList.of( + TypeCode.BOOL, + TypeCode.INT64, + TypeCode.FLOAT32, + TypeCode.FLOAT64, + TypeCode.NUMERIC, + TypeCode.STRING, + TypeCode.JSON, + TypeCode.BYTES, + TypeCode.DATE, + TypeCode.TIMESTAMP, + TypeCode.INT64); + } else { + expectedTypes = + ImmutableList.of( + TypeCode.BOOL, + TypeCode.INT64, + TypeCode.FLOAT32, + TypeCode.FLOAT64, + TypeCode.NUMERIC, + TypeCode.STRING, + TypeCode.JSON, + TypeCode.BYTES, + TypeCode.DATE, + TypeCode.TIMESTAMP); + } for (int col = 0; col < expectedTypes.size(); col++) { assertEquals(expectedTypes.get(col), paramTypes.get("p" + (col + 1)).getCode()); int arrayCol = col + expectedTypes.size(); @@ -613,6 +671,9 @@ public void testInsertAllTypes() { params.get("p" + ++col).getStringValue()); assertEquals(DATE_VALUE.toString(), params.get("p" + ++col).getStringValue()); assertEquals(TIMESTAMP_VALUE.toString(), params.get("p" + ++col).getStringValue()); + if (dialect == Dialect.POSTGRESQL) { + assertEquals(String.valueOf(PG_OID_VALUE), params.get("p" + ++col).getStringValue()); + } assertEquals( BOOL_ARRAY_VALUE, @@ -678,6 +739,13 @@ public void testInsertAllTypes() { ? null : Timestamp.parseTimestamp(value.getStringValue())) .collect(Collectors.toList())); + if (dialect == Dialect.POSTGRESQL) { + assertEquals( + PG_OID_ARRAY_VALUE, + params.get("p" + ++col).getListValue().getValuesList().stream() + .map(value -> value.hasNullValue() ? null : Long.valueOf(value.getStringValue())) + .collect(Collectors.toList())); + } } } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java index 759f058aa03..e13cfa91c1f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ChecksumResultSetTest.java @@ -69,6 +69,8 @@ public class ChecksumResultSetTest { .to(Value.json("{\"color\":\"red\",\"value\":\"#ff0\"}")) .set("pgJsonbVal") .to(Value.pgJsonb("{\"color\":\"red\",\"value\":\"#00f\"}")) + .set("pgOidVal") + .to(Value.pgOid(2 * 2)) .set("protoMessageVal") .to(SingerInfo.newBuilder().setSingerId(23).build()) .set("protoEnumVal") @@ -114,6 +116,8 @@ public class ChecksumResultSetTest { .to( Value.pgJsonbArray( Arrays.asList("{\"color\":\"red\",\"value\":\"#f00\"}", null, "[]"))) + .set("pgOidArray") + .to(Value.pgOidArray(Arrays.asList(2L, null, 1L, 0L))) .set("protoMessageArray") .to( Value.protoMessageArray( @@ -138,6 +142,7 @@ public void testRetry() { Type.StructField.of("stringVal", Type.string()), Type.StructField.of("jsonVal", Type.json()), Type.StructField.of("pgJsonbVal", Type.pgJsonb()), + Type.StructField.of("pgOidVal", Type.pgOid()), Type.StructField.of( "protoMessageVal", Type.proto(SingerInfo.getDescriptor().getFullName())), Type.StructField.of( @@ -157,6 +162,7 @@ public void testRetry() { Type.StructField.of("stringArray", Type.array(Type.string())), Type.StructField.of("jsonArray", Type.array(Type.json())), Type.StructField.of("pgJsonbArray", Type.array(Type.pgJsonb())), + Type.StructField.of("pgOidArray", Type.array(Type.pgOid())), Type.StructField.of( "protoMessageArray", Type.array(Type.proto(SingerInfo.getDescriptor().getFullName()))), @@ -182,6 +188,8 @@ public void testRetry() { .to(Value.json("{\"color\":\"red\",\"value\":\"#f00\"}")) .set("pgJsonbVal") .to(Value.pgJsonb("{\"color\":\"red\",\"value\":\"#f00\"}")) + .set("pgOidVal") + .to(Value.pgOid(2)) .set("protoMessageVal") .to(SingerInfo.newBuilder().setSingerId(98).setNationality("C1").build()) .set("protoEnumVal") @@ -230,6 +238,8 @@ public void testRetry() { .to( Value.pgJsonbArray( Arrays.asList("{\"color\":\"red\",\"value\":\"#f00\"}", null, "{}"))) + .set("pgOidArray") + .to(Value.pgOidArray(Arrays.asList(1L, null, 2L))) .set("protoMessageArray") .to( Value.protoMessageArray( @@ -260,6 +270,8 @@ public void testRetry() { .to(Value.json(null)) .set("pgJsonbVal") .to(Value.pgJsonb(null)) + .set("pgOidVal") + .to(Value.pgOid(null)) .set("protoMessageVal") .to(Value.protoMessage(null, SingerInfo.getDescriptor().getFullName())) .set("protoEnumVal") @@ -294,6 +306,8 @@ public void testRetry() { .toJsonArray(null) .set("pgJsonbArray") .toPgJsonbArray(null) + .set("pgOidArray") + .toPgOidArray((Iterable) null) .set("protoMessageArray") .to(Value.protoMessageArray(null, SingerInfo.getDescriptor())) .set("protoEnumArray") diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java index ef38df0db3d..da4b87200c3 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java @@ -68,59 +68,80 @@ public static Type[] generateAllTypes(Dialect dialect) { : Type.newBuilder().setCode(TypeCode.JSON).build(), Type.newBuilder().setCode(TypeCode.BYTES).build(), Type.newBuilder().setCode(TypeCode.DATE).build(), - Type.newBuilder().setCode(TypeCode.TIMESTAMP).build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.BOOL)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.INT64)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT32)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT64)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType( - dialect == Dialect.POSTGRESQL - ? Type.newBuilder() - .setCode(TypeCode.NUMERIC) - .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC) - : Type.newBuilder().setCode(TypeCode.NUMERIC)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.STRING)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType( - dialect == Dialect.POSTGRESQL - ? Type.newBuilder() - .setCode(TypeCode.JSON) - .setTypeAnnotation(TypeAnnotationCode.PG_JSONB) - : Type.newBuilder().setCode(TypeCode.JSON)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.BYTES)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.DATE)) - .build(), - Type.newBuilder() - .setCode(TypeCode.ARRAY) - .setArrayElementType(Type.newBuilder().setCode(TypeCode.TIMESTAMP)) - .build())); + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build())); + if (dialect == Dialect.POSTGRESQL) { + types.add( + Type.newBuilder() + .setCode(TypeCode.INT64) + .setTypeAnnotation(TypeAnnotationCode.PG_OID) + .build()); + } + types.addAll( + Arrays.asList( + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.BOOL)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.INT64)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT32)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT64)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType( + dialect == Dialect.POSTGRESQL + ? Type.newBuilder() + .setCode(TypeCode.NUMERIC) + .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC) + : Type.newBuilder().setCode(TypeCode.NUMERIC)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.STRING)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType( + dialect == Dialect.POSTGRESQL + ? Type.newBuilder() + .setCode(TypeCode.JSON) + .setTypeAnnotation(TypeAnnotationCode.PG_JSONB) + : Type.newBuilder().setCode(TypeCode.JSON)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.BYTES)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.DATE)) + .build(), + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType(Type.newBuilder().setCode(TypeCode.TIMESTAMP)) + .build())); appendProtoTypes(types, dialect); + + if (dialect == Dialect.POSTGRESQL) { + types.add( + Type.newBuilder() + .setCode(TypeCode.ARRAY) + .setArrayElementType( + Type.newBuilder() + .setCode(TypeCode.INT64) + .setTypeAnnotation(TypeAnnotationCode.PG_OID)) + .build()); + } + Type[] typeArray = new Type[types.size()]; typeArray = types.toArray(typeArray); return typeArray; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryTest.java index 170ce75e696..18044c452b5 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryTest.java @@ -285,6 +285,29 @@ public void bindFloat32Null() { assertThat(row.isNull(0)).isTrue(); } + @Test + public void bindPgOid() { + if (dialect.dialect == Dialect.POSTGRESQL) { + Struct row = + execute( + Statement.newBuilder(selectValueQuery).bind("p1").to(Value.pgOid(1234)), + Type.pgOid()); + assertThat(row.isNull(0)).isFalse(); + assertThat(row.getLong(0)).isEqualTo(1234); + } + } + + @Test + public void bindPgOidNull() { + if (dialect.dialect == Dialect.POSTGRESQL) { + Struct row = + execute( + Statement.newBuilder(selectValueQuery).bind("p1").to(Value.pgOid(null)), + Type.pgOid()); + assertThat(row.isNull(0)).isTrue(); + } + } + @Test public void bindFloat64() { Struct row = execute(Statement.newBuilder(selectValueQuery).bind("p1").to(2.0), Type.float64());