@rip-lang/db 1.3.80 → 1.3.81

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.
@@ -32,17 +32,20 @@ export LogicalTypeId =
32
32
  UBIGINT: 31
33
33
  TIMESTAMP_TZ: 32
34
34
  TIME_TZ: 34
35
+ TIME_NS: 35
35
36
  BIT: 36
36
37
  BIGNUM: 39
37
38
  UHUGEINT: 49
38
39
  HUGEINT: 50
39
40
  UUID: 54
41
+ GEOMETRY: 60
40
42
  STRUCT: 100
41
43
  LIST: 101
42
44
  MAP: 102
43
45
  ENUM: 104
44
46
  UNION: 107
45
47
  ARRAY: 108
48
+ VARIANT: 109
46
49
 
47
50
  # Shared TextEncoder instance (avoid allocating per call)
48
51
  textEncoder = new TextEncoder()
@@ -233,7 +236,7 @@ serializeVector = (s, column, values) ->
233
236
 
234
237
  switch typeId
235
238
  when LogicalTypeId.VARCHAR, LogicalTypeId.CHAR
236
- s.writeList 102, values, (s, v) -> s.writeString String(v ?? '')
239
+ s.writeList 102, values, (s, v) -> s.writeString stringify(v)
237
240
 
238
241
  when LogicalTypeId.BOOLEAN
239
242
  s.writeFieldId 102
@@ -341,8 +344,87 @@ serializeVector = (s, column, values) ->
341
344
  dv.setBigInt64 i * 16 + 8, hi, true
342
345
  s.writeData bytes
343
346
 
347
+ when LogicalTypeId.TIME
348
+ s.writeFieldId 102
349
+ bytes = new Uint8Array values.length * 8
350
+ dv = new DataView bytes.buffer
351
+ for v, i in values
352
+ dv.setBigInt64 i * 8, (if v? then timeToMicros v else 0n), true
353
+ s.writeData bytes
354
+
355
+ when LogicalTypeId.TIME_TZ
356
+ s.writeFieldId 102
357
+ bytes = new Uint8Array values.length * 8
358
+ dv = new DataView bytes.buffer
359
+ for v, i in values
360
+ dv.setBigUint64 i * 8, (if v? then timeToPackedTZ v else 0n), true
361
+ s.writeData bytes
362
+
363
+ when LogicalTypeId.TIME_NS
364
+ s.writeFieldId 102
365
+ bytes = new Uint8Array values.length * 8
366
+ dv = new DataView bytes.buffer
367
+ for v, i in values
368
+ dv.setBigInt64 i * 8, (if v? then timeToNanos v else 0n), true
369
+ s.writeData bytes
370
+
371
+ when LogicalTypeId.TIMESTAMP_SEC
372
+ s.writeFieldId 102
373
+ bytes = new Uint8Array values.length * 8
374
+ dv = new DataView bytes.buffer
375
+ for v, i in values
376
+ dv.setBigInt64 i * 8, (if v? then timestampToSecs v else 0n), true
377
+ s.writeData bytes
378
+
379
+ when LogicalTypeId.TIMESTAMP_MS
380
+ s.writeFieldId 102
381
+ bytes = new Uint8Array values.length * 8
382
+ dv = new DataView bytes.buffer
383
+ for v, i in values
384
+ dv.setBigInt64 i * 8, (if v? then timestampToMillis v else 0n), true
385
+ s.writeData bytes
386
+
387
+ when LogicalTypeId.TIMESTAMP_NS
388
+ s.writeFieldId 102
389
+ bytes = new Uint8Array values.length * 8
390
+ dv = new DataView bytes.buffer
391
+ for v, i in values
392
+ dv.setBigInt64 i * 8, (if v? then timestampToNanos v else 0n), true
393
+ s.writeData bytes
394
+
395
+ when LogicalTypeId.INTERVAL
396
+ s.writeFieldId 102
397
+ bytes = new Uint8Array values.length * 16
398
+ dv = new DataView bytes.buffer
399
+ for v, i in values
400
+ { months, days, micros } = if v? then parseInterval v else { months: 0, days: 0, micros: 0n }
401
+ dv.setInt32 i * 16, months, true
402
+ dv.setInt32 i * 16 + 4, days, true
403
+ dv.setBigInt64 i * 16 + 8, micros, true
404
+ s.writeData bytes
405
+
406
+ when LogicalTypeId.HUGEINT
407
+ s.writeFieldId 102
408
+ bytes = new Uint8Array values.length * 16
409
+ dv = new DataView bytes.buffer
410
+ for v, i in values
411
+ { lo, hi } = stringToHugeint v
412
+ dv.setBigUint64 i * 16, lo, true
413
+ dv.setBigInt64 i * 16 + 8, hi, true
414
+ s.writeData bytes
415
+
416
+ when LogicalTypeId.UHUGEINT
417
+ s.writeFieldId 102
418
+ bytes = new Uint8Array values.length * 16
419
+ dv = new DataView bytes.buffer
420
+ for v, i in values
421
+ { lo, hi } = stringToUhugeint v
422
+ dv.setBigUint64 i * 16, lo, true
423
+ dv.setBigUint64 i * 16 + 8, hi, true
424
+ s.writeData bytes
425
+
344
426
  else
