Skip to content

Commit 82fe604

Browse files
committed
Add sqlight-compatible query functions
1 parent fedc3d6 commit 82fe604

5 files changed

Lines changed: 306 additions & 33 deletions

File tree

rust/src/main.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ pub struct Execute {
3232
pub params: Vec<Value>,
3333
}
3434

35+
#[derive(Debug, SchemaRead, SchemaWrite)]
36+
pub struct Run {
37+
pub db: String,
38+
pub sql: String,
39+
}
40+
41+
#[derive(Debug, SchemaRead, SchemaWrite)]
42+
pub enum RunResult {
43+
Ok,
44+
Error(String),
45+
}
46+
3547
#[derive(Debug, SchemaRead, SchemaWrite)]
3648
pub enum Updated {
3749
Ok(u64),
@@ -96,6 +108,7 @@ define_requests! {
96108
Crap => BadRequest,
97109
Select => RowsResult,
98110
Execute => Updated,
111+
Run => RunResult,
99112
}
100113

101114
#[tokio::main]
@@ -180,6 +193,14 @@ async fn handle_request(cache: &DbCache, req: Requests) -> Responses {
180193
};
181194
response_enum(p, resp)
182195
}
196+
197+
Requests::Run(run, p) => {
198+
let resp = match run_batch(cache, run).await {
199+
Ok(()) => RunResult::Ok,
200+
Err(e) => RunResult::Error(e),
201+
};
202+
response_enum(p, resp)
203+
}
183204
}
184205
}
185206

@@ -281,6 +302,12 @@ async fn execute_statement(cache: &DbCache, execute: Execute) -> Result<u64, Str
281302
stmt.execute(params).await.map_err(|e| e.to_string())
282303
}
283304

305+
async fn run_batch(cache: &DbCache, run: Run) -> Result<(), String> {
306+
let cached = get_cached_db(cache, &run.db).await?;
307+
let cached = cached.lock().await;
308+
cached.conn.execute_batch(&run.sql).await.map_err(|e| e.to_string())
309+
}
310+
284311
#[cfg(test)]
285312
mod tests {
286313
use super::*;

src/pturso.gleam

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import gleam/list
2+
import gleam/result.{try}
3+
import gleam/dynamic/decode
14
import gleam/dynamic.{type Dynamic}
25

36
/// Parameter values for queries (needed for bincode encoding)
@@ -9,29 +12,103 @@ pub type Param {
912
Blob(BitArray)
1013
}
1114

12-
pub type Row =
13-
List(Dynamic)
15+
pub type Port
1416

15-
pub type Connection
17+
pub type Connection {
18+
Connection(port: Port, db: String)
19+
}
20+
21+
pub type Error {
22+
DatabaseError(message: String)
23+
DecodeError(errors: List(decode.DecodeError))
24+
}
1625

1726
@external(erlang, "pturso_ffi", "start")
18-
pub fn start(binary_path: String) -> Result(Connection, String)
27+
pub fn start(binary_path: String) -> Result(Port, String)
1928

2029
@external(erlang, "pturso_ffi", "stop")
21-
pub fn stop(conn: Connection) -> Nil
30+
pub fn stop(conn: Port) -> Nil
2231

32+
/// Raw select function for queries that return results.
33+
/// Considered "low level" as it takes in Port and DB separately
34+
/// and is implemented in Erlang.
2335
@external(erlang, "pturso_ffi", "select")
2436
pub fn select(
25-
conn: Connection,
37+
conn: Port,
2638
db: String,
2739
query: String,
2840
params: List(Param),
29-
) -> Result(List(Row), String)
41+
) -> Result(List(Dynamic), String)
3042

