@bullet./paraql 0.1.2 → 0.2.0

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.
Files changed (32) hide show
  1. package/README.md +39 -36
  2. package/binding.c +279 -119
  3. package/index.js +66 -28
  4. package/lib/codecs.js +12 -30
  5. package/lib/constants.js +36 -3
  6. package/lib/statement.d.ts +2 -10
  7. package/lib/statement.js +13 -27
  8. package/lib/vfs.js +54 -64
  9. package/package.json +1 -1
  10. package/prebuilds/android-arm/bullet.__paraql.bare +0 -0
  11. package/prebuilds/android-arm/bullet.__paraql.node +0 -0
  12. package/prebuilds/android-arm64/bullet.__paraql.bare +0 -0
  13. package/prebuilds/android-arm64/bullet.__paraql.node +0 -0
  14. package/prebuilds/android-ia32/bullet.__paraql.bare +0 -0
  15. package/prebuilds/android-ia32/bullet.__paraql.node +0 -0
  16. package/prebuilds/android-x64/bullet.__paraql.bare +0 -0
  17. package/prebuilds/android-x64/bullet.__paraql.node +0 -0
  18. package/prebuilds/darwin-arm64/bullet.__paraql.bare +0 -0
  19. package/prebuilds/darwin-arm64/bullet.__paraql.node +0 -0
  20. package/prebuilds/darwin-x64/bullet.__paraql.bare +0 -0
  21. package/prebuilds/darwin-x64/bullet.__paraql.node +0 -0
  22. package/prebuilds/ios-arm64/bullet.__paraql.bare +0 -0
  23. package/prebuilds/ios-arm64-simulator/bullet.__paraql.bare +0 -0
  24. package/prebuilds/ios-x64-simulator/bullet.__paraql.bare +0 -0
  25. package/prebuilds/linux-arm64/bullet.__paraql.bare +0 -0
  26. package/prebuilds/linux-arm64/bullet.__paraql.node +0 -0
  27. package/prebuilds/linux-x64/bullet.__paraql.bare +0 -0
  28. package/prebuilds/linux-x64/bullet.__paraql.node +0 -0
  29. package/prebuilds/win32-arm64/bullet.__paraql.bare +0 -0
  30. package/prebuilds/win32-arm64/bullet.__paraql.node +0 -0
  31. package/prebuilds/win32-x64/bullet.__paraql.bare +0 -0
  32. package/prebuilds/win32-x64/bullet.__paraql.node +0 -0
package/index.js CHANGED
@@ -2,6 +2,7 @@ const { Buffer } = require("buffer")
2
2
  const ReadyResource = require("ready-resource")
3
3
 
4
4
  const binding = require("./binding")
5
+ const constants = require("./lib/constants")
5
6
  const errors = require("./lib/errors")
6
7
  const ParaQLStatement = require("./lib/statement")
7
8
  const ParaVFS = require("./lib/vfs")
@@ -13,14 +14,15 @@ module.exports = exports = class ParaQL extends ReadyResource {
13
14
  key = null
14
15
  }
15
16
 
16
- const { cacheSize = 1024, ...opts } = options
17
+ const { authorize = null, ...opts } = options
17
18
 
18
19
  super()
19
20
 
20
21
  this._handle = null
21
22
  this._vfs = new ParaVFS(this, store, key, opts)
22
- this._cacheSize = cacheSize
23
- this._cache = new Map()
23
+
24
+ this._authorize = authorize
25
+ this._initiator = null
24
26
  }
25
27
 