345
- s.writeList 102, values, (s, v) -> s.writeString String(v ?? '')
427
+ s.writeList 102, values, (s, v) -> s.writeString stringify(v)
346
428
 
347
429
  s.writeEndMarker()
348
430
 
@@ -350,6 +432,10 @@ serializeVector = (s, column, values) ->
350
432
  # Helper functions
351
433
  # ==============================================================================
352
434
 
435
+ stringify = (v) ->
436
+ return '' unless v?
437
+ if typeof v is 'object' then JSON.stringify(v) else String(v)
438
+
353
439
  createValidityBitmap = (values) ->
354
440
  # Must be uint64-aligned (8-byte chunks) — the UI reads validity with getBigUint64
355
441
  byteCount = Math.ceil(values.length / 64) * 8
@@ -391,6 +477,95 @@ uuidToHugeint = (uuid) ->
391
477
  lo = full & ((1n << 64n) - 1n)
392
478
  { lo, hi }
393
479
 
480
+ timeToMicros = (value) ->
481
+ return 0n unless value
482
+ match = String(value).match /^(\d+):(\d+):(\d+)(?:\.(\d+))?/
483
+ return 0n unless match
484
+ h = parseInt match[1], 10
485
+ m = parseInt match[2], 10
486
+ s = parseInt match[3], 10
487
+ frac = match[4] or ''
488
+ fracUs = parseInt frac.padEnd(6, '0').slice(0, 6), 10
489
+ BigInt(h * 3600 + m * 60 + s) * 1000000n + BigInt(fracUs)
490
+
491
+ timeToPackedTZ = (value) ->
492
+ return 0n unless value
493
+ match = String(value).match /^(\d+):(\d+):(\d+)(?:\.(\d+))?([+-])(\d+):(\d+)$/
494
+ return 0n unless match
495
+ h = parseInt match[1], 10
496
+ m = parseInt match[2], 10
497
+ s = parseInt match[3], 10
498
+ frac = match[4] or ''
499
+ fracUs = parseInt frac.padEnd(6, '0').slice(0, 6), 10
500
+ sign = if match[5] is '+' then 1 else -1
501
+ offH = parseInt match[6], 10
502
+ offM = parseInt match[7], 10
503
+ micros = BigInt(h * 3600 + m * 60 + s) * 1000000n + BigInt(fracUs)
504
+ offsetSec = sign * (offH * 3600 + offM * 60)
505
+ (micros << 24n) | BigInt(offsetSec + 86399)
506
+
507
+ timeToNanos = (value) ->
508
+ return 0n unless value
509
+ match = String(value).match /^(\d+):(\d+):(\d+)(?:\.(\d+))?/
510
+ return 0n unless match
511
+ h = parseInt match[1], 10
512
+ m = parseInt match[2], 10
513
+ s = parseInt match[3], 10
514
+ frac = match[4] or ''
515
+ fracNs = parseInt frac.padEnd(9, '0').slice(0, 9), 10
516
+ BigInt(h * 3600 + m * 60 + s) * 1000000000n + BigInt(fracNs)
517
+
518
+ timestampToSecs = (value) ->
519
+ if value instanceof Date then BigInt Math.floor value.getTime() / 1000
520
+ else if typeof value is 'string' then BigInt Math.floor new Date(value).getTime() / 1000
521
+ else if typeof value is 'number' then BigInt value
522
+ else if typeof value is 'bigint' then value
523
+ else 0n
524
+
525
+ timestampToMillis = (value) ->
526
+ if value instanceof Date then BigInt value.getTime()
527
+ else if typeof value is 'string' then BigInt new Date(value).getTime()
528
+ else if typeof value is 'number' then BigInt(value) * 1000n
529
+ else if typeof value is 'bigint' then value
530
+ else 0n
531
+
532
+ timestampToNanos = (value) ->
533
+ if value instanceof Date then BigInt(value.getTime()) * 1000000n
534
+ else if typeof value is 'string' then BigInt(new Date(value).getTime()) * 1000000n
535
+ else if typeof value is 'number' then BigInt(value) * 1000000000n
536
+ else if typeof value is 'bigint' then value
537
+ else 0n
538
+
539
+ parseInterval = (value) ->
540
+ return { months: 0, days: 0, micros: 0n } unless value
541
+ str = String value
542
+ months = 0
543
+ days = 0
544
+ micros = 0n
545
+ if m = str.match /(-?\d+)\s+month/
546
+ months = parseInt m[1], 10
547
+ if m = str.match /(-?\d+)\s+day/
548
+ days = parseInt m[1], 10
549
+ if m = str.match /(-?[\d.]+)\s+second/
550
+ micros = BigInt Math.round parseFloat(m[1]) * 1000000
551
+ { months, days, micros }
552
+
553
+ stringToHugeint = (value) ->
554
+ return { lo: 0n, hi: 0n } unless value?
555
+ big = BigInt value
556
+ mask64 = (1n << 64n) - 1n
557
+ lo = big & mask64
558
+ hi = big >> 64n
559
+ { lo, hi }
560
+
561
+ stringToUhugeint = (value) ->
562
+ return { lo: 0n, hi: 0n } unless value?
563
+ big = BigInt value
564
+ mask64 = (1n << 64n) - 1n
565
+ lo = big & mask64
566
+ hi = (big >> 64n) & mask64
567
+ { lo, hi }
568
+
394
569
  mapDuckDBType = (typeName) ->
