Skip to content

Commit 3181844

Browse files
author
Daniele Briggi
committed
feat(statement): server side prepared statement
1 parent 9df82a9 commit 3181844

21 files changed

+412
-306
lines changed

.devcontainer/Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM oven/bun:debian
2+
3+
# Config Bun
4+
ENV PATH="~/.bun/bin:${PATH}"
5+
RUN ln -s /usr/local/bin/bun /usr/local/bin/node
6+
7+
# Update packages
8+
RUN if [ "debian" == "alpine" ] ; then apk update ; else apt-get update ; fi
9+
10+
# Install Git
11+
RUN if [ "debian" == "alpine" ] ; then apk add git ; else apt-get install -y git ; fi
12+

.devcontainer/devcontainer.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
2+
// README at: https://github.com/marcosgomesneto/bun-devcontainers/tree/main/src/basic-bun
3+
{
4+
"name": "Bun",
5+
"dockerFile": "Dockerfile",
6+
// Configure tool-specific properties.
7+
"customizations": {
8+
// Configure properties specific to VS Code.
9+
"vscode": {
10+
// Add the IDs of extensions you want installed when the container is created.
11+
"extensions": [
12+
"oven.bun-vscode"
13+
]
14+
}
15+
},
16+
"features": {
17+
"ghcr.io/devcontainers/features/node:1": {}
18+
}
19+
}

.vscode/extensions.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
{
2-
"recommendations": ["kavod-io.vscode-jest-test-adapter", "esbenp.prettier-vscode", "github.vscode-github-actions", "hbenl.vscode-test-explorer"]
2+
"recommendations": [
3+
"esbenp.prettier-vscode",
4+
"github.vscode-github-actions",
5+
"hbenl.vscode-test-explorer"
6+
]
37
}

bun.lockb

-1 Bytes
Binary file not shown.

examples/with-javascript-express/package-lock.json

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sqlitecloud/drivers",
3-
"version": "1.0.309",
3+
"version": "1.0.325",
44
"description": "SQLiteCloud drivers for Typescript/Javascript in edge, web and node clients",
55
"main": "./lib/index.js",
66
"types": "./lib/index.d.ts",
@@ -73,7 +73,7 @@
7373
"eslint-plugin-node": "^11.1.0",
7474
"eslint-plugin-prettier": "^3.4.1",
7575
"express": "^4.19.2",
76-
"husky": "^9.0.11",
76+
"husky": "^9.1.7",
7777
"jest": "^29.7.0",
7878
"prettier": "^3.2.5",
7979
"sqlite3": "^5.1.7",

src/drivers/connection-tls.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* connection-tls.ts - connection via tls socket and sqlitecloud protocol
33
*/
44

