@google-cloud/spanner
Version:
Cloud Spanner Client Library for Node.js
1,051 lines • 33 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.codec = exports.Interval = exports.PGOid = exports.PGJsonb = exports.ProtoEnum = exports.ProtoMessage = exports.PGNumeric = exports.Numeric = exports.Struct = exports.Int = exports.Float = exports.Float32 = exports.SpannerDate = void 0;
/*!
* Copyright 2017 Google Inc. All Rights Reserved.
*
* 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.
*/
const service_1 = require("./common-grpc/service");
const precise_date_1 = require("@google-cloud/precise-date");
const helper_1 = require("./helper");
const big_js_1 = require("big.js");
const is = require("is");
const protos_1 = require("../protos/protos");
const google_gax_1 = require("google-gax");
/**
* Date-like object used to represent Cloud Spanner Dates. DATE types represent
* a logical calendar date, independent of time zone. DATE values do not
* represent a specific 24-hour period. Rather, a given DATE value represents a
* different 24-hour period when interpreted in a different time zone. Because
* of this, all values passed to {@link Spanner.date} will be interpreted as
* local time.
*
* To represent an absolute point in time, use {@link Spanner.timestamp}.
*
* @see Spanner.date
* @see https://cloud.google.com/spanner/docs/data-types#date-type
*
* @class
* @extends Date
*
* @param {string|number} [date] String representing the date or number
* representing the year. If year is a number between 0 and 99, then year is
* assumed to be 1900 + year.
* @param {number} [month] Number representing the month (0 = January).
* @param {number} [date] Number representing the date.
*
* @example
* ```
* Spanner.date('3-3-1933');
* ```
*/
class SpannerDate extends Date {
constructor(...dateFields) {
const yearOrDateString = dateFields[0];
// yearOrDateString could be 0 (number).
if (yearOrDateString === null || yearOrDateString === undefined) {
dateFields[0] = new Date().toDateString();
}
// JavaScript Date objects will interpret ISO date strings as Zulu time,
// but by formatting it, we can infer local time.
if (/^\d{4}-\d{1,2}-\d{1,2}/.test(yearOrDateString)) {
const [year, month, date] = yearOrDateString.split(/-|T/);
dateFields = [`${month}-${date}-${year}`];
}
super(...dateFields.slice(0, 3));
}
/**
* Returns the date in ISO date format.
* `YYYY-MM-DD`
*
* @returns {string}
*/
toJSON() {
const year = this.getFullYear().toString();
const month = (this.getMonth() + 1).toString();
const date = this.getDate().toString();
return `${year.padStart(4, '0')}-${month.padStart(2, '0')}-${date.padStart(2, '0')}`;
}
}
exports.SpannerDate = SpannerDate;
/**
* Using an abstract class to simplify checking for wrapped numbers.
*
* @private
*/
class WrappedNumber {
value;
}
/**
* @typedef Float32
* @see Spanner.float32
*/
class Float32 extends WrappedNumber {
value;
constructor(value) {
super();
this.value = value;
}
valueOf() {
return Number(this.value);
}
}
exports.Float32 = Float32;
/**
* @typedef Float
* @see Spanner.float
*/
class Float extends WrappedNumber {
value;
constructor(value) {
super();
this.value = value;
}
valueOf() {
return Number(this.value);
}
}
exports.Float = Float;
/**
* @typedef Int
* @see Spanner.int
*/
class Int extends WrappedNumber {
value;
constructor(value) {
super();
this.value = value.toString();
}
valueOf() {
const num = Number(this.value);
if (num > Number.MAX_SAFE_INTEGER) {
throw new google_gax_1.GoogleError(`Integer ${this.value} is out of bounds.`);
}
return num;
}
}
exports.Int = Int;
/**
* @typedef Struct
* @see Spanner.struct
*/
class Struct extends Array {
/**
* Converts struct into a pojo (plain old JavaScript object).
*
* @param {JSONOptions} [options] JSON options.
* @returns {object}
*/
toJSON(options) {
return exports.codec.convertFieldsToJson(this, options);
}
/**
* Converts an array of fields to a struct.
*
* @private
*
* @param {object[]} fields List of struct fields.
* @return {Struct}
*/
static fromArray(fields) {
return new Struct(...fields);
}
/**
* Converts a JSON object to a struct.
*
* @private
*
* @param {object} json Struct JSON.
* @return {Struct}
*/
static fromJSON(json) {
const fields = Object.keys(json || {}).map(name => {
const value = json[name];
return { name, value };
});
return Struct.fromArray(fields);
}
}
exports.Struct = Struct;
/**
* @typedef Numeric
* @see Spanner.numeric
*/
class Numeric {
value;
constructor(value) {
this.value = value;
}
valueOf() {
return new big_js_1.Big(this.value);
}
toJSON() {
return this.valueOf().toJSON();
}
}
exports.Numeric = Numeric;
/**
* @typedef PGNumeric
* @see Spanner.pgNumeric
*/
class PGNumeric {
value;
constructor(pgValue) {
this.value = pgValue.toString();
}
valueOf() {
if (this.value.toLowerCase() === 'nan') {
throw new Error(`${this.value} cannot be converted to a numeric value`);
}
return new big_js_1.Big(this.value);
}
toJSON() {
return this.valueOf().toJSON();
}
}
exports.PGNumeric = PGNumeric;
/**
* @typedef ProtoMessage
* @see Spanner.protoMessage
*/
class ProtoMessage {
value;
fullName;
messageFunction;
constructor(protoMessageParams) {
this.fullName = protoMessageParams.fullName;
this.messageFunction = protoMessageParams.messageFunction;
if (protoMessageParams.value instanceof Buffer) {
this.value = protoMessageParams.value;
}
else if (protoMessageParams.messageFunction) {
this.value = protoMessageParams.messageFunction['encode'](protoMessageParams.value).finish();
}
else {
throw new google_gax_1.GoogleError(`protoMessageParams cannot be used to construct
the ProtoMessage. Pass the serialized buffer of the
proto message as the value or provide the message object along with the
corresponding messageFunction generated by protobufjs-cli.`);
}
}
toJSON() {
if (this.messageFunction) {
return this.messageFunction['toObject'](this.messageFunction['decode'](this.value));
}
return this.value.toString();
}
}
exports.ProtoMessage = ProtoMessage;
/**
* @typedef ProtoEnum
* @see Spanner.protoEnum
*/
class ProtoEnum {
value;
fullName;
enumObject;
constructor(protoEnumParams) {
this.fullName = protoEnumParams.fullName;
this.enumObject = protoEnumParams.enumObject;
/**
* @code{IProtoEnumParams} can accept either a number or a string as a value so
* converting to string and checking whether it's numeric using regex.
*/
if (/^\d+$/.test(protoEnumParams.value.toString())) {
this.value = protoEnumParams.value.toString();
}
else if (protoEnumParams.enumObject &&
protoEnumParams.enumObject[protoEnumParams.value]) {
this.value = protoEnumParams.enumObject[protoEnumParams.value];
}
else {
throw new google_gax_1.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.`);
}
}
toJSON() {
if (this.enumObject) {
return Object.getPrototypeOf(this.enumObject)[this.value];
}
return this.value.toString();
}
}
exports.ProtoEnum = ProtoEnum;
/**
* @typedef PGJsonb
* @see Spanner.pgJsonb
*/
class PGJsonb {
value;
constructor(pgValue) {
if (typeof pgValue === 'string') {
pgValue = JSON.parse(pgValue);
}
this.value = pgValue;
}
toString() {
return JSON.stringify(this.value);
}
}
exports.PGJsonb = PGJsonb;
/**
* @typedef PGOid
* @see Spanner.pgOid
*/
class PGOid extends WrappedNumber {
value;
constructor(value) {
super();
this.value = value.toString();
}
valueOf() {
const num = Number(this.value);
if (num > Number.MAX_SAFE_INTEGER) {
throw new google_gax_1.GoogleError(`PG.OID ${this.value} is out of bounds.`);
}
return num;
}
}
exports.PGOid = PGOid;
/**
* @typedef Interval
* @see Spanner.interval
*/
class Interval {
months;
days;
nanoseconds;
// Regex to parse ISO8601 duration format: P[n]Y[n]M[n]DT[n]H[n]M[n][.fffffffff]S
// Only seconds can be fractional, and can have at most 9 digits after decimal point.
// Both '.' and ',' are considered valid decimal point.
static ISO8601_PATTERN = /^P(?!$)(-?\d+Y)?(-?\d+M)?(-?\d+D)?(T(?=-?[.,]?\d)(-?\d+H)?(-?\d+M)?(-?(((\d+)([.,]\d{1,9})?)|([.,]\d{1,9}))S)?)?$/;
static MONTHS_PER_YEAR = 12;
static DAYS_PER_MONTH = 30;
static HOURS_PER_DAY = 24;
static MINUTES_PER_HOUR = 60;
static SECONDS_PER_MINUTE = 60;
static SECONDS_PER_HOUR = Interval.MINUTES_PER_HOUR * Interval.SECONDS_PER_MINUTE;
static MILLISECONDS_PER_SECOND = 1000;
static MICROSECONDS_PER_MILLISECOND = 1000;
static NANOSECONDS_PER_MICROSECOND = 1000;
static NANOSECONDS_PER_MILLISECOND = Interval.MICROSECONDS_PER_MILLISECOND *
Interval.NANOSECONDS_PER_MICROSECOND;
static NANOSECONDS_PER_SECOND = Interval.MILLISECONDS_PER_SECOND *
Interval.MICROSECONDS_PER_MILLISECOND *
Interval.NANOSECONDS_PER_MICROSECOND;
static NANOSECONDS_PER_DAY = BigInt(Interval.HOURS_PER_DAY) *
BigInt(Interval.SECONDS_PER_HOUR) *
BigInt(Interval.NANOSECONDS_PER_SECOND);
static NANOSECONDS_PER_MONTH = BigInt(Interval.DAYS_PER_MONTH) * Interval.NANOSECONDS_PER_DAY;
static ZERO = new Interval(0, 0, BigInt(0));
/**
* @param months months part of the `Interval`
* @param days days part of the `Interval`
* @param nanoseconds nanoseconds part of the `Interval`
*/
constructor(months, days, nanoseconds) {
if (!is.integer(months)) {
throw new google_gax_1.GoogleError(`Invalid months: ${months}, months should be an integral value`);
}
if (!is.integer(days)) {
throw new google_gax_1.GoogleError(`Invalid days: ${days}, days should be an integral value`);
}
if (is.null(nanoseconds) || is.undefined(nanoseconds)) {
throw new google_gax_1.GoogleError(`Invalid nanoseconds: ${nanoseconds}, nanoseconds should be a valid bigint value`);
}
this.months = months;
this.days = days;
this.nanoseconds = nanoseconds;
}
/**
* @returns months part of the `Interval`.
*/
getMonths() {
return this.months;
}
/**
* @returns days part of the `Interval`.
*/
getDays() {
return this.days;
}
/**
* @returns nanoseconds part of the `Interval`.
*/
getNanoseconds() {
return this.nanoseconds;
}
/**
* Constructs an `Interval` with specified months.
*/
static fromMonths(months) {
return new Interval(months, 0, BigInt(0));
}
/**
* Constructs an `Interval` with specified days.
*/
static fromDays(days) {
return new Interval(0, days, BigInt(0));
}
/**
* Constructs an `Interval` with specified seconds.
*/
static fromSeconds(seconds) {
if (!is.integer(seconds)) {
throw new google_gax_1.GoogleError(`Invalid seconds: ${seconds}, seconds should be an integral value`);
}
return new Interval(0, 0, BigInt(Interval.NANOSECONDS_PER_SECOND) * BigInt(seconds));
}
/**
* Constructs an `Interval` with specified milliseconds.
*/
static fromMilliseconds(milliseconds) {
if (!is.integer(milliseconds)) {
throw new google_gax_1.GoogleError(`Invalid milliseconds: ${milliseconds}, milliseconds should be an integral value`);
}
return new Interval(0, 0, BigInt(Interval.NANOSECONDS_PER_MILLISECOND) * BigInt(milliseconds));
}
/**
* Constructs an `Interval` with specified microseconds.
*/
static fromMicroseconds(microseconds) {
if (!is.integer(microseconds)) {
throw new google_gax_1.GoogleError(`Invalid microseconds: ${microseconds}, microseconds should be an integral value`);
}
return new Interval(0, 0, BigInt(Interval.NANOSECONDS_PER_MICROSECOND) * BigInt(microseconds));
}
/**
* Constructs an `Interval` with specified nanoseconds.
*/
static fromNanoseconds(nanoseconds) {
return new Interval(0, 0, nanoseconds);
}
/**
* Constructs an Interval from ISO8601 duration format: `P[n]Y[n]M[n]DT[n]H[n]M[n][.fffffffff]S`.
* Only seconds can be fractional, and can have at most 9 digits after decimal point.
* Both '.' and ',' are considered valid decimal point.
*/
static fromISO8601(isoString) {
const matcher = Interval.ISO8601_PATTERN.exec(isoString);
if (!matcher) {
throw new google_gax_1.GoogleError(`Invalid ISO8601 duration string: ${isoString}`);
}
const getNullOrDefault = (groupIdx) => matcher[groupIdx] === undefined ? '0' : matcher[groupIdx];
const years = parseInt(getNullOrDefault(1).replace('Y', ''));
const months = parseInt(getNullOrDefault(2).replace('M', ''));
const days = parseInt(getNullOrDefault(3).replace('D', ''));
const hours = parseInt(getNullOrDefault(5).replace('H', ''));
const minutes = parseInt(getNullOrDefault(6).replace('M', ''));
const seconds = (0, big_js_1.Big)(getNullOrDefault(7).replace('S', '').replace(',', '.'));
const totalMonths = (0, big_js_1.Big)(years)
.mul((0, big_js_1.Big)(Interval.MONTHS_PER_YEAR))
.add((0, big_js_1.Big)(months))
.toNumber();
if (!Number.isSafeInteger(totalMonths)) {
throw new google_gax_1.GoogleError('Total months is outside of the range of safe integer');
}
const totalNanoseconds = BigInt(seconds
.add((0, big_js_1.Big)((BigInt(hours) * BigInt(Interval.SECONDS_PER_HOUR)).toString()))
.add((0, big_js_1.Big)((BigInt(minutes) * BigInt(Interval.SECONDS_PER_MINUTE)).toString()))
.mul((0, big_js_1.Big)(this.NANOSECONDS_PER_SECOND))
.toString());
return new Interval(totalMonths, days, totalNanoseconds);
}
/**
* @returns string representation of Interval in ISO8601 duration format: `P[n]Y[n]M[n]DT[n]H[n]M[n][.fffffffff]S`
*/
toISO8601() {
if (this.equals(Interval.ZERO)) {
return 'P0Y';
}
// months part is normalized to years and months.
let result = 'P';
if (this.months !== 0) {
const years_part = Math.trunc(this.months / Interval.MONTHS_PER_YEAR);
const months_part = this.months - years_part * Interval.MONTHS_PER_YEAR;
if (years_part !== 0) {
result += `${years_part}Y`;
}
if (months_part !== 0) {
result += `${months_part}M`;
}
}
if (this.days !== 0) {
result += `${this.days}D`;
}
// Nanoseconds part is normalized to hours, minutes and nanoseconds.
if (this.nanoseconds !== BigInt(0)) {
result += 'T';
let nanoseconds = this.nanoseconds;
const hours_part = nanoseconds /
BigInt(Interval.NANOSECONDS_PER_SECOND * Interval.SECONDS_PER_HOUR);
nanoseconds =
nanoseconds -
hours_part *
BigInt(Interval.NANOSECONDS_PER_SECOND * Interval.SECONDS_PER_HOUR);
const minutes_part = nanoseconds /
BigInt(Interval.NANOSECONDS_PER_SECOND * Interval.SECONDS_PER_MINUTE);
nanoseconds =
nanoseconds -
minutes_part *
BigInt(Interval.NANOSECONDS_PER_SECOND * Interval.SECONDS_PER_MINUTE);
const zero_bigint = BigInt(0);
if (hours_part !== zero_bigint) {
result += `${hours_part}H`;
}
if (minutes_part !== zero_bigint) {
result += `${minutes_part}M`;
}
let sign = '';
if (nanoseconds < zero_bigint) {
sign = '-';
nanoseconds = -nanoseconds;
}
// Nanoseconds are converted to seconds and fractional part.
const seconds_part = nanoseconds / BigInt(Interval.NANOSECONDS_PER_SECOND);
nanoseconds =
nanoseconds - seconds_part * BigInt(Interval.NANOSECONDS_PER_SECOND);
if (seconds_part !== zero_bigint || nanoseconds !== zero_bigint) {
result += `${sign}${seconds_part}`;
if (nanoseconds !== zero_bigint) {
// Fractional part is kept in a group of 3
// For e.g.: PT0.5S will be normalized to PT0.500S
result += `.${nanoseconds
.toString()
.padStart(9, '0')
.replace(/(0{3})+$/, '')}`;
}
result += 'S';
}
}
return result;
}
equals(other) {
if (!other) {
return false;
}
return (this.months === other.months &&
this.days === other.days &&
this.nanoseconds === other.nanoseconds);
}
valueOf() {
return this;
}
/**
* @returns JSON representation for Interval.
* Interval is represented in ISO8601 duration format string in JSON.
*/
toJSON() {
return this.toISO8601().toString();
}
}
exports.Interval = Interval;
/**
* @typedef JSONOptions
* @property {boolean} [wrapNumbers=false] Indicates if the numbers should be
* wrapped in Int/Float wrappers.
* @property {boolean} [wrapStructs=false] Indicates if the structs should be
* wrapped in Struct wrapper.
* @property {boolean} [includeNameless=false] Indicates if nameless columns
* should be included in the result. If true, nameless columns will be
* assigned the name '_{column_index}'.
*/
/**
* Wherever a row or struct object is returned, it is assigned a "toJSON"
* function. This function will generate the JSON for that row.
*
* @private
*
* @param {array} row The row to generate JSON for.
* @param {JSONOptions} [options] JSON options.
* @returns {object}
*/
function convertFieldsToJson(fields, options) {
const json = {};
const defaultOptions = {
wrapNumbers: false,
wrapStructs: false,
includeNameless: false,
};
options = Object.assign(defaultOptions, options);
let index = 0;
for (const { name, value } of fields) {
if (!name && !options.includeNameless) {
continue;
}
const fieldName = name ? name : `_${index}`;
try {
json[fieldName] = convertValueToJson(value, options);
}
catch (e) {
e.message = [
`Serializing column "${fieldName}" encountered an error: ${e.message}`,
'Call row.toJSON({ wrapNumbers: true }) to receive a custom type.',
].join(' ');
throw e;
}
index++;
}
return json;
}
/**
* Attempts to convert a wrapped or nested value into a native JavaScript type.
*
* @private
*
* @param {*} value The value to convert.
* @param {JSONOptions} options JSON options.
* @return {*}
*/
function convertValueToJson(value, options) {
if (!options.wrapNumbers && value instanceof WrappedNumber) {
return value.valueOf();
}
if (value instanceof Struct) {
if (!options.wrapStructs) {
return value.toJSON(options);
}
return value.map(({ name, value }) => {
value = convertValueToJson(value, options);
return { name, value };
});
}
if (Array.isArray(value)) {
return value.map(child => convertValueToJson(child, options));
}
if (value instanceof ProtoMessage || value instanceof ProtoEnum) {
return value.toJSON();
}
return value;
}
/**
* Re-decode after the generic gRPC decoding step.
*
* @private
*
* @param {*} value Value to decode
* @param {object[]} type Value type object.
* @param columnMetadata Optional parameter to deserialize data
* @returns {*}
*/
function decode(value, type, columnMetadata) {
if (is.null(value)) {
return null;
}
let decoded = value;
let fields;
switch (type.code) {
case protos_1.google.spanner.v1.TypeCode.BYTES:
case 'BYTES':
decoded = Buffer.from(decoded, 'base64');
break;
case protos_1.google.spanner.v1.TypeCode.PROTO:
case 'PROTO':
decoded = Buffer.from(decoded, 'base64');
decoded = new ProtoMessage({
value: decoded,
fullName: type.protoTypeFqn,
messageFunction: columnMetadata,
});
break;
case protos_1.google.spanner.v1.TypeCode.ENUM:
case 'ENUM':
decoded = new ProtoEnum({
value: decoded,
fullName: type.protoTypeFqn,
enumObject: columnMetadata,
});
break;
case protos_1.google.spanner.v1.TypeCode.FLOAT32:
case 'FLOAT32':
decoded = new Float32(decoded);
break;
case protos_1.google.spanner.v1.TypeCode.FLOAT64:
case 'FLOAT64':
decoded = new Float(decoded);
break;
case protos_1.google.spanner.v1.TypeCode.INT64:
case 'INT64':
if (type.typeAnnotation ===
protos_1.google.spanner.v1.TypeAnnotationCode.PG_OID ||
type.typeAnnotation === 'PG_OID') {
decoded = new PGOid(decoded);
break;
}
decoded = new Int(decoded);
break;
case protos_1.google.spanner.v1.TypeCode.NUMERIC:
case 'NUMERIC':
if (type.typeAnnotation ===
protos_1.google.spanner.v1.TypeAnnotationCode.PG_NUMERIC ||
type.typeAnnotation === 'PG_NUMERIC') {
decoded = new PGNumeric(decoded);
break;
}
decoded = new Numeric(decoded);
break;
case protos_1.google.spanner.v1.TypeCode.TIMESTAMP:
case 'TIMESTAMP':
decoded = new precise_date_1.PreciseDate(decoded);
break;
case protos_1.google.spanner.v1.TypeCode.DATE:
case 'DATE':
decoded = new SpannerDate(decoded);
break;
case protos_1.google.spanner.v1.TypeCode.JSON:
case 'JSON':
if (type.typeAnnotation ===
protos_1.google.spanner.v1.TypeAnnotationCode.PG_JSONB ||
type.typeAnnotation === 'PG_JSONB') {
decoded = new PGJsonb(decoded);
break;
}
decoded = JSON.parse(decoded);
break;
case protos_1.google.spanner.v1.TypeCode.INTERVAL:
case 'INTERVAL':
decoded = Interval.fromISO8601(decoded);
break;
case protos_1.google.spanner.v1.TypeCode.ARRAY:
case 'ARRAY':
decoded = decoded.map(value => {
return decode(value, type.arrayElementType, columnMetadata);
});
break;
case protos_1.google.spanner.v1.TypeCode.STRUCT:
case 'STRUCT':
fields = type.structType.fields.map(({ name, type }, index) => {
const value = decode((!Array.isArray(decoded) && decoded[name]) || decoded[index], type, columnMetadata);
return { name, value };
});
decoded = Struct.fromArray(fields);
break;
default:
break;
}
return decoded;
}
/**
* Encode a value in the format the API expects.
*
* @private
*
* @param {*} value The value to be encoded.
* @returns {object} google.protobuf.Value
*/
function encode(value) {
return service_1.GrpcService.encodeValue_(encodeValue(value));
}
/**
* Formats values into expected format of google.protobuf.Value. The actual
* conversion to a google.protobuf.Value object happens via
* `Service.encodeValue_`
*
* @private
*
* @param {*} value The value to be encoded.
* @returns {*}
*/
function encodeValue(value) {
if (is.number(value) && !is.decimal(value)) {
return value.toString();
}
if (is.date(value)) {
return value.toJSON();
}
if (value instanceof WrappedNumber) {
return value.value;
}
if (value instanceof Numeric) {
return value.value;
}
if (value instanceof PGNumeric) {
return value.value;
}
if (Buffer.isBuffer(value)) {
return value.toString('base64');
}
if (value instanceof ProtoMessage) {
return value.value.toString('base64');
}
if (value instanceof ProtoEnum) {
return value.value;
}
if (value instanceof Struct) {
return Array.from(value).map(field => encodeValue(field.value));
}
if (is.array(value)) {
return value.map(encodeValue);
}
if (value instanceof PGJsonb) {
return value.toString();
}
if (value instanceof Interval) {
return value.toISO8601();
}
if (is.object(value)) {
return JSON.stringify(value);
}
return value;
}
/**
* Just a map with friendlier names for the types.
*
* @private
* @enum {string}
*/
const TypeCode = {
unspecified: 'TYPE_CODE_UNSPECIFIED',
bool: 'BOOL',
int64: 'INT64',
pgOid: 'INT64',
float32: 'FLOAT32',
float64: 'FLOAT64',
numeric: 'NUMERIC',
pgNumeric: 'NUMERIC',
timestamp: 'TIMESTAMP',
date: 'DATE',
string: 'STRING',
bytes: 'BYTES',
json: 'JSON',
jsonb: 'JSON',
interval: 'INTERVAL',
proto: 'PROTO',
enum: 'ENUM',
array: 'ARRAY',
struct: 'STRUCT',
};
/**
* @typedef {ParamType} StructField
* @property {string} name The name of the field.
*/
/**
* @typedef {object} ParamType
* @property {string} type The param type. Must be one of the following:
* - float32
* - float64
* - int64
* - numeric
* - bool
* - string
* - bytes
* - json
* - interval
* - proto
* - enum
* - timestamp
* - date
* - struct
* - array
* @property {StructField[]} [fields] **For struct types only**. Type
* definitions for the individual fields.
* @property {string|ParamType} [child] **For array types only**. The array
* element type.
*/
/**
* Get the corresponding Spanner data type for the provided value.
*
* @private
*
* @param {*} value - The value.
* @returns {object}
*
* @example
* ```
* codec.getType(NaN);
* // {type: 'float64'}
* ```
*/
function getType(value) {
const isSpecialNumber = is.infinite(value) || (is.number(value) && isNaN(value));
if (value instanceof Float32) {
return { type: 'float32' };
}
if (is.decimal(value) || isSpecialNumber || value instanceof Float) {
return { type: 'float64' };
}
if (is.number(value) || value instanceof Int) {
return { type: 'int64' };
}
if (value instanceof Numeric) {
return { type: 'numeric' };
}
if (value instanceof PGNumeric) {
return { type: 'pgNumeric' };
}
if (value instanceof PGJsonb) {
return { type: 'pgJsonb' };
}
if (value instanceof PGOid) {
return { type: 'pgOid' };
}
if (value instanceof Interval) {
return { type: 'interval' };
}
if (value instanceof ProtoMessage) {
return { type: 'proto', fullName: value.fullName };
}
if (value instanceof ProtoEnum) {
return { type: 'enum', fullName: value.fullName };
}
if (is.boolean(value)) {
return { type: 'bool' };
}
if (is.string(value)) {
return { type: 'string' };
}
if (Buffer.isBuffer(value)) {
return { type: 'bytes' };
}
if (value instanceof SpannerDate) {
return { type: 'date' };
}
if (is.date(value)) {
return { type: 'timestamp' };
}
if (value instanceof Struct) {
return {
type: 'struct',
fields: Array.from(value).map(({ name, value }) => {
return Object.assign({ name }, getType(value));
}),
};
}
if (is.array(value)) {
let child;
for (let i = 0; i < value.length; i++) {
child = value[i];
if (!is.null(child)) {
break;
}
}
return {
type: 'array',
child: getType(child),
};
}
if (is.object(value)) {
return { type: 'json' };
}
return { type: 'unspecified' };
}
/**
* Converts a value to google.protobuf.ListValue
*
* @private
*
* @param {*} value The value to convert.
* @returns {object}
*/
function convertToListValue(value) {
const values = (0, helper_1.toArray)(value).map(exports.codec.encode);
return { values };
}
/**
* Converts milliseconds to google.protobuf.Timestamp
*
* @private
*
* @param {number} ms The milliseconds to convert.
* @returns {object}
*/
function convertMsToProtoTimestamp(ms) {
const rawSeconds = ms / 1000;
const seconds = Math.floor(rawSeconds);
const nanos = Math.round((rawSeconds - seconds) * 1e9);
return { seconds, nanos };
}
/**
* Converts google.protobuf.Timestamp to Date object.
*
* @private
*
* @param {object} timestamp The protobuf timestamp.
* @returns {Date}
*/
function convertProtoTimestampToDate({ nanos = 0, seconds = 0, }) {
const ms = Math.floor(nanos) / 1e6;
const s = Math.floor(seconds);
return new Date(s * 1000 + ms);
}
/**
* Encodes paramTypes into correct structure.
*
* @private
*
* @param {object|string} [config='unspecified'] Type config.
* @return {object}
*/
function createTypeObject(friendlyType) {
if (!friendlyType) {
friendlyType = 'unspecified';
}
if (typeof friendlyType === 'string') {
friendlyType = { type: friendlyType };
}
const config = friendlyType;
const code = TypeCode[config.type] || TypeCode.unspecified;
const type = {
code,
};
if (code === 'PROTO' || code === 'ENUM') {
type.protoTypeFqn = config.fullName || '';
}
if (code === 'ARRAY') {
type.arrayElementType = exports.codec.createTypeObject(config.child);
}
if (code === 'STRUCT') {
type.structType = {
fields: (0, helper_1.toArray)(config.fields).map(field => {
return { name: field.name, type: exports.codec.createTypeObject(field) };
}),
};
}
if (friendlyType.type === 'pgNumeric') {
type.typeAnnotation =
protos_1.google.spanner.v1.TypeAnnotationCode.PG_NUMERIC;
}
else if (friendlyType.type === 'jsonb') {
type.typeAnnotation = protos_1.google.spanner.v1.TypeAnnotationCode.PG_JSONB;
}
else if (friendlyType.type === 'pgOid') {
type.typeAnnotation = protos_1.google.spanner.v1.TypeAnnotationCode.PG_OID;
}
return type;
}
exports.codec = {
convertToListValue,
convertMsToProtoTimestamp,
convertProtoTimestampToDate,
createTypeObject,
SpannerDate,
Float32,
Float,
Int,
Numeric,
PGNumeric,
PGJsonb,
ProtoMessage,
ProtoEnum,
PGOid,
Interval,
convertFieldsToJson,
decode,
encode,
getType,
Struct,
};
//# sourceMappingURL=codec.js.map