395
570
  return LogicalTypeId.VARCHAR unless typeName
396
571
  upper = String(typeName).toUpperCase()
@@ -412,14 +587,26 @@ mapDuckDBType = (typeName) ->
412
587
  when 'DATE' then LogicalTypeId.DATE
413
588
  when 'TIME' then LogicalTypeId.TIME
414
589
  when 'TIMESTAMP', 'DATETIME' then LogicalTypeId.TIMESTAMP
415
- when 'TIMESTAMP WITH TIME ZONE', 'TIMESTAMPTZ' then LogicalTypeId.TIMESTAMP_TZ
590
+ when 'TIMESTAMP_S', 'TIMESTAMP_SEC' then LogicalTypeId.TIMESTAMP_SEC
591
+ when 'TIMESTAMP_MS' then LogicalTypeId.TIMESTAMP_MS
592
+ when 'TIMESTAMP_NS' then LogicalTypeId.TIMESTAMP_NS
593
+ when 'TIMESTAMP WITH TIME ZONE', 'TIMESTAMPTZ', 'TIMESTAMP_TZ' then LogicalTypeId.TIMESTAMP_TZ
594
+ when 'TIME WITH TIME ZONE', 'TIMETZ', 'TIME_TZ' then LogicalTypeId.TIME_TZ
595
+ when 'TIME_NS' then LogicalTypeId.TIME_NS
416
596
  when 'VARCHAR', 'TEXT', 'STRING', 'CHAR', 'BPCHAR' then LogicalTypeId.VARCHAR
417
- when 'BLOB', 'BYTEA', 'BINARY', 'VARBINARY' then LogicalTypeId.BLOB
418
597
  when 'UUID' then LogicalTypeId.UUID
