dolphindb 2.0.800 → 2.0.900

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/browser.js CHANGED
@@ -4,7 +4,7 @@ dayjs.extend(DayjsCustomParseFormat);
4
4
  import ipaddrjs from 'ipaddr.js';
5
5
  const { fromByteArray: buf2ipaddr } = ipaddrjs;
6
6
  import 'xshell/prototype.browser.js';
7
- import { blue } from 'xshell/chalk.browser.js';
7
+ import { blue, cyan, green, grey, magenta } from 'xshell/chalk.browser.js';
8
8
  import { concat, assert } from 'xshell/utils.browser.js';
9
9
  import { connect_websocket } from 'xshell/net.browser.js';
10
10
  export var DdbForm;
@@ -118,15 +118,6 @@ export const nulls = {
118
118
  double: -Number.MAX_VALUE,
119
119
  bytes16: Uint8Array.from(new Array(16).fill(0))
120
120
  };
121
- export const timezone_offset = 1000 * 60 * new Date().getTimezoneOffset();
122
- export var StreamingStatusCode;
123
- (function (StreamingStatusCode) {
124
- StreamingStatusCode[StreamingStatusCode["ok"] = 0] = "ok";
125
- StreamingStatusCode[StreamingStatusCode["connection_existed"] = 1] = "connection_existed";
126
- StreamingStatusCode[StreamingStatusCode["not_leader"] = 2] = "not_leader";
127
- StreamingStatusCode[StreamingStatusCode["other"] = 3] = "other";
128
- StreamingStatusCode[StreamingStatusCode["error"] = 4] = "error";
129
- })(StreamingStatusCode = StreamingStatusCode || (StreamingStatusCode = {}));
130
121
  /** 可以表示所有 DolphinDB 数据库中的数据类型 */
