@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.
- package/INTERNALS.md +324 -0
- package/README.md +93 -237
- package/bin/rip-db +3 -3
- package/build.zig +88 -0
- package/db.rip +66 -80
- package/lib/darwin-arm64/duckdb.node +0 -0
- package/lib/duckdb.mjs +246 -333
- package/package.json +11 -5
- package/src/duckdb.zig +1156 -0
- package/PROTOCOL.md +0 -258
- package/db.html +0 -122
- package/lib/duckdb-binary.rip +0 -525
package/PROTOCOL.md
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
# DuckDB UI Binary Protocol Specification
|
|
2
|
-
|
|
3
|
-
This document describes the binary protocol used by DuckDB's built-in UI to communicate
|
|
4
|
-
with the database server. rip-db implements this protocol, allowing the official DuckDB UI
|
|
5
|
-
to connect transparently.
|
|
6
|
-
|
|
7
|
-
## Architecture
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
┌─────────────┐ HTTP POST ┌─────────────┐ SQL ┌─────────────┐
|
|
11
|
-
│ DuckDB UI │ ──────────────────▶│ rip-db │ ──────────────▶│ DuckDB │
|
|
12
|
-
│ (Browser) │◀────────────────── │ Server │◀────────────── │ Database │
|
|
13
|
-
└─────────────┘ Binary Response └─────────────┘ Results └─────────────┘
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Implemented Endpoints
|
|
17
|
-
|
|
18
|
-
| Endpoint | Method | Body | Response | Status |
|
|
19
|
-
|-------------------|--------|----------------|-------------------|----------|
|
|
20
|
-
| `/ddb/run` | POST | SQL (text) | Binary result | ✅ Done |
|
|
21
|
-
| `/ddb/interrupt` | POST | Empty | Empty result | ✅ Done |
|
|
22
|
-
| `/ddb/tokenize` | POST | SQL (text) | Binary tokens | ✅ Done |
|
|
23
|
-
| `/info` | GET | - | Empty + headers | ✅ Done |
|
|
24
|
-
|
|
25
|
-
## Request Headers
|
|
26
|
-
|
|
27
|
-
All headers are optional unless noted.
|
|
28
|
-
|
|
29
|
-
| Header | Encoding | Purpose |
|
|
30
|
-
|-------------------------------------|----------|-----------------------------------|
|
|
31
|
-
| `X-DuckDB-UI-Connection-Name` | Plain | Named connection (for persistence) |
|
|
32
|
-
| `X-DuckDB-UI-Database-Name` | Base64 | Target database |
|
|
33
|
-
| `X-DuckDB-UI-Schema-Name` | Base64 | Target schema |
|
|
34
|
-
| `X-DuckDB-UI-Parameter-Count` | Plain | Number of prepared stmt params |
|
|
35
|
-
| `X-DuckDB-UI-Parameter-Value-{n}` | Base64 | Param value (0-indexed) |
|
|
36
|
-
| `X-DuckDB-UI-Result-Row-Limit` | Plain | Max rows to return |
|
|
37
|
-
| `X-DuckDB-UI-Result-Database-Name` | Base64 | Store results in this database |
|
|
38
|
-
| `X-DuckDB-UI-Result-Schema-Name` | Base64 | Store results in this schema |
|
|
39
|
-
| `X-DuckDB-UI-Result-Table-Name` | Base64 | Store results in this table |
|
|
40
|
-
| `X-DuckDB-UI-Result-Table-Row-Limit`| Plain | Max rows to store in table |
|
|
41
|
-
| `X-DuckDB-UI-Errors-As-JSON` | Plain | Return errors as JSON |
|
|
42
|
-
| `X-DuckDB-UI-Request-Description` | Plain | Human-readable description |
|
|
43
|
-
|
|
44
|
-
## Binary Serialization Format
|
|
45
|
-
|
|
46
|
-
### Primitives
|
|
47
|
-
|
|
48
|
-
#### varint (Variable-length Integer)
|
|
49
|
-
```
|
|
50
|
-
while (byte & 0x80):
|
|
51
|
-
result |= (byte & 0x7F) << shift
|
|
52
|
-
shift += 7
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
#### uint8
|
|
56
|
-
Single byte, unsigned.
|
|
57
|
-
|
|
58
|
-
#### uint16 (Field ID)
|
|
59
|
-
2 bytes, little-endian.
|
|
60
|
-
|
|
61
|
-
#### string
|
|
62
|
-
```
|
|
63
|
-
length: varint
|
|
64
|
-
data: UTF-8 bytes (length count)
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
#### data (raw bytes)
|
|
68
|
-
```
|
|
69
|
-
length: varint
|
|
70
|
-
data: raw bytes (length count)
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
#### nullable<T>
|
|
74
|
-
```
|
|
75
|
-
present: uint8 (0 = null, non-zero = present)
|
|
76
|
-
value: T (only if present)
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
#### list<T>
|
|
80
|
-
```
|
|
81
|
-
count: varint
|
|
82
|
-
items: T[] (count items)
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Object Structure
|
|
86
|
-
|
|
87
|
-
Objects use field IDs to identify properties:
|
|
88
|
-
```
|
|
89
|
-
field_id: uint16 (little-endian)
|
|
90
|
-
value: <type depends on field>
|
|
91
|
-
...
|
|
92
|
-
end: 0xFFFF (end marker)
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Response Types
|
|
96
|
-
|
|
97
|
-
#### SuccessResult
|
|
98
|
-
```
|
|
99
|
-
field_100: boolean (true)
|
|
100
|
-
field_101: ColumnNamesAndTypes
|
|
101
|
-
field_102: list<DataChunk>
|
|
102
|
-
0xFFFF
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
#### ErrorResult
|
|
106
|
-
```
|
|
107
|
-
field_100: boolean (false)
|
|
108
|
-
field_101: string (error message)
|
|
109
|
-
0xFFFF
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
#### EmptyResult
|
|
113
|
-
```
|
|
114
|
-
(no fields)
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
#### TokenizeResult
|
|
118
|
-
```
|
|
119
|
-
field_100: list<varint> (offsets)
|
|
120
|
-
field_101: list<varint> (token types)
|
|
121
|
-
0xFFFF
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### ColumnNamesAndTypes
|
|
125
|
-
```
|
|
126
|
-
field_100: list<string> (column names)
|
|
127
|
-
field_101: list<Type> (column types)
|
|
128
|
-
0xFFFF
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Type
|
|
132
|
-
```
|
|
133
|
-
field_100: uint8 (LogicalTypeId)
|
|
134
|
-
field_101: nullable<TypeInfo> (extra info for complex types)
|
|
135
|
-
0xFFFF
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### DataChunk
|
|
139
|
-
```
|
|
140
|
-
field_100: varint (row count)
|
|
141
|
-
field_101: list<Vector> (vectors - one per column)
|
|
142
|
-
0xFFFF
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### Vector
|
|
146
|
-
|
|
147
|
-
All vectors start with:
|
|
148
|
-
```
|
|
149
|
-
field_100: uint8 (allValid flag - 0 means some nulls)
|
|
150
|
-
field_101: data (validity bitmap - only if allValid=0)
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
Then type-specific data:
|
|
154
|
-
|
|
155
|
-
#### Data Vector (numeric/temporal types)
|
|
156
|
-
```
|
|
157
|
-
field_102: data (raw bytes - type-specific encoding)
|
|
158
|
-
0xFFFF
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
#### String Vector (CHAR, VARCHAR)
|
|
162
|
-
```
|
|
163
|
-
field_102: list<string> (string values)
|
|
164
|
-
0xFFFF
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
## LogicalTypeId Constants
|
|
168
|
-
|
|
169
|
-
```javascript
|
|
170
|
-
const LogicalTypeId = {
|
|
171
|
-
BOOLEAN: 10,
|
|
172
|
-
TINYINT: 11,
|
|
173
|
-
SMALLINT: 12,
|
|
174
|
-
INTEGER: 13,
|
|
175
|
-
BIGINT: 14,
|
|
176
|
-
DATE: 15,
|
|
177
|
-
TIME: 16,
|
|
178
|
-
TIMESTAMP_SEC: 17,
|
|
179
|
-
TIMESTAMP_MS: 18,
|
|
180
|
-
TIMESTAMP: 19,
|
|
181
|
-
TIMESTAMP_NS: 20,
|
|
182
|
-
DECIMAL: 21,
|
|
183
|
-
FLOAT: 22,
|
|
184
|
-
DOUBLE: 23,
|
|
185
|
-
CHAR: 24,
|
|
186
|
-
VARCHAR: 25,
|
|
187
|
-
BLOB: 26,
|
|
188
|
-
INTERVAL: 27,
|
|
189
|
-
UTINYINT: 28,
|
|
190
|
-
USMALLINT: 29,
|
|
191
|
-
UINTEGER: 30,
|
|
192
|
-
UBIGINT: 31,
|
|
193
|
-
TIMESTAMP_TZ: 32,
|
|
194
|
-
TIME_TZ: 34,
|
|
195
|
-
BIT: 36,
|
|
196
|
-
BIGNUM: 39,
|
|
197
|
-
UHUGEINT: 49,
|
|
198
|
-
HUGEINT: 50,
|
|
199
|
-
UUID: 54,
|
|
200
|
-
STRUCT: 100,
|
|
201
|
-
LIST: 101,
|
|
202
|
-
MAP: 102,
|
|
203
|
-
ENUM: 104,
|
|
204
|
-
UNION: 107,
|
|
205
|
-
ARRAY: 108,
|
|
206
|
-
};
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## Data Type Byte Sizes
|
|
210
|
-
|
|
211
|
-
| Type | Bytes | Format |
|
|
212
|
-
|---------------|-------|-------------------------------------|
|
|
213
|
-
| BOOLEAN | 1 | 0 or non-zero |
|
|
214
|
-
| TINYINT | 1 | int8 |
|
|
215
|
-
| UTINYINT | 1 | uint8 |
|
|
216
|
-
| SMALLINT | 2 | int16 LE |
|
|
217
|
-
| USMALLINT | 2 | uint16 LE |
|
|
218
|
-
| INTEGER | 4 | int32 LE |
|
|
219
|
-
| UINTEGER | 4 | uint32 LE |
|
|
220
|
-
| BIGINT | 8 | int64 LE |
|
|
221
|
-
| UBIGINT | 8 | uint64 LE |
|
|
222
|
-
| HUGEINT | 16 | int128 LE |
|
|
223
|
-
| UHUGEINT | 16 | uint128 LE |
|
|
224
|
-
| FLOAT | 4 | IEEE 754 float32 |
|
|
225
|
-
| DOUBLE | 8 | IEEE 754 float64 |
|
|
226
|
-
| DATE | 4 | int32 (days since 1970-01-01) |
|
|
227
|
-
| TIME | 8 | int64 (microseconds since midnight) |
|
|
228
|
-
| TIMESTAMP | 8 | int64 (microseconds since epoch) |
|
|
229
|
-
| TIMESTAMP_MS | 8 | int64 (milliseconds since epoch) |
|
|
230
|
-
| TIMESTAMP_SEC | 8 | int64 (seconds since epoch) |
|
|
231
|
-
| TIMESTAMP_NS | 8 | int64 (nanoseconds since epoch) |
|
|
232
|
-
| TIMESTAMP_TZ | 8 | same as TIMESTAMP |
|
|
233
|
-
| TIME_TZ | 8 | int64 (micros + offset encoded) |
|
|
234
|
-
| INTERVAL | 16 | months(4) + days(4) + micros(8) |
|
|
235
|
-
| UUID | 16 | 128-bit UUID |
|
|
236
|
-
| DECIMAL | varies| depends on width |
|
|
237
|
-
| ENUM | varies| depends on enum size |
|
|
238
|
-
|
|
239
|
-
## Validity Bitmap
|
|
240
|
-
|
|
241
|
-
When `allValid` is 0, the validity bitmap indicates which values are NULL:
|
|
242
|
-
- Bit order: LSB first within each byte
|
|
243
|
-
- Bit meaning: 1 = valid, 0 = NULL
|
|
244
|
-
- Size: ceil(rowCount / 8) bytes
|
|
245
|
-
|
|
246
|
-
```javascript
|
|
247
|
-
// Check if row i is valid
|
|
248
|
-
const byteIndex = Math.floor(i / 8);
|
|
249
|
-
const bitIndex = i % 8;
|
|
250
|
-
const isValid = (validity[byteIndex] >> bitIndex) & 1;
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
## References
|
|
254
|
-
|
|
255
|
-
- [DuckDB UI GitHub](https://github.com/duckdb/duckdb-ui)
|
|
256
|
-
- [BinaryDeserializer.ts](https://github.com/duckdb/duckdb-ui/blob/main/ts/pkgs/duckdb-ui-client/src/serialization/classes/BinaryDeserializer.ts)
|
|
257
|
-
- [DuckDB BinarySerializer](https://github.com/duckdb/duckdb/blob/main/src/include/duckdb/common/serializer/binary_serializer.hpp)
|
|
258
|
-
- [Vector::Serialize](https://github.com/duckdb/duckdb/blob/main/src/common/types/vector.cpp)
|
package/db.html
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<title>DuckDB Console</title>
|
|
6
|
-
<style>
|
|
7
|
-
* { box-sizing: border-box; }
|
|
8
|
-
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; padding: 20px; background: #1a1a2e; color: #eee; }
|
|
9
|
-
h1 { margin: 0 0 20px; font-size: 24px; color: #fff; }
|
|
10
|
-
.container { max-width: 1200px; margin: 0 auto; }
|
|
11
|
-
textarea { width: 100%; height: 120px; padding: 12px; font-family: 'Monaco', 'Menlo', monospace; font-size: 14px; background: #16213e; color: #0f0; border: 1px solid #0f4c75; border-radius: 6px; resize: vertical; }
|
|
12
|
-
.buttons { margin: 10px 0; }
|
|
13
|
-
button { padding: 10px 24px; font-size: 14px; background: #0f4c75; color: #fff; border: none; border-radius: 6px; cursor: pointer; margin-right: 10px; }
|
|
14
|
-
button:hover { background: #1b6ca8; }
|
|
15
|
-
.info { font-size: 12px; color: #888; margin-bottom: 10px; }
|
|
16
|
-
.results { margin-top: 20px; }
|
|
17
|
-
table { width: 100%; border-collapse: collapse; background: #16213e; border-radius: 6px; overflow: hidden; }
|
|
18
|
-
th { background: #0f4c75; padding: 10px; text-align: left; font-weight: 600; }
|
|
19
|
-
td { padding: 8px 10px; border-bottom: 1px solid #1b2838; }
|
|
20
|
-
tr:hover td { background: #1b2838; }
|
|
21
|
-
.error { color: #ff6b6b; background: #2d1f1f; padding: 12px; border-radius: 6px; }
|
|
22
|
-
.meta { color: #888; font-size: 12px; margin-top: 10px; }
|
|
23
|
-
.status { padding: 4px 8px; border-radius: 4px; font-size: 12px; }
|
|
24
|
-
.status.ok { background: #1d4d1d; color: #4ade80; }
|
|
25
|
-
.status.err { background: #4d1d1d; color: #ff6b6b; }
|
|
26
|
-
</style>
|
|
27
|
-
</head>
|
|
28
|
-
<body>
|
|
29
|
-
<div class="container">
|
|
30
|
-
<h1>🦆 DuckDB Console</h1>
|
|
31
|
-
<div class="info">Connected to: <span id="db">loading...</span></div>
|
|
32
|
-
<textarea id="sql" placeholder="SELECT * FROM users LIMIT 10;">SELECT * FROM users;</textarea>
|
|
33
|
-
<div class="buttons">
|
|
34
|
-
<button onclick="run()">▶ Run (Cmd+Enter)</button>
|
|
35
|
-
<button onclick="tables()">📋 Tables</button>
|
|
36
|
-
<button onclick="status()">ℹ️ Status</button>
|
|
37
|
-
</div>
|
|
38
|
-
<div id="results" class="results"></div>
|
|
39
|
-
</div>
|
|
40
|
-
<script>
|
|
41
|
-
const sqlEl = document.getElementById('sql');
|
|
42
|
-
const resultsEl = document.getElementById('results');
|
|
43
|
-
|
|
44
|
-
// Load status on startup
|
|
45
|
-
fetch('/status').then(r => r.json()).then(d => {
|
|
46
|
-
document.getElementById('db').textContent = d.database;
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Cmd+Enter to run
|
|
50
|
-
sqlEl.addEventListener('keydown', e => {
|
|
51
|
-
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
|
52
|
-
e.preventDefault();
|
|
53
|
-
run();
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
function run() {
|
|
58
|
-
const sql = sqlEl.value.trim();
|
|
59
|
-
if (!sql) return;
|
|
60
|
-
fetch('/sql', {
|
|
61
|
-
method: 'POST',
|
|
62
|
-
headers: { 'Content-Type': 'application/json' },
|
|
63
|
-
body: JSON.stringify({ sql })
|
|
64
|
-
})
|
|
65
|
-
.then(r => r.json())
|
|
66
|
-
.then(renderResult)
|
|
67
|
-
.catch(e => renderError(e.message));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function tables() {
|
|
71
|
-
sqlEl.value = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'main';";
|
|
72
|
-
run();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function status() {
|
|
76
|
-
fetch('/status')
|
|
77
|
-
.then(r => r.json())
|
|
78
|
-
.then(data => {
|
|
79
|
-
resultsEl.innerHTML = `
|
|
80
|
-
<div style="background:#16213e;padding:16px;border-radius:6px;">
|
|
81
|
-
<div><strong>Database:</strong> ${data.database}</div>
|
|
82
|
-
<div><strong>Tables:</strong> ${data.tables?.join(', ') || 'none'}</div>
|
|
83
|
-
<div><strong>Time:</strong> ${data.time}</div>
|
|
84
|
-
</div>`;
|
|
85
|
-
})
|
|
86
|
-
.catch(e => renderError(e.message));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function renderResult(data) {
|
|
90
|
-
if (data.error) {
|
|
91
|
-
renderError(data.error);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (!data.meta?.length) {
|
|
95
|
-
resultsEl.innerHTML = `<div class="meta"><span class="status ok">✓ OK</span> Query executed in ${data.time}s</div>`;
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
let html = '<table><tr>';
|
|
99
|
-
data.meta.forEach(col => {
|
|
100
|
-
html += `<th>${col.name}<br><span style="font-weight:normal;font-size:11px;color:#888">${col.type}</span></th>`;
|
|
101
|
-
});
|
|
102
|
-
html += '</tr>';
|
|
103
|
-
data.data.forEach(row => {
|
|
104
|
-
html += '<tr>';
|
|
105
|
-
row.forEach(val => {
|
|
106
|
-
html += `<td>${val === null ? '<span style="color:#666">NULL</span>' : val}</td>`;
|
|
107
|
-
});
|
|
108
|
-
html += '</tr>';
|
|
109
|
-
});
|
|
110
|
-
html += `</table><div class="meta"><span class="status ok">✓ OK</span> ${data.rows} row${data.rows !== 1 ? 's' : ''} in ${data.time}s</div>`;
|
|
111
|
-
resultsEl.innerHTML = html;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function renderError(msg) {
|
|
115
|
-
resultsEl.innerHTML = `<div class="error"><span class="status err">✗ Error</span> ${msg}</div>`;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Run initial query
|
|
119
|
-
run();
|
|
120
|
-
</script>
|
|
121
|
-
</body>
|
|
122
|
-
</html>
|