419
598
  when 'INTERVAL' then LogicalTypeId.INTERVAL
420
- when 'JSON' then LogicalTypeId.VARCHAR
599
+ when 'BLOB', 'BYTEA', 'BINARY', 'VARBINARY' then LogicalTypeId.VARCHAR
600
+ when 'BIT', 'BITSTRING' then LogicalTypeId.VARCHAR
601
+ when 'ENUM' then LogicalTypeId.VARCHAR
602
+ when 'LIST' then LogicalTypeId.VARCHAR
603
+ when 'STRUCT' then LogicalTypeId.VARCHAR
604
+ when 'MAP' then LogicalTypeId.VARCHAR
605
+ when 'UNION' then LogicalTypeId.VARCHAR
606
+ when 'ARRAY' then LogicalTypeId.VARCHAR
607
+ when 'JSON', 'VARIANT' then LogicalTypeId.VARCHAR
421
608
  else
422
- if upper.startsWith 'DECIMAL' then LogicalTypeId.VARCHAR # Serialize as string to preserve exact precision
609
+ if upper.startsWith 'DECIMAL' then LogicalTypeId.VARCHAR
423
610
  else if upper.startsWith 'VARCHAR' then LogicalTypeId.VARCHAR
424
611
  else if upper.startsWith 'CHAR' then LogicalTypeId.CHAR
425
612
  else LogicalTypeId.VARCHAR
