1
|
5
|
/*!
|
2
|
5
|
* Copyright 2017 Google Inc. All Rights Reserved.
|
3
|
5
|
*
|
4
|
5
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
5
|
* you may not use this file except in compliance with the License.
|
6
|
5
|
* You may obtain a copy of the License at
|
7
|
5
|
*
|
8
|
5
|
* http://www.apache.org/licenses/LICENSE-2.0
|
9
|
5
|
*
|
10
|
5
|
* Unless required by applicable law or agreed to in writing, software
|
11
|
5
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
5
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
5
|
* See the License for the specific language governing permissions and
|
14
|
5
|
* limitations under the License.
|
15
|
5
|
*/
|
16
|
5
|
import {GrpcService} from './common-grpc/service';
|
17
|
5
|
import {PreciseDate} from '@google-cloud/precise-date';
|
18
|
5
|
import arrify = require('arrify');
|
19
|
5
|
import {Big} from 'big.js';
|
20
|
5
|
import * as is from 'is';
|
21
|
5
|
import {common as p} from 'protobufjs';
|
22
|
5
|
import {google as spannerClient} from '../protos/protos';
|
23
|
5
|
|
24
|
5
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
25
|
5
|
export type Value = any;
|
26
|
5
|
|
27
|
5
|
export interface Field {
|
28
|
5
|
name: string;
|
29
|
5
|
value: Value;
|
30
|
5
|
}
|
31
|
5
|
|
32
|
5
|
export interface Json {
|
33
|
5
|
[field: string]: Value;
|
34
|
5
|
}
|
35
|
5
|
|
36
|
5
|
export interface JSONOptions {
|
37
|
5
|
wrapNumbers?: boolean;
|
38
|
5
|
wrapStructs?: boolean;
|
39
|
5
|
}
|
40
|
5
|
|
41
|
5
|
// https://github.com/Microsoft/TypeScript/issues/27920
|
42
|
5
|
type DateFields = [number, number, number];
|
43
|
5
|
|
44
|
5
|
/**
|
45
|
5
|
* Date-like object used to represent Cloud Spanner Dates. DATE types represent
|
46
|
5
|
* a logical calendar date, independent of time zone. DATE values do not
|
47
|
5
|
* represent a specific 24-hour period. Rather, a given DATE value represents a
|
48
|
5
|
* different 24-hour period when interpreted in a different time zone. Because
|
49
|
5
|
* of this, all values passed to {@link Spanner.date} will be interpreted as
|
50
|
5
|
* local time.
|
51
|
5
|
*
|
52
|
5
|
* To represent an absolute point in time, use {@link Spanner.timestamp}.
|
53
|
5
|
*
|
54
|
5
|
* @see Spanner.date
|
55
|
5
|
* @see https://cloud.google.com/spanner/docs/data-types#date-type
|
56
|
5
|
*
|
57
|
5
|
* @class
|
58
|
5
|
* @extends Date
|
59
|
5
|
*
|
60
|
5
|
* @param {string|number} [date] String representing the date or number
|
61
|
5
|
* representing the year. If year is a number between 0 and 99, then year is
|
62
|
5
|
* assumed to be 1900 + year.
|
63
|
5
|
* @param {number} [month] Number representing the month (0 = January).
|
64
|
5
|
* @param {number} [date] Number representing the date.
|
65
|
5
|
*
|
66
|
5
|
* @example
|
67
|
5
|
* Spanner.date('3-3-1933');
|
68
|
5
|
*/
|
69
|
5
|
export class SpannerDate extends Date {
|
70
|
5
|
constructor(dateString?: string);
|
71
|
5
|
constructor(year: number, month: number, date: number);
|
72
|
5
|
constructor(...dateFields: Array<string | number | undefined>) {
|
73
|
5
|
const yearOrDateString = dateFields[0];
|
74
|
5
|
|
75
|
5
|
// yearOrDateString could be 0 (number).
|
76
|
5
|
if (yearOrDateString === null || yearOrDateString === undefined) {
|
77
|
5
|
dateFields[0] = new Date().toDateString();
|
78
|
5
|
}
|
79
|
5
|
|
80
|
5
|
// JavaScript Date objects will interpret ISO date strings as Zulu time,
|
81
|
5
|
// but by formatting it, we can infer local time.
|
82
|
5
|
if (/^\d{4}-\d{1,2}-\d{1,2}/.test(yearOrDateString as string)) {
|
83
|
5
|
const [year, month, date] = (yearOrDateString as string).split(/-|T/);
|
84
|
5
|
dateFields = [`${month}-${date}-${year}`];
|
85
|
5
|
}
|
86
|
5
|
|
87
|
5
|
super(...(dateFields.slice(0, 3) as DateFields));
|
88
|
5
|
}
|
89
|
5
|
/**
|
90
|
5
|
* Returns the date in ISO date format.
|
91
|
5
|
* `YYYY-[M]M-[D]D`
|
92
|
5
|
*
|
93
|
5
|
* @returns {string}
|
94
|
5
|
*/
|
95
|
5
|
toJSON(): string {
|
96
|
5
|
const year = this.getFullYear();
|
97
|
5
|
let month = (this.getMonth() + 1).toString();
|
98
|
5
|
let date = this.getDate().toString();
|
99
|
5
|
|
100
|
5
|
if (month.length === 1) {
|
101
|
5
|
month = `0${month}`;
|
102
|
5
|
}
|
103
|
5
|
|
104
|
5
|
if (date.length === 1) {
|
105
|
5
|
date = `0${date}`;
|
106
|
5
|
}
|
107
|
5
|
|
108
|
5
|
return `${year}-${month}-${date}`;
|
109
|
5
|
}
|
110
|
5
|
}
|
111
|
5
|
|
112
|
5
|
/**
|
113
|
5
|
* Using an abstract class to simplify checking for wrapped numbers.
|
114
|
5
|
*
|
115
|
5
|
* @private
|
116
|
5
|
*/
|
117
|
5
|
abstract class WrappedNumber {
|
118
|
5
|
value!: string | number;
|
119
|
5
|
abstract valueOf(): number;
|
120
|
5
|
}
|
121
|
5
|
|
122
|
5
|
/**
|
123
|
5
|
* @typedef Float
|
124
|
5
|
* @see Spanner.float
|
125
|
5
|
*/
|
126
|
5
|
export class Float extends WrappedNumber {
|
127
|
5
|
value: number;
|
128
|
5
|
constructor(value: number) {
|
129
|
5
|
super();
|
130
|
5
|
this.value = value;
|
131
|
5
|
}
|
132
|
5
|
valueOf(): number {
|
133
|
5
|
return Number(this.value);
|
134
|
5
|
}
|
135
|
5
|
}
|
136
|
5
|
|
137
|
5
|
/**
|
138
|
5
|
* @typedef Int
|
139
|
5
|
* @see Spanner.int
|
140
|
5
|
*/
|
141
|
5
|
export class Int extends WrappedNumber {
|
142
|
5
|
value: string;
|
143
|
5
|
constructor(value: string) {
|
144
|
5
|
super();
|
145
|
5
|
this.value = value.toString();
|
146
|
5
|
}
|
147
|
5
|
valueOf(): number {
|
148
|
5
|
const num = Number(this.value);
|
149
|
5
|
if (num > Number.MAX_SAFE_INTEGER) {
|
150
|
5
|
throw new Error(`Integer ${this.value} is out of bounds.`);
|
151
|
5
|
}
|
152
|
5
|
return num;
|
153
|
5
|
}
|
154
|
5
|
}
|
155
|
5
|
|
156
|
5
|
/**
|
157
|
5
|
* @typedef Struct
|
158
|
5
|
* @see Spanner.struct
|
159
|
5
|
*/
|
160
|
5
|
export class Struct extends Array<Field> {
|
161
|
5
|
/**
|
162
|
5
|
* Converts struct into a pojo (plain old JavaScript object).
|
163
|
5
|
*
|
164
|
5
|
* @param {JSONOptions} [options] JSON options.
|
165
|
5
|
* @returns {object}
|
166
|
5
|
*/
|
167
|
5
|
toJSON(options?: JSONOptions): Json {
|
168
|
5
|
return codec.convertFieldsToJson(this, options);
|
169
|
5
|
}
|
170
|
5
|
/**
|
171
|
5
|
* Converts an array of fields to a struct.
|
172
|
5
|
*
|
173
|
5
|
* @private
|
174
|
5
|
*
|
175
|
5
|
* @param {object[]} fields List of struct fields.
|
176
|
5
|
* @return {Struct}
|
177
|
5
|
*/
|
178
|
5
|
static fromArray(fields: Field[]): Struct {
|
179
|
5
|
return new Struct(...fields);
|
180
|
5
|
}
|
181
|
5
|
/**
|
182
|
5
|
* Converts a JSON object to a struct.
|
183
|
5
|
*
|
184
|
5
|
* @private
|
185
|
5
|
*
|
186
|
5
|
* @param {object} json Struct JSON.
|
187
|
5
|
* @return {Struct}
|
188
|
5
|
*/
|
189
|
5
|
static fromJSON(json: Json): Struct {
|
190
|
5
|
const fields = Object.keys(json || {}).map(name => {
|
191
|
5
|
const value = json[name];
|
192
|
5
|
return {name, value};
|
193
|
5
|
});
|
194
|
5
|
return Struct.fromArray(fields);
|
195
|
5
|
}
|
196
|
5
|
}
|
197
|
5
|
|
198
|
5
|
/**
|
199
|
5
|
* @typedef Numeric
|
200
|
5
|
* @see Spanner.numeric
|
201
|
5
|
*/
|
202
|
5
|
export class Numeric {
|
203
|
5
|
value: string;
|
204
|
5
|
constructor(value: string) {
|
205
|
5
|
this.value = value;
|
206
|
5
|
}
|
207
|
5
|
valueOf(): Big {
|
208
|
5
|
return new Big(this.value);
|
209
|
5
|
}
|
210
|
5
|
toJSON(): string {
|
211
|
5
|
return this.valueOf().toJSON();
|
212
|
5
|
}
|
213
|
5
|
}
|
214
|
5
|
|
215
|
5
|
/**
|
216
|
5
|
* @typedef JSONOptions
|
217
|
5
|
* @property {boolean} [wrapNumbers=false] Indicates if the numbers should be
|
218
|
5
|
* wrapped in Int/Float wrappers.
|
219
|
5
|
* @property {boolean} [wrapStructs=false] Indicates if the structs should be
|
220
|
5
|
* wrapped in Struct wrapper.
|
221
|
5
|
*/
|
222
|
5
|
/**
|
223
|
5
|
* Wherever a row or struct object is returned, it is assigned a "toJSON"
|
224
|
5
|
* function. This function will generate the JSON for that row.
|
225
|
5
|
*
|
226
|
5
|
* @private
|
227
|
5
|
*
|
228
|
5
|
* @param {array} row The row to generate JSON for.
|
229
|
5
|
* @param {JSONOptions} [options] JSON options.
|
230
|
5
|
* @returns {object}
|
231
|
5
|
*/
|
232
|
5
|
function convertFieldsToJson(fields: Field[], options?: JSONOptions): Json {
|
233
|
5
|
const json: Json = {};
|
234
|
5
|
|
235
|
5
|
const defaultOptions = {wrapNumbers: false, wrapStructs: false};
|
236
|
5
|
|
237
|
5
|
options = Object.assign(defaultOptions, options);
|
238
|
5
|
|
239
|
5
|
for (const {name, value} of fields) {
|
240
|
5
|
if (!name) {
|
241
|
5
|
continue;
|
242
|
5
|
}
|
243
|
5
|
|
244
|
5
|
try {
|
245
|
5
|
json[name] = convertValueToJson(value, options);
|
246
|
5
|
} catch (e) {
|
247
|
5
|
e.message = [
|
248
|
5
|
`Serializing column "${name}" encountered an error: ${e.message}`,
|
249
|
5
|
'Call row.toJSON({ wrapNumbers: true }) to receive a custom type.',
|
250
|
5
|
].join(' ');
|
251
|
5
|
throw e;
|
252
|
5
|
}
|
253
|
5
|
}
|
254
|
5
|
|
255
|
5
|
return json;
|
256
|
5
|
}
|
257
|
5
|
|
258
|
5
|
/**
|
259
|
5
|
* Attempts to convert a wrapped or nested value into a native JavaScript type.
|
260
|
5
|
*
|
261
|
5
|
* @private
|
262
|
5
|
*
|
263
|
5
|
* @param {*} value The value to convert.
|
264
|
5
|
* @param {JSONOptions} options JSON options.
|
265
|
5
|
* @return {*}
|
266
|
5
|
*/
|
267
|
5
|
function convertValueToJson(value: Value, options: JSONOptions): Value {
|
268
|
5
|
if (!options.wrapNumbers && value instanceof WrappedNumber) {
|
269
|
5
|
return value.valueOf();
|
270
|
5
|
}
|
271
|
5
|
|
272
|
5
|
if (value instanceof Struct) {
|
273
|
5
|
if (!options.wrapStructs) {
|
274
|
5
|
return value.toJSON(options);
|
275
|
5
|
}
|
276
|
5
|
|
277
|
5
|
return value.map(({name, value}) => {
|
278
|
5
|
value = convertValueToJson(value, options);
|
279
|
5
|
return {name, value};
|
280
|
5
|
});
|
281
|
5
|
}
|
282
|
5
|
|
283
|
5
|
if (Array.isArray(value)) {
|
284
|
5
|
return value.map(child => convertValueToJson(child, options));
|
285
|
5
|
}
|
286
|
5
|
|
287
|
5
|
return value;
|
288
|
5
|
}
|
289
|
5
|
|
290
|
5
|
/**
|
291
|
5
|
* Re-decode after the generic gRPC decoding step.
|
292
|
5
|
*
|
293
|
5
|
* @private
|
294
|
5
|
*
|
295
|
5
|
* @param {*} value Value to decode
|
296
|
5
|
* @param {object[]} type Value type object.
|
297
|
5
|
* @returns {*}
|
298
|
5
|
*/
|
299
|
5
|
function decode(value: Value, type: spannerClient.spanner.v1.Type): Value {
|
300
|
5
|
if (is.null(value)) {
|
301
|
5
|
return null;
|
302
|
5
|
}
|
303
|
5
|
|
304
|
5
|
let decoded = value;
|
305
|
5
|
let fields;
|
306
|
5
|
|
307
|
5
|
switch (type.code) {
|
308
|
5
|
case spannerClient.spanner.v1.TypeCode.BYTES:
|
309
|
5
|
case 'BYTES':
|
310
|
5
|
decoded = Buffer.from(decoded, 'base64');
|
311
|
5
|
break;
|
312
|
5
|
case spannerClient.spanner.v1.TypeCode.FLOAT64:
|
313
|
5
|
case 'FLOAT64':
|
314
|
5
|
decoded = new Float(decoded);
|
315
|
5
|
break;
|
316
|
5
|
case spannerClient.spanner.v1.TypeCode.INT64:
|
317
|
5
|
case 'INT64':
|
318
|
5
|
decoded = new Int(decoded);
|
319
|
5
|
break;
|
320
|
5
|
case spannerClient.spanner.v1.TypeCode.NUMERIC:
|
321
|
5
|
case 'NUMERIC':
|
322
|
5
|
decoded = new Numeric(decoded);
|
323
|
5
|
break;
|
324
|
5
|
case spannerClient.spanner.v1.TypeCode.TIMESTAMP:
|
325
|
5
|
case 'TIMESTAMP':
|
326
|
5
|
decoded = new PreciseDate(decoded);
|
327
|
5
|
break;
|
328
|
5
|
case spannerClient.spanner.v1.TypeCode.DATE:
|
329
|
5
|
case 'DATE':
|
330
|
5
|
decoded = new SpannerDate(decoded);
|
331
|
5
|
break;
|
332
|
5
|
case spannerClient.spanner.v1.TypeCode.ARRAY:
|
333
|
5
|
case 'ARRAY':
|
334
|
5
|
decoded = decoded.map(value => {
|
335
|
5
|
return decode(
|
336
|
5
|
value,
|
337
|
5
|
type.arrayElementType! as spannerClient.spanner.v1.Type
|
338
|
5
|
);
|
339
|
5
|
});
|
340
|
5
|
break;
|
341
|
5
|
case spannerClient.spanner.v1.TypeCode.STRUCT:
|
342
|
5
|
case 'STRUCT':
|
343
|
5
|
fields = type.structType!.fields!.map(({name, type}, index) => {
|
344
|
5
|
const value = decode(
|
345
|
5
|
decoded[name!] || decoded[index],
|
346
|
5
|
type as spannerClient.spanner.v1.Type
|
347
|
5
|
);
|
348
|
5
|
return {name, value};
|
349
|
5
|
});
|
350
|
5
|
decoded = Struct.fromArray(fields as Field[]);
|
351
|
5
|
break;
|
352
|
5
|
default:
|
353
|
5
|
break;
|
354
|
5
|
}
|
355
|
5
|
|
356
|
5
|
return decoded;
|
357
|
5
|
}
|
358
|
5
|
|
359
|
5
|
/**
|
360
|
5
|
* Encode a value in the format the API expects.
|
361
|
5
|
*
|
362
|
5
|
* @private
|
363
|
5
|
*
|
364
|
5
|
* @param {*} value The value to be encoded.
|
365
|
5
|
* @returns {object} google.protobuf.Value
|
366
|
5
|
*/
|
367
|
5
|
function encode(value: Value): p.IValue {
|
368
|
5
|
return GrpcService.encodeValue_(encodeValue(value));
|
369
|
5
|
}
|
370
|
5
|
|
371
|
5
|
/**
|
372
|
5
|
* Formats values into expected format of google.protobuf.Value. The actual
|
373
|
5
|
* conversion to a google.protobuf.Value object happens via
|
374
|
5
|
* `Service.encodeValue_`
|
375
|
5
|
*
|
376
|
5
|
* @private
|
377
|
5
|
*
|
378
|
5
|
* @param {*} value The value to be encoded.
|
379
|
5
|
* @returns {*}
|
380
|
5
|
*/
|
381
|
5
|
function encodeValue(value: Value): Value {
|
382
|
5
|
if (is.number(value) && !is.decimal(value)) {
|
383
|
5
|
return value.toString();
|
384
|
5
|
}
|
385
|
5
|
|
386
|
5
|
if (is.date(value)) {
|
387
|
5
|
return value.toJSON();
|
388
|
5
|
}
|
389
|
5
|
|
390
|
5
|
if (value instanceof WrappedNumber) {
|
391
|
5
|
return value.value;
|
392
|
5
|
}
|
393
|
5
|
|
394
|
5
|
if (value instanceof Numeric) {
|
395
|
5
|
return value.value;
|
396
|
5
|
}
|
397
|
5
|
|
398
|
5
|
if (Buffer.isBuffer(value)) {
|
399
|
5
|
return value.toString('base64');
|
400
|
5
|
}
|
401
|
5
|
|
402
|
5
|
if (value instanceof Struct) {
|
403
|
5
|
return Array.from(value).map(field => encodeValue(field.value));
|
404
|
5
|
}
|
405
|
5
|
|
406
|
5
|
if (is.array(value)) {
|
407
|
5
|
return value.map(encodeValue);
|
408
|
5
|
}
|
409
|
5
|
|
410
|
5
|
return value;
|
411
|
5
|
}
|
412
|
5
|
|
413
|
5
|
/**
|
414
|
5
|
* Just a map with friendlier names for the types.
|
415
|
5
|
*
|
416
|
5
|
* @private
|
417
|
5
|
* @enum {string}
|
418
|
5
|
*/
|
419
|
5
|
const TypeCode: {
|
420
|
5
|
[name: string]: keyof typeof spannerClient.spanner.v1.TypeCode;
|
421
|
5
|
} = {
|
422
|
5
|
unspecified: 'TYPE_CODE_UNSPECIFIED',
|
423
|
5
|
bool: 'BOOL',
|
424
|
5
|
int64: 'INT64',
|
425
|
5
|
float64: 'FLOAT64',
|
426
|
5
|
numeric: 'NUMERIC',
|
427
|
5
|
timestamp: 'TIMESTAMP',
|
428
|
5
|
date: 'DATE',
|
429
|
5
|
string: 'STRING',
|
430
|
5
|
bytes: 'BYTES',
|
431
|
5
|
array: 'ARRAY',
|
432
|
5
|
struct: 'STRUCT',
|
433
|
5
|
};
|
434
|
5
|
|
435
|
5
|
/**
|
436
|
5
|
* Conveniece Type object that simplifies specifying the data type, the array
|
437
|
5
|
* child type and/or struct fields.
|
438
|
5
|
*
|
439
|
5
|
* @private
|
440
|
5
|
*/
|
441
|
5
|
export interface Type {
|
442
|
5
|
type: string;
|
443
|
5
|
fields?: FieldType[];
|
444
|
5
|
child?: Type;
|
445
|
5
|
}
|
446
|
5
|
|
447
|
5
|
interface FieldType extends Type {
|
448
|
5
|
name: string;
|
449
|
5
|
}
|
450
|
5
|
|
451
|
5
|
/**
|
452
|
5
|
* @typedef {ParamType} StructField
|
453
|
5
|
* @property {string} name The name of the field.
|
454
|
5
|
*/
|
455
|
5
|
/**
|
456
|
5
|
* @typedef {object} ParamType
|
457
|
5
|
* @property {string} type The param type. Must be one of the following:
|
458
|
5
|
* - float64
|
459
|
5
|
* - int64
|
460
|
5
|
* - numeric
|
461
|
5
|
* - bool
|
462
|
5
|
* - string
|
463
|
5
|
* - bytes
|
464
|
5
|
* - timestamp
|
465
|
5
|
* - date
|
466
|
5
|
* - struct
|
467
|
5
|
* - array
|
468
|
5
|
* @property {StructField[]} [fields] **For struct types only**. Type
|
469
|
5
|
* definitions for the individual fields.
|
470
|
5
|
* @property {string|ParamType} [child] **For array types only**. The array
|
471
|
5
|
* element type.
|
472
|
5
|
*/
|
473
|
5
|
/**
|
474
|
5
|
* Get the corresponding Spanner data type for the provided value.
|
475
|
5
|
*
|
476
|
5
|
* @private
|
477
|
5
|
*
|
478
|
5
|
* @param {*} value - The value.
|
479
|
5
|
* @returns {object}
|
480
|
5
|
*
|
481
|
5
|
* @example
|
482
|
5
|
* codec.getType(NaN);
|
483
|
5
|
* // {type: 'float64'}
|
484
|
5
|
*/
|
485
|
5
|
function getType(value: Value): Type {
|
486
|
5
|
const isSpecialNumber =
|
487
|
5
|
is.infinite(value) || (is.number(value) && isNaN(value));
|
488
|
5
|
|
489
|
5
|
if (is.decimal(value) || isSpecialNumber || value instanceof Float) {
|
490
|
5
|
return {type: 'float64'};
|
491
|
5
|
}
|
492
|
5
|
|
493
|
5
|
if (is.number(value) || value instanceof Int) {
|
494
|
5
|
return {type: 'int64'};
|
495
|
5
|
}
|
496
|
5
|
|
497
|
5
|
if (value instanceof Numeric) {
|
498
|
5
|
return {type: 'numeric'};
|
499
|
5
|
}
|
500
|
5
|
|
501
|
5
|
if (is.boolean(value)) {
|
502
|
5
|
return {type: 'bool'};
|
503
|
5
|
}
|
504
|
5
|
|
505
|
5
|
if (is.string(value)) {
|
506
|
5
|
return {type: 'string'};
|
507
|
5
|
}
|
508
|
5
|
|
509
|
5
|
if (Buffer.isBuffer(value)) {
|
510
|
5
|
return {type: 'bytes'};
|
511
|
5
|
}
|
512
|
5
|
|
513
|
5
|
if (value instanceof SpannerDate) {
|
514
|
5
|
return {type: 'date'};
|
515
|
5
|
}
|
516
|
5
|
|
517
|
5
|
if (is.date(value)) {
|
518
|
5
|
return {type: 'timestamp'};
|
519
|
5
|
}
|
520
|
5
|
|
521
|
5
|
if (value instanceof Struct) {
|
522
|
5
|
return {
|
523
|
5
|
type: 'struct',
|
524
|
5
|
fields: Array.from(value).map(({name, value}) => {
|
525
|
5
|
return Object.assign({name}, getType(value));
|
526
|
5
|
}),
|
527
|
5
|
};
|
528
|
5
|
}
|
529
|
5
|
|
530
|
5
|
if (is.array(value)) {
|
531
|
5
|
let child;
|
532
|
5
|
|
533
|
5
|
for (let i = 0; i < value.length; i++) {
|
534
|
5
|
child = value[i];
|
535
|
5
|
|
536
|
5
|
if (!is.null(child)) {
|
537
|
5
|
break;
|
538
|
5
|
}
|
539
|
5
|
}
|
540
|
5
|
|
541
|
5
|
return {
|
542
|
5
|
type: 'array',
|
543
|
5
|
child: getType(child),
|
544
|
5
|
};
|
545
|
5
|
}
|
546
|
5
|
|
547
|
5
|
return {type: 'unspecified'};
|
548
|
5
|
}
|
549
|
5
|
|
550
|
5
|
/**
|
551
|
5
|
* Converts a value to google.protobuf.ListValue
|
552
|
5
|
*
|
553
|
5
|
* @private
|
554
|
5
|
*
|
555
|
5
|
* @param {*} value The value to convert.
|
556
|
5
|
* @returns {object}
|
557
|
5
|
*/
|
558
|
5
|
function convertToListValue<T>(value: T): p.IListValue {
|
559
|
5
|
const values = (arrify(value) as T[]).map(codec.encode);
|
560
|
5
|
return {values};
|
561
|
5
|
}
|
562
|
5
|
|
563
|
5
|
/**
|
564
|
5
|
* Converts milliseconds to google.protobuf.Timestamp
|
565
|
5
|
*
|
566
|
5
|
* @private
|
567
|
5
|
*
|
568
|
5
|
* @param {number} ms The milliseconds to convert.
|
569
|
5
|
* @returns {object}
|
570
|
5
|
*/
|
571
|
5
|
function convertMsToProtoTimestamp(
|
572
|
5
|
ms: number
|
573
|
5
|
): spannerClient.protobuf.ITimestamp {
|
574
|
5
|
const rawSeconds = ms / 1000;
|
575
|
5
|
const seconds = Math.floor(rawSeconds);
|
576
|
5
|
const nanos = Math.round((rawSeconds - seconds) * 1e9);
|
577
|
5
|
return {seconds, nanos};
|
578
|
5
|
}
|
579
|
5
|
|
580
|
5
|
/**
|
581
|
5
|
* Converts google.protobuf.Timestamp to Date object.
|
582
|
5
|
*
|
583
|
5
|
* @private
|
584
|
5
|
*
|
585
|
5
|
* @param {object} timestamp The protobuf timestamp.
|
586
|
5
|
* @returns {Date}
|
587
|
5
|
*/
|
588
|
5
|
function convertProtoTimestampToDate({
|
589
|
5
|
nanos = 0,
|
590
|
5
|
seconds = 0,
|
591
|
5
|
}: p.ITimestamp): Date {
|
592
|
5
|
const ms = Math.floor(nanos) / 1e6;
|
593
|
5
|
const s = Math.floor(seconds as number);
|
594
|
5
|
return new Date(s * 1000 + ms);
|
595
|
5
|
}
|
596
|
5
|
|
597
|
5
|
/**
|
598
|
5
|
* Encodes paramTypes into correct structure.
|
599
|
5
|
*
|
600
|
5
|
* @private
|
601
|
5
|
*
|
602
|
5
|
* @param {object|string} [config='unspecified'] Type config.
|
603
|
5
|
* @return {object}
|
604
|
5
|
*/
|
605
|
5
|
function createTypeObject(
|
606
|
5
|
friendlyType?: string | Type
|
607
|
5
|
): spannerClient.spanner.v1.Type {
|
608
|
5
|
if (!friendlyType) {
|
609
|
5
|
friendlyType = 'unspecified';
|
610
|
5
|
}
|
611
|
5
|
|
612
|
5
|
if (is.string(friendlyType)) {
|
613
|
5
|
friendlyType = {type: friendlyType} as Type;
|
614
|
5
|
}
|
615
|
5
|
|
616
|
5
|
const config: Type = friendlyType as Type;
|
617
|
5
|
const code: keyof typeof spannerClient.spanner.v1.TypeCode =
|
618
|
5
|
TypeCode[config.type] || TypeCode.unspecified;
|
619
|
5
|
const type: spannerClient.spanner.v1.Type = {
|
620
|
5
|
code,
|
621
|
5
|
} as spannerClient.spanner.v1.Type;
|
622
|
5
|
|
623
|
5
|
if (code === 'ARRAY') {
|
624
|
5
|
type.arrayElementType = codec.createTypeObject(config.child);
|
625
|
5
|
}
|
626
|
5
|
|
627
|
5
|
if (code === 'STRUCT') {
|
628
|
5
|
type.structType = {
|
629
|
5
|
fields: arrify(config.fields!).map(field => {
|
630
|
5
|
return {name: field.name, type: codec.createTypeObject(field)};
|
631
|
5
|
}),
|
632
|
5
|
};
|
633
|
5
|
}
|
634
|
5
|
|
635
|
5
|
return type;
|
636
|
5
|
}
|
637
|
5
|
|
638
|
5
|
export const codec = {
|
639
|
5
|
convertToListValue,
|
640
|
5
|
convertMsToProtoTimestamp,
|
641
|
5
|
convertProtoTimestampToDate,
|
642
|
5
|
createTypeObject,
|
643
|
5
|
SpannerDate,
|
644
|
5
|
Float,
|
645
|
5
|
Int,
|
646
|
5
|
Numeric,
|
647
|
5
|
convertFieldsToJson,
|
648
|
5
|
decode,
|
649
|
5
|
encode,
|
650
|
5
|
getType,
|
651
|
5
|
Struct,
|
652
|
5
|
};
|