Skip to content

Commit

Permalink
Return raw SQL query results in sql-sqlite-node (#3607)
Browse files Browse the repository at this point in the history
Co-authored-by: Tim <[email protected]>
  • Loading branch information
thewilkybarkid and tim-smart authored Sep 17, 2024
1 parent 10bf621 commit e38b3dc
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 37 deletions.
10 changes: 10 additions & 0 deletions .changeset/early-buckets-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@effect/sql-sqlite-node": patch
---

Return raw SQL query results in sql-sqlite-node

```ts
response = yield* sql`INSERT INTO test (name) VALUES ('hello')`.raw
assert.deepStrictEqual(response, { changes: 1, lastInsertRowid: 1 })
```
67 changes: 37 additions & 30 deletions packages/sql-sqlite-node/src/SqliteClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,61 +88,65 @@ interface SqliteConnection extends Connection {
export const make = (
options: SqliteClientConfig
): Effect.Effect<SqliteClient, never, Scope.Scope> =>
Effect.gen(function*(_) {
Effect.gen(function*() {
const compiler = Statement.makeCompilerSqlite(options.transformQueryNames)
const transformRows = Statement.defaultTransforms(
options.transformResultNames!
).array

const makeConnection = Effect.gen(function*(_) {
const makeConnection = Effect.gen(function*() {
const scope = yield* Effect.scope
const db = new Sqlite(options.filename, {
readonly: options.readonly ?? false
})
yield* _(Effect.addFinalizer(() => Effect.sync(() => db.close())))
yield* Scope.addFinalizer(scope, Effect.sync(() => db.close()))

if (options.disableWAL !== true) {
db.pragma("journal_mode = WAL")
}

const prepareCache = yield* _(
Cache.make({
capacity: options.prepareCacheSize ?? 200,
timeToLive: options.prepareCacheTTL ?? Duration.minutes(10),
lookup: (sql: string) =>
Effect.try({
try: () => db.prepare(sql),
catch: (cause) => new SqlError({ cause, message: "Failed to prepare statement" })
})
})
)
const prepareCache = yield* Cache.make({
capacity: options.prepareCacheSize ?? 200,
timeToLive: options.prepareCacheTTL ?? Duration.minutes(10),
lookup: (sql: string) =>
Effect.try({
try: () => db.prepare(sql),
catch: (cause) => new SqlError({ cause, message: "Failed to prepare statement" })
})
})

const runStatement = (
statement: Sqlite.Statement,
params: ReadonlyArray<Statement.Primitive> = []
params: ReadonlyArray<Statement.Primitive>,
raw: boolean
) =>
Effect.try({
try: () => {
if (statement.reader) {
return statement.all(...params) as ReadonlyArray<any>
return statement.all(...params)
}
statement.run(...params)
return []
const result = statement.run(...params)
return raw ? result as unknown as ReadonlyArray<any> : []
},
catch: (cause) => new SqlError({ cause, message: "Failed to execute statement" })
})

const run = (
sql: string,
params: ReadonlyArray<Statement.Primitive> = []
) => Effect.flatMap(prepareCache.get(sql), (s) => runStatement(s, params))

const runUnprepared = (
sql: string,
params: ReadonlyArray<Statement.Primitive> = []
) => Effect.map(runStatement(db.prepare(sql), params), transformRows)
params: ReadonlyArray<Statement.Primitive>,
raw = false
) =>
Effect.flatMap(
prepareCache.get(sql),
(s) => runStatement(s, params, raw)
)

const runTransform = options.transformResultNames
? (sql: string, params?: ReadonlyArray<Statement.Primitive>) => Effect.map(run(sql, params), transformRows)
? (sql: string, params: ReadonlyArray<Statement.Primitive>) =>
Effect.map(
run(sql, params),
transformRows
)
: run

const runValues = (
Expand Down Expand Up @@ -173,7 +177,7 @@ export const make = (
return runTransform(sql, params)
},
executeRaw(sql, params) {
return run(sql, params)
return run(sql, params, true)
},
executeValues(sql, params) {
return runValues(sql, params)
Expand All @@ -182,7 +186,10 @@ export const make = (
return run(sql, params)
},
executeUnprepared(sql, params) {
return runUnprepared(sql, params)
return Effect.map(
runStatement(db.prepare(sql), params ?? [], false),
transformRows
)
},
executeStream(_sql, _params) {
return Effect.dieMessage("executeStream not implemented")
Expand All @@ -206,8 +213,8 @@ export const make = (
})
})

const semaphore = yield* _(Effect.makeSemaphore(1))
const connection = yield* _(makeConnection)
const semaphore = yield* Effect.makeSemaphore(1)
const connection = yield* makeConnection

const acquirer = semaphore.withPermits(1)(Effect.succeed(connection))
const transactionAcquirer = Effect.uninterruptibleMask((restore) =>
Expand Down
37 changes: 30 additions & 7 deletions packages/sql-sqlite-node/test/Client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,36 @@ describe("Client", () => {
it.scoped("should work", () =>
Effect.gen(function*(_) {
const sql = yield* _(makeClient)
yield* _(sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)`)
yield* _(sql`INSERT INTO test (name) VALUES ('hello')`)
let rows = yield* _(sql`SELECT * FROM test`)
assert.deepStrictEqual(rows, [{ id: 1, name: "hello" }])
yield* _(sql`INSERT INTO test (name) VALUES ('world')`, sql.withTransaction)
rows = yield* _(sql`SELECT * FROM test`)
assert.deepStrictEqual(rows, [
let response
response = yield* _(sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)`)
assert.deepStrictEqual(response, [])
response = yield* _(sql`INSERT INTO test (name) VALUES ('hello')`)
assert.deepStrictEqual(response, [])
response = yield* _(sql`SELECT * FROM test`)
assert.deepStrictEqual(response, [{ id: 1, name: "hello" }])
response = yield* _(sql`INSERT INTO test (name) VALUES ('world')`, sql.withTransaction)
assert.deepStrictEqual(response, [])
response = yield* _(sql`SELECT * FROM test`)
assert.deepStrictEqual(response, [
{ id: 1, name: "hello" },
{ id: 2, name: "world" }
])
}))

it.scoped("should work with raw", () =>
Effect.gen(function*(_) {
const sql = yield* _(makeClient)
let response
response = yield* _(sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)`.raw)
assert.deepStrictEqual(response, { changes: 0, lastInsertRowid: 0 })
response = yield* _(sql`INSERT INTO test (name) VALUES ('hello')`.raw)
assert.deepStrictEqual(response, { changes: 1, lastInsertRowid: 1 })
response = yield* _(sql`SELECT * FROM test`.raw)
assert.deepStrictEqual(response, [{ id: 1, name: "hello" }])
response = yield* _(sql`INSERT INTO test (name) VALUES ('world')`.raw, sql.withTransaction)
assert.deepStrictEqual(response, { changes: 1, lastInsertRowid: 2 })
response = yield* _(sql`SELECT * FROM test`)
assert.deepStrictEqual(response, [
{ id: 1, name: "hello" },
{ id: 2, name: "world" }
])
Expand Down

0 comments on commit e38b3dc

Please sign in to comment.