package/lib/duckdb.mjs CHANGED
@@ -121,7 +121,7 @@ const lib = dlopen(libPath, {
121
121
  duckdb_vector_get_validity: { args: ['ptr'], returns: 'ptr' },
122
122
  duckdb_destroy_data_chunk: { args: ['ptr'], returns: 'void' },
123
123
 
124
- // Logical type introspection (for DECIMAL, ENUM, LIST, STRUCT)
124
+ // Logical type introspection (for DECIMAL, ENUM, LIST, STRUCT, ARRAY)
125
125
  duckdb_column_logical_type: { args: ['ptr', 'u64'], returns: 'ptr' },
126
126
  duckdb_destroy_logical_type: { args: ['ptr'], returns: 'void' },
127
127
  duckdb_get_type_id: { args: ['ptr'], returns: 'i32' },
@@ -132,14 +132,17 @@ const lib = dlopen(libPath, {
132
132
  duckdb_enum_dictionary_size: { args: ['ptr'], returns: 'u32' },
133
133
  duckdb_enum_dictionary_value: { args: ['ptr', 'u64'], returns: 'ptr' },
134
134
 
135
- // Nested type vector access (LIST, STRUCT)
135
+ // Nested type vector access (LIST, STRUCT, ARRAY)
136
136
  duckdb_list_vector_get_child: { args: ['ptr'], returns: 'ptr' },
137
- duckdb_list_vector_get_size: { args: ['ptr'], returns: 'u64' }, // Not yet used; available for LIST size checks
137
+ duckdb_list_vector_get_size: { args: ['ptr'], returns: 'u64' },
138
138
  duckdb_struct_vector_get_child: { args: ['ptr', 'u64'], returns: 'ptr' },
139
139
  duckdb_struct_type_child_count: { args: ['ptr'], returns: 'u64' },
140
140
  duckdb_struct_type_child_name: { args: ['ptr', 'u64'], returns: 'ptr' },
141
141
  duckdb_struct_type_child_type: { args: ['ptr', 'u64'], returns: 'ptr' },
142
142
  duckdb_list_type_child_type: { args: ['ptr'], returns: 'ptr' },
143
+ duckdb_array_vector_get_child: { args: ['ptr'], returns: 'ptr' },
144
+ duckdb_array_type_child_type: { args: ['ptr'], returns: 'ptr' },
145
+ duckdb_array_type_array_size: { args: ['ptr'], returns: 'u64' },
143
146
 
144
147
  // Memory
145
148
  duckdb_free: { args: ['ptr'], returns: 'void' },
@@ -183,7 +186,11 @@ const DUCKDB_TYPE = {
183
186
  UUID: 27,
184
187
  UNION: 28,
185
188
  BIT: 29,
186
- TIMESTAMP_TZ: 32,
189
+ TIME_TZ: 30,
190
+ TIMESTAMP_TZ: 31,
191
+ UHUGEINT: 32,
192
+ ARRAY: 33,
193
+ TIME_NS: 39,
187
194
  };
188
195
 
189
196
  export { DUCKDB_TYPE };
@@ -406,12 +413,13 @@ class Connection {
406
413
  //
407
414
  // Contract:
408
415
  // BIGINT/UBIGINT → number (lossy above 2^53, JSON-safe)
409
- // DECIMAL/HUGEINT → string (preserves precision)
416
+ // DECIMAL/HUGEINT/UHUGEINT → string (preserves precision)
410
417
  // All timestamps → Date (UTC)
411
418
  // UUID → string (formatted)
412
419
  // VARCHAR/BLOB → string
413
420
  // ENUM → string (dictionary lookup)
414
- // LISTarray, STRUCT → object, MAP → object
421
+ // TIME/TIME_NS/TIME_TZstring (formatted)
422
+ // LIST/ARRAY → array, STRUCT → object, MAP → object
415
423
  // ---------------------------------------------------------------------------
416
424
 
417
425
  #extractChunks(resultPtr) {
@@ -431,7 +439,8 @@ class Connection {
431
439
 
432
440
  // Get logical type metadata for complex types
433
441
  if (type === DUCKDB_TYPE.DECIMAL || type === DUCKDB_TYPE.ENUM ||
434
- type === DUCKDB_TYPE.LIST || type === DUCKDB_TYPE.STRUCT || type === DUCKDB_TYPE.MAP) {
442
+ type === DUCKDB_TYPE.LIST || type === DUCKDB_TYPE.STRUCT ||
443
+ type === DUCKDB_TYPE.MAP || type === DUCKDB_TYPE.ARRAY) {
435
444
  const logType = lib.duckdb_column_logical_type(rp, BigInt(c));
436
445
  if (logType) {
437
446
  if (type === DUCKDB_TYPE.DECIMAL) {
@@ -488,6 +497,15 @@ class Connection {
488
497
  const b = allocPtr(); new DataView(b.buffer).setBigUint64(0, BigInt(keyLogType), true);
489
498
  lib.duckdb_destroy_logical_type(ptr(b));
490
499
  }
500
+ } else if (type === DUCKDB_TYPE.ARRAY) {
501
+ col.arraySize = Number(lib.duckdb_array_type_array_size(logType));
502
+ const childLogType = lib.duckdb_array_type_child_type(logType);
503
+ if (childLogType) {
504
+ col.childType = lib.duckdb_get_type_id(childLogType);
505
+ const ltBuf2 = allocPtr();
506
+ new DataView(ltBuf2.buffer).setBigUint64(0, BigInt(childLogType), true);
507
+ lib.duckdb_destroy_logical_type(ptr(ltBuf2));
508
+ }
491
509
  }
492
510
  const ltBuf = allocPtr();
493
511
  new DataView(ltBuf.buffer).setBigUint64(0, BigInt(logType), true);
@@ -592,6 +610,13 @@ class Connection {
592
610
  return value.toString();
593
611
  }
594
612
 
613
+ case DUCKDB_TYPE.UHUGEINT: {
614
+ const lo = ffiRead.u64(dataPtr, row * 16);
615
+ const hi = ffiRead.u64(dataPtr, row * 16 + 8);
616
+ const value = (BigInt(hi) << 64n) | BigInt(lo);
617
+ return value.toString();
618
+ }
619
+
595
620
  case DUCKDB_TYPE.DECIMAL: {
596
621
  // Read based on internal type, divide by 10^scale, return as string
597
622
  const scale = col?.decimalScale || 0;
@@ -649,6 +674,39 @@ class Connection {
649
674
  (frac > 0 ? `.${String(frac).padStart(6,'0').replace(/0+$/, '')}` : '');
650
675
  }
651
676
 
677
+ case DUCKDB_TYPE.TIME_NS: {
678
+ const ns = ffiRead.i64(dataPtr, row * 8);
679
+ const totalUs = Number(ns / 1000n);
680
+ const subUs = Number(ns % 1000n);
681
+ const totalSec = Math.floor(totalUs / 1000000);
682
+ const h = Math.floor(totalSec / 3600);
683
+ const m = Math.floor((totalSec % 3600) / 60);
684
+ const s = totalSec % 60;
685
+ const fracUs = totalUs % 1000000;
686
+ const fracNs = fracUs * 1000 + subUs;
687
+ return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}` +
688
+ (fracNs > 0 ? `.${String(fracNs).padStart(9,'0').replace(/0+$/, '')}` : '');
689
+ }
690
+
691
+ case DUCKDB_TYPE.TIME_TZ: {
692
+ // Stored as uint64: upper 40 bits = microseconds, lower 24 bits = offset + 86399
693
+ const bits = ffiRead.u64(dataPtr, row * 8);
694
+ const us = Number(bits >> 24n);
695
+ const offsetSec = Number(bits & 0xFFFFFFn) - 86399;
696
+ const totalSec = Math.floor(us / 1000000);
697
+ const h = Math.floor(totalSec / 3600);
698
+ const m = Math.floor((totalSec % 3600) / 60);
699
+ const s = totalSec % 60;
700
+ const frac = us % 1000000;
701
+ const absOff = Math.abs(offsetSec);
702
+ const offH = Math.floor(absOff / 3600);
703
+ const offM = Math.floor((absOff % 3600) / 60);
704
+ const sign = offsetSec >= 0 ? '+' : '-';
705
+ return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}` +
706
+ (frac > 0 ? `.${String(frac).padStart(6,'0').replace(/0+$/, '')}` : '') +
707
+ `${sign}${String(offH).padStart(2,'0')}:${String(offM).padStart(2,'0')}`;
708
+ }
709
+
652
710
  case DUCKDB_TYPE.UUID:
653
711
  return readUUID(dataPtr, row);
654
712
 
@@ -755,6 +813,27 @@ class Connection {
755
813
  return obj;
756
814
  }
757
815
 
816
+ case DUCKDB_TYPE.ARRAY: {
817
+ // Fixed-size array: child elements are contiguous, arraySize elements per row
818
+ if (!vec) return null;
819
+ const arraySize = col?.arraySize || 0;
820
+ const childVec = lib.duckdb_array_vector_get_child(vec);
821
+ const childData = lib.duckdb_vector_get_data(childVec);
822
+ const childValidity = lib.duckdb_vector_get_validity(childVec);
823
+ const childType = col?.childType || DUCKDB_TYPE.VARCHAR;
824
+ const baseIdx = row * arraySize;
825
+ const result = [];
826
+ for (let i = 0; i < arraySize; i++) {
827
+ const childRow = baseIdx + i;
828
+ if (!isValid(childValidity, childRow)) {
829
+ result.push(null);
830
+ } else {
831
+ result.push(this.#readValue(childData, childRow, childType, null, childVec));
832
+ }
833
+ }
834
+ return result;
835
+ }
836
+
758
837
  case DUCKDB_TYPE.UNION:
759
838
  case DUCKDB_TYPE.BIT:
760
839
  return null; // Rarely used types
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/db",
3
- "version": "1.3.80",
3
+ "version": "1.3.81",
4
4
  "description": "DuckDB server with official DuckDB UI - pure Bun FFI",
5
5
  "type": "module",
6
6
  "main": "db.rip",
@@ -13,7 +13,8 @@
13
13
  },
14
14
  "scripts": {
15
15
  "start": "rip db.rip",
16
- "dev": "rip db.rip :memory:"
16
+ "dev": "rip db.rip :memory:",
17
+ "test": "bun test --preload ../../rip-loader.js test/"
17
18
  },
18
19
  "keywords": [
19
20
  "db",
@@ -38,7 +39,7 @@
38
39
  "author": "Steve Shreeve <steve.shreeve@gmail.com>",
39
40
  "license": "MIT",
40
41
  "dependencies": {
41
- "rip-lang": ">=3.13.92",
42
+ "rip-lang": ">=3.13.93",
42
43
  "@rip-lang/server": ">=1.3.0"
43
44
  },
44
45
  "files": [