26
28
  get name() {
@@ -55,7 +57,11 @@ module.exports = exports = class ParaQL extends ReadyResource {
55
57
  await this._vfs.ready()
56
58
 
57
59
  try {
58
- this._handle = await binding.open("paraql.db")
60
+ this._handle = await binding.open(
61
+ this,
62
+ this.name,
63
+ this._authorize ? this._on_authorize : null,
64
+ )
59
65
  } catch (err) {
60
66
  throw errors.from(err)
61
67
  }
@@ -64,11 +70,6 @@ module.exports = exports = class ParaQL extends ReadyResource {
64
70
  async _close() {
65
71
  if (this.opened) {
66
72
  try {
67
- for (const stmt of this._cache.values()) {
68
- await stmt.finalize()
69
- }
70
-
71
- this._cache.clear()
72
73
  await binding.close(this._handle)
73
74
  } catch (err) {
74
75
  throw errors.from(err)
@@ -78,6 +79,34 @@ module.exports = exports = class ParaQL extends ReadyResource {
78
79
  await this._vfs.close()
79
80
  }
80
81
 
82
+ async _on_authorize(action, param1, param2, param3, param4, callback) {
83
+ if (!this._initiator || this.key.equals(this._initiator)) return callback(true)
84
+
85
+ try {
86
+ const result = await this._authorize(
87
+ this._initiator,
88
+ action,
89
+ param1,
90
+ param2,
91
+ param3,
92
+ param4,
93
+ )
94
+ callback(!!result)
95
+ } catch {
96
+ callback(false)
97
+ }
98
+ }
99
+
100
+ static async deserialize(store, data, options) {
101
+ const db = new ParaQL(store, null, options)
102
+
103
+ await db.ready()
104
+
105
+ await db._vfs.deserialize(data)
106
+
107
+ return db
108
+ }
109
+
81
110
  async addWriter(key) {
82
111
  await this._vfs.addWriter(key)
83
112
  }
@@ -98,11 +127,28 @@ module.exports = exports = class ParaQL extends ReadyResource {
98
127
  return this._vfs.info()
99
128
  }
100
129
 
101
- async _exec(sql) {
130
+ async serialize() {
131
+ if (!this.opened) await this.ready()
132
+
133
+ if (this.closed) throw errors.ALREADY_CLOSED()
134
+
135
+ try {
136
+ const data = await binding.serialize(this._handle)
137
+
138
+ return Buffer.from(data)
139
+ } catch (err) {
140
+ throw errors.from(err)
141
+ }
142
+ }
143
+
144
+ async _exec(sql, initiator) {
102
145
  try {
146
+ this._initiator = initiator
103
147
  await binding.exec(this._handle, sql)
104
148
  } catch (err) {
105
149
  throw errors.from(err)
150
+ } finally {
151
+ this._initiator = null
106
152
  }
107
153
  }
108
154
 
@@ -114,35 +160,27 @@ module.exports = exports = class ParaQL extends ReadyResource {
114
160
  await this._vfs.exec(sql)
115
161
  }
116
162
 
117
- async prepare(sql) {
163
+ async prepare(sql, initiator = this.local) {
118
164
  if (!this.opened) await this.ready()
119
165
 
120
166
  if (this.closed) throw errors.ALREADY_CLOSED()
121
167
 
122
- let stmt = this._cache.get(sql)
123
-
124
- if (stmt) {
125
- this._cache.delete(sql)
126
- this._cache.set(sql, stmt)
127
- return stmt
128
- }
129
-
130
- stmt = new ParaQLStatement(this, sql)
131
-
132
- await stmt.ready()
168
+ this._initiator = initiator
133
169
 
134
- this._cache.set(sql, stmt)
170
+ try {
171
+ const stmt = new ParaQLStatement(this, sql)
135
172
 
136
- if (this._cache.size > this._cacheSize) {
137
- const oldest = this._cache.values().next().value
173
+ await stmt.ready()
138
174
 
139
- await oldest.finalize()
175
+ return stmt
176
+ } finally {
177
+ this._initiator = null
140
178
  }
141
-
142
- return stmt
143
179
  }
144
180
  }
145
181
 
182
+ exports.AUTHORIZE_ACTION = constants.AUTHORIZE_ACTION
183
+
146
184
  function isObject(o) {
147
185
  return typeof o === "object" && o && !Buffer.isBuffer(o)
148
186
  }
package/lib/codecs.js CHANGED
@@ -9,6 +9,10 @@ const op = {
9
9
  c.uint8.preencode(state, operation.type)
10
10
 
11
11
  switch (operation.type) {
12
+ case OPERATION.DESERIALIZE: {
13
+ c.buffer.preencode(state, operation.data)
14
+ break
15
+ }
12
16
  case OPERATION.WRITER_ADD:
13
17
  case OPERATION.WRITER_DEL: {
14
18
  c.fixed32.preencode(state, operation.key)
@@ -24,22 +28,16 @@ const op = {
24
28
  c.any.preencode(state, operation.positional)
25
29
  break
26
30
  }
27
- case OPERATION.STMT: {
28
- c.string.preencode(state, operation.sql)
29
- }
30
- case OPERATION.BATCH: {
31
- c.any.preencode(state, operation.named)
32
- c.any.preencode(state, operation.positional)
33
- }
34
- case OPERATION.FLUSH: {
35
- break
36
- }
37
31
  }
38
32
  },
39
33
  encode(state, operation) {
40
34
  c.uint8.encode(state, operation.type)
41
35
 
42
36
  switch (operation.type) {
37
+ case OPERATION.DESERIALIZE: {
38
+ c.buffer.encode(state, operation.data)
39
+ break
40
+ }
43
41
  case OPERATION.WRITER_ADD:
44
42
  case OPERATION.WRITER_DEL: {
45
43
  c.fixed32.encode(state, operation.key)
@@ -55,16 +53,6 @@ const op = {
55
53
  c.any.encode(state, operation.positional)
56
54
  break
57
55
  }
58
- case OPERATION.STMT: {
59
- c.string.encode(state, operation.sql)
60
- }
61
- case OPERATION.BATCH: {
62
- c.any.encode(state, operation.named)
63
- c.any.encode(state, operation.positional)
64
- }
65
- case OPERATION.FLUSH: {
66
- break
67
- }
68
56
  }
69
57
  },
70
58
  decode(state) {
@@ -73,6 +61,10 @@ const op = {
73
61
  const operation = { type }
74
62
 
75
63
  switch (type) {
64
+ case OPERATION.DESERIALIZE: {
65
+ operation.data = c.buffer.decode(state)
66
+ break
67
+ }
76
68
  case OPERATION.WRITER_ADD:
77
69
  case OPERATION.WRITER_DEL: {
78
70
  operation.key = c.fixed32.decode(state)
@@ -88,16 +80,6 @@ const op = {
88
80
  operation.positional = c.any.decode(state)
89
81
  break
90
82
  }
91
- case OPERATION.STMT: {
92
- operation.sql = c.string.decode(state)
93
- }
94
- case OPERATION.BATCH: {
95
- operation.named = c.any.decode(state)
96
- operation.positional = c.any.decode(state)
97
- }
98
- case OPERATION.FLUSH: {
99
- break
100
- }
101
83
  }
102
84
 
103
85
  return operation
package/lib/constants.js CHANGED
@@ -1,10 +1,43 @@
1
1
  module.exports.PAGE_SIZE = 4096
2
2
  module.exports.OPERATION = {
3
+ DESERIALIZE: 0,
3
4
  WRITER_ADD: 1,
4
5
  WRITER_DEL: 2,
5
6
  EXEC: 8,
6
7
  RUN: 9,
7
- STMT: 10,
8
- BATCH: 11,
9
- FLUSH: 12,
8
+ }
9
+ module.exports.AUTHORIZE_ACTION = {
10
+ CREATE_INDEX: 1,
11
+ CREATE_TABLE: 2,
12
+ CREATE_TEMP_INDEX: 3,
13
+ CREATE_TEMP_TABLE: 4,
14
+ CREATE_TEMP_TRIGGER: 5,
15
+ CREATE_TEMP_VIEW: 6,
16
+ CREATE_TRIGGER: 7,
17
+ CREATE_VIEW: 8,
18
+ DELETE: 9,
19
+ DROP_INDEX: 10,
20
+ DROP_TABLE: 11,
21
+ DROP_TEMP_INDEX: 12,
22
+ DROP_TEMP_TABLE: 13,
23
+ DROP_TEMP_TRIGGER: 14,
24
+ DROP_TEMP_VIEW: 15,
25
+ DROP_TRIGGER: 16,
26
+ DROP_VIEW: 17,
27
+ INSERT: 18,
28
+ PRAGMA: 19,
29
+ READ: 20,
30
+ SELECT: 21,
31
+ TRANSACTION: 22,
32
+ UPDATE: 23,
33
+ ATTACH: 24,
34
+ DETACH: 25,
35
+ ALTER_TABLE: 26,
36
+ REINDEX: 27,
37
+ ANALYZE: 28,
38
+ CREATE_VTABLE: 29,
39
+ DROP_VTABLE: 30,
40
+ FUNCTION: 31,
41
+ SAVEPOINT: 32,
42
+ RECURSIVE: 33,
10
43
  }
@@ -5,10 +5,6 @@ interface ParaQLStatement extends ReadyResource {
5
5
  readonly sourceSQL: string
6
6
  readonly batching: boolean
7
7
 
8
- batch(on: boolean): void
9
-
10
- flush(): Promise<ParaQLStatement.FlushResult>
11
-
12
8
  finalize(): Promise<void>
13
9
 
14
10
  all<T extends ParaQLStatement.Row = ParaQLStatement.Row>(
@@ -21,6 +17,8 @@ interface ParaQLStatement extends ReadyResource {
21
17
 
22
18
  run(...params: ParaQLStatement.Parameters): Promise<ParaQLStatement.RunResult | undefined>
23
19
 
20
+ batch(...params: ParaQLStatement.Parameters[]): Promise<void>
21
+
24
22
  iterate<T extends ParaQLStatement.Row = ParaQLStatement.Row>(
25
23
  ...params: ParaQLStatement.Parameters
26
24
  ): AsyncIterableIterator<T>
@@ -44,12 +42,6 @@ declare namespace ParaQLStatement {
44
42
  changes: number
45
43
  lastInsertRowid: number
46
44
  }
47
-
48
- export interface FlushResult {
49
- changes: number
50
- lastInsertRowid: number
51
- errors: number
52
- }
53
45
  }
54
46
 
55
47
  export = ParaQLStatement
package/lib/statement.js CHANGED
@@ -11,7 +11,6 @@ module.exports = class ParaQLStatement extends ReadyResource {
11
11
  this._db = db
12
12
  this._sourceSQL = sql
13
13
  this._handle = null
14
- this._queue = null
15
14
  }
16
15
 
17
16
  async _open() {
@@ -27,8 +26,6 @@ module.exports = class ParaQLStatement extends ReadyResource {
27
26
  try {
28
27
  await binding.finalize(this._handle)
29
28
  this._handle = null
30
- this._queue = null
31
- this._db._cache.delete(this._sourceSQL)
32
29
  } catch (err) {
33
30
  throw errors.from(err)
34
31
  }
@@ -39,28 +36,10 @@ module.exports = class ParaQLStatement extends ReadyResource {
39
36
  return this._sourceSQL
40
37
  }
41
38
 
42
- get batching() {
43
- return !!this._queue
44
- }
45
-
46
- batch(on) {
47
- if (on === false) {
48
- this._queue = null
49
- } else {
50
- this._queue = this._queue ?? []
51
- }
52
- }
53
-
54
39
  async finalize() {
55
40
  await this.close()
56
41
  }
57
42
 
58
- async flush() {
59
- const queue = this._queue
60
- this._queue = []
61
- return this._db._vfs.flush(this._sourceSQL, queue)
62
- }
63
-
64
43
  async _bind(named, positional) {
65
44
  for (const key in named) {
66
45
  if (Array.isArray(named[key])) named[key] = JSON.stringify(named[key])
@@ -117,12 +96,19 @@ module.exports = class ParaQLStatement extends ReadyResource {
117
96
 
118
97
  const [named, positional] = splitParameters(params)
119
98
 
120
- if (this._queue) {
121
- this._queue.push({ named, positional })
122
- return null
123
- } else {
124
- return this._db._vfs.run(this._sourceSQL, named, positional)
125
- }
99
+ return this._db._vfs.run(this._sourceSQL, named, positional)
100
+ }
101
+
102
+ async batch(...params) {
103
+ if (this.closed) throw errors.ALREADY_CLOSED()
104
+
105
+ const batch = params.map((p) => {
106
+ const [named, positional] = splitParameters(p)
107
+
108
+ return { sql: this._sourceSQL, named, positional }
109
+ })
110
+
111
+ return this._db._vfs.batch(batch)
126
112
  }
127
113
 
128
114
  async *iterate(...params) {
package/lib/vfs.js CHANGED
@@ -113,15 +113,22 @@ module.exports = class ParaVFS extends ReadyResource {
113
113
  }
114
114
 
115
115
  async _apply(nodes, view, host) {
116
- let stmt = null
117
- let changes = 0
118
- let lastInsertRowid = 0
119
- let errors = 0
120
-
121
116
  for (const node of nodes) {
122
117
  const op = Operation.decode(this.compressed ? await inflate(node.value) : node.value)
123
118
 
124
119
  switch (op.type) {
120
+ case OPERATION.DESERIALIZE: {
121
+ this._write(view)
122
+ try {
123
+ await this._xWrite(this.name, op.data, 0, () => {})
124
+ await this._flush(this.name)
125
+ this._interactive?.resolve()
126
+ } catch (err) {
127
+ this._interactive?.reject(err)
128
+ }
129
+ this._write(null)
130
+ break
131
+ }
125
132
  case OPERATION.WRITER_ADD: {
126
133
  const batch = view.write()
127
134
  host.addWriter(op.key)
@@ -137,7 +144,7 @@ module.exports = class ParaVFS extends ReadyResource {
137
144
  case OPERATION.EXEC: {
138
145
  this._write(view)
139
146
  try {
140
- await this._db._exec(op.sql)
147
+ await this._db._exec(op.sql, node.key)
141
148
  this._interactive?.resolve()
142
149
  } catch (err) {
143
150
  this._interactive?.reject(err)
@@ -147,39 +154,19 @@ module.exports = class ParaVFS extends ReadyResource {
147
154
  }
148
155
  case OPERATION.RUN: {
149
156
  this._write(view)
157
+ let stmt = null
150
158
  try {
151
- stmt = await this._db.prepare(op.sql)
159
+ stmt = await this._db.prepare(op.sql, node.key)
152
160
  const result = await stmt._run(op.named, op.positional)
153
161
  this._interactive?.resolve(result)
154
162
  } catch (err) {
155
163
  this._interactive?.reject(err)
164
+ } finally {
165
+ await stmt?.finalize()
156
166
  }
157
167
  this._write(null)
158
168
  break
159
169
  }
160
- case OPERATION.STMT: {
161
- try {
162
- stmt = await this._db.prepare(op.sql)
163
- } catch (err) {
164
- throw errors.INTERNAL(`Received invalid statement: ${op.sql}`)
165
- }
166
- }
167
- case OPERATION.BATCH: {
168
- this._write(view)
169
- try {
170
- const result = await stmt._run(op.named, op.positional)
171
- changes += result.changes
172
- lastInsertRowid = result.lastInsertRowid
173
- } catch (err) {
174
- errors++
175
- }
176
- this._write(null)
177
- break
178
- }
179
- case OPERATION.FLUSH: {
180
- this._interactive?.resolve({ changes, lastInsertRowid, errors })
181
- break
182
- }
183
170
  }
184
171
  }
185
172
  }
@@ -196,8 +183,8 @@ module.exports = class ParaVFS extends ReadyResource {
196
183
  await this.bee.append(
197
184
  this.compressed
198
185
  ? await deflate(Operation.encode({ type: OPERATION.WRITER_ADD, key }), {
199
- level: this._compressionLevel,
200
- })
186
+ level: this._compressionLevel,
187
+ })
201
188
  : Operation.encode({ type: OPERATION.WRITER_ADD, key }),
202
189
  )
203
190
  }
@@ -206,8 +193,8 @@ module.exports = class ParaVFS extends ReadyResource {
206
193
  await this.bee.append(
207
194
  this.compressed
208
195
  ? await deflate(Operation.encode({ type: OPERATION.WRITER_DEL, key }), {
209
- level: this._compressionLevel,
210
- })
196
+ level: this._compressionLevel,
197
+ })
211
198
  : Operation.encode({ type: OPERATION.WRITER_DEL, key }),
212
199
  )
213
200
  }
@@ -249,17 +236,17 @@ module.exports = class ParaVFS extends ReadyResource {
249
236
  return { database, temporary, total: database + temporary }
250
237
  }
251
238
 
252
- async exec(sql) {
239
+ async deserialize(data) {
253
240
  this._interactive = new Deferred()
254
241
  try {
255
- const promise = this.bee.append(
242
+ const promise = await this.bee.append(
256
243
  this.compressed
257
- ? await deflate(Operation.encode({ type: OPERATION.EXEC, sql }), {
258
- level: this._compressionLevel,
259
- })
260
- : Operation.encode({ type: OPERATION.EXEC, sql }),
244
+ ? await deflate(Operation.encode({ type: OPERATION.DESERIALIZE, data }), {
245
+ level: this._compressionLevel,
246
+ })
247
+ : Operation.encode({ type: OPERATION.DESERIALIZE, data }),
261
248
  )
262
- await this._interactive.promise
249
+ return await this._interactive.promise
263
250
  .then(() => promise)
264
251
  .catch(async (err) => {
265
252
  await promise
@@ -270,21 +257,18 @@ module.exports = class ParaVFS extends ReadyResource {
270
257
  }
271
258
  }
272
259
 
273
- async run(sql, named, positional) {
260
+ async exec(sql) {
274
261
  this._interactive = new Deferred()
275
262
  try {
276
263
  const promise = this.bee.append(
277
264
  this.compressed
278
- ? await deflate(Operation.encode({ type: OPERATION.RUN, sql, named, positional }), {
279
- level: this._compressionLevel,
280
- })
281
- : Operation.encode({ type: OPERATION.RUN, sql, named, positional }),
265
+ ? await deflate(Operation.encode({ type: OPERATION.EXEC, sql }), {
266
+ level: this._compressionLevel,
267
+ })
268
+ : Operation.encode({ type: OPERATION.EXEC, sql }),
282
269
  )
283
- return await this._interactive.promise
284
- .then(async (result) => {
285
- await promise
286
- return result
287
- })
270
+ await this._interactive.promise
271
+ .then(() => promise)
288
272
  .catch(async (err) => {
289
273
  await promise
290
274
  throw err
@@ -294,23 +278,15 @@ module.exports = class ParaVFS extends ReadyResource {
294
278
  }
295
279
  }
296
280
 
297
- async flush(sql, operations) {
281
+ async run(sql, named, positional) {
298
282
  this._interactive = new Deferred()
299
283
  try {
300
284
  const promise = this.bee.append(
301
285
  this.compressed
302
- ? await Promise.all(
303
- [
304
- { type: OPERATION.STMT, sql },
305
- ...operations.map((op) => ({ type: OPERATION.BATCH, ...op })),
306
- { type: OPERATION.FLUSH },
307
- ].map((op) => deflate(Operation.encode(op), { level: this._compressionLevel })),
308
- )
309
- : [
310
- { type: OPERATION.STMT, sql },
311
- ...operations.map((op) => ({ type: OPERATION.BATCH, ...op })),
312
- { type: OPERATION.FLUSH },
313
- ].map(Operation.encode),
286
+ ? await deflate(Operation.encode({ type: OPERATION.RUN, sql, named, positional }), {
287
+ level: this._compressionLevel,
288
+ })
289
+ : Operation.encode({ type: OPERATION.RUN, sql, named, positional }),
314
290
  )
315
291
  return await this._interactive.promise
316
292
  .then(async (result) => {
@@ -326,6 +302,20 @@ module.exports = class ParaVFS extends ReadyResource {
326
302
  }
327
303
  }
328
304
 
305
+ async batch(batch) {
306
+ await this.bee.append(
307
+ this.compressed
308
+ ? await Promise.all(
309
+ batch.map((op) =>
310
+ deflate(Operation.encode({ type: OPERATION.RUN, ...op }), {
311
+ level: this._compressionLevel,
312
+ }),
313
+ ),
314
+ )
315
+ : batch.map((op) => Operation.encode({ type: OPERATION.RUN, ...op })),
316
+ )
317
+ }
318
+
329
319
  async _get(name, index) {
330
320
  const tmp = name !== this.name
331
321
  const key = PageKey.encode([name, index])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bullet./paraql",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Parallel multi-writer relational database with SQL syntax. Native support for encryption, compression, and vector search.",
5
5
  "author": {
6
6
  "name": "Tomas Ravinskas",