@rip-lang/db 0.10.0 → 1.0.1

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.
@@ -1,525 +0,0 @@
1
- # DuckDB Binary Protocol Serializer
2
- #
3
- # Implements the binary serialization format used by DuckDB's official UI.
4
- # This allows rip-db to serve responses that the DuckDB UI can understand.
5
- #
6
- # Protocol spec: See PROTOCOL.md in this directory
7
-
8
- # ==============================================================================
9
- # LogicalTypeId - matches DuckDB's internal type IDs
10
- # ==============================================================================
11
-
12
- export LogicalTypeId =
13
- BOOLEAN: 10
14
- TINYINT: 11
15
- SMALLINT: 12
16
- INTEGER: 13
17
- BIGINT: 14
18
- DATE: 15
19
- TIME: 16
20
- TIMESTAMP_SEC: 17
21
- TIMESTAMP_MS: 18
22
- TIMESTAMP: 19
23
- TIMESTAMP_NS: 20
24
- DECIMAL: 21
25
- FLOAT: 22
26
- DOUBLE: 23
27
- CHAR: 24
28
- VARCHAR: 25
29
- BLOB: 26
30
- INTERVAL: 27
31
- UTINYINT: 28
32
- USMALLINT: 29
33
- UINTEGER: 30
34
- UBIGINT: 31
35
- TIMESTAMP_TZ: 32
36
- TIME_TZ: 34
37
- BIT: 36
38
- BIGNUM: 39
39
- UHUGEINT: 49
40
- HUGEINT: 50
41
- UUID: 54
42
- STRUCT: 100
43
- LIST: 101
44
- MAP: 102
45
- ENUM: 104
46
- UNION: 107
47
- ARRAY: 108
48
-
49
- # ==============================================================================
50
- # BinarySerializer - writes the DuckDB binary format
51
- # ==============================================================================
52
-
53
- export class BinarySerializer
54
- constructor: ->
55
- @buffer = []
56
-
57
- # ---------------------------------------------------------------------------
58
- # Primitive writers
59
- # ---------------------------------------------------------------------------
60
-
61
- writeUint8: (value) ->
62
- @buffer.push value & 0xFF
63
-
64
- writeUint16LE: (value) ->
65
- @buffer.push value & 0xFF
66
- @buffer.push (value >> 8) & 0xFF
67
-
68
- writeUint32LE: (value) ->
69
- @buffer.push value & 0xFF
70
- @buffer.push (value >> 8) & 0xFF
71
- @buffer.push (value >> 16) & 0xFF
72
- @buffer.push (value >> 24) & 0xFF
73
-
74
- writeInt32LE: (value) ->
75
- @writeUint32LE value >>> 0
76
-
77
- writeUint64LE: (value) ->
78
- big = BigInt value
79
- for i in [0...8]
80
- @buffer.push Number (big >> BigInt(i * 8)) & 0xFFn
81
-
82
- writeInt64LE: (value) ->
83
- @writeUint64LE value
84
-
85
- writeFloat32: (value) ->
86
- buf = new ArrayBuffer 4
87
- new DataView(buf).setFloat32 0, value, true
88
- bytes = new Uint8Array buf
89
- @buffer.push ...bytes
90
-
91
- writeFloat64: (value) ->
92
- buf = new ArrayBuffer 8
93
- new DataView(buf).setFloat64 0, value, true
94
- bytes = new Uint8Array buf
95
- @buffer.push ...bytes
96
-
97
- writeVarInt: (value) ->
98
- v = value >>> 0
99
- loop
100
- byte = v & 0x7F
101
- v >>>= 7
102
- if v isnt 0
103
- @buffer.push byte | 0x80
104
- else
105
- @buffer.push byte
106
- break
107
-
108
- writeString: (str) ->
109
- encoder = new TextEncoder()
110
- bytes = encoder.encode str
111
- @writeVarInt bytes.length
112
- @buffer.push ...bytes
113
-
114
- writeData: (data) ->
115
- bytes = if data instanceof Uint8Array then data else new Uint8Array data
116
- @writeVarInt bytes.length
117
- @buffer.push ...bytes
118
-
119
- # ---------------------------------------------------------------------------
120
- # Object structure writers
121
- # ---------------------------------------------------------------------------
122
-
123
- writeFieldId: (id) ->
124
- @writeUint16LE id
125
-
126
- writeEndMarker: ->
127
- @writeUint16LE 0xFFFF
128
-
129
- writeBoolean: (fieldId, value) ->
130
- @writeFieldId fieldId
131
- @writeUint8 if value then 1 else 0
132
-
133
- writePropertyString: (fieldId, value) ->
134
- @writeFieldId fieldId
135
- @writeString value
136
-
137
- writePropertyVarInt: (fieldId, value) ->
138
- @writeFieldId fieldId
139
- @writeVarInt value
140
-
141
- writeList: (fieldId, items, writer) ->
142
- @writeFieldId fieldId
143
- @writeVarInt items.length
144
- for item, i in items
145
- writer this, item, i
146
-
147
- # ---------------------------------------------------------------------------
148
- # Get final buffer
149
- # ---------------------------------------------------------------------------
150
-
151
- toArrayBuffer: ->
152
- new Uint8Array(@buffer).buffer
153
-
154
- toUint8Array: ->
155
- new Uint8Array @buffer
156
-
157
- # ==============================================================================
158
- # Result serializers
159
- # ==============================================================================
160
-
161
- export serializeSuccessResult = (columns, rows) ->
162
- s = new BinarySerializer()
163
-
164
- # field_100: success = true
165
- s.writeBoolean 100, true
166
-
167
- # field_101: ColumnNamesAndTypes
168
- s.writeFieldId 101
169
- serializeColumnNamesAndTypes s, columns
170
-
171
- # field_102: list<DataChunk> (one chunk with all rows)
172
- s.writeFieldId 102
173
- s.writeVarInt 1
174
- serializeDataChunk s, columns, rows
175
-
176
- s.writeEndMarker()
177
- s.toArrayBuffer()
178
-
179
- export serializeErrorResult = (message) ->
180
- s = new BinarySerializer()
181
- s.writeBoolean 100, false
182
- s.writePropertyString 101, message
183
- s.writeEndMarker()
184
- s.toArrayBuffer()
185
-
186
- export serializeEmptyResult = ->
187
- new BinarySerializer().toArrayBuffer()
188
-
189
- export serializeTokenizeResult = (tokens) ->
190
- s = new BinarySerializer()
191
- s.writeList 100, tokens.map((t) -> t.offset), (s, v) -> s.writeVarInt v
192
- s.writeList 101, tokens.map((t) -> t.type), (s, v) -> s.writeVarInt v
193
- s.writeEndMarker()
194
- s.toArrayBuffer()
195
-
196
- # ==============================================================================
197
- # Internal serializers
198
- # ==============================================================================
199
-
200
- serializeColumnNamesAndTypes = (s, columns) ->
201
- s.writeList 100, columns, (s, col) -> s.writeString col.name
202
- s.writeList 101, columns, (s, col) -> serializeType s, col
203
- s.writeEndMarker()
204
-
205
- serializeType = (s, column) ->
206
- typeId = mapDuckDBType column.type
207
- s.writeFieldId 100
208
- s.writeUint8 typeId
209
- s.writeFieldId 101
210
- s.writeUint8 0 # null (no extra type info for basic types)
211
- s.writeEndMarker()
212
-
213
- serializeDataChunk = (s, columns, rows) ->
214
- s.writePropertyVarInt 100, rows.length
215
- s.writeList 101, columns, (s, col, colIdx) ->
216
- values = rows.map (row) -> row[colIdx] ? row[col.name]
217
- serializeVector s, col, values
218
- s.writeEndMarker()
219
-
220
- serializeVector = (s, column, values) ->
221
- typeId = mapDuckDBType column.type
222
- hasNulls = values.some (v) -> v is null or v is undefined
223
- allValid = if hasNulls then 0 else 1
224
-
225
- s.writeFieldId 100
226
- s.writeUint8 allValid
227
-
228
- if not allValid
229
- s.writeFieldId 101
230
- s.writeData createValidityBitmap values
231
-
232
- switch typeId
233
- when LogicalTypeId.VARCHAR, LogicalTypeId.CHAR
234
- s.writeList 102, values, (s, v) -> s.writeString String(v ? '')
235
-
236
- when LogicalTypeId.BOOLEAN
237
- s.writeFieldId 102
238
- bytes = new Uint8Array values.length
239
- for v, i in values
240
- bytes[i] = if v then 1 else 0
241
- s.writeData bytes
242
-
243
- when LogicalTypeId.TINYINT, LogicalTypeId.UTINYINT
244
- s.writeFieldId 102
245
- bytes = new Uint8Array values.length
246
- for v, i in values
247
- bytes[i] = (v ? 0) & 0xFF
248
- s.writeData bytes
249
-
250
- when LogicalTypeId.SMALLINT
251
- s.writeFieldId 102
252
- bytes = new Uint8Array values.length * 2
253
- dv = new DataView bytes.buffer
254
- for v, i in values
255
- dv.setInt16 i * 2, v ? 0, true
256
- s.writeData bytes
257
-
258
- when LogicalTypeId.USMALLINT
259
- s.writeFieldId 102
260
- bytes = new Uint8Array values.length * 2
261
- dv = new DataView bytes.buffer
262
- for v, i in values
263
- dv.setUint16 i * 2, v ? 0, true
264
- s.writeData bytes
265
-
266
- when LogicalTypeId.INTEGER
267
- s.writeFieldId 102
268
- bytes = new Uint8Array values.length * 4
269
- dv = new DataView bytes.buffer
270
- for v, i in values
271
- dv.setInt32 i * 4, v ? 0, true
272
- s.writeData bytes
273
-
274
- when LogicalTypeId.UINTEGER
275
- s.writeFieldId 102
276
- bytes = new Uint8Array values.length * 4
277
- dv = new DataView bytes.buffer
278
- for v, i in values
279
- dv.setUint32 i * 4, v ? 0, true
280
- s.writeData bytes
281
-
282
- when LogicalTypeId.BIGINT
283
- s.writeFieldId 102
284
- bytes = new Uint8Array values.length * 8
285
- dv = new DataView bytes.buffer
286
- for v, i in values
287
- dv.setBigInt64 i * 8, BigInt(v ? 0), true
288
- s.writeData bytes
289
-
290
- when LogicalTypeId.UBIGINT
291
- s.writeFieldId 102
292
- bytes = new Uint8Array values.length * 8
293
- dv = new DataView bytes.buffer
294
- for v, i in values
295
- dv.setBigUint64 i * 8, BigInt(v ? 0), true
296
- s.writeData bytes
297
-
298
- when LogicalTypeId.FLOAT
299
- s.writeFieldId 102
300
- bytes = new Uint8Array values.length * 4
301
- dv = new DataView bytes.buffer
302
- for v, i in values
303
- dv.setFloat32 i * 4, v ? 0, true
304
- s.writeData bytes
305
-
306
- when LogicalTypeId.DOUBLE
307
- s.writeFieldId 102
308
- bytes = new Uint8Array values.length * 8
309
- dv = new DataView bytes.buffer
310
- for v, i in values
311
- dv.setFloat64 i * 8, v ? 0, true
312
- s.writeData bytes
313
-
314
- when LogicalTypeId.DATE
315
- s.writeFieldId 102
316
- bytes = new Uint8Array values.length * 4
317
- dv = new DataView bytes.buffer
318
- for v, i in values
319
- days = if v? then dateToDays v else 0
320
- dv.setInt32 i * 4, days, true
321
- s.writeData bytes
322
-
323
- when LogicalTypeId.TIMESTAMP, LogicalTypeId.TIMESTAMP_TZ
324
- s.writeFieldId 102
325
- bytes = new Uint8Array values.length * 8
326
- dv = new DataView bytes.buffer
327
- for v, i in values
328
- micros = if v? then timestampToMicros v else 0n
329
- dv.setBigInt64 i * 8, micros, true
330
- s.writeData bytes
331
-
332
- else
333
- s.writeList 102, values, (s, v) -> s.writeString String(v ? '')
334
-
335
- s.writeEndMarker()
336
-
337
- # ==============================================================================
338
- # Helper functions
339
- # ==============================================================================
340
-
341
- createValidityBitmap = (values) ->
342
- byteCount = Math.ceil values.length / 8
343
- bitmap = new Uint8Array byteCount
344
- for v, i in values
345
- if v? and v isnt null
346
- byteIdx = Math.floor i / 8
347
- bitIdx = i % 8
348
- bitmap[byteIdx] |= 1 << bitIdx
349
- bitmap
350
-
351
- dateToDays = (value) ->
352
- if value instanceof Date
353
- Math.floor value.getTime() / (24 * 60 * 60 * 1000)
354
- else if typeof value is 'string'
355
- Math.floor new Date(value).getTime() / (24 * 60 * 60 * 1000)
356
- else if typeof value is 'number'
357
- value
358
- else
359
- 0
360
-
361
- timestampToMicros = (value) ->
362
- if value instanceof Date
363
- BigInt(value.getTime()) * 1000n
364
- else if typeof value is 'string'
365
- BigInt(new Date(value).getTime()) * 1000n
366
- else if typeof value is 'number'
367
- BigInt(value) * 1000n
368
- else if typeof value is 'bigint'
369
- value
370
- else
371
- 0n
372
-
373
- mapDuckDBType = (typeName) ->
374
- return LogicalTypeId.VARCHAR unless typeName
375
- upper = String(typeName).toUpperCase()
376
-
377
- switch upper
378
- when 'BOOLEAN', 'BOOL' then LogicalTypeId.BOOLEAN
379
- when 'TINYINT', 'INT1' then LogicalTypeId.TINYINT
380
- when 'SMALLINT', 'INT2' then LogicalTypeId.SMALLINT
381
- when 'INTEGER', 'INT4', 'INT', 'SIGNED' then LogicalTypeId.INTEGER
382
- when 'BIGINT', 'INT8', 'LONG' then LogicalTypeId.BIGINT
383
- when 'UTINYINT' then LogicalTypeId.UTINYINT
384
- when 'USMALLINT' then LogicalTypeId.USMALLINT
385
- when 'UINTEGER', 'UINT' then LogicalTypeId.UINTEGER
386
- when 'UBIGINT' then LogicalTypeId.UBIGINT
387
- when 'HUGEINT' then LogicalTypeId.HUGEINT
388
- when 'UHUGEINT' then LogicalTypeId.UHUGEINT
389
- when 'FLOAT', 'FLOAT4', 'REAL' then LogicalTypeId.FLOAT
390
- when 'DOUBLE', 'FLOAT8', 'NUMERIC' then LogicalTypeId.DOUBLE
391
- when 'DATE' then LogicalTypeId.DATE
392
- when 'TIME' then LogicalTypeId.TIME
393
- when 'TIMESTAMP', 'DATETIME' then LogicalTypeId.TIMESTAMP
394
- when 'TIMESTAMP WITH TIME ZONE', 'TIMESTAMPTZ' then LogicalTypeId.TIMESTAMP_TZ
395
- when 'VARCHAR', 'TEXT', 'STRING', 'CHAR', 'BPCHAR' then LogicalTypeId.VARCHAR
396
- when 'BLOB', 'BYTEA', 'BINARY', 'VARBINARY' then LogicalTypeId.BLOB
397
- when 'UUID' then LogicalTypeId.UUID
398
- when 'INTERVAL' then LogicalTypeId.INTERVAL
399
- when 'JSON' then LogicalTypeId.VARCHAR
400
- else
401
- if upper.startsWith 'DECIMAL' then LogicalTypeId.DOUBLE
402
- else if upper.startsWith 'VARCHAR' then LogicalTypeId.VARCHAR
403
- else if upper.startsWith 'CHAR' then LogicalTypeId.CHAR
404
- else LogicalTypeId.VARCHAR
405
-
406
- # ==============================================================================
407
- # SQL Tokenizer (for syntax highlighting)
408
- # ==============================================================================
409
-
410
- TokenType =
411
- IDENTIFIER: 0
412
- NUMERIC_CONSTANT: 1
413
- STRING_CONSTANT: 2
414
- OPERATOR: 3
415
- KEYWORD: 4
416
- COMMENT: 5
417
-
418
- SQL_KEYWORDS = new Set [
419
- 'SELECT', 'FROM', 'WHERE', 'AND', 'OR', 'NOT', 'IN', 'IS', 'NULL',
420
- 'AS', 'ON', 'JOIN', 'LEFT', 'RIGHT', 'INNER', 'OUTER', 'FULL', 'CROSS',
421
- 'ORDER', 'BY', 'ASC', 'DESC', 'LIMIT', 'OFFSET', 'GROUP', 'HAVING',
422
- 'UNION', 'ALL', 'DISTINCT', 'INSERT', 'INTO', 'VALUES', 'UPDATE', 'SET',
423
- 'DELETE', 'CREATE', 'TABLE', 'INDEX', 'VIEW', 'DROP', 'ALTER', 'ADD',
424
- 'PRIMARY', 'KEY', 'FOREIGN', 'REFERENCES', 'CONSTRAINT', 'DEFAULT',
425
- 'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'CAST', 'TRUE', 'FALSE',
426
- 'WITH', 'RECURSIVE', 'OVER', 'PARTITION', 'WINDOW', 'ROWS',
427
- 'RANGE', 'BETWEEN', 'UNBOUNDED', 'PRECEDING', 'FOLLOWING', 'CURRENT',
428
- 'ROW', 'EXISTS', 'ANY', 'SOME', 'LIKE', 'ILIKE', 'SIMILAR', 'ESCAPE'
429
- ]
430
-
431
- export tokenizeSQL = (sql) ->
432
- tokens = []
433
- i = 0
434
-
435
- while i < sql.length
436
- start = i
437
- char = sql[i]
438
-
439
- if /\s/.test char
440
- i++
441
- continue
442
-
443
- if char is '-' and sql[i + 1] is '-'
444
- i++ while i < sql.length and sql[i] isnt '\n'
445
- tokens.push { offset: start, type: TokenType.COMMENT }
446
- continue
447
-
448
- if char is '/' and sql[i + 1] is '*'
449
- i += 2
450
- i++ while i < sql.length - 1 and not (sql[i] is '*' and sql[i + 1] is '/')
451
- i += 2
452
- tokens.push { offset: start, type: TokenType.COMMENT }
453
- continue
454
-
455
- if char is "'"
456
- i++
457
- while i < sql.length
458
- if sql[i] is "'"
459
- if sql[i + 1] is "'"
460
- i += 2
461
- else
462
- i++
463
- break
464
- else
465
- i++
466
- tokens.push { offset: start, type: TokenType.STRING_CONSTANT }
467
- continue
468
-
469
- if char is '"'
470
- i++
471
- i++ while i < sql.length and sql[i] isnt '"'
472
- i++ if i < sql.length
473
- tokens.push { offset: start, type: TokenType.IDENTIFIER }
474
- continue
475
-
476
- if /[0-9]/.test(char) or (char is '.' and /[0-9]/.test(sql[i + 1] or ''))
477
- i++ while i < sql.length and /[0-9.eE+-]/.test sql[i]
478
- tokens.push { offset: start, type: TokenType.NUMERIC_CONSTANT }
479
- continue
480
-
481
- if /[a-zA-Z_]/.test char
482
- i++ while i < sql.length and /[a-zA-Z0-9_]/.test sql[i]
483
- word = sql.slice(start, i).toUpperCase()
484
- type = if SQL_KEYWORDS.has word then TokenType.KEYWORD else TokenType.IDENTIFIER
485
- tokens.push { offset: start, type }
486
- continue
487
-
488
- if /[+\-*/%=<>!&|^~]/.test char
489
- if sql.slice(i, i + 2) in ['<=', '>=', '<>', '!=', '||', '&&', '::', '->']
490
- i += 2
491
- else i++
492
- tokens.push { offset: start, type: TokenType.OPERATOR }
493
- continue
494
-
495
- if /[(),;.\[\]{}]/.test char
496
- i++
497
- tokens.push { offset: start, type: TokenType.OPERATOR }
498
- continue
499
-
500
- i++
501
-
502
- tokens
503
-
504
- # ==============================================================================
505
- # Type inference from JavaScript values
506
- # ==============================================================================
507
-
508
- export inferType = (value) ->
509
- return 'VARCHAR' if value is null or value is undefined
510
-
511
- switch typeof value
512
- when 'boolean' then 'BOOLEAN'
513
- when 'number'
514
- if Number.isInteger value
515
- if value >= -2147483648 and value <= 2147483647 then 'INTEGER' else 'BIGINT'
516
- else 'DOUBLE'
517
- when 'bigint' then 'BIGINT'
518
- when 'string'
519
- if /^\d{4}-\d{2}-\d{2}$/.test value then 'DATE'
520
- else if /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/.test value then 'TIMESTAMP'
521
- else 'VARCHAR'
522
- when 'object'
523
- if value instanceof Date then 'TIMESTAMP'
524
- else 'VARCHAR'
525
- else 'VARCHAR'