43+
/// Raw execute function for queries that do not return anything.
44+
/// Considered "low level" as it takes in Port and DB separately
45+
/// and is implemented in Erlang.
3146
@external(erlang, "pturso_ffi", "execute")
3247
pub fn execute(
33-
conn: Connection,
48+
conn: Port,
3449
db: String,
3550
query: String,
3651
params: List(Param),
3752
) -> Result(Int, String)
53+
54+
/// Run one or more SQL statements without using prepared statements.
55+
/// Supports multiple statements separated by semicolons.
56+
/// Returns nothing on success.
57+
@external(erlang, "pturso_ffi", "run")
58+
pub fn run(conn: Port, db: String, sql: String) -> Result(Nil, String)
59+
60+
/// Creates a Connection object.
61+
/// Note that this doesn't actually cause a real "connection" to
62+
/// be created. Actual DB connections are created lazily as
63+
/// queries come in.
64+
/// This is useful if you want to use the sqlight-compatible
65+
/// query and exec functions.
66+
pub fn connect(port: Port, to db: String) -> Connection {
67+
Connection(port:, db:)
68+
}
69+
70+
/// sqlight-compatible query function.
71+
pub fn query(
72+
sql: String,
73+
on conn: Connection,
74+
with params: List(Param),
75+
expecting decoder: decode.Decoder(a),
76+
) -> Result(List(a), Error) {
77+
use rows <- try(select(
78+
conn.port,
79+
conn.db,
80+
sql,
81+
params,
82+
) |> result.map_error(DatabaseError))
83+
84+
let decoded = list.map(rows, decode.run(_, decoder))
85+
86+
let successes = list.filter_map(decoded, fn(x) {
87+
case x {
88+
Ok(i) -> Ok(i)
89+
Error(_) -> Error(Nil)
90+
}
91+
})
92+
93+
let errors = list.filter_map(decoded, fn(x) {
94+
case x {
95+
Ok(_) -> Error(Nil)
96+
Error(e) -> Ok(e)
97+
}
98+
})
99+
100+
case successes, errors {
101+
s, [] -> Ok(s)
102+
_, [_, ..] as e -> Error(DecodeError(list.flatten(e)))
103+
}
104+
}
105+
106+
/// sqlight-compatible exec function.
107+
/// Runs one or more SQL statements without prepared statements.
108+
pub fn exec(
109+
sql: String,
110+
on conn: Connection,
111+
) -> Result(Nil, Error) {
112+
run(conn.port, conn.db, sql)
113+
|> result.map_error(DatabaseError)
114+
}

src/pturso_bincode.erl

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,14 @@ decode_value(<<4:32/little-unsigned, Rest/binary>>) ->
149149

150150
%%====================================================================
151151
%% Request Encoding
152-
%% Rust: enum Requests { Crap=0, Select=1, Insert=2 }
152+
%% Rust: enum Requests { Crap=0, Select=1, Execute=2, Run=3 }
153153
%%====================================================================
154154

155155
-type crap_request() :: {crap, binary()}.
156156
-type select_request() :: {select, binary(), binary(), [value()]}.
157157
-type insert_request() :: {insert, binary(), binary(), [value()]}.
158-
-type request() :: crap_request() | select_request() | insert_request().
158+
-type run_request() :: {run, binary(), binary()}.
159+
-type request() :: crap_request() | select_request() | insert_request() | run_request().
159160

160161
-spec encode_request(request()) -> binary().
161162
encode_request({crap, Reason}) ->
@@ -171,22 +172,29 @@ encode_request({select, Db, Query, Params}) ->
171172
ParamsBin/binary>>;
172173

173174
encode_request({insert, Db, Query, Params}) ->
174-
%% Requests::Insert(Insert { db, query, params })
175+
%% Requests::Execute(Execute { db, query, params })
175176
ParamsBin = encode_vec(fun encode_value/1, Params),
176177
<<(encode_u32(2))/binary,
177178
(encode_string(Db))/binary,
178179
(encode_string(Query))/binary,
179-
ParamsBin/binary>>.
180+
ParamsBin/binary>>;
181+
182+
encode_request({run, Db, Sql}) ->
183+
%% Requests::Run(Run { db, sql })
184+
<<(encode_u32(3))/binary,
185+
(encode_string(Db))/binary,
186+
(encode_string(Sql))/binary>>.
180187

