Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## v0.10.0 - 2024-11-22

- Added support for prepared statements. They can be created with `prepare` and
queried with `query_prepared`.

## v0.9.1 - 2024-08-19

- Fixed a bug where bit arrays could bind to the incorrect SQLite type.
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ pub fn main() {
"
let assert Ok([#("Nubi", 4), #("Ginny", 6)]) =
sqlight.query(sql, on: conn, with: [sqlight.int(7)], expecting: cat_decoder)

let assert Ok(prepared) =
sqlight.prepare(sql, on: conn, expecting: cat_decoder)
let assert Ok([#("Nubi", 4), #("Ginny", 6)]) =
sqlight.query_prepared(prepared, with: [sqlight.int(7)])
}
```

Expand Down
2 changes: 1 addition & 1 deletion gleam.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = "sqlight"
version = "0.9.1"
version = "0.10.0"
licences = ["Apache-2.0"]
description = "Use SQLite from Gleam!"

Expand Down
49 changes: 49 additions & 0 deletions src/sqlight.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ import gleam/string

pub type Connection

type Statement

pub opaque type PreparedStatement(t) {
PreparedStatement(
connection: Connection,
prepared_statement: Statement,
decoder: Decoder(t),
)
}

/// A value that can be sent to SQLite as one of the arguments to a
/// parameterised SQL query.
pub type Value
Expand Down Expand Up @@ -389,6 +399,33 @@ pub fn query(
Ok(rows)
}

pub fn prepare(
sql: String,
on connection: Connection,
expecting decoder: Decoder(t),
) -> Result(PreparedStatement(t), Error) {
do_prepare(sql, connection)
|> result.then(fn(prepared_statement) {
Ok(PreparedStatement(connection, prepared_statement, decoder))
})
}

pub fn query_prepared(
prepared_statement: PreparedStatement(t),
with arguments: List(Value),
) -> Result(List(t), Error) {
use rows <- result.then(run_prepared_query(
prepared_statement.prepared_statement,
prepared_statement.connection,
arguments,
))
use rows <- result.then(
list.try_map(over: rows, with: prepared_statement.decoder)
|> result.map_error(decode_error),
)
Ok(rows)
}

@external(erlang, "sqlight_ffi", "query")
@external(javascript, "./sqlight_ffi.js", "query")
fn run_query(
Expand All @@ -397,6 +434,18 @@ fn run_query(
c: List(Value),
) -> Result(List(Dynamic), Error)

@external(erlang, "sqlight_ffi", "prepare")
@external(javascript, "./sqlight_ffi.js", "prepare")
fn do_prepare(a: String, b: Connection) -> Result(Statement, Error)

@external(erlang, "sqlight_ffi", "query_prepared")
@external(javascript, "./sqlight_ffi.js", "query_prepared")
fn run_prepared_query(
a: Statement,
b: Connection,
c: List(Value),
) -> Result(List(Dynamic), Error)

@external(erlang, "sqlight_ffi", "coerce_value")
@external(javascript, "./sqlight_ffi.js", "coerce_value")
fn coerce_value(a: a) -> Value
Expand Down
27 changes: 25 additions & 2 deletions src/sqlight_ffi.erl
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
-module(sqlight_ffi).

-export([
status/0, query/3, exec/2, coerce_value/1, coerce_blob/1, null/0, open/1, close/1
status/0, query/3, prepare/2, query_prepared/3, exec/2, coerce_value/1, coerce_blob/1, null/0, open/1, close/1
]).

open(Name) ->
case esqlite3:open(unicode:characters_to_list(Name)) of
{ok, Connection} -> {ok, Connection};
{error, Code} ->
{error, Code} ->
Code1 = sqlight:error_code_from_int(Code),
{error, {sqlight_error, Code1, <<>>, -1}}
end.
Expand All @@ -24,6 +24,29 @@ query(Sql, Connection, Arguments) when is_binary(Sql) ->
Rows -> {ok, lists:map(fun erlang:list_to_tuple/1, Rows)}
end.

prepare(Sql, Connection) when is_binary(Sql) ->
case esqlite3:prepare(Connection, Sql, [{persistent, true}]) of
{error, Code} -> to_error(Connection, Code);
{ok, Statement} -> {ok, Statement}
end.

query_prepared(Statement, Connection, Arguments) ->
case
(case esqlite3:bind(Statement, Arguments) of
ok ->
esqlite3:fetchall(Statement);
{error, _} = Error ->
Error
end)
of
{error, Code} ->
esqlite3:reset(Statement),
to_error(Connection, Code);
Rows ->
esqlite3:reset(Statement),
{ok, lists:map(fun erlang:list_to_tuple/1, Rows)}
end.

exec(Sql, Connection) ->
case esqlite3:exec(Connection, Sql) of
{error, Code} -> to_error(Connection, Code);
Expand Down
40 changes: 36 additions & 4 deletions src/sqlight_ffi.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ import { List, Ok, Error as GlError } from "./gleam.mjs";
import { SqlightError, error_code_from_int } from "./sqlight.mjs";
import { DB } from "https://deno.land/x/[email protected]/mod.ts";

function wrapDB(db) {
return {
db: db,
statements: [],
};
}

export function open(path) {
return new Ok(new DB(path));
return new Ok(wrapDB(new DB(path)));
}

export function close(connection) {
connection.close();
for(let statement of connection.statements) {
statement.finalize();
}
connection.statements = [];
connection.db.close();
return new Ok(undefined);
}

Expand All @@ -26,7 +37,7 @@ export function status(connection) {

export function exec(sql, connection) {
try {
connection.execute(sql);
connection.db.execute(sql);
return new Ok(undefined);
} catch (error) {
return convert_error(error);
Expand All @@ -36,7 +47,28 @@ export function exec(sql, connection) {
export function query(sql, connection, parameters) {
let rows;
try {
rows = connection.query(sql, parameters.toArray());
rows = connection.db.query(sql, parameters.toArray());
} catch (error) {
return convert_error(error);
}
return new Ok(List.fromArray(rows));
}

export function prepare(sql, connection) {
let statement;
try {
statement = connection.db.prepareQuery(sql);
connection.statements.push(statement);
} catch (error) {
return convert_error(error);
}
return new Ok(statement);
}

export function query_prepared(statement, _connection, parameters) {
let rows;
try {
rows = statement.all(parameters.toArray());
} catch (error) {
return convert_error(error);
}
Expand Down
Loading