Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 103 additions & 33 deletions handwritten/spanner/src/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {GrpcService} from './common-grpc/service';
import {PreciseDate} from '@google-cloud/precise-date';
import {
isArray,
Expand Down Expand Up @@ -938,7 +937,7 @@
return val => val;
}

let decoder: (val: any) => any;

Check warning on line 940 in handwritten/spanner/src/codec.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 940 in handwritten/spanner/src/codec.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

switch (type.code) {
case spannerClient.spanner.v1.TypeCode.BYTES:
Expand All @@ -952,8 +951,8 @@
decoder = val => {
const decoded = Buffer.from(val, 'base64');
if (columnMetadata) {
return (columnMetadata as any)['toObject'](

Check warning on line 954 in handwritten/spanner/src/codec.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
(columnMetadata as any)['decode'](decoded),

Check warning on line 955 in handwritten/spanner/src/codec.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
);
}
return decoded.toString();
Expand Down Expand Up @@ -981,7 +980,7 @@
columnMetadata &&
Object.prototype.hasOwnProperty.call(columnMetadata, val)
) {
enumVal = (columnMetadata as any)[val];

Check warning on line 983 in handwritten/spanner/src/codec.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
} else {
throw new GoogleError(
'protoEnumParams cannot be used for constructing the ProtoEnum. Pass the number as the value or provide the enum string constant as the value along with the corresponding enumObject generated by protobufjs-cli.',
Expand All @@ -997,7 +996,7 @@
return proto[enumVal];
}
if (Object.prototype.hasOwnProperty.call(columnMetadata, enumVal)) {
return (columnMetadata as any)[enumVal];

Check warning on line 999 in handwritten/spanner/src/codec.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
}
}
return enumVal;
Expand Down Expand Up @@ -1129,7 +1128,7 @@
name !== null &&
name !== undefined &&
Object.prototype.hasOwnProperty.call(columnMetadata, name)
? (columnMetadata as any)[name]

Check warning on line 1131 in handwritten/spanner/src/codec.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
: undefined,
options,
),
Expand Down Expand Up @@ -1299,79 +1298,150 @@
/**
* Encode a value in the format the API expects.
*
* This emits the `google.protobuf.Value` shape in a single pass: values are
* normalized and wrapped in their protobuf kind at the same time, so neither an
* intermediate representation nor a per-value `ObjectToStructConverter` is
* allocated. The branches prioritize primitive and common types for fast
* dispatch in hot query execution paths.
*
* @private
*
* @param {*} value The value to be encoded.
* @returns {object} google.protobuf.Value
*/
function encode(value: Value): p.IValue {
return GrpcService.encodeValue_(encodeValue(value));
switch (typeof value) {
case 'string':
return {stringValue: value};
case 'number':
// Only non-integer, finite numbers are sent as a protobuf number. Every
// other numeric value — integers (INT64 is a string on the wire) as well
// as NaN and ±Infinity — is sent as a string.
return Number.isFinite(value) && !Number.isInteger(value)
? {numberValue: value}
: {stringValue: value.toString()};
case 'boolean':
return {boolValue: value};
case 'object':
return value === null ? {nullValue: 0} : encodeObject(value);
default:
throw new Error(`Value of type ${typeof value} not recognized.`);
}
}

/**
* Formats values into expected format of google.protobuf.Value. The actual
* conversion to a google.protobuf.Value object happens via
* `Service.encodeValue_`
* Encodes a non-null object into a google.protobuf.Value.
*
* @private
*
* @param {*} value The value to be encoded.
* @returns {*}
* @param {object} value The value to be encoded.
* @returns {object} google.protobuf.Value
*/
function encodeValue(value: Value): Value {
if (isNumber(value) && !isDecimal(value)) {
return value.toString();
function encodeObject(value: object): p.IValue {
if (value instanceof Date) {
// `Date.prototype.toJSON` returns null for invalid dates.
const json: string | null = value.toJSON();
return json === null ? {nullValue: 0} : {stringValue: json};
}

if (isDate(value)) {
return value.toJSON();
if (value instanceof WrappedNumber) {
// `Float`/`Float32` hold a number, `Int`/`PGOid` hold a string.
return encodeNumberOrString(value.value);
}

if (value instanceof WrappedNumber) {
return value.value;
if (Array.isArray(value)) {
return {listValue: {values: encodeArrayValues(value)}};
}

if (value instanceof Numeric) {
return value.value;
if (Buffer.isBuffer(value)) {
return {stringValue: value.toString('base64')};
}

if (value instanceof PGNumeric) {
return value.value;
if (value instanceof Numeric || value instanceof PGNumeric) {
return {stringValue: value.value};
}

if (Buffer.isBuffer(value)) {
return value.toString('base64');
if (value instanceof Interval) {
return {stringValue: value.toISO8601()};
}

if (value instanceof ProtoMessage) {
return value.value.toString('base64');
return {stringValue: value.value.toString('base64')};
}

if (value instanceof ProtoEnum) {
return value.value;
// Holds either the numeric constant or its string representation.
return encodeNumberOrString(value.value);
}

if (value instanceof Struct) {
return Array.from(value).map(field => encodeValue(field.value));
if (value instanceof PGJsonb) {
return {stringValue: value.toString()};
}

if (isArray(value)) {
return value.map(encodeValue);
if (isObject(value)) {
const json = JSON.stringify(value);
// `JSON.stringify` returns undefined only when an object's `toJSON()`
// method returns undefined. Other objects without a JSON representation
// (such as Functions, Symbols, and Maps) fail `isObject` and throw below.
// Note: `JSON.stringify` will throw a TypeError for circular structures
// or BigInt property values.
if (json !== undefined) {
return {stringValue: json};
}
}

if (value instanceof PGJsonb) {
return value.toString();
if (
value instanceof Number ||
value instanceof String ||
value instanceof Boolean
) {
// Boxed primitives are never produced by the public API, but were accepted
// by the previous implementation.
return encode(value.valueOf());
}

if (value instanceof Interval) {
return value.toISO8601();
if (isDate(value)) {
// A `Date` from another realm (e.g. a `vm` context) fails `instanceof`.
const json: string | null = (value as Date).toJSON();
return json === null ? {nullValue: 0} : {stringValue: json};
}

if (isObject(value)) {
return JSON.stringify(value);
throw new Error('Value of type object not recognized.');
}
Comment thread
olavloite marked this conversation as resolved.

/**
* Encodes the members of an array — or the field values of a {@link Struct},
* which is itself an array — into google.protobuf.Value messages.
*
* @private
*
* @param {Array} value The array to be encoded.
* @returns {object[]} google.protobuf.Value[]
*/
function encodeArrayValues(value: Value[]): p.IValue[] {
const {length} = value;
const values: p.IValue[] = new Array(length);
const isStruct = value instanceof Struct;

for (let i = 0; i < length; i++) {
values[i] = encode(isStruct ? value[i].value : value[i]);
}

return value;
return values;
}

/**
* Wraps an already normalized value in the matching google.protobuf.Value kind.
*
* @private
*
* @param {string|number} value The value to be wrapped.
* @returns {object} google.protobuf.Value
*/
function encodeNumberOrString(value: string | number): p.IValue {
return typeof value === 'number'
? {numberValue: value}
: {stringValue: value};
}

/**
Expand Down
Loading
Loading