Skip to content

Commit

Permalink
feat: add support incoming binary values (#27)
Browse files Browse the repository at this point in the history
* feat: add support incoming binary values

* fix: use production endpoint by default

* fix: do not generate db ids longer than 30 chars

* fix: use production endpoint

* fix: region of test instance did no longer support PG

* test: try with prod endpoint

Co-authored-by: Jim King <jsking@google.com>
  • Loading branch information
olavloite and Vizerai authored Feb 11, 2022
1 parent d72cba2 commit 2ef7563
Show file tree
Hide file tree
Showing 18 changed files with 377 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,17 @@ protected String spannerParse() {
protected byte[] binaryParse() {
ByteArrayOutputStream arrayStream = new ByteArrayOutputStream();
try {
arrayStream.write(toBinary(1, Types.INTEGER)); // dimension
arrayStream.write(toBinary(1, Types.INTEGER)); // Set null flag
arrayStream.write(toBinary(this.arrayType, Types.INTEGER)); // Set type
arrayStream.write(toBinary(this.item.size(), Types.INTEGER)); // Set array length
arrayStream.write(toBinary(0, Types.INTEGER)); // Lower bound (?)
arrayStream.write(IntegerParser.binaryParse(1)); // dimension
arrayStream.write(IntegerParser.binaryParse(1)); // Set null flag
arrayStream.write(IntegerParser.binaryParse(this.arrayType)); // Set type
arrayStream.write(IntegerParser.binaryParse(this.item.size())); // Set array length
arrayStream.write(IntegerParser.binaryParse(0)); // Lower bound (?)
for (Object currentItem : this.item) {
if (currentItem == null) {
arrayStream.write(toBinary(-1, Types.INTEGER));
arrayStream.write(IntegerParser.binaryParse(-1));
} else {
byte[] data = Parser.create(currentItem, this.arrayType).binaryParse();
arrayStream.write(toBinary(data.length, Types.INTEGER));
arrayStream.write(IntegerParser.binaryParse(data.length));
arrayStream.write(data);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

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

import java.nio.charset.StandardCharsets;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.postgresql.util.PGbytea;
Expand All @@ -32,6 +33,24 @@ public BinaryParser(Object item) {
this.item = (byte[]) item;
}

public BinaryParser(byte[] item, FormatCode formatCode) {
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;
default:
throw new IllegalArgumentException("Unsupported format: " + formatCode);
}
}

@Override
protected String stringParse() {
return PGbytea.toPGString(this.item);
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 org.postgresql.util.ByteConverter;

/**
* Parse specified data to boolean. For most cases it is simply translating from chars 't'/'f' to
Expand All @@ -34,6 +35,20 @@ public BooleanParser(Object item) {
this.item = (Boolean) 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);
}
}

@Override
public Boolean getItem() {
return this.item;
Expand All @@ -48,4 +63,11 @@ protected String stringParse() {
protected String spannerParse() {
return Boolean.toString(this.item);
}

@Override
protected byte[] binaryParse() {
byte[] result = new byte[1];
ByteConverter.bool(result, 0, this.item);
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
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,10 +33,20 @@ public DateParser(Object item) {
this.item = (java.sql.Date) item;
}

public DateParser(byte[] item) {
long days = ByteConverter.int4(item, 0) + PG_EPOCH_DAYS;
this.validateRange(days);
this.item = java.sql.Date.valueOf(LocalDate.ofEpochDay(days));
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);
}
}

/**
Expand Down Expand Up @@ -74,9 +83,9 @@ protected String stringParse() {

@Override
protected byte[] binaryParse() {
Long days = this.item.toLocalDate().toEpochDay() - PG_EPOCH_DAYS;
this.validateRange(days);
return toBinary(days.intValue(), Types.INTEGER);
long days = this.item.toLocalDate().toEpochDay() - PG_EPOCH_DAYS;
int daysAsInt = validateRange(days);
return IntegerParser.binaryParse(daysAsInt);
}

/**
Expand All @@ -85,9 +94,10 @@ protected byte[] binaryParse() {
*
* @param days Number of days to validate.
*/
private void validateRange(long days) {
private int validateRange(long days) {
if (days > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Date is out of range, epoch day=" + days);
}
return (int) days;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +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. */
public class DoubleParser extends Parser<Double> {
Expand All @@ -29,8 +29,17 @@ public DoubleParser(Object item) {
this.item = (Double) item;
}

public DoubleParser(byte[] item) {
this.item = Double.valueOf(new String(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);
}
}

@Override
Expand All @@ -45,6 +54,8 @@ protected String stringParse() {

@Override
protected byte[] binaryParse() {
return toBinary(this.item, Types.DOUBLE);
byte[] result = new byte[8];
ByteConverter.float8(result, 0, this.item);
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +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. */
public class IntegerParser extends Parser<Integer> {
Expand All @@ -29,8 +29,17 @@ public IntegerParser(Object item) {
this.item = (Integer) item;
}

public IntegerParser(byte[] item) {
this.item = Integer.valueOf(new String(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);
}
}

@Override
Expand All @@ -45,6 +54,12 @@ protected String stringParse() {

@Override
protected byte[] binaryParse() {
return toBinary(this.item, Types.INTEGER);
return binaryParse(this.item);
}

public static byte[] binaryParse(int value) {
byte[] result = new byte[4];
ByteConverter.int4(result, 0, value);
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +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. */
public class LongParser extends Parser<Long> {
Expand All @@ -29,8 +29,17 @@ public LongParser(Object item) {
this.item = (Long) item;
}

public LongParser(byte[] item) {
this.item = Long.valueOf(new String(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);
}
}

@Override
Expand All @@ -45,6 +54,8 @@ protected String stringParse() {

@Override
protected byte[] binaryParse() {
return toBinary(this.item, Types.BIGINT);
byte[] result = new byte[8];
ByteConverter.int8(result, 0, this.item);
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

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

import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.postgresql.util.ByteConverter;

/** Translate from wire protocol to {@link Number}. */
public class NumericParser extends Parser<Number> {
public NumericParser(ResultSet item, int position) throws SQLException {
// This should be either a BigDecimal value or a Double.NaN.
this.item = (Number) item.getObject(position);
}

public NumericParser(Object item) {
this.item = (Number) 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);
}
}

@Override
public Number getItem() {
return this.item;
}

@Override
protected String stringParse() {
return Double.isNaN(this.item.doubleValue()) ? "NaN" : ((BigDecimal) this.item).toPlainString();
}

@Override
protected byte[] binaryParse() {
if (Double.isNaN(this.item.doubleValue())) {
return "NaN".getBytes(StandardCharsets.UTF_8);
}
return ByteConverter.numeric((BigDecimal) this.item);
}
}
Loading

0 comments on commit 2ef7563

Please sign in to comment.