Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support incoming binary values #27

Merged
merged 10 commits into from
Feb 11, 2022
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