131
122
  export class DdbObj {
132
123
  static dec = new TextDecoder('utf-8');
@@ -522,8 +513,37 @@ export class DdbObj {
522
513
  // 00 reserved
523
514
  // 70 11 01 00 block.lengths = [70000 (0x00011170)]
524
515
  // 01 00 00 00 02 00 00 00 ... 279992 more bytes> block.data
516
+ // decimal array vector
517
+ // a = array(DECIMAL32(2)[], 0, 10)
518
+ // append!(a, [
519
+ // [1, 2, 3],
520
+ // [4, 5],
521
+ // [6, 7, 8],
522
+ // [9, 10]
523
+ // ])
524
+ // print(a)
525
+ // a
526
+ // [[1.00,2.00,3.00],[4.00,5.00],[6.00,7.00,8.00],[9.00,10.00]]
527
+ // <Buffer 65 01 type = 101 = 64 + 37 (decimal32) , form = vector
528
+ // 04 00 00 00 0a 00 00 00 rows = 4, cols = 10
529
+ // scale (只有 decimal32 才有)
530
+ // 02 00 00 00 scale = 2
531
+ // block 0
532
+ // 04 00 block.rows = 4
533
+ // 01 block.unit = 1
534
+ // 00 reserved
535
+ // 03 02 03 02 block.lengths = [3, 2, 3, 2]
536
+ // 64 00 00 00 c8 00 00 00 2c 01 00 00 90 01 00 00 f4 01 00 00 58 02 00 00 bc 02 00 00 ... 12 more bytes> [100, 200, 300, ...]
537
+ // block 1
538
+ // ...
539
+ const type_ = type - 64;
525
540
  const cols = dv.getUint32(4, le);
526
541
  let blocks = [];
542
+ // decimal32/64 会在所有 blocks 之前多一个 scale
543
+ if (type_ === DdbType.decimal32 || type_ === DdbType.decimal64) {
544
+ blocks.scale = dv.getInt32(i_items_start, le);
545
+ i_items_start += 4;
546
+ }
527
547
  let i_block_start = i_items_start;
528
548
  // 解析一个 block
529
549
  for (let i_row = 0; i_row < rows;) {
@@ -552,7 +572,20 @@ export class DdbObj {
552
572
  let total_length = 0;
553
573
  for (const x of lengths)
554
574
  total_length += x;
555
- const [len_items, data] = this.parse_vector_items(buf.subarray(i_data_start), le, type - 64, total_length);
575
+ let len_items;
576
+ let data;
577
+ switch (type_) {
578
+ case DdbType.decimal32:
579
+ len_items = total_length * 4;
580
+ data = new Int32Array(buf.buffer.slice(buf.byteOffset + i_data_start, buf.byteOffset + i_data_start + len_items));
581
+ break;
582
+ case DdbType.decimal64:
583
+ len_items = total_length * 8;
584
+ data = new BigInt64Array(buf.buffer.slice(buf.byteOffset + i_data_start, buf.byteOffset + i_data_start + len_items));
585
+ break;
586
+ default:
587
+ [len_items, data] = this.parse_vector_items(buf.subarray(i_data_start), le, type - 64, total_length);
588
+ }
556
589
  blocks.push({
557
590
  unit,
558
591
  rows,
@@ -867,6 +900,10 @@ export class DdbObj {
867
900
  if (form === DdbForm.vector && 64 <= type && type < 128)
868
901
  return [
869
902
  Uint32Array.of(this.rows, this.cols),
903
+ ...(type - 64 === DdbType.decimal32 || type - 64 === DdbType.decimal64) ?
904
+ [Int32Array.of(this.value.scale)]
905
+ :
906
+ [],
870
907
  ...this.value.map(block => ([
871
908
  Uint16Array.of(block.rows),
872
909
  Uint8Array.of(block.unit, 0),
@@ -925,10 +962,7 @@ export class DdbObj {
925
962
  })();
926
963
  if (!body)
927
964
  return new Uint8Array(0);
928
- return concat([
929
- header,
930
- ...body
931
- ]);
965
+ return concat([header, ...body]);
932
966
  }
933
967
  static pack_vector_body(value, type, length) {
934
968
  switch (type) {
@@ -1018,7 +1052,7 @@ export class DdbObj {
1018
1052
  throw new Error(`vector ${DdbType[type]} 暂不支持序列化`);
1019
1053
  }
1020
1054
  }
1021
- toString(options) {
1055
+ toString(options = { nullstr: true, quote: true }) {
1022
1056
  const type = this.inspect_type();
1023
1057
  const data = (() => {
1024
1058
  switch (this.form) {
@@ -1054,7 +1088,18 @@ export class DdbObj {
1054
1088
  for (const length of lengths) {
1055
1089
  let _items = new Array(Math.min(limit, length));
1056
1090
  for (let i = 0; i < _items.length; i++)
1057
- _items[i] = format(_type, data[acc_len + i], this.le, options);
1091
+ if (_type === DdbType.decimal32 || _type === DdbType.decimal64) {
1092
+ const x = data[acc_len + i];
1093
+ if (x === nulls.int64 && _type === DdbType.decimal64 ||
1094
+ x === nulls.int32 && _type === DdbType.decimal32)
1095
+ return '';
1096
+ const { scale } = this.value;
1097
+ const s = String(x < 0 ? -x : x).padStart(scale, '0');
1098
+ const str = (x < 0 ? '-' : '') + (scale ? `${s.slice(0, -scale) || '0'}.${s.slice(-scale)}` : s);
1099
+ _items[i] = options.colors ? green(str) : str;
1100
+ }
1101
+ else
1102
+ _items[i] = format(_type, data[acc_len + i], this.le, options);
1058
1103
  items[i_items++] = format_array(_items, length > limit);
1059
1104
  acc_len += length;
1060
1105
  }
@@ -1165,27 +1210,11 @@ export class DdbObj {
1165
1210
  }
1166
1211
  to_cols() {
1167
1212
  assert(this.form === DdbForm.table, 'form must be DdbForm.table, otherwise it cannot to_cols');
1168
- return this.value.map(col => {
1169
- let col_ = {
1170
- title: col.name,
1171
- dataIndex: col.name,
1172
- };
1173
- switch (col.type) {
1174
- case DdbType.char:
1175
- col_.render = (value) => format(DdbType.char, value, this.le);
1176
- break;
1177
- case DdbType.timestamp:
1178
- col_.render = (value) => timestamp2str(value);
1179
- break;
1180
- case DdbType.date:
1181
- col_.render = (value) => date2str(value);
1182
- break;
1183
- case DdbType.ipaddr:
1184
- col_.render = (value) => ipaddr2str(value, this.le);
1185
- break;
1186
- }
1187
- return col_;
1188
- });
1213
+ return this.value.map(col => ({
1214
+ title: col.name,
1215
+ dataIndex: col.name,
1216
+ render: (value) => format(col.type, value, col.le, { nullstr: false, quote: false })
1217
+ }));
1189
1218
  }
1190
1219
  to_rows() {
1191
1220
  assert(this.form === DdbForm.table, 'form must be DdbForm.table, otherwise it cannot to_rows');
@@ -1242,8 +1271,8 @@ let _decimals = 20;
1242
1271
  let _formatter = Intl.NumberFormat('en-US', { maximumFractionDigits: 20 });
1243
1272
  /** Formats a single element (value) as a string according to DdbType, null returns a 'null' string */
1244
1273
  export function format(type, value, le, options = {}) {
1274
+ const { decimals, nullstr = false, colors = false, quote = false } = options;
1245
1275
  const formatter = (() => {
1246
- const { decimals } = options;
1247
1276
  if (decimals === undefined || decimals === null)
1248
1277
  return default_formatter;
1249
1278
  if (decimals !== _decimals) {
@@ -1252,117 +1281,176 @@ export function format(type, value, le, options = {}) {
1252
1281
  }
1253
1282
  return _formatter;
1254
1283
  })();
1284
+ function get_nullstr() {
1285
+ return nullstr ?
1286
+ colors ? grey('null') : 'null'
1287
+ :
1288
+ '';
1289
+ }
1255
1290
  switch (type) {
1256
1291
  case DdbType.bool:
1257
- return String((value === null || value === nulls.int8) ?
1258
- null
1259
- :
1260
- Boolean(value));
1292
+ if (value === null || value === nulls.int8)
1293
+ return get_nullstr();
1294
+ else {
1295
+ const str = String(Boolean(value));
1296
+ return colors ? blue(str) : str;
1297
+ }
1261
1298
  case DdbType.char:
1262
- return String((value === null || value === nulls.int8) ?
1263
- null
1264
- :
1265
- // ascii printable
1266
- // http://facweb.cs.depaul.edu/sjost/it212/documents/ascii-pr.htm
1267
- (32 <= value && value <= 126) ?
1268
- String.fromCharCode(value).quote()
1269
- :
1270
- value);
1299
+ if (value === null || value === nulls.int8)
1300
+ return get_nullstr();
1301
+ else {
1302
+ let str =
1303
+ // ascii printable
1304
+ // http://facweb.cs.depaul.edu/sjost/it212/documents/ascii-pr.htm
1305
+ (32 <= value && value <= 126) ?
1306
+ String.fromCharCode(value)
1307
+ :
1308
+ '\\' + String(value);
1309
+ if (quote)
1310
+ str = str.quote();
1311
+ return colors ? green(str) : str;
1312
+ }
1271
1313
  case DdbType.short:
1272
- return (value === null || value === nulls.int16) ?
1273
- 'null'
1274
- :
1275
- default_formatter.format(value);
1314
+ if (value === null || value === nulls.int16)
1315
+ return get_nullstr();
1316
+ else {
1317
+ const str = default_formatter.format(value);
1318
+ return colors ? green(str) : str;
1319
+ }
1276
1320
  case DdbType.int:
1277
- return (value === null || value === nulls.int32) ?
1278
- 'null'
1279
- :
1280
- default_formatter.format(value);
1321
+ if (value === null || value === nulls.int32)
1322
+ return get_nullstr();
1323
+ else {
1324
+ const str = default_formatter.format(value);
1325
+ return colors ? green(str) : str;
1326
+ }
1281
1327
  case DdbType.long:
1282
- return (value === null || value === nulls.int64) ?
1283
- 'null'
1284
- :
1285
- default_formatter.format(value);
1328
+ if (value === null || value === nulls.int64)
1329
+ return get_nullstr();
1330
+ else {
1331
+ const str = default_formatter.format(value);
1332
+ return colors ? green(str) : str;
1333
+ }
1286
1334
  case DdbType.date:
1287
- return (value === null || value === nulls.int32) ?
1288
- 'null'
1289
- :
1290
- date2str(value);
1335
+ if (value === null || value === nulls.int16)
1336
+ return get_nullstr();
1337
+ else {
1338
+ const str = date2str(value);
1339
+ return colors ? magenta(str) : str;
1340
+ }
1291
1341
  case DdbType.month:
1292
- return (value === null || value === nulls.int32) ?
1293
- 'null'
1294
- :
1295
- month2str(value);
1342
+ if (value === null || value === nulls.int32)
1343
+ return get_nullstr();
1344
+ else {
1345
+ const str = month2str(value);
1346
+ return colors ? magenta(str) : str;
1347
+ }
1296
1348
  case DdbType.time:
1297
- return (value === null || value === nulls.int32) ?
1298
- 'null'
1299
- :
1300
- time2str(value);
1349
+ if (value === null || value === nulls.int32)
1350
+ return get_nullstr();
1351
+ else {
1352
+ const str = time2str(value);
1353
+ return colors ? magenta(str) : str;
1354
+ }
1301
1355
  case DdbType.minute:
1302
- return (value === null || value === nulls.int32) ?
1303
- 'null'
1304
- :
1305
- minute2str(value);
1356
+ if (value === null || value === nulls.int16)
1357
+ return get_nullstr();
1358
+ else {
1359
+ const str = minute2str(value);
1360
+ return colors ? magenta(str) : str;
1361
+ }
1306
1362
  case DdbType.second:
1307
- return (value === null || value === nulls.int32) ?
1308
- 'null'
1309
- :
1310
- second2str(value);
1363
+ if (value === null || value === nulls.int32)
1364
+ return get_nullstr();
1365
+ else {
1366
+ const str = second2str(value);
1367
+ return colors ? magenta(str) : str;
1368
+ }
1311
1369
  case DdbType.datetime:
1312
- return (value === null || value === nulls.int32) ?
1313
- 'null'
1314
- :
1315
- datetime2str(value);
1370
+ if (value === null || value === nulls.int32)
1371
+ return get_nullstr();
1372
+ else {
1373
+ const str = datetime2str(value);
1374
+ return colors ? magenta(str) : str;
1375
+ }
1316
1376
  case DdbType.timestamp:
1317
- return (value === null || value === nulls.int64) ?
1318
- 'null'
1319
- :
1320
- timestamp2str(value);
1377
+ if (value === null || value === nulls.int64)
1378
+ return get_nullstr();
1379
+ else {
1380
+ const str = timestamp2str(value);
1381
+ return colors ? magenta(str) : str;
1382
+ }
1321
1383
  case DdbType.nanotime:
1322
- return (value === null || value === nulls.int64) ?
1323
- 'null'
1324
- :
1325
- nanotime2str(value);
1384
+ if (value === null || value === nulls.int64)
1385
+ return get_nullstr();
1386
+ else {
1387
+ const str = nanotime2str(value);
1388
+ return colors ? magenta(str) : str;
1389
+ }
1326
1390
  case DdbType.nanotimestamp:
1327
- return (value === null || value === nulls.int64) ?
1328
- 'null'
1329
- :
1330
- nanotimestamp2str(value);
1391
+ if (value === null || value === nulls.int64)
1392
+ return get_nullstr();
1393
+ else {
1394
+ const str = nanotimestamp2str(value);
1395
+ return colors ? magenta(str) : str;
1396
+ }
1331
1397
  case DdbType.float:
1332
- return (value === null || value === nulls.float32) ?
1333
- 'null'
1334
- :
1335
- formatter.format(value);
1398
+ if (value === null || value === nulls.float32)
1399
+ return get_nullstr();
1400
+ else {
1401
+ const str = formatter.format(value);
1402
+ return colors ? green(str) : str;
1403
+ }
1336
1404
  case DdbType.double:
1337
- return (value === null || value === nulls.double) ?
1338
- 'null'
1339
- :
1340
- formatter.format(value);
1405
+ if (value === null || value === nulls.double)
1406
+ return get_nullstr();
1407
+ else {
1408
+ const str = formatter.format(value);
1409
+ return colors ? green(str) : str;
1410
+ }
1341
1411
  case DdbType.symbol:
1342
- case DdbType.string:
1343
- return value.quote('single');
1344
- case DdbType.uuid:
1345
- return uuid2str(value, le);
1346
- case DdbType.functiondef:
1347
- return value.name.quote('single');
1412
+ case DdbType.string: {
1413
+ let str = value;
1414
+ if (quote)
1415
+ str = str.quote('single');
1416
+ return colors ? cyan(str) : str;
1417
+ }
1418
+ case DdbType.uuid: {
1419
+ const str = uuid2str(value, le);
1420
+ return colors ? cyan(str) : str;
1421
+ }
1422
+ case DdbType.functiondef: {
1423
+ const str = value.name.quote('single');
1424
+ return colors ? cyan(str) : str;
1425
+ }
1348
1426
  case DdbType.handle:
1349
1427
  case DdbType.code:
1350
- case DdbType.resource:
1351
- return value.quote('single');
1428
+ case DdbType.resource: {
1429
+ const str = value.quote('single');
1430
+ return colors ? cyan(str) : str;
1431
+ }
1352
1432
  case DdbType.datehour:
1353
- return (value === null || value === nulls.int32) ?
1354
- 'null'
1355
- :
1356
- datehour2str(value);
1357
- case DdbType.ipaddr:
1358
- return ipaddr2str(value, le);
1359
- case DdbType.int128:
1360
- return int1282str(value, le);
1361
- case DdbType.blob:
1362
- return (value.length > 100 ?
1433
+ if (value === null || value === nulls.int32)
1434
+ return get_nullstr();
1435
+ else {
1436
+ const str = datehour2str(value);
1437
+ return colors ? magenta(str) : str;
1438
+ }
1439
+ case DdbType.ipaddr: {
1440
+ const str = ipaddr2str(value, le);
1441
+ return colors ? cyan(str) : str;
1442
+ }
1443
+ case DdbType.int128: {
1444
+ const str = int1282str(value, le);
1445
+ return colors ? cyan(str) : str;
1446
+ }
1447
+ case DdbType.blob: {
1448
+ const str = value.length > 100 ?
1363
1449
  DdbObj.dec.decode(value.subarray(0, 98)) + '…'
1364
1450
  :
1365
- DdbObj.dec.decode(value)).quote('single');
1451
+ DdbObj.dec.decode(value);
1452
+ return colors ? cyan(str) : str;
1453
+ }
1366
1454
  case DdbType.point: {
1367
1455
  const [x, y] = value;
1368
1456
  return `(${format(DdbType.double, x, le, options)}, ${format(DdbType.double, y, le, options)})`;
@@ -1373,7 +1461,8 @@ export function format(type, value, le, options = {}) {
1373
1461
  }
1374
1462
  case DdbType.duration: {
1375
1463
  const { data, unit } = value;
1376
- return `${data}${DdbDurationUnit[unit]}`;
1464
+ const str = `${data}${DdbDurationUnit[unit]}`;
1465
+ return colors ? magenta(str) : str;
1377
1466
  }
1378
1467
  case DdbType.decimal32:
1379
1468
  case DdbType.decimal64: {
@@ -1381,16 +1470,18 @@ export function format(type, value, le, options = {}) {
1381
1470
  if (data === null ||
1382
1471
  (data === nulls.int64 && type === DdbType.decimal64) ||
1383
1472
  (data === nulls.int32 && type === DdbType.decimal32))
1384
- return 'null';
1473
+ return get_nullstr();
1385
1474
  const s = String(data < 0 ? -data : data).padStart(scale, '0');
1386
- return (data < 0 ? '-' : '') + (scale ? `${s.slice(0, -scale) || '0'}.${s.slice(-scale)}` : s);
1475
+ const str = (data < 0 ? '-' : '') + (scale ? `${s.slice(0, -scale) || '0'}.${s.slice(-scale)}` : s);
1476
+ return colors ? green(str) : str;
1387
1477
  }
1388
1478
  default:
1389
1479
  return String(value);
1390
1480
  }
1391
1481
  }
1392
- /** formatted vector, the index-th item in the collection is a string, a null value returns a 'null' string */
1482
+ /** formatted vector, the index-th item in the collection is a string */
1393
1483
  export function formati(obj, index, options = {}) {
1484
+ assert(index < obj.rows, 'index < obj.rows');
1394
1485
  if (64 <= obj.type && obj.type < 128) { // array vector
1395
1486
  // 因为 array vector 目前只支持:Logical, Integral(不包括 INT128, COMPRESS 类型), Floating, Temporal
1396
1487
  // 都对应 TypedArray 中的一格,所以 lengths.length 等于 block 中的 row 的个数
@@ -1418,18 +1509,26 @@ export function formati(obj, index, options = {}) {
1418
1509
  const limit = 10;
1419
1510
  let items = new Array(Math.min(limit, length));
1420
1511
  for (let i = 0; i < items.length; i++)
1421
- items[i] = format(_type, data[acc_len + i], obj.le, options);
1512
+ if (_type === DdbType.decimal32 || _type === DdbType.decimal64) {
1513
+ const x = data[acc_len + i];
1514
+ if (x === nulls.int64 && _type === DdbType.decimal64 ||
1515
+ x === nulls.int32 && _type === DdbType.decimal32)
1516
+ return '';
1517
+ const { scale } = obj.value;
1518
+ const s = String(x < 0 ? -x : x).padStart(scale, '0');
1519
+ const str = (x < 0 ? '-' : '') + (scale ? `${s.slice(0, -scale) || '0'}.${s.slice(-scale)}` : s);
1520
+ items[i] = options.colors ? green(str) : str;
1521
+ }
1522
+ else
1523
+ items[i] = format(_type, data[acc_len + i], obj.le, options);
1422
1524
  return (items.join(', ') + (length > limit ? ', ...' : '')).bracket('square');
1423
1525
  }
1424
1526
  }
1425
1527
  }
1426
1528
  switch (obj.type) {
1427
- case DdbType.string:
1428
- case DdbType.symbol:
1429
- return obj.value[index];
1430
1529
  case DdbType.symbol_extended: {
1431
1530
  const { base, data } = obj.value;
1432
- return base[data[index]];
1531
+ return format(DdbType.string, base[data[index]], obj.le, options);
1433
1532
  }
1434
1533
  case DdbType.uuid:
1435
1534
  case DdbType.int128:
@@ -1453,7 +1552,8 @@ export function formati(obj, index, options = {}) {
1453
1552
  x === nulls.int32 && obj.type === DdbType.decimal32)
1454
1553
  return '';
1455
1554
  const s = String(x < 0 ? -x : x).padStart(scale, '0');
1456
- return (x < 0 ? '-' : '') + (scale ? `${s.slice(0, -scale) || '0'}.${s.slice(-scale)}` : s);
1555
+ const str = (x < 0 ? '-' : '') + (scale ? `${s.slice(0, -scale) || '0'}.${s.slice(-scale)}` : s);
1556
+ return options.colors ? green(str) : str;
1457
1557
  }
1458
1558
  default:
1459
1559
  return format(obj.type, obj.value[index], obj.le, options);
@@ -1756,10 +1856,14 @@ export class DdbTable extends DdbObj {
1756
1856
  }
1757
1857
  }
1758
1858
  export function date2ms(date) {
1859
+ // 将 server 的本地时间 (以 ms 为单位,1970.01.01 00:00:00 作为零点) 当成是客户端这里的本地的时间,然后根据本地的时区信息转换为 utc 时间
1860
+ // 本地的时区与实际的时间值相关,timezone offset 可能会受到夏令时 (DST) 的影响
1861
+ // 得到的 utc 毫秒数交给 js date 或者 dayjs 去格式化
1862
+ const ms = 1000 * 3600 * 24 * date;
1759
1863
  return (date === null || date === nulls.int32) ?
1760
1864
  null
1761
1865
  :
1762
- timezone_offset + 1000 * 3600 * 24 * date;
1866
+ 1000 * 60 * new Date(ms).getTimezoneOffset() + ms;
1763
1867
  }
1764
1868
  export function date2str(date, format = 'YYYY.MM.DD') {
1765
1869
  return (date === null || date === nulls.int32) ?
@@ -1786,7 +1890,7 @@ export function time2ms(time) {
1786
1890
  return (time === null || time === nulls.int32) ?
1787
1891
  null
1788
1892
  :
1789
- timezone_offset + time;
1893
+ 1000 * 60 * new Date(time).getTimezoneOffset() + time;
1790
1894
  }
1791
1895
  export function time2str(time, format = 'HH:mm:ss.SSS') {
1792
1896
  return (time === null || time === nulls.int32) ?
@@ -1795,10 +1899,11 @@ export function time2str(time, format = 'HH:mm:ss.SSS') {
1795
1899
  dayjs(time2ms(time)).format(format);
1796
1900
  }
1797
1901
  export function minute2ms(minute) {
1902
+ const ms = 60 * 1000 * minute;
1798
1903
  return (minute === null || minute === nulls.int32) ?
1799
1904
  null
1800
1905
  :
1801
- timezone_offset + 60 * 1000 * minute;
1906
+ 1000 * 60 * new Date(ms).getTimezoneOffset() + ms;
1802
1907
  }
1803
1908
  export function minute2str(minute, format = 'HH:mm[m]') {
1804
1909
  return (minute === null || minute === nulls.int32) ?
@@ -1807,10 +1912,11 @@ export function minute2str(minute, format = 'HH:mm[m]') {
1807
1912
  dayjs(minute2ms(minute)).format(format);
1808
1913
  }
1809
1914
  export function second2ms(second) {
1915
+ const ms = 1000 * second;
1810
1916
  return (second === null || second === nulls.int32) ?
1811
1917
  null
1812
1918
  :
1813
- timezone_offset + 1000 * second;
1919
+ 1000 * 60 * new Date(ms).getTimezoneOffset() + ms;
1814
1920
  }
1815
1921
  export function second2str(second, format = 'HH:mm:ss') {
1816
1922
  return (second === null || second === nulls.int32) ?
@@ -1819,10 +1925,11 @@ export function second2str(second, format = 'HH:mm:ss') {
1819
1925
  dayjs(second2ms(second)).format(format);
1820
1926
  }
1821
1927
  export function datetime2ms(datetime) {
1928
+ const ms = 1000 * datetime;
1822
1929
  return (datetime === null || datetime === nulls.int32) ?
1823
1930
  null
1824
1931
  :
1825
- timezone_offset + 1000 * datetime;
1932
+ 1000 * 60 * new Date(ms).getTimezoneOffset() + ms;
1826
1933
  }
1827
1934
  export function datetime2str(datetime, format = 'YYYY.MM.DD HH:mm:ss') {
1828
1935
  return (datetime === null || datetime === nulls.int32) ?
@@ -1831,10 +1938,11 @@ export function datetime2str(datetime, format = 'YYYY.MM.DD HH:mm:ss') {
1831
1938
  dayjs(datetime2ms(datetime)).format(format);
1832
1939
  }
1833
1940
  export function timestamp2ms(timestamp) {
1941
+ const ms = Number(timestamp);
1834
1942
  return (timestamp === null || timestamp === nulls.int64) ?
1835
1943
  null
1836
1944
  :
1837
- timezone_offset + Number(timestamp);
1945
+ 1000 * 60 * new Date(ms).getTimezoneOffset() + ms;
1838
1946
  }
1839
1947
  /** format timestamp (bigint) to string
1840
1948
  - timestamp: bigint value
@@ -1849,16 +1957,18 @@ export function timestamp2str(timestamp, format = 'YYYY.MM.DD HH:mm:ss.SSS') {
1849
1957
  dayjs(timestamp2ms(timestamp)).format(format);
1850
1958
  }
1851
1959
  export function datehour2ms(datehour) {
1960
+ const ms = 1000 * 3600 * datehour;
1852
1961
  return (datehour === null || datehour === nulls.int32) ?
1853
1962
  null
1854
1963
  :
1855
- timezone_offset + 1000 * 3600 * datehour;
1964
+ 1000 * 60 * new Date(ms).getTimezoneOffset() + ms;
1856
1965
  }
1857
1966
  export function datehour2str(datehour, format = 'YYYY.MM.DDTHH') {
1967
+ const ms = 1000 * 3600 * datehour;
1858
1968
  return (datehour === null || datehour === nulls.int32) ?
1859
1969
  'null'
1860
1970
  :
1861
- dayjs(timezone_offset + 1000 * 3600 * datehour).format(format);
1971
+ dayjs(1000 * 60 * new Date(ms).getTimezoneOffset() + ms).format(format);
1862
1972
  }
1863
1973
  /** parse timestamp string to bigint value
1864
1974
  - str: timestamp string, If it is an empty string or 'null', it will return the corresponding empty value (nulls.int64)
@@ -1869,9 +1979,10 @@ export function datehour2str(datehour, format = 'YYYY.MM.DDTHH') {
1869
1979
  export function str2timestamp(str, format = 'YYYY.MM.DD HH:mm:ss.SSS') {
1870
1980
  if (!str || str === 'null')
1871
1981
  return nulls.int64;
1872
- assert('The length of the timestamp string must be equal to the length of the format string');
1873
- return BigInt(-timezone_offset +
1874
- dayjs(str, format).valueOf());
1982
+ assert(str.length === format.length, 'The length of the timestamp string must be equal to the length of the format string');
1983
+ const ms = dayjs(str, format).valueOf();
1984
+ return BigInt(-(1000 * 60 * new Date(ms).getTimezoneOffset()) +
1985
+ ms);
1875
1986
  }
1876
1987
  export function nanotime2ns(nanotime) {
1877
1988
  return nanotimestamp2ns(nanotime);
@@ -1886,15 +1997,17 @@ export function nanotime2str(nanotime, format = 'HH:mm:ss.SSSSSSSSS') {
1886
1997
  const i_second_end = i_second_start + 2;
1887
1998
  const i_nanosecond_start = format.indexOf('SSSSSSSSS', i_second_end);
1888
1999
  assert(i_nanosecond_start !== -1, 'Format string must contain nanosecond format (SSSSSSSSS)');
1889
- return (dayjs(timezone_offset + (Number(nanotime) / 1000000)).format(format.slice(0, i_second_end)) +
2000
+ const ms = Number(nanotime) / 1000000;
2001
+ return (dayjs(1000 * 60 * new Date(ms).getTimezoneOffset() + ms).format(format.slice(0, i_second_end)) +
1890
2002
  format.slice(i_second_end, i_nanosecond_start) +
1891
2003
  String(nanotime % 1000000000n).padStart(9, '0'));
1892
2004
  }
1893
2005
  export function nanotimestamp2ns(nanotimestamp) {
2006
+ const ms = Number(nanotimestamp / 1000000n);
1894
2007
  return (nanotimestamp === null || nanotimestamp === nulls.int64) ?
1895
2008
  null
1896
2009
  :
1897
- BigInt(timezone_offset) * 1000000n + nanotimestamp;
2010
+ BigInt(1000 * 60 * new Date(ms).getTimezoneOffset()) * 1000000n + nanotimestamp;
1898
2011
  }
1899
2012
  /** format nanotimestamp value (bigint) to string
1900
2013
  - nanotimestamp: bigint value
@@ -1919,7 +2032,8 @@ export function nanotimestamp2str(nanotimestamp, format = 'YYYY.MM.DD HH:mm:ss.S
1919
2032
  assert(i_nanosecond_start !== -1, 'Format string must contain nanosecond format (SSSSSSSSS)');
1920
2033
  const remainder = nanotimestamp % 1000000000n;
1921
2034
  const borrow = remainder < 0n;
1922
- return (dayjs(timezone_offset +
2035
+ const ms = Number(nanotimestamp / 1000000n);
2036
+ return (dayjs(1000 * 60 * new Date(ms).getTimezoneOffset() +
1923
2037
  // 去掉 9 位的纳秒部分,转化为毫秒
1924
2038
  Number((nanotimestamp - remainder + (borrow ? -1000000000n : 0n)) / 1000000n)).format(format.slice(0, i_second_end)) +
1925
2039
  format.slice(i_second_end, i_nanosecond_start) +
@@ -1944,8 +2058,9 @@ export function str2nanotimestamp(str, format = 'YYYY.MM.DD HH:mm:ss.SSSSSSSSS')
1944
2058
  const i_second_end = i_second_start + 2;
1945
2059
  const i_nanosecond_start = format.indexOf('SSSSSSSSS', i_second_end);
1946
2060
  assert(i_nanosecond_start !== -1, 'Format string must contain nanosecond format (SSSSSSSSS)');
1947
- return (BigInt(-timezone_offset +
1948
- dayjs(str.slice(0, i_second_end), format.slice(0, i_second_end)).valueOf()) * 1000000n
2061
+ const ms = dayjs(str.slice(0, i_second_end), format.slice(0, i_second_end)).valueOf();
2062
+ return (BigInt(-(1000 * 60 * new Date(ms).getTimezoneOffset()) +
2063
+ ms) * 1000000n
1949
2064
  +
1950
2065
  BigInt(str.slice(i_nanosecond_start, i_nanosecond_start + 9)));
1951
2066
  }
@@ -1971,7 +2086,14 @@ export function int1282str(buffer, le = true) {
1971
2086
  return [...buf].map(x => x.toString(16)
1972
2087
  .padStart(2, '0')).join('');
1973
2088
  }
1974
- export const winsize = 40;
2089
+ export const winsize = 10_0000;
2090
+ export class ConnectionError extends Error {
2091
+ ddb;
2092
+ constructor(ddb) {
2093
+ super(`websocket is disconnected: ${ddb.url}`);
2094
+ this.ddb = ddb;
2095
+ }
2096
+ }
1975
2097
  export class DDB {
1976
2098
  /** 当前的 session id (http 或 tcp) */
1977
2099
  sid = '0';
@@ -2036,9 +2158,10 @@ export class DDB {
2036
2158
  constructor(url, options = {}) {
2037
2159
  if (!url) {
2038
2160
  const _url = new URL(location.href);
2039
- const hostname = _url.searchParams.get('hostname') || location.hostname;
2040
- const port = _url.searchParams.get('port') || location.port;
2041
- url = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${hostname}:${port}/`;
2161
+ const dev = _url.pathname.endsWith('/console/');
2162
+ const hostname = _url.searchParams.get('hostname') || (dev ? '127.0.0.1' : location.hostname);
2163
+ const port = _url.searchParams.get('port') || (dev ? '8848' : location.port);
2164
+ url = `${dev ? (_url.searchParams.get('tls') === '1' ? 'wss' : 'ws') : (location.protocol === 'https:' ? 'wss' : 'ws')}://${hostname}${port ? `:${port}` : ''}/`;
2042
2165
  }
2043
2166
  this.url = url;
2044
2167
  if (options.autologin !== undefined)
@@ -2053,52 +2176,50 @@ export class DDB {
2053
2176
  this.streaming = options.streaming;
2054
2177
  }
2055
2178
  on_message(event) {
2056
- console.log('This is the default on_message before calling this.rpc, it should not be called unless the server pushes the message first after the connection is established');
2057
- console.log(event.data);
2179
+ // 这里的实现一定会被 connect 中的实现覆盖
2058
2180
  }
2059
- /** Establish the actual WebSocket connection to the DolphinDB corresponding to the URL
2060
- - options?:
2061
- - url?: DolphinDB WebSocket URL. By default, the WebSocket URL passed in when the instance is initialized is used
2062
- - autologin?: Whether to log in automatically after establishing a connection, default `true`
2063
- - username?: DolphinDB username, default `'admin'`
2064
- - password?: DolphinDB password, default `'123456'`
2065
- - python?: set python session flag, default `false`
2066
- - streaming?: When this option is set, the WebSocket connection is only used for streaming data
2067
- */
2068
- async connect({ url, autologin, username, password, python, streaming } = {}) {
2069
- if (url !== undefined)
2070
- this.url = url;
2071
- if (autologin !== undefined)
2072
- this.autologin = autologin;
2073
- if (username !== undefined)
2074
- this.username = username;
2075
- if (password !== undefined)
2076
- this.password = password;
2077
- if (python !== undefined)
2078
- this.python = python;
2079
- if (streaming !== undefined)
2080
- this.streaming = streaming;
2081
- this.disconnect();
2082
- await connect_websocket(this.url, {
2083
- protocols: (() => {
2084
- if (this.streaming)
2085
- return 'streaming';
2086
- if (this.python)
2087
- return 'python';
2088
- })(),
2089
- on_open: async (event, websocket) => {
2090
- this.websocket = websocket;
2181
+ /** Establish a actual websocket connection to the DolphindB corresponding to the URL
2182
+ After calling, it will ensure that it has been connected to the database (ensure that websocket.ReadyState is open), otherwise an error will be reported
2183
+ this.autologin automatically log in when it is true */
2184
+ async connect() {
2185
+ if (this.connected)
2186
+ return;
2187
+ const ptail = this.pconnect;
2188
+ let resolve;
2189
+ this.pconnect = new Promise((_resolve, _reject) => {
2190
+ resolve = _resolve;
2191
+ });
2192
+ await ptail;
2193
+ try {
2194
+ if (!this.connected) {
2195
+ this.on_message = (event) => {
2196
+ assert(false, 'This is the default on_message before calling this.rpc, and it should not be called. Unless the server pushed message first after the connection is established');
2197
+ };
2198
+ this.presult = Promise.resolve(null);
2199
+ this.pnode_run_defined = false;
2200
+ this.websocket = await connect_websocket(this.url, {
2201
+ protocols: (() => {
2202
+ if (this.streaming)
2203
+ return 'streaming';
2204
+ if (this.python)
2205
+ return 'python';
2206
+ })(),
2207
+ on_message: (event) => {
2208
+ this.on_message(event);
2209
+ }
2210
+ });
2091
2211
  if (this.streaming)
2092
2212
  await this.subscribe();
2093
- else
2213
+ else {
2094
2214
  await this.rpc('connect', {});
2095
- },
2096
- on_message: (event) => {
2097
- this.on_message(event);
2215
+ if (this.autologin)
2216
+ await this.call('login', [this.username, this.password], { urgent: true });
2217
+ }
2098
2218
  }
2099
- });
2100
- if (!this.streaming && this.autologin)
2101
- await this.call('login', [this.username, this.password], { urgent: true });
2219
+ }
2220
+ finally {
2221
+ resolve();
2222
+ }
2102
2223
  }
2103
2224
  get_rpc_options({ urgent = false, secondary = false, async: _async = false, pickle = false, clear = false, api = false, compress = false, cancellable = true, priority = urgent ? 8 : 4, parallelism = 8, root_id = '', limit, } = {}) {
2104
2225
  let flag = 0;
@@ -2134,12 +2255,9 @@ export class DDB {
2134
2255
  disconnect() {
2135
2256
  if (this.connected)
2136
2257
  this.websocket.close(1000);
2137
- this.on_message = () => { };
2138
- this.presult = Promise.resolve(null);
2139
- this.pconnect = Promise.resolve();
2140
- this.pnode_run_defined = false;
2141
2258
  }
2142
2259
  /** rpc through websocket (function/script/variable command)
2260
+ When the DDB is not connected, the call will be automatically connected. When the connection is disconnected, the call will throw the Connectionerror
2143
2261
  - type: API 类型: 'script' | 'function' | 'variable'
2144
2262
  - options:
2145
2263
  - urgent?: 决定 `行为标识` 那一行字符串的取值(只适用于 script 和 function)
@@ -2149,24 +2267,12 @@ export class DDB {
2149
2267
  为 false 时返回的 DdbObj 仅含有 buffer 和 le,不做解析,以便后续转发、序列化
2150
2268
  */
2151
2269
  async rpc(type, { script, func, args = [], vars = [], urgent, listener, parse_object, }) {
2152
- if (!this.websocket) {
2153
- const ptail = this.pconnect;
2154
- let resolve;
2155
- this.pconnect = new Promise((_resolve, _reject) => {
2156
- resolve = _resolve;
2157
- });
2158
- await ptail;
2159
- try {
2160
- if (!this.websocket)
2161
- await this.connect();
2162
- }
2163
- finally {
2164
- resolve();
2165
- }
2166
- }
2270
+ if (!this.websocket)
2271
+ await this.connect();
2167
2272
  if (!this.connected)
2168
- throw new Error(`${this.url} is already disconnected`);
2273
+ throw new ConnectionError(this);
2169
2274
  if (func === 'pnode_run' && !this.pnode_run_defined) {
2275
+ // 保证并发调用 rpc 时只定义一次 pnode_run
2170
2276
  const ptail = this.ppnoderun;
2171
2277
  let resolve;
2172
2278
  this.ppnoderun = new Promise((_resolve, _reject) => {
@@ -2360,8 +2466,9 @@ export class DDB {
2360
2466
  async cancel() {
2361
2467
  let ddb = new DDB(this.url, this);
2362
2468
  try {
2469
+ // 因为是新建的连接,而且执行完脚本之后马上就关闭了,所以不用考虑变量泄漏的问题
2363
2470
  await ddb.eval(`jobs = exec rootJobId from getConsoleJobs() where sessionId = ${this.sid}\n` +
2364
- 'if (size(jobs))\n' +
2471
+ (this.python ? 'if size(jobs):\n' : 'if (size(jobs))\n') +
2365
2472
  ' cancelConsoleJob(jobs)\n', { urgent: true });
2366
2473
  }
2367
2474
  finally {
@@ -2422,110 +2529,58 @@ export class DDB {
2422
2529
  }
2423
2530
  /** Internal stream subscription method */
2424
2531
  async subscribe() {
2425
- const args = DdbObj.to_ddbobjs([
2426
- new DdbVectorString(this.autologin ?
2427
- [this.username, this.password]
2428
- :
2429
- // 不自动登录也要传空的 string vector, 否则 server 无法解析
2430
- []),
2431
- // todo: 还有用吗?需要获取客户端 IP 吗
2532
+ await this.rpc('connect', {});
2533
+ if (this.autologin)
2534
+ await this.call('login', [this.username, this.password]);
2535
+ console.log('Subscribed to streaming table, colnames:',
2536
+ // string[3](['time', 'stock', 'price'])
2537
+ this.streaming.colnames = (await this.call('publishTable', [
2432
2538
  'localhost',
2433
- // todo: 端口还有用吗?
2434
2539
  new DdbInt(0),
2435
2540
  this.streaming.table,
2436
2541
  (this.streaming.action ||= `api_js_${new Date().getTime()}`),
2437
2542
  // new DdbInt(-1), // offset
2438
2543
  // filter
2439
2544
  // allow exists
2440
- ]);
2441
- return new Promise((resolve, reject) => {
2442
- let first = true;
2443
- let second = true;
2444
- // 先准备好收到 websocket message 的 callback
2445
- this.on_message = ({ data: buffer }) => {
2446
- if (first) {
2447
- first = false;
2448
- try {
2449
- const dv = new DataView(buffer);
2450
- const status = dv.getUint8(1);
2451
- this.le = Boolean(dv.getUint8(2));
2452
- const nobjs = dv.getUint32(3, this.le);
2453
- // 解析返回数据,从 server 实现看其实只有一个 DdbObj
2454
- let offset = 7;
2455
- let objs = new Array(nobjs);
2456
- const buf = new Uint8Array(buffer);
2457
- for (let i = 0; i < nobjs; i++) {
2458
- const obj = DdbObj.parse(buf.subarray(offset), this.le);
2459
- offset += obj.length;
2460
- objs[i] = obj;
2461
- console.log(`订阅流表的响应数据: objs[${i}]: ${obj.toString()}`);
2462
- }
2463
- this.streaming.colnames = objs[0].value;
2464
- if (status === StreamingStatusCode.ok)
2465
- console.log('Subscribed to streaming table:', { status: StreamingStatusCode[status], result: objs[0] });
2466
- else
2467
- reject(new Error(`Failed to subscribe to streaming table: { status: ${StreamingStatusCode[status]}, error: ${objs[0]?.value} }`));
2468
- }
2469
- catch (error) {
2470
- reject(error);
2471
- }
2472
- }
2473
- else {
2474
- try {
2475
- const dv = new DataView(buffer);
2476
- const buf = new Uint8Array(buffer);
2477
- let { streaming } = this;
2478
- streaming.time = dv.getBigInt64(1, this.le);
2479
- streaming.id = dv.getBigInt64(9, this.le);
2480
- const i_topic_end = buf.indexOf(0, 17);
2481
- streaming.topic = this.dec.decode(buf.subarray(17, i_topic_end));
2482
- if (second) {
2483
- second = false;
2484
- // 是 table
2485
- streaming.schema = DdbObj.parse(buf.subarray(i_topic_end + 1), this.le);
2486
- console.log('Received streaming table schema:', streaming.schema);
2487
- streaming.window = {
2488
- offset: 0,
2489
- rows: 0,
2490
- segments: [],
2491
- };
2492
- assert(streaming.schema.rows === 0, 'schema.rows === 0');
2493
- resolve(streaming.schema);
2494
- }
2495
- else {
2496
- // 是 column 片段组成的 any vector
2497
- const data = streaming.data = DdbObj.parse(buf.subarray(i_topic_end + 1), this.le);
2498
- let { window: win } = streaming;
2499
- win.rows += data.rows;
2500
- win.segments.push(data);
2501
- if (win.rows >= winsize * 2 && win.segments.length >= 2) {
2502
- let winsize_ = 0;
2503
- let i = win.segments.length - 1;
2504
- // 往前移动至首个累计 winsize_ 超过 winsize 的位置
2505
- for (; winsize_ < winsize; i--)
2506
- winsize_ += win.segments[i].rows;
2507
- win.segments = win.segments.slice(i);
2508
- win.offset += win.rows - winsize_;
2509
- win.rows = winsize_;
2510
- console.log('exceeds winsize, shrink window.segments', win);
2511
- }
2512
- streaming.handler(streaming);
2513
- }
2514
- }
2515
- catch (error) {
2516
- reject(error);
2517
- }
2545
+ ])).value);
2546
+ this.streaming.window = {
2547
+ offset: 0,
2548
+ rows: 0,
2549
+ segments: [],
2550
+ };
2551
+ // 先准备好收到 websocket message 的 callback
2552
+ this.on_message = ({ data: buffer }) => {
2553
+ let { streaming } = this;
2554
+ try {
2555
+ const dv = new DataView(buffer);
2556
+ const buf = new Uint8Array(buffer);
2557
+ streaming.time = dv.getBigInt64(1, this.le);
2558
+ streaming.id = dv.getBigInt64(9, this.le);
2559
+ const i_topic_end = buf.indexOf(0, 17);
2560
+ streaming.topic = this.dec.decode(buf.subarray(17, i_topic_end));
2561
+ // 是 column 片段组成的 any vector
2562
+ const data = streaming.data = DdbObj.parse(buf.subarray(i_topic_end + 1), this.le);
2563
+ let { window: win } = streaming;
2564
+ win.rows += streaming.rows = data.value[0].rows;
2565
+ win.segments.push(data);
2566
+ if (win.rows >= winsize * 2 && win.segments.length >= 2) {
2567
+ let winsize_ = 0;
2568
+ let i = win.segments.length - 1;
2569
+ // 往前移动至首个累计 winsize_ 超过 winsize 的位置
2570
+ for (; winsize_ < winsize; i--)
2571
+ winsize_ += win.segments[i].value[0].rows;
2572
+ win.segments = win.segments.slice(i);
2573
+ win.offset += win.rows - winsize_;
2574
+ win.rows = winsize_;
2518
2575
  }
2519
- };
2520
- // 发送订阅 websocket message
2521
- this.websocket.send(concat([
2522
- Uint8Array.of(1, // from = SubscriberFromType::FROM_API
2523
- 1, // type = SubscriberRPCType::RPC_START
2524
- Number(DDB.le_client)),
2525
- Uint32Array.of(args.length),
2526
- ...args.map(obj => obj.pack())
2527
- ]));
2528
- });
2576
+ }
2577
+ catch (error) {
2578
+ // 将 error 交给 handler 处理
2579
+ streaming.error = error;
2580
+ }
2581
+ this.streaming.handler(streaming);
2582
+ };
2583
+ return this.streaming;
2529
2584
  }
2530
2585
  }
2531
2586
  export let ddb = window.ddb = new DDB();