5-
import { type SQLiteCloudConfig, SQLiteCloudError, type ErrorCallback, type ResultsCallback } from './types'
5+
import { type SQLiteCloudConfig, SQLiteCloudError, type ErrorCallback, type ResultsCallback, SQLiteCloudCommand } from './types'
66
import { SQLiteCloudConnection } from './connection'
77
import { getInitializationCommands } from './utilities'
88
import {
@@ -23,8 +23,6 @@ import { Buffer } from 'buffer'
2323

2424
import * as tls from 'tls'
2525

26-
import fs from 'fs'
27-
2826
/**
2927
* Implementation of SQLiteCloudConnection that connects to the database using specific tls APIs
3028
* that connect to native sockets or tls sockets and communicates via raw, binary protocol.
@@ -104,13 +102,17 @@ export class SQLiteCloudTlsConnection extends SQLiteCloudConnection {
104102
}
105103

106104
/** Will send a command immediately (no queueing), return the rowset/result or throw an error */
107-
transportCommands(commands: string, callback?: ResultsCallback): this {
105+
transportCommands(commands: string | SQLiteCloudCommand, callback?: ResultsCallback): this {
108106
// connection needs to be established?
109107
if (!this.socket) {
110108
callback?.call(this, new SQLiteCloudError('Connection not established', { errorCode: 'ERR_CONNECTION_NOT_ESTABLISHED' }))
111109
return this
112110
}
113111

112+
if (typeof commands === 'string') {
113+
commands = { query: commands } as SQLiteCloudCommand
114+
}
115+
114116
// reset buffer and rowset chunks, define response callback
115117
this.buffer = Buffer.alloc(0)
116118
this.startedOn = new Date()
@@ -148,7 +150,7 @@ export class SQLiteCloudTlsConnection extends SQLiteCloudConnection {
148150
// buffer to accumulate incoming data until an whole command is received and can be parsed
149151
private buffer: Buffer = Buffer.alloc(0)
150152
private startedOn: Date = new Date()
151-
private executingCommands?: string
153+
private executingCommands?: SQLiteCloudCommand
152154

153155
// callback to be called when a command is finished processing
154156
private processCallback?: ResultsCallback

src/drivers/connection.ts

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
* connection.ts - base abstract class for sqlitecloud server connections
33
*/
44

5-
import { SQLiteCloudConfig, SQLiteCloudError, ErrorCallback, ResultsCallback } from './types'
6-
import { validateConfiguration, prepareSql } from './utilities'
5+
import { SQLiteCloudConfig, SQLiteCloudError, ErrorCallback, ResultsCallback, SQLiteCloudCommand } from './types'
6+
import { validateConfiguration } from './utilities'
77
import { OperationsQueue } from './queue'
88
import { anonimizeCommand, getUpdateResults } from './utilities'
99

@@ -62,7 +62,7 @@ export abstract class SQLiteCloudConnection {
6262
protected abstract connectTransport(config: SQLiteCloudConfig, callback?: ErrorCallback): this
6363

6464
/** Send a command, return the rowset/result or throw an error */
65-
protected abstract transportCommands(commands: string, callback?: ResultsCallback): this
65+
protected abstract transportCommands(commands: string | SQLiteCloudCommand, callback?: ResultsCallback): this
6666

6767
/** Will log to console if verbose mode is enabled */
6868
protected log(message: string, ...optionalParams: any[]): void {
@@ -85,7 +85,7 @@ export abstract class SQLiteCloudConnection {
8585
}
8686

8787
/** Will enquee a command to be executed and callback with the resulting rowset/result/error */
88-
public sendCommands(commands: string, callback?: ResultsCallback): this {
88+
public sendCommands(commands: string | SQLiteCloudCommand, callback?: ResultsCallback): this {
8989
this.operations.enqueue(done => {
9090
if (!this.connected) {
9191
const error = new SQLiteCloudError('Connection not established', { errorCode: 'ERR_CONNECTION_NOT_ESTABLISHED' })
@@ -112,28 +112,23 @@ export abstract class SQLiteCloudConnection {
112112
* metadata in case of insert, update, delete.
113113
*/
114114
public async sql(sql: TemplateStringsArray | string, ...values: any[]): Promise<any> {
115-
let preparedSql = ''
115+
let query = ''
116116

117117
// sql is a TemplateStringsArray, the 'raw' property is specific to TemplateStringsArray
118118
if (Array.isArray(sql) && 'raw' in sql) {
119119
sql.forEach((string, i) => {
120-
preparedSql += string + (i < values.length ? '?' : '')
120+
query += string + (i < values.length ? '?' : '')
121121
})
122-
preparedSql = prepareSql(preparedSql, ...values)
122+
} else if (typeof sql === 'string') {
123+
query = sql
123124
} else {
124-
if (typeof sql === 'string') {
125-
if (values?.length > 0) {
126-
preparedSql = prepareSql(sql, ...values)
127-
} else {
128-
preparedSql = sql
129-
}
130-
} else {
131-
throw new Error('Invalid sql')
132-
}
125+
throw new Error('Invalid sql')
133126
}
134127

128+
const commands: SQLiteCloudCommand = { query, parameters: values }
129+
135130
return new Promise((resolve, reject) => {
136-
this.sendCommands(preparedSql, (error, results) => {
131+
this.sendCommands(commands, (error, results) => {
137132
if (error) {
138133
reject(error)
139134
} else {

src/drivers/database.ts

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212

1313
import { SQLiteCloudConnection } from './connection'
1414
import { SQLiteCloudRowset } from './rowset'
15-
import { SQLiteCloudConfig, SQLiteCloudError, RowCountCallback, SQLiteCloudArrayType } from './types'
16-
import { prepareSql, popCallback } from './utilities'
17-
import { Statement } from './statement'
15+
import { SQLiteCloudConfig, SQLiteCloudError, RowCountCallback, SQLiteCloudArrayType, SQLiteCloudCommand } from './types'
16+
import { popCallback } from './utilities'
1817
import { ErrorCallback, ResultsCallback, RowCallback, RowsCallback } from './types'
1918
import EventEmitter from 'eventemitter3'
2019
import { isBrowser } from './utilities'
2120
import { PubSub } from './pubsub'
21+
import { Statement } from './statement'
2222

2323
// Uses eventemitter3 instead of node events for browser compatibility
2424
// https://github.com/primus/eventemitter3
@@ -204,12 +204,12 @@ export class Database extends EventEmitter {
204204
public run<T>(sql: string, params: any, callback?: ResultsCallback<T>): this
205205
public run(sql: string, ...params: any[]): this {
206206
const { args, callback } = popCallback<ResultsCallback>(params)
207-
const preparedSql = args?.length > 0 ? prepareSql(sql, ...args) : sql
207+
const command: SQLiteCloudCommand = { query: sql, parameters: args?.flat() }
208208
this.getConnection((error, connection) => {
209209
if (error || !connection) {
210210
this.handleError(null, error as Error, callback)
211211
} else {
212-
connection.sendCommands(preparedSql, (error, results) => {
212+
connection.sendCommands(command, (error, results) => {
213213
if (error) {
214214
this.handleError(connection, error, callback)
215215
} else {
@@ -237,12 +237,12 @@ export class Database extends EventEmitter {
237237
public get<T>(sql: string, params: any, callback?: RowCallback<T>): this
238238
public get(sql: string, ...params: any[]): this {
239239
const { args, callback } = popCallback<RowCallback>(params)
240-
const preparedSql = args?.length > 0 ? prepareSql(sql, ...args) : sql
240+
const command: SQLiteCloudCommand = { query: sql, parameters: args?.flat() }
241241
this.getConnection((error, connection) => {
242242
if (error || !connection) {
243243
this.handleError(null, error as Error, callback)
244244
} else {
245-
connection.sendCommands(preparedSql, (error, results) => {
245+
connection.sendCommands(command, (error, results) => {
246246
if (error) {
247247
this.handleError(connection, error, callback)
248248
} else {
@@ -275,12 +275,12 @@ export class Database extends EventEmitter {
275275
public all<T>(sql: string, params: any, callback?: RowsCallback<T>): this
276276
public all(sql: string, ...params: any[]): this {
277277
const { args, callback } = popCallback<RowsCallback>(params)
278-
const preparedSql = args?.length > 0 ? prepareSql(sql, ...args) : sql
278+
const command: SQLiteCloudCommand = { query: sql, parameters: args?.flat() }
279279
this.getConnection((error, connection) => {
280280
if (error || !connection) {
281281
this.handleError(null, error as Error, callback)
282282
} else {
283-
connection.sendCommands(preparedSql, (error, results) => {
283+
connection.sendCommands(command, (error, results) => {
284284
if (error) {
285285
this.handleError(connection, error, callback)
286286
} else {
@@ -316,12 +316,12 @@ export class Database extends EventEmitter {
316316
// extract optional parameters and one or two callbacks
317317
const { args, callback, complete } = popCallback<RowCallback>(params)
318318

319-
const preparedSql = args?.length > 0 ? prepareSql(sql, ...args) : sql
319+
const command: SQLiteCloudCommand = { query: sql, parameters: args?.flat() }
320320
this.getConnection((error, connection) => {
321321
if (error || !connection) {
322322
this.handleError(null, error as Error, callback)
323323
} else {
324-
connection.sendCommands(preparedSql, (error, rowset) => {
324+
connection.sendCommands(command, (error, rowset) => {
325325
if (error) {
326326
this.handleError(connection, error, callback)
327327
} else {
@@ -352,8 +352,7 @@ export class Database extends EventEmitter {
352352
* they are bound to the prepared statement before calling the callback.
353353
*/
354354
public prepare<T = any>(sql: string, ...params: any[]): Statement<T> {
355-
const { args, callback } = popCallback(params)
356-
return new Statement(this, sql, ...args, callback)
355+
return new Statement(this, sql, ...params)
357356
}
358357

359358
/**
@@ -444,34 +443,29 @@ export class Database extends EventEmitter {
444443
* @returns An array of rows in case of selections or an object with
445444
* metadata in case of insert, update, delete.
446445
*/
447-
448446
public async sql(sql: TemplateStringsArray | string, ...values: any[]): Promise<any> {
449-
let preparedSql = ''
447+
let query = ''
450448

451449
// sql is a TemplateStringsArray, the 'raw' property is specific to TemplateStringsArray
452450
if (Array.isArray(sql) && 'raw' in sql) {
451+
query = ''
453452
sql.forEach((string, i) => {
454-
preparedSql += string + (i < values.length ? '?' : '')
453+
query += string + (i < values.length ? '?' : '')
455454
})
456-
preparedSql = prepareSql(preparedSql, ...values)
455+
} else if (typeof sql === 'string') {
456+
query = sql
457457
} else {
458-
if (typeof sql === 'string') {
459-
if (values?.length > 0) {
460-
preparedSql = prepareSql(sql, ...values)
461-
} else {
462-
preparedSql = sql
463-
}
464-
} else {
465-
throw new Error('Invalid sql')
466-
}
458+
throw new Error('Invalid sql')
467459
}
468460

461+
const commands: SQLiteCloudCommand = { query, parameters: values }
462+
469463
return new Promise((resolve, reject) => {
470464
this.getConnection((error, connection) => {
471465
if (error || !connection) {
472466
reject(error)
473467
} else {
474-
connection.sendCommands(preparedSql, (error, results) => {
468+
connection.sendCommands(commands, (error, results) => {
475469
if (error) {
476470
reject(error)
477471
} else {

0 commit comments

Comments
 (0)