181188
%%====================================================================
182189
%% Response Decoding
183-
%% Rust: enum Responses { BadRequest=0, RowsResult=1, Updated=2 }
190+
%% Rust: enum Responses { BadRequest=0, RowsResult=1, Updated=2, RunResult=3 }
184191
%%====================================================================
185192

186193
-type bad_request_response() :: {bad_request, binary()}.
187194
-type rows_result_response() :: {rows_result, {ok, [[value()]]} | {error, binary()}}.
188195
-type updated_response() :: {updated, {ok, non_neg_integer()} | {error, binary()}}.
189-
-type response() :: bad_request_response() | rows_result_response() | updated_response().
196+
-type run_result_response() :: {run_result, ok | {error, binary()}}.
197+
-type response() :: bad_request_response() | rows_result_response() | updated_response() | run_result_response().
190198

191199
-spec decode_response(binary()) -> response().
192200
decode_response(<<0:32/little-unsigned, Rest/binary>>) ->
@@ -200,7 +208,11 @@ decode_response(<<1:32/little-unsigned, Rest/binary>>) ->
200208

201209
decode_response(<<2:32/little-unsigned, Rest/binary>>) ->
202210
%% Updated: enum { Ok(u64)=0, Error(String)=1 }
203-
decode_updated(Rest).
211+
decode_updated(Rest);
212+
213+
decode_response(<<3:32/little-unsigned, Rest/binary>>) ->
214+
%% RunResult: enum { Ok=0, Error(String)=1 }
215+
decode_run_result(Rest).
204216

205217
decode_rows_result(<<0:32/little-unsigned, Rest/binary>>) ->
206218
%% RowsResult::Ok(Vec<Vec<Value>>)
@@ -221,3 +233,11 @@ decode_updated(<<1:32/little-unsigned, Rest/binary>>) ->
221233
%% Updated::Error(String)
222234
{Reason, <<>>} = decode_string(Rest),
223235
{updated, {error, Reason}}.
236+
237+
decode_run_result(<<0:32/little-unsigned, _Rest/binary>>) ->
238+
%% RunResult::Ok
239+
{run_result, ok};
240+
decode_run_result(<<1:32/little-unsigned, Rest/binary>>) ->
241+
%% RunResult::Error(String)
242+
{Reason, <<>>} = decode_string(Rest),
243+
{run_result, {error, Reason}}.

src/pturso_ffi.erl

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
start/1,
66
stop/1,
77
select/4,
8-
execute/4
8+
execute/4,
9+
run/3
910
]).
1011

1112
%%====================================================================
@@ -33,7 +34,8 @@ select({connection, Pid}, Db, Query, Params) ->
3334
Request = {select, Db, Query, ErlParams},
3435
case pturso_port:call(Pid, Request) of
3536
{rows_result, {ok, Rows}} ->
36-
DynamicRows = [[erl_value_to_dynamic(V) || V <- Row] || Row <- Rows],
37+
%% Return rows as tuples so decode.field(index, decoder) works
38+
DynamicRows = [list_to_tuple([erl_value_to_dynamic(V) || V <- Row]) || Row <- Rows],
3739
{ok, DynamicRows};
3840
{rows_result, {error, Reason}} ->
3941
{error, Reason};
@@ -55,6 +57,18 @@ execute({connection, Pid}, Db, Query, Params) ->
5557
{error, Reason}
5658
end.
5759

60+
-spec run({connection, pid()}, binary(), binary()) -> {ok, nil} | {error, binary()}.
61+
run({connection, Pid}, Db, Sql) ->
62+
Request = {run, Db, Sql},
63+
case pturso_port:call(Pid, Request) of
64+
{run_result, ok} ->
65+
{ok, nil};
66+
{run_result, {error, Reason}} ->
67+
{error, Reason};
68+
{bad_request, Reason} ->
69+
{error, Reason}
70+
end.
71+
5872
%%====================================================================
5973
%% Internal functions
6074
%%====================================================================

0 commit comments

Comments
 (0)