erlang-sqlite3-1.1.4~dfsg0/0000755000175000017500000000000012560407355016056 5ustar debalancedebalanceerlang-sqlite3-1.1.4~dfsg0/GNUmakefile0000644000175000017500000000240612560407355020132 0ustar debalancedebalanceREBAR=./rebar REBAR_DEBUG=$(REBAR) -C rebar.debug.config REBAR_COMPILE=$(REBAR) get-deps compile REBAR_DEBUG_COMPILE=$(REBAR_DEBUG) get-deps compile LAST_CONFIG:=$(shell cat config.tmp) PLT=dialyzer/sqlite3.plt all: config_normal compile debug: config_debug $(REBAR_DEBUG_COMPILE) compile: $(REBAR_COMPILE) test: $(REBAR_COMPILE) eunit clean: -rm -rf deps ebin priv/*.so doc/* .eunit/* c_src/*.o config.tmp docs: $(REBAR_COMPILE) doc static: config_debug $(REBAR_DEBUG_COMPILE) ifeq ($(wildcard $(PLT)),) dialyzer --build_plt --apps kernel stdlib erts --output_plt $(PLT) else dialyzer --plt $(PLT) -r ebin endif cross_compile: config_cross $(REBAR_COMPILE) -C rebar.cross_compile.config valgrind: config_debug $(REBAR_DEBUG_COMPILE) valgrind --tool=memcheck --leak-check=yes --num-callers=20 ./test.sh ifeq ($(LAST_CONFIG),normal) config_normal: ; else config_normal: clean rm -f config.tmp echo "normal" > config.tmp endif ifeq ($(LAST_CONFIG),debug) config_debug: ; else config_debug: clean rm -f config.tmp echo "debug" > config.tmp endif ifeq ($(LAST_CONFIG),cross) config_cross: ; else config_cross: clean rm -f config.tmp echo "cross" > config.tmp endif .PHONY: all compile test clean docs static valgrind config_normal config_debug config_cross erlang-sqlite3-1.1.4~dfsg0/test/0000755000175000017500000000000012560407355017035 5ustar debalancedebalanceerlang-sqlite3-1.1.4~dfsg0/test/sqlite3_test.erl0000644000175000017500000003204512560407355022170 0ustar debalancedebalance%%%------------------------------------------------------------------- %%% File : sqlite3_test.erl %%% Author : Tee Teoh %%% Description : %%% %%% Created : 10 Jun 2008 by Tee Teoh %%%------------------------------------------------------------------- -module(sqlite3_test). %% ==================================================================== %% API %% ==================================================================== %% -------------------------------------------------------------------- %% Function: %% Description: %% -------------------------------------------------------------------- -include_lib("eunit/include/eunit.hrl"). -define(FuncTest(Name), {??Name, fun Name/0}). drop_all_tables(Db) -> Tables = sqlite3:list_tables(Db), [sqlite3:drop_table(Db, Table) || Table <- Tables], Tables. drop_table_if_exists(Db, Table) -> case lists:member(Table, sqlite3:list_tables(Db)) of true -> sqlite3:drop_table(Db, Table); false -> ok end. rows(SqlExecReply) -> case SqlExecReply of [{columns, _Columns}, {rows, Rows}] -> Rows; {error, Code, Reason} -> {error, Code, Reason} end. all_test_() -> {setup, fun open_db/0, fun close_db/1, [?FuncTest(basic_functionality), ?FuncTest(parametrized), ?FuncTest(negative), ?FuncTest(blob), ?FuncTest(escaping), ?FuncTest(select_many_records), ?FuncTest(nonexistent_table_info), ?FuncTest(large_number), ?FuncTest(unicode), ?FuncTest(acc_string_encoding), ?FuncTest(large_offset), ?FuncTest(issue23), ?FuncTest(issue13), ?FuncTest(enable_load_extension)]}. anonymous_test() -> {ok, Pid} = sqlite3:open(anonymous, []), try ?assertEqual(undefined, whereis(anonymous)), ?assertNot(filelib:is_file("./anonymous.db")) after sqlite3:close(Pid) end. open_db() -> sqlite3:open(ct, [in_memory]). close_db({ok, _Pid}) -> sqlite3:close(ct); close_db(_) -> ok. basic_functionality() -> Columns = ["id", "name", "age", "wage"], AllRows = [{1, <<"abby">>, 20, 2000}, {2, <<"marge">>, 30, 2000}], AbbyOnly = [{1, <<"abby">>, 20, 2000}], TableInfo = [{id, integer, [{primary_key, [asc, autoincrement]}]}, {name, text, [not_null, unique]}, {age, integer, not_null}, {wage, integer}], TableInfo1 = lists:keyreplace(age, 1, TableInfo, {age, integer, [not_null]}), drop_all_tables(ct), ?assertMatch( {error, 21, _}, sqlite3:sql_exec(ct, "-- Comment")), ?assertEqual( [], sqlite3:list_tables(ct)), ok = sqlite3:create_table(ct, user, TableInfo), ?assertEqual( [user, sqlite_sequence], sqlite3:list_tables(ct)), ?assertEqual( TableInfo1, sqlite3:table_info(ct, user)), ?assertEqual( {rowid, 1}, sqlite3:write(ct, user, [{name, "abby"}, {age, 20}, {<<"wage">>, 2000}])), ?assertEqual( {rowid, 2}, sqlite3:write(ct, user, [{name, "marge"}, {age, 30}, {wage, 2000}])), ?assertMatch( {error, 19, _}, sqlite3:write(ct, user, [{name, "marge"}, {age, 30}, {wage, 2000}])), ?assertEqual( [{columns, Columns}, {rows, AllRows}], sqlite3:sql_exec(ct, "select * from user;")), ?assertEqual( [{columns, Columns}, {rows, AllRows}], sqlite3:read_all(ct, user)), ?assertEqual( [{columns, ["name"]}, {rows, [{<<"abby">>}, {<<"marge">>}]}], sqlite3:read_all(ct, user, [name])), ?assertEqual( [{columns, Columns}, {rows, AbbyOnly}], sqlite3:read(ct, user, {name, "abby"})), ?assertEqual( [{columns, Columns}, {rows, AllRows}], sqlite3:read(ct, user, {wage, 2000})), ?assertEqual( ok, sqlite3:delete(ct, user, {name, "marge"})), ?assertEqual( ok, sqlite3:update(ct, user, {name, "abby"}, [{wage, 3000}])), ?assertEqual( [{columns, Columns}, {rows, [{1, <<"abby">>, 20, 3000}]}], sqlite3:sql_exec(ct, "select * from user;")), ?assertEqual( ok, sqlite3:drop_table(ct, user)). parametrized() -> drop_table_if_exists(ct, user1), sqlite3:create_table(ct, user1, [{id, integer}, {name, text}]), sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (?, ?)", [{1, 1}, {2, "john"}]), sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (?3, ?5)", [{3, 2}, {5, "joe"}]), sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (:id, @name)", [{":id", 3}, {'@name', <<"jack">>}]), sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (?, ?)", [4, "james"]), ?assertMatch( {error, _, _}, sqlite3:sql_exec(ct, "INSERT INTO user1 (id, name) VALUES (?, ?)", [4, bad_sql_value])), ?assertEqual( [{columns, ["id", "name"]}, {rows, [{1, <<"john">>}, {2, <<"joe">>}, {3, <<"jack">>}, {4, <<"james">>}]}], sqlite3:read_all(ct, user1)), sqlite3:drop_table(ct, user1), sqlite3:create_table(ct, user1, [{i, integer}, {d, double}, {b, blob}]), sqlite3:sql_exec(ct, "INSERT INTO user1 (i, d, b) VALUES (?, ?, ?)", [null, 1.0, {blob, <<1,0,0>>}]), ?assertEqual( [{columns, ["i", "d", "b"]}, {rows, [{null, 1.0, {blob, <<1,0,0>>}}]}], sqlite3:read_all(ct, user1)). negative() -> drop_table_if_exists(ct, negative), sqlite3:create_table(ct, negative, [{id, int}]), ?assertEqual({error, badarg}, sqlite3:write(ct, negative, [{id, bad_sql_value}])). blob() -> drop_table_if_exists(ct, blobs), sqlite3:create_table(ct, blobs, [{blob_col, blob}]), sqlite3:write(ct, blobs, [{blob_col, {blob, <<0,255,1,2>>}}]), ?assertEqual( [{columns, ["blob_col"]}, {rows, [{{blob, <<0,255,1,2>>}}]}], sqlite3:read_all(ct, blobs)). escaping() -> drop_table_if_exists(ct, escaping), sqlite3:create_table(ct, escaping, [{str, text}]), Strings = ["a'", "b\"c", "d''e", "f\"\""], Input = [[{str, String}] || String <- Strings], ExpectedRows = [{list_to_binary(String)} || String <- Strings], sqlite3:write_many(ct, escaping, Input), ?assertEqual( [{columns, ["str"]}, {rows, ExpectedRows}], sqlite3:read_all(ct, escaping)). select_many_records() -> N = 1024, drop_table_if_exists(ct, many_records), sqlite3:create_table(ct, many_records, [{id, integer}, {name, text}]), sqlite3:write_many(ct, many_records, [[{id, X}, {name, "bar"}] || X <- lists:seq(1, N)]), Columns = ["id", "name"], ?assertEqual( [{columns, Columns}, {rows, [{1, <<"bar">>}]}], sqlite3:read(ct, many_records, {id, 1})), [?assertEqual( M, length(rows(sqlite3:sql_exec( ct, io_lib:format("select * from many_records limit ~p;", [M]))))) || M <- [10, 100, 1000]], ?assertEqual( N, length(rows(sqlite3:sql_exec(ct, "select * from many_records;")))). %% note that inserts are actually serialized by gen_server concurrent_inserts_test() -> N = 1024, sqlite3:open(concurrent, [in_memory]), %% doing this test not in memory is much slower! drop_table_if_exists(concurrent, t), sqlite3:create_table(concurrent, t, [{id0, integer}]), Self = self(), [spawn(fun () -> sqlite3:write(concurrent, t, [{id0, X}]), Self ! {finished, N} end) || X <- lists:seq(1, N)], loop_concurrent_inserts(N), ?assertEqual( N, length(rows(sqlite3:read_all(concurrent, t)))), sqlite3:close(concurrent). loop_concurrent_inserts(0) -> ok; loop_concurrent_inserts(N) -> receive {finished, _} -> loop_concurrent_inserts(N - 1) end. nonexistent_table_info() -> ?assertEqual(table_does_not_exist, sqlite3:table_info(ct, nonexistent)). large_number() -> N1 = 9223372036854775807, N2 = -9223372036854775808, Query1 = io_lib:format("select ~p, ~p", [N1, N2]), ?assertEqual([{N1, N2}], rows(sqlite3:sql_exec(ct, Query1))), Query2 = "select ?, ?", ?assertEqual([{N1, N2}], rows(sqlite3:sql_exec(ct, Query2, [N1, N2]))), ?assertNot([{N1 + 1, N2 - 1}] == rows(sqlite3:sql_exec(ct, Query2, [N1 + 1, N2 - 1]))). unicode() -> UnicodeString = [1102,1085,1080,1082,1086,1076], %% "Unicode" in Russian, in UTF-8 drop_table_if_exists(ct, unicode), sqlite3:create_table(ct, unicode, [{str, text}]), sqlite3:write(ct, unicode, [{str, UnicodeString}]), ?assertEqual([{unicode:characters_to_binary(UnicodeString)}], rows(sqlite3:read_all(ct, unicode))). acc_string_encoding() -> ?assertEqual([{62}], rows(sqlite3:sql_exec(ct, "SELECT ? + ?", [30,32]))). prepared_test() -> Columns = ["id", "name", "age", "wage"], Abby = {1, <<"abby">>, 20, 2000}, Marge = {2, <<"marge">>, 30, 2000}, TableInfo = [{id, integer, [primary_key]}, {name, text, [unique]}, {age, integer}, {wage, integer}], sqlite3:open(prepared, [in_memory]), ok = sqlite3:create_table(prepared, user, TableInfo), sqlite3:write(prepared, user, [{name, "abby"}, {age, 20}, {wage, 2000}]), sqlite3:write(prepared, user, [{name, "marge"}, {age, 30}, {wage, 2000}]), {ok, Ref1} = sqlite3:prepare(prepared, "SELECT * FROM user"), {ok, Ref2} = sqlite3:prepare(prepared, "SELECT * FROM user WHERE name = ?"), ?assertMatch({error, _}, sqlite3:next(prepared, make_ref())), ?assertEqual(Columns, sqlite3:columns(prepared, Ref1)), ?assertEqual(Abby, sqlite3:next(prepared, Ref1)), ?assertEqual(ok, sqlite3:reset(prepared, Ref1)), ?assertEqual(Abby, sqlite3:next(prepared, Ref1)), ?assertEqual(Marge, sqlite3:next(prepared, Ref1)), ?assertEqual(done, sqlite3:next(prepared, Ref1)), ?assertEqual(ok, sqlite3:finalize(prepared, Ref1)), ?assertMatch({error, _}, sqlite3:next(prepared, Ref1)), ?assertEqual(ok, sqlite3:reset(prepared, Ref2)), ?assertEqual(ok, sqlite3:bind(prepared, Ref2, ["marge"])), ?assertEqual(Marge, sqlite3:next(prepared, Ref2)), ?assertEqual(done, sqlite3:next(prepared, Ref2)), ?assertEqual(ok, sqlite3:finalize(prepared, Ref2)), sqlite3:close(prepared). script_test() -> sqlite3:open(script, [in_memory]), Script = string:join( ["CREATE TABLE person(", "id INTEGER", ");", " ", "-- Comment", "INSERT INTO person (id) VALUES (1);", "INSERT INTO person (id) VALUES (2);", " " ], "\n"), ?assertEqual( [ok, ok, ok], sqlite3:sql_exec_script(script, Script)), ?assertEqual( [{columns,["id"]},{rows,[{1},{2}]}], sqlite3:read_all(script, person)), Script2 = "select * from person; update person set id=3 where id=2", ?assertEqual( [[{columns,["id"]},{rows,[{1},{2}]}], ok], sqlite3:sql_exec_script(script, Script2)), ?assertEqual( [{columns,["id"]},{rows,[{1},{3}]}], sqlite3:read_all(script, person)), BadScript = string:join( ["CREATE TABLE person2(", "id INTEGER", ");", " ", "-- Comment", "SYNTAX ERROR;", "INSERT INTO person (id) VALUES (2);", " " ], "\n"), ?assertEqual( [ok, {error, 1, "near \"SYNTAX\": syntax error"}], sqlite3:sql_exec_script(script, BadScript)), sqlite3:close(script). large_offset() -> drop_table_if_exists(ct, large_offset), ok = sqlite3:create_table(ct, large_offset, [{id, integer}]), ?assertMatch( [{columns, ["id"]}, {rows, []}, {error, 20, _}], sqlite3:sql_exec(ct, "select * from large_offset limit 1 offset 9223372036854775808")). issue13() -> drop_table_if_exists(ct, issue13), ok = sqlite3:create_table(ct, issue13, [{foo, integer}]), sqlite3:write_many(ct, issue13, [[{foo, X}] || X <- [-1, 0, 127, 128, 255, 256]]), ?assertEqual( [{columns, ["foo"]}, {rows, [{255}, {256}]}], sqlite3:sql_exec(ct, "select foo from issue13 where foo > 128;")), ?assertEqual( [{columns, ["foo"]}, {rows, [{255}, {256}]}], sqlite3:sql_exec(ct, "select foo from issue13 where foo > ?;", [128.0])), ?assertEqual( [{columns, ["foo"]}, {rows, [{255}, {256}]}], sqlite3:sql_exec(ct, "select foo from issue13 where foo > ?;", [128])). enable_load_extension() -> ?assertEqual(ok, sqlite3:enable_load_extension(ct, 1)). issue23() -> sqlite3:open(issue23, [in_memory]), ok = sqlite3:create_table(issue23, issue23, [{issue23, integer}]), SingleStmt = "SELECT * FROM issue23;", SingleStmtResult = [{columns, ["issue23"]}, {rows, []}], ScriptResult = sqlite3:sql_exec_script(issue23,[SingleStmt ++ SingleStmt]), ?assertEqual([SingleStmtResult, SingleStmtResult], ScriptResult), sqlite3:close(issue23). % create, read, update, delete %%==================================================================== %% Internal functions %%==================================================================== erlang-sqlite3-1.1.4~dfsg0/.project0000644000175000017500000000073412560407355017531 0ustar debalancedebalance erlang-sqlite3 org.erlide.core.erlbuilder org.erlide.core.builder.dialyzer org.erlide.core.erlnature erlang-sqlite3-1.1.4~dfsg0/rebar.debug.config0000644000175000017500000000107112560407355021424 0ustar debalancedebalance% -*- mode: erlang -*- {erl_opts, [debug_info, {d, 'DEBUG'}]}. %% required for dialyzer {port_envs, [{"^(?!.*win32)", "DRV_CFLAGS", "$DRV_CFLAGS -D DEBUG -g -Wall -Wextra -Wno-unused-parameter"}, {"^(?!.*win32)", "DRV_LDFLAGS", "$DRV_LDFLAGS -lsqlite3"}, {"win32", "CFLAGS", "/DDEBUG /IF:/MyProgramming/sqlite-amalgamation /Ic_src /W4 /wd4100 /wd4204"}, {"win32", "LDFLAGS", "sqlite3.lib"}]}. {cover_enabled, true}. {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}. {dialyzer_opts, [{plt, "dialyzer/sqlite3.plt"}]}. erlang-sqlite3-1.1.4~dfsg0/rebar.config0000644000175000017500000000070212560407355020337 0ustar debalancedebalance% -*- mode: erlang -*- {port_envs, [{"^(?!.*win32)", "DRV_CFLAGS", "$DRV_CFLAGS -Wall -Wextra -Wno-unused-parameter -Wstrict-prototypes"}, {"^(?!.*win32)", "DRV_LDFLAGS", "$DRV_LDFLAGS -lsqlite3"}, {"win32", "CFLAGS", "/O2 /Isqlite3_amalgamation /Ic_src /W4 /wd4100 /wd4204 /wd9024"}, {"win32", "LDFLAGS", "sqlite3.lib"}]}. {cover_enabled, true}. {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}. erlang-sqlite3-1.1.4~dfsg0/LICENSE0000644000175000017500000000020512560407355017060 0ustar debalancedebalanceThis software is released under the Erlang Public License Version 1.1 which is available from the Erlang/OTP R12-B2 distribution. erlang-sqlite3-1.1.4~dfsg0/include/0000755000175000017500000000000012560407355017501 5ustar debalancedebalanceerlang-sqlite3-1.1.4~dfsg0/include/sqlite3.hrl0000644000175000017500000000353712560407355021604 0ustar debalancedebalance-ifdef(DEBUG). -include_lib("eunit/include/eunit.hrl"). %% for debugging macros -define(dbg(Message), ?debugMsg(Message)). -define(dbgF(Format, Data), ?debugFmt(Format, Data)). -define(dbgVal(Expr), ?debugVal(Expr)). -define(dbgTime(Text, Expr), ?debugTime(Text, Expr)). -else. -define(dbg(_Message), ok). -define(dbgF(_Format, _Data), ok). -define(dbgVal(Expr), Expr). -define(dbgTime(_Text, Expr), Expr). %% -ifdef(TEST). %% -include_lib("eunit/include/eunit.hrl"). %% for debugging macros %% -else. %% -define(debugMsg(_Message), ok). %% -define(debugFmt(_Format, _Data), ok). %% -define(debugVal(Expr), Expr). %% -define(debugTime(_Text, Expr), Expr). %% -endif. -endif. -define(NULL_ATOM, null). -type sql_id() :: atom() | binary() | string(). -type table_id() :: sql_id(). -type column_id() :: sql_id(). -type sql_value() :: number() | ?NULL_ATOM | iodata() | {blob, binary()}. -type sql_type() :: integer | text | double | real | blob | string(). -type pk_constraint() :: autoincrement | desc | asc. -type pk_constraints() :: pk_constraint() | [pk_constraint()]. -type column_constraint() :: non_null | primary_key | {primary_key, pk_constraints()} | unique | {default, sql_value()}. -type column_constraints() :: column_constraint() | [column_constraint()]. -type table_constraint() :: {primary_key, [atom()]} | {unique, [atom()]}. -type table_constraints() :: table_constraint() | [table_constraint()]. -type table_info() :: [{column_id(), sql_type()} | {column_id(), sql_type(), column_constraints()}]. -type sqlite_error() :: {error, integer(), string()} | {error, term()}. -type sql_params() :: [sql_value() | {atom() | string() | integer(), sql_value()}]. -type sql_non_query_result() :: ok | sqlite_error() | {rowid, integer()}. -type sql_result() :: sql_non_query_result() | [{columns, [column_id()]} | {rows, [tuple()]} | sqlite_error()]. erlang-sqlite3-1.1.4~dfsg0/test.erl0000755000175000017500000000257212560407355017552 0ustar debalancedebalance#!/usr/bin/env escript %%! -smp enable -pa ebin -sname testsqlite3 -record(user, {name, age, wage}). test() -> file:delete("ct.db"), sqlite3:open(ct), sqlite3:create_table(ct, user, [{id, integer, [primary_key]}, {name, text}, {age, integer}, {wage, integer}]), [user] = sqlite3:list_tables(ct), [{id, integer, [primary_key]}, {name, text}, {age, integer}, {wage, integer}] = sqlite3:table_info(ct, user), {rowid, Id1} = sqlite3:write(ct, user, [{name, "abby"}, {age, 20}, {wage, 2000}]), Id1 = 1, {rowid, Id2} = sqlite3:write(ct, user, [{name, "marge"}, {age, 30}, {wage, 2000}]), Id2 = 2, [{columns, Columns}, {rows, Rows1}] = sqlite3:sql_exec(ct, "select * from user;"), Columns = ["id", "name", "age", "wage"], Rows1 = [{1, <<"abby">>, 20, 2000}, {2, <<"marge">>, 30, 2000}], [{columns, Columns}, {rows, Rows2}] = sqlite3:read(ct, user, {name, "abby"}), Rows2 = [{1, <<"abby">>, 20, 2000}], [{columns, Columns}, {rows, Rows1}] = sqlite3:read(ct, user, {wage, 2000}), sqlite3:delete(ct, user, {name, "marge"}), [{columns, Columns}, {rows, Rows2}] = sqlite3:sql_exec(ct, "select * from user;"), sqlite3:drop_table(ct, user), sqlite3:close(ct), io:format("Tests passed~n"). main(_) -> try test() of _ -> ok catch Class:Error -> io:format("~p:~p:~p~n", [Class, Error, erlang:get_stacktrace()]) end. erlang-sqlite3-1.1.4~dfsg0/README.md0000644000175000017500000000452212560407355017340 0ustar debalancedebalance# Erlang wrapper for SQLite3 This library allows you to work with SQLite3 databases from Erlang. It is compatible with Windows and Linux, and should probably work on other OSes as well. [![Build Status](https://travis-ci.org/alexeyr/erlang-sqlite3.svg?branch=master)](https://travis-ci.org/alexeyr/erlang-sqlite3) See also [esqlite](https://github.com/mmzeeman/esqlite) for an alternative library. ## Requirements Erlang/OTP R14B or later is required (tested up to 17.3 at this writing), and SQLite 3 minimum version is 3.6.1. ## Compiling ### Linux 1. Install SQLite3 by running `sudo apt-get install sqlite3` or the equivalent for your package manager, or by [compiling from the source](http://source.online.free.fr/Linux_HowToCompileSQLite.html). 2. `make`. ### Cross-compiling If you want to use erlang-sqlite3 on an embedded device, it can be cross-compiled. 1. Cross-compile [SQLite3](http://www.sqlite.org/cvstrac/wiki?p=HowToCompile) and [Erlang](http://www.erlang.org/doc/installation_guide/INSTALL-CROSS.html). 2. Change variables and paths in `rebar.cross_compile.config.sample` to the desired values and rename it to `rebar.cross_compile.config`. 3. `make cross_compile`. ### Windows with MS Visual C++ To build both SQLite3 and sqlite3-erlang: 1. If MSVC tools (`cl`, `link`, etc.) are not in the path, run `vcvars32.bat` or `vcvars64.bat` depending on whether you use 32-bit or 64-bit Erlang. `build_port_win32.bat` and `build_port_win64.bat` have the standard paths for VC10.0. 2. `nmake`. Alternately, you can use prebuilt versions of `sqlite3.dll` and `sqlite3.def`. To make `sqlite3.lib`, use `lib /def:sqlite3.def`. Then remove `sqlite3.dll` and `sqlite3.lib` targets from `Makefile` and do as above. ### Potential compilation problems * If SQLite was built with `SQLITE_OMIT_LOAD_EXTENSION` option, you'll need to undefine `ERLANG_SQLITE3_LOAD_EXTENSION` macro in . ## Running the test suite ### Linux `make test` ### Windows 1. `nmake tests` 2. If you get the error `"Error loading sqlite3_drv: The specified module could not be found"`, this is because `sqlite3.dll` isn't in the search path. ## Example usage See tests `test/sqlite3_test.erl` for a starting point. On Windows note that `sqlite3.dll` must be in your application's working directory or somewhere in the DLL search path. ## Authors See ./AUTHORS erlang-sqlite3-1.1.4~dfsg0/Makefile0000644000175000017500000000202512560407355017515 0ustar debalancedebalanceREBAR=rebar REBAR_COMPILE=$(REBAR) get-deps compile PLT=dialyzer\sqlite3.plt all: compile compile: sqlite3.dll sqlite3.lib $(REBAR_COMPILE) debug: sqlite3.dll sqlite3.lib $(REBAR_COMPILE) -C rebar.debug.config tests: compile sqlite3.dll cp sqlite3.dll priv rebar skip-deps=true eunit clean: if exist deps del /Q deps if exist ebin del /Q ebin if exist priv del /Q priv if exist doc\* del /Q doc\* if exist .eunit del /Q .eunit if exist c_src\*.o del /Q c_src\*.o if exist dialyzer del /Q dialyzer if exist sqlite3.* del /Q sqlite3.* docs: compile rebar doc static: compile @if not exist $(PLT) \ (mkdir dialyzer & dialyzer --build_plt --apps kernel stdlib erts --output_plt $(PLT)); \ else \ (dialyzer --plt $(PLT) -r ebin) cross_compile: clean $(REBAR_COMPILE) -C rebar.cross_compile.config sqlite3.dll: sqlite3_amalgamation\sqlite3.c sqlite3_amalgamation\sqlite3.h cl /O2 sqlite3_amalgamation\sqlite3.c /Isqlite3_amalgamation /link /dll /out:sqlite3.dll sqlite3.lib: sqlite3.dll lib /out:sqlite3.lib sqlite3.objerlang-sqlite3-1.1.4~dfsg0/src/0000755000175000017500000000000012560407355016645 5ustar debalancedebalanceerlang-sqlite3-1.1.4~dfsg0/src/sqlite3_lib.erl0000644000175000017500000004420412560407355021567 0ustar debalancedebalance%%%------------------------------------------------------------------- %%% File : sqlite3_lib.erl %%% @author Tee Teoh %%% @copyright 21 Jun 2008 by Tee Teoh %%% @version 1.0.0 %%% @doc Library module for sqlite3 %%% %%% @type table_id() = atom() | binary() | string(). %%% @end %%%------------------------------------------------------------------- -module(sqlite3_lib). -include("sqlite3.hrl"). %% API -export([col_type_to_atom/1]). -export([value_to_sql/1, value_to_sql_unsafe/1, sql_to_value/1, escape/1, bin_to_hex/1]). -export([write_value_sql/1, write_col_sql/1]). -export([create_table_sql/2, create_table_sql/3, drop_table_sql/1]). -export([add_columns_sql/2]). -export([write_sql/2, update_sql/4, update_set_sql/1, delete_sql/3]). -export([read_sql/1, read_sql/2, read_sql/3, read_sql/4, read_cols_sql/1]). %%==================================================================== %% API %%==================================================================== %%-------------------------------------------------------------------- %% @doc Maps sqlite3 column type. %% @end %%-------------------------------------------------------------------- -spec col_type_to_string(sql_type()) -> string(). col_type_to_string(integer) -> "INTEGER"; col_type_to_string(text) -> "TEXT"; col_type_to_string(double) -> "REAL"; col_type_to_string(real) -> "REAL"; col_type_to_string(blob) -> "BLOB"; col_type_to_string(Atom) when is_atom(Atom) -> string:to_upper(atom_to_list(Atom)); col_type_to_string(String) when is_list(String) -> String. %%-------------------------------------------------------------------- %% @doc Maps sqlite3 column type. %% @end %%-------------------------------------------------------------------- -spec col_type_to_atom(string()) -> atom(). col_type_to_atom("INTEGER") -> integer; col_type_to_atom("TEXT") -> text; col_type_to_atom("REAL") -> double; col_type_to_atom("BLOB") -> blob; col_type_to_atom(String) -> list_to_atom(string:to_lower(String)). %%-------------------------------------------------------------------- %% @doc %% Converts an Erlang term to an SQL string. %% Currently supports integers, floats, 'null' atom, and iodata %% (binaries and iolists) which are treated as SQL strings. %% %% Note that it opens opportunity for injection if an iolist includes %% single quotes! Replace all single quotes (') with '' manually, or %% use value_to_sql/1 if you are not sure if your strings contain %% single quotes (e.g. can be entered by users). %% @end %%-------------------------------------------------------------------- -spec value_to_sql_unsafe(sql_value()) -> iolist(). value_to_sql_unsafe(X) -> case X of _ when is_integer(X) -> integer_to_list(X); _ when is_float(X) -> float_to_list(X); true -> "1"; false -> "0"; undefined -> "NULL"; ?NULL_ATOM -> "NULL"; {blob, Blob} -> ["x'", bin_to_hex(Blob), $']; _ -> [$', unicode:characters_to_binary(X), $'] %% assumes no $' inside strings! end. %%-------------------------------------------------------------------- %% @doc %% Converts an Erlang term to an SQL string. %% Currently supports integers, floats, 'null' atom, and iodata %% (binaries and iolists) which are treated as SQL strings. %% %% All single quotes (') will be replaced with ''. %% @end %%-------------------------------------------------------------------- -spec value_to_sql(sql_value()) -> iolist(). value_to_sql(X) -> case X of _ when is_integer(X) -> integer_to_list(X); _ when is_float(X) -> float_to_list(X); true -> "1"; false -> "0"; undefined -> "NULL"; ?NULL_ATOM -> "NULL"; {blob, Blob} -> ["x'", bin_to_hex(Blob), $']; _ -> [$', unicode:characters_to_binary(escape(X)), $'] end. %%-------------------------------------------------------------------- %% @doc %% Converts an SQL value to an Erlang term. %% @end %%-------------------------------------------------------------------- sql_to_value(String) -> case String of "NULL" -> null; "CURRENT_TIME" -> current_time; "CURRENT_DATE" -> current_date; "CURRENT_TIMESTAMP" -> current_timestamp; [FirstChar | Tail] -> case FirstChar of $' -> sql_string(Tail); $x -> sql_blob(Tail); $X -> sql_blob(Tail); $+ -> sql_number(Tail); $- -> -sql_number(Tail); Digit when $0 =< Digit, Digit =< $9 -> sql_number(Tail) end end. %%-------------------------------------------------------------------- %% @doc %% Creates the values portion of the sql stmt. %% @end %%-------------------------------------------------------------------- -spec write_value_sql(sql_value()) -> iolist(). write_value_sql(Values) -> map_intersperse(fun value_to_sql/1, Values, ", "). %%-------------------------------------------------------------------- %% @doc Creates the column/data stmt for SQL. %% @end %%-------------------------------------------------------------------- -spec write_col_sql([column_id()]) -> iolist(). write_col_sql(Cols) -> map_intersperse(fun to_iolist/1, Cols, ", "). %%-------------------------------------------------------------------- %% @doc Returns copy of IoData with all ' replaced by '' %% @end %%-------------------------------------------------------------------- -spec escape(iodata()) -> iodata(). escape(IoData) -> re:replace(IoData, "'", "''", [global, unicode]). %%-------------------------------------------------------------------- %% @doc Converts a plain binary to its hexadecimal encoding, to be %% passed as a blob literal. %% @end %%-------------------------------------------------------------------- -spec bin_to_hex(iodata()) -> binary(). bin_to_hex(Binary) -> << <<(half_byte_to_hex(X)):8>> || <> <= Binary>>. %%-------------------------------------------------------------------- %% @doc %% Creates update set stmt. %% Currently supports integer, double/float and strings. %% @end %%-------------------------------------------------------------------- -spec update_set_sql([{atom(), sql_value()}]) -> iolist(). update_set_sql(Data) -> ColValueToSqlFun = fun({Col, Value}) -> [atom_to_list(Col), " = ", value_to_sql(Value)] end, map_intersperse(ColValueToSqlFun, Data, ", "). %%-------------------------------------------------------------------- %% @doc %% Creates list of columns for select stmt. %% @end %%-------------------------------------------------------------------- -spec read_cols_sql([column_id()]) -> iolist(). read_cols_sql(Columns) -> map_intersperse(fun to_iolist/1, Columns, ", "). %%-------------------------------------------------------------------- %% @doc Generates a table create stmt in SQL. %% @end %%-------------------------------------------------------------------- -spec create_table_sql(table_id(), table_info()) -> iolist(). create_table_sql(Tbl, Columns) -> {Type, TName} = encode_table_id(Tbl), ["CREATE TABLE ", TName, " (", map_intersperse(fun column_sql_for_create_table/1, Columns, ", "), ", CHECK ('", Type, "'='", Type, "'));"]. %%-------------------------------------------------------------------- %% @doc Generates a table create stmt in SQL. %% @end %%-------------------------------------------------------------------- -spec create_table_sql(table_id(), table_info(), table_constraints()) -> iolist(). create_table_sql(Tbl, Columns, TblConstraints) -> {Type, TName} = encode_table_id(Tbl), ["CREATE TABLE ", TName, " (", map_intersperse(fun column_sql_for_create_table/1, Columns, ", "), ", ", table_constraint_sql(TblConstraints), ", CHECK ('", Type, "'='", Type, "'));"]. %%-------------------------------------------------------------------- %% @doc Generates a alter table stmt in SQL. %% @end %%-------------------------------------------------------------------- -spec add_columns_sql(table_id(),table_info()) -> iolist(). add_columns_sql(Tbl, Columns) -> {_, TName} = encode_table_id(Tbl), ["ALTER TABLE ", TName, " ADD COLUMN ", map_intersperse(fun column_sql_for_create_table/1, Columns, ", "),";"]. %%-------------------------------------------------------------------- %% @doc %% Using Key as the column name and Data as list of column names %% and values pairs it creates the proper update SQL stmt for the %% record with matching Value. %% @end %%-------------------------------------------------------------------- -spec update_sql(table_id(), column_id(), sql_value(), [{column_id(), sql_value()}]) -> iolist(). update_sql(Tbl, Key, Value, Data) -> {_, TName} = encode_table_id(Tbl), ["UPDATE ", TName, " SET ", update_set_sql(Data), " WHERE ", to_iolist(Key), " = ", value_to_sql(Value), ";"]. %%-------------------------------------------------------------------- %% @doc Taking Data as list of column names and values pairs it creates the %% proper insertion SQL stmt. %% @end %%-------------------------------------------------------------------- -spec write_sql(table_id(), [{column_id(), sql_value()}]) -> iolist(). write_sql(Tbl, Data) -> {Cols, Values} = lists:unzip(Data), ["INSERT INTO ", to_iolist(Tbl), " (", write_col_sql(Cols), ") values (", write_value_sql(Values), ");"]. %%-------------------------------------------------------------------- %% @doc Returns all records from table Tbl. %% @end %%-------------------------------------------------------------------- -spec read_sql(table_id()) -> iolist(). read_sql(Tbl) -> ["SELECT * FROM ", to_iolist(Tbl), ";"]. %%-------------------------------------------------------------------- %% @doc %% Returns only specified Columns of all records from table Tbl. %% @end %%-------------------------------------------------------------------- -spec read_sql(table_id(), [column_id()]) -> iolist(). read_sql(Tbl, Columns) -> ["SELECT ", read_cols_sql(Columns), " FROM ", to_iolist(Tbl), ";"]. %%-------------------------------------------------------------------- %% @doc Using Key as the column name searches for the record with %% matching Value. %% @end %%-------------------------------------------------------------------- -spec read_sql(table_id(), column_id(), sql_value()) -> iolist(). read_sql(Tbl, Key, Value) -> ["SELECT * FROM ", to_iolist(Tbl), " WHERE ", to_iolist(Key), " = ", value_to_sql(Value), ";"]. %%-------------------------------------------------------------------- %% @doc %% Using Key as the column name searches for the record with %% matching Value and returns only specified Columns. %% @end %%-------------------------------------------------------------------- -spec read_sql(table_id(), column_id(), sql_value(), [column_id()]) -> iolist(). read_sql(Tbl, Key, Value, Columns) -> ["SELECT ", read_cols_sql(Columns), " FROM ", to_iolist(Tbl), " WHERE ", to_iolist(Key), " = ", value_to_sql(Value), ";"]. %%-------------------------------------------------------------------- %% @doc Using Key as the column name searches for the record with %% matching Value then deletes that record. %% @end %%-------------------------------------------------------------------- -spec delete_sql(table_id(), column_id(), sql_value()) -> iolist(). delete_sql(Tbl, Key, Value) -> ["DELETE FROM ", to_iolist(Tbl), " WHERE ", to_iolist(Key), " = ", value_to_sql(Value), ";"]. %%-------------------------------------------------------------------- %% @doc Drop the table Tbl from the database %% @end %%-------------------------------------------------------------------- -spec drop_table_sql(table_id()) -> iolist(). drop_table_sql(Tbl) -> ["DROP TABLE ", to_iolist(Tbl), ";"]. %%==================================================================== %% Internal functions %%==================================================================== %% @doc Works like string:join for iolists -spec map_intersperse(fun((X) -> iolist()), [X], iolist()) -> iolist(). map_intersperse(_Fun, [], _Sep) -> []; map_intersperse(Fun, [Elem], _Sep) -> [Fun(Elem)]; map_intersperse(Fun, [Head | Tail], Sep) -> [Fun(Head), Sep | map_intersperse(Fun, Tail, Sep)]. half_byte_to_hex(X) when X < 10 -> $0 + X; half_byte_to_hex(X) -> $a + X - 10. -spec sql_number(string()) -> number() | {error, not_a_number}. sql_number(NumberStr) -> case string:to_integer(NumberStr) of {Int, []} -> Int; _ -> case string:to_float(NumberStr) of {Float, []} -> Float; _ -> {error, not_a_number} end end. -spec sql_string(string()) -> binary(). sql_string(StringWithEscapedQuotes) -> Res1 = re:replace(StringWithEscapedQuotes, "''", "'", [global, {return, binary}, unicode]), erlang:binary_part(Res1, 0, byte_size(Res1) - 1). -spec sql_blob(string()) -> binary(). sql_blob([$' | Tail]) -> hex_str_to_bin(Tail, <<>>). hex_str_to_bin("'", Acc) -> Acc; %% single quote at the end of blob literal hex_str_to_bin([X, Y | Tail], Acc) -> hex_str_to_bin(Tail, <>). column_sql_for_create_table({Name, Type}) -> [atom_to_list(Name), " ", col_type_to_string(Type)]; column_sql_for_create_table({Name, Type, Constraints}) -> [atom_to_list(Name), " ", col_type_to_string(Type), " ", constraint_sql(Constraints)]. -spec pk_constraint_sql(pk_constraints()) -> iolist(). pk_constraint_sql(Constraint) -> case Constraint of desc -> "DESC"; asc -> "ASC"; autoincrement -> "AUTOINCREMENT"; _ when is_list(Constraint) -> map_intersperse(fun pk_constraint_sql/1, Constraint, " ") end. -spec constraint_sql(column_constraints()) -> iolist(). constraint_sql(Constraint) -> case Constraint of primary_key -> "PRIMARY KEY"; {primary_key, C} -> ["PRIMARY KEY ", pk_constraint_sql(C)]; unique -> "UNIQUE"; not_null -> "NOT NULL"; {default, DefaultValue} -> ["DEFAULT ", value_to_sql(DefaultValue)]; {raw, S} when is_list(S) -> S; _ when is_list(Constraint) -> map_intersperse(fun constraint_sql/1, Constraint, " ") end. -spec table_constraint_sql(table_constraints()) -> iolist(). table_constraint_sql(TableConstraint) -> case TableConstraint of {primary_key, Columns} -> ["PRIMARY KEY(", map_intersperse(fun indexed_column_sql/1, Columns, ", "), ")"]; {unique, Columns} -> ["UNIQUE(", map_intersperse(fun indexed_column_sql/1, Columns, ", "), ")"]; %% TODO: foreign key {raw, S} when is_list(S) -> S; _ when is_list(TableConstraint) -> map_intersperse(fun table_constraint_sql/1, TableConstraint, ", ") end. indexed_column_sql({ColumnName, asc}) -> [atom_to_list(ColumnName), " ASC"]; indexed_column_sql({ColumnName, desc}) -> [atom_to_list(ColumnName), " DESC"]; indexed_column_sql(ColumnName) -> atom_to_list(ColumnName). encode_table_id(A) when is_atom(A) -> {"am", atom_to_list(A)}; encode_table_id(B) when is_binary(B) -> {"bin", B}; encode_table_id(L) when is_list(L) -> {"lst", L}. to_iolist(A) when is_atom(A) -> atom_to_list(A); to_iolist(L) when is_list(L) -> L; to_iolist(B) when is_binary(B) -> B. %%-------------------------------------------------------------------- %% @type sql_value() = number() | 'null' | iodata(). %% %% Values accepted in SQL statements include numbers, atom 'null', %% and io:iolist(). %% @end %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- %% Tests %%-------------------------------------------------------------------- -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -define(assertFlat(Expected, Value), ?assertEqual(iolist_to_binary(Expected), iolist_to_binary(Value))). quote_test() -> ?assertFlat("'abc'", value_to_sql("abc")), ?assertFlat("'a''b''''c'", value_to_sql("a'b''c")). number_test() -> ?assertEqual(sql_number(""), {error, not_a_number}), ?assertEqual(sql_number("a"), {error, not_a_number}), ?assertEqual(sql_number(" 1"), {error, not_a_number}), ?assertEqual(sql_number("1 "), {error, not_a_number}), ?assertEqual(sql_number(" 1 "), {error, not_a_number}), ?assertEqual(sql_number("0"), 0), ?assertEqual(sql_number("0.0"), 0.0), ?assertEqual(sql_number("-0.0"), 0.0), ?assertEqual(sql_number("+0.0"), 0.0). create_table_sql_test() -> ?assertFlat( "CREATE TABLE user (id INTEGER PRIMARY KEY, name TEXT, CHECK ('am'='am'));", create_table_sql(user, [{id, integer, [primary_key]}, {name, text}])), ?assertFlat( "CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, CHECK ('bin'='bin'));", create_table_sql(<<"user">>, [{id, integer, [{primary_key, autoincrement}]}, {name, text}])), ?assertFlat( "CREATE TABLE user (id INTEGER PRIMARY KEY DESC, name TEXT, CHECK ('lst'='lst'));", create_table_sql("user", [{id, integer, [{primary_key, desc}]}, {name, text}])), ?assertFlat( "CREATE TABLE user (id INTEGER, name TEXT, PRIMARY KEY(id), CHECK ('am'='am'));", create_table_sql(user, [{id, integer}, {name, text}], [{primary_key, [id]}])). update_sql_test() -> ?assertFlat( "UPDATE user SET name = 'a' WHERE id = 1;", update_sql(user, id, 1, [{name, "a"}])). write_sql_test() -> ?assertFlat( "INSERT INTO user (id, name) values (1, 'a');", write_sql(user, [{id, 1}, {name, "a"}])). read_sql_test() -> ?assertFlat( "SELECT * FROM user;", read_sql(user)), ?assertFlat( "SELECT id, name FROM user;", read_sql(user, [id, name])), ?assertFlat( "SELECT * FROM user WHERE id = 1;", read_sql(user, id, 1)), ?assertFlat( "SELECT id, name FROM user WHERE id = 1;", read_sql(user, <<"id">>, 1, [id, "name"])). delete_sql_test() -> ?assertFlat( "DELETE FROM user WHERE id = 1;", delete_sql(user, "id", 1)). drop_table_sql_test() -> ?assertFlat( "DROP TABLE user;", drop_table_sql(user)). -endif. erlang-sqlite3-1.1.4~dfsg0/src/sqlite3.app.src0000644000175000017500000000025412560407355021522 0ustar debalancedebalance{application, sqlite3, [ {description, "SQLite3 Interface"}, {vsn, "1.1.4"}, {modules, []}, {registered, []}, {applications, [kernel, stdlib]}, {env, []} ]}. erlang-sqlite3-1.1.4~dfsg0/src/sqlite3.erl0000644000175000017500000014431112560407355020741 0ustar debalancedebalance%%%------------------------------------------------------------------- %%% File : sqlite3.erl %%% @author Tee Teoh %%% @copyright 21 Jun 2008 by Tee Teoh %%% @version 1.0.0 %%% @doc Library module for sqlite3 %%% %%% @type table_id() = atom() | binary() | string() %%% @end %%%------------------------------------------------------------------- -module(sqlite3). -include("sqlite3.hrl"). -export_types([sql_value/0, sql_type/0, table_info/0, sqlite_error/0, sql_params/0, sql_non_query_result/0, sql_result/0]). -behaviour(gen_server). %% API -export([open/1, open/2]). -export([start_link/1, start_link/2]). -export([stop/0, close/1, close_timeout/2]). -export([enable_load_extension/2]). -export([sql_exec/1, sql_exec/2, sql_exec_timeout/3, sql_exec_script/2, sql_exec_script_timeout/3, sql_exec/3, sql_exec_timeout/4]). -export([prepare/2, bind/3, next/2, reset/2, clear_bindings/2, finalize/2, columns/2, prepare_timeout/3, bind_timeout/4, next_timeout/3, reset_timeout/3, clear_bindings_timeout/3, finalize_timeout/3, columns_timeout/3]). -export([create_table/2, create_table/3, create_table/4, create_table_timeout/4, create_table_timeout/5]). -export([add_columns/2, add_columns/3]). -export([list_tables/0, list_tables/1, list_tables_timeout/2, table_info/1, table_info/2, table_info_timeout/3]). -export([write/2, write/3, write_timeout/4, write_many/2, write_many/3, write_many_timeout/4]). -export([update/3, update/4, update_timeout/5]). -export([read_all/2, read_all/3, read_all_timeout/3, read_all_timeout/4, read/2, read/3, read/4, read_timeout/4, read_timeout/5]). -export([delete/2, delete/3, delete_timeout/4]). -export([drop_table/1, drop_table/2, drop_table_timeout/3]). -export([vacuum/0, vacuum/1, vacuum_timeout/2]). %% -export([create_function/3]). -export([value_to_sql/1, value_to_sql_unsafe/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define('DRIVER_NAME', 'sqlite3_drv'). -record(state, {port, ops = [], refs = dict:new()}). %%==================================================================== %% API %%==================================================================== %%-------------------------------------------------------------------- %% @doc %% Opens the sqlite3 database in file DbName.db in the working directory %% (creating this file if necessary). This is the same as open/1. %% @end %%-------------------------------------------------------------------- -type option() :: {file, string()} | temporary | in_memory. -type result() :: {'ok', pid()} | 'ignore' | {'error', any()}. -type db() :: atom() | pid(). -spec start_link(atom()) -> result(). start_link(DbName) -> open(DbName, []). %%-------------------------------------------------------------------- %% @doc %% Opens a sqlite3 database creating one if necessary. By default the %% database will be called DbName.db in the current path. This can be changed %% by passing the option {file, DbFile :: String()}. DbFile must be the %% full path to the sqlite3 db file. start_link/1 can be use with stop/0, %% sql_exec/1, create_table/2, list_tables/0, table_info/1, write/2, %% read/2, delete/2 and drop_table/1. This is the same as open/2. %% @end %%-------------------------------------------------------------------- -spec start_link(atom(), [option()]) -> result(). start_link(DbName, Options) -> open(DbName, Options). %%-------------------------------------------------------------------- %% @doc %% Opens the sqlite3 database in file DbName.db in the working directory %% (creating this file if necessary). %% @end %%-------------------------------------------------------------------- -spec open(atom()) -> result(). open(DbName) -> open(DbName, []). %%-------------------------------------------------------------------- %% @spec open(DbName :: atom(), Options :: [option()]) -> {ok, Pid :: pid()} | ignore | {error, Error} %% @type option() = {file, DbFile :: string()} | in_memory | temporary %% %% @doc %% Opens a sqlite3 database creating one if necessary. By default the database %% will be called DbName.db in the current path (unless Db is 'anonymous', see below). %% This can be changed by passing the option {file, DbFile :: string()}. DbFile %% must be the full path to the sqlite3 db file. Can be used to open multiple sqlite3 %% databases per node. Must be use in conjunction with stop/1, sql_exec/2, %% create_table/3, list_tables/1, table_info/2, write/3, read/3, delete/3 %% and drop_table/2. If the name is an atom other than 'anonymous', it's used for %% registering the gen_server and must be unique. If the name is 'anonymous', %% the process isn't registered. %% @end %%-------------------------------------------------------------------- -spec open(atom(), [option()]) -> result(). open(DbName, Options) -> IsAnonymous = DbName =:= anonymous, Opts = case proplists:lookup(file, Options) of none -> DbFile = case proplists:is_defined(in_memory, Options) of true -> ":memory:"; false -> case IsAnonymous orelse proplists:is_defined(temporary, Options) of true -> ""; false -> "./" ++ atom_to_list(DbName) ++ ".db" end end, [{file, DbFile} | Options]; {file, _} -> Options end, if IsAnonymous -> gen_server:start_link(?MODULE, Opts, []); true -> gen_server:start_link({local, DbName}, ?MODULE, Opts, []) end. %%-------------------------------------------------------------------- %% @doc %% Closes the Db sqlite3 database. %% @end %%-------------------------------------------------------------------- -spec close(db()) -> 'ok'. close(Db) -> gen_server:call(Db, close). %%-------------------------------------------------------------------- %% @doc %% Closes the Db sqlite3 database. %% @end %%-------------------------------------------------------------------- -spec close_timeout(db(), timeout()) -> 'ok'. close_timeout(Db, Timeout) -> gen_server:call(Db, close, Timeout). %%-------------------------------------------------------------------- %% @doc %% Closes the sqlite3 database. %% @end %%-------------------------------------------------------------------- -spec stop() -> 'ok'. stop() -> close(?MODULE). enable_load_extension(Db, Value) -> gen_server:call(Db, {enable_load_extension, Value}). %%-------------------------------------------------------------------- %% @doc %% Executes the Sql statement directly. %% @end %%-------------------------------------------------------------------- -spec sql_exec(iodata()) -> sql_result(). sql_exec(SQL) -> sql_exec(?MODULE, SQL). %%-------------------------------------------------------------------- %% @doc %% Executes the Sql statement directly on the Db database. Returns the %% result of the Sql call. %% @end %%-------------------------------------------------------------------- -spec sql_exec(db(), iodata()) -> sql_result(). sql_exec(Db, SQL) -> gen_server:call(Db, {sql_exec, SQL}). %%-------------------------------------------------------------------- %% @doc %% Executes the Sql statement with parameters Params directly on the Db %% database. Returns the result of the Sql call. %% @end %%-------------------------------------------------------------------- -spec sql_exec(db(), iodata(), [sql_value() | {atom() | string() | integer(), sql_value()}]) -> sql_result(). sql_exec(Db, SQL, Params) -> gen_server:call(Db, {sql_bind_and_exec, SQL, Params}). %%-------------------------------------------------------------------- %% @doc %% Executes the Sql statement directly on the Db database. Returns the %% result of the Sql call. %% @end %%-------------------------------------------------------------------- -spec sql_exec_timeout(db(), iodata(), timeout()) -> sql_result(). sql_exec_timeout(Db, SQL, Timeout) -> gen_server:call(Db, {sql_exec, SQL}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Executes the Sql statement with parameters Params directly on the Db %% database. Returns the result of the Sql call. %% @end %%-------------------------------------------------------------------- -spec sql_exec_timeout(db(), iodata(), [sql_value() | {atom() | string() | integer(), sql_value()}], timeout()) -> sql_result(). sql_exec_timeout(Db, SQL, Params, Timeout) -> gen_server:call(Db, {sql_bind_and_exec, SQL, Params}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Executes the Sql script (consisting of semicolon-separated statements) %% directly on the Db database. %% %% If an error happens while executing a statement, no further statements are executed. %% %% The return value is the list of results of all executed statements. %% @end %%-------------------------------------------------------------------- -spec sql_exec_script(db(), iodata()) -> [sql_result()]. sql_exec_script(Db, SQL) -> gen_server:call(Db, {sql_exec_script, SQL}). %%-------------------------------------------------------------------- %% @doc %% Executes the Sql script (consisting of semicolon-separated statements) %% directly on the Db database. %% %% If an error happens while executing a statement, no further statements are executed. %% %% The return value is the list of results of all executed statements. %% @end %%-------------------------------------------------------------------- -spec sql_exec_script_timeout(db(), iodata(), timeout()) -> [sql_result()]. sql_exec_script_timeout(Db, SQL, Timeout) -> gen_server:call(Db, {sql_exec_script, SQL}, Timeout). -spec prepare(db(), iodata()) -> {ok, reference()} | sqlite_error(). prepare(Db, SQL) -> gen_server:call(Db, {prepare, SQL}). -spec bind(db(), reference(), sql_params()) -> sql_non_query_result(). bind(Db, Ref, Params) -> gen_server:call(Db, {bind, Ref, Params}). -spec next(db(), reference()) -> tuple() | done | sqlite_error(). next(Db, Ref) -> gen_server:call(Db, {next, Ref}). -spec reset(db(), reference()) -> sql_non_query_result(). reset(Db, Ref) -> gen_server:call(Db, {reset, Ref}). -spec clear_bindings(db(), reference()) -> sql_non_query_result(). clear_bindings(Db, Ref) -> gen_server:call(Db, {clear_bindings, Ref}). -spec finalize(db(), reference()) -> sql_non_query_result(). finalize(Db, Ref) -> gen_server:call(Db, {finalize, Ref}). -spec columns(db(), reference()) -> sql_non_query_result(). columns(Db, Ref) -> gen_server:call(Db, {columns, Ref}). -spec prepare_timeout(db(), iodata(), timeout()) -> {ok, reference()} | sqlite_error(). prepare_timeout(Db, SQL, Timeout) -> gen_server:call(Db, {prepare, SQL}, Timeout). -spec bind_timeout(db(), reference(), sql_params(), timeout()) -> sql_non_query_result(). bind_timeout(Db, Ref, Params, Timeout) -> gen_server:call(Db, {bind, Ref, Params}, Timeout). -spec next_timeout(db(), reference(), timeout()) -> tuple() | done | sqlite_error(). next_timeout(Db, Ref, Timeout) -> gen_server:call(Db, {next, Ref}, Timeout). -spec reset_timeout(db(), reference(), timeout()) -> sql_non_query_result(). reset_timeout(Db, Ref, Timeout) -> gen_server:call(Db, {reset, Ref}, Timeout). -spec clear_bindings_timeout(db(), reference(), timeout()) -> sql_non_query_result(). clear_bindings_timeout(Db, Ref, Timeout) -> gen_server:call(Db, {clear_bindings, Ref}, Timeout). -spec finalize_timeout(db(), reference(), timeout()) -> sql_non_query_result(). finalize_timeout(Db, Ref, Timeout) -> gen_server:call(Db, {finalize, Ref}, Timeout). -spec columns_timeout(db(), reference(), timeout()) -> sql_non_query_result(). columns_timeout(Db, Ref, Timeout) -> gen_server:call(Db, {columns, Ref}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Creates the Tbl table using TblInfo as the table structure. The %% table structure is a list of {column name, column type} pairs. %% e.g. [{name, text}, {age, integer}] %% %% Returns the result of the create table call. %% @end %%-------------------------------------------------------------------- -spec create_table(table_id(), table_info()) -> sql_non_query_result(). create_table(Tbl, Columns) -> create_table(?MODULE, Tbl, Columns). %%-------------------------------------------------------------------- %% @doc %% Creates the Tbl table in Db using Columns as the table structure. %% The table structure is a list of {column name, column type} pairs. %% e.g. [{name, text}, {age, integer}] %% %% Returns the result of the create table call. %% @end %%-------------------------------------------------------------------- -spec create_table(db(), table_id(), table_info()) -> sql_non_query_result(). create_table(Db, Tbl, Columns) -> gen_server:call(Db, {create_table, Tbl, Columns}). %%-------------------------------------------------------------------- %% @doc %% Creates the Tbl table in Db using Columns as the table structure. %% The table structure is a list of {column name, column type} pairs. %% e.g. [{name, text}, {age, integer}] %% %% Returns the result of the create table call. %% @end %%-------------------------------------------------------------------- -spec create_table_timeout(db(), table_id(), table_info(), timeout()) -> sql_non_query_result(). create_table_timeout(Db, Tbl, Columns, Timeout) -> gen_server:call(Db, {create_table, Tbl, Columns}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Creates the Tbl table in Db using Columns as the table structure and %% Constraints as table constraints. %% The table structure is a list of {column name, column type} pairs. %% e.g. [{name, text}, {age, integer}] %% %% Returns the result of the create table call. %% @end %%-------------------------------------------------------------------- -spec create_table(db(), table_id(), table_info(), table_constraints()) -> sql_non_query_result(). create_table(Db, Tbl, Columns, Constraints) -> gen_server:call(Db, {create_table, Tbl, Columns, Constraints}). %%-------------------------------------------------------------------- %% @doc %% Creates the Tbl table in Db using Columns as the table structure and %% Constraints as table constraints. %% The table structure is a list of {column name, column type} pairs. %% e.g. [{name, text}, {age, integer}] %% %% Returns the result of the create table call. %% @end %%-------------------------------------------------------------------- -spec create_table_timeout(db(), table_id(), table_info(), table_constraints(), timeout()) -> sql_non_query_result(). create_table_timeout(Db, Tbl, Columns, Constraints, Timeout) -> gen_server:call(Db, {create_table, Tbl, Columns, Constraints}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Add columns to table structure %% table structure is a list of {column name, column type} pairs. %% e.g. [{name, text}, {age, integer}] %% %% Returns the result of the create table call. %% @end %%-------------------------------------------------------------------- -spec add_columns(table_id(), table_info()) -> sql_non_query_result(). add_columns(Tbl, Columns) -> add_columns(?MODULE, Tbl, Columns). %%-------------------------------------------------------------------- %% @doc %% Add columns to table structure %% The table structure is a list of {column name, column type} pairs. %% e.g. [{name, text}, {age, integer}] %% %% Returns the result of the create table call. %% @end %%-------------------------------------------------------------------- -spec add_columns(db(), table_id(), table_info()) -> sql_non_query_result(). add_columns(Db, Tbl, Columns) -> gen_server:call(Db, {add_columns, Tbl, Columns}). %%-------------------------------------------------------------------- %% @doc %% Returns a list of tables. %% @end %%-------------------------------------------------------------------- -spec list_tables() -> [table_id()]. list_tables() -> list_tables(?MODULE). %%-------------------------------------------------------------------- %% @doc %% Returns a list of tables for Db. %% @end %%-------------------------------------------------------------------- -spec list_tables(db()) -> [table_id()]. list_tables(Db) -> gen_server:call(Db, list_tables). %%-------------------------------------------------------------------- %% @doc %% Returns a list of tables for Db. %% @end %%-------------------------------------------------------------------- -spec list_tables_timeout(db(), timeout()) -> [table_id()]. list_tables_timeout(Db, Timeout) -> gen_server:call(Db, list_tables, Timeout). %%-------------------------------------------------------------------- %% @doc %% Returns table schema for Tbl. %% @end %%-------------------------------------------------------------------- -spec table_info(table_id()) -> table_info(). table_info(Tbl) -> table_info(?MODULE, Tbl). %%-------------------------------------------------------------------- %% @doc %% Returns table schema for Tbl in Db. %% @end %%-------------------------------------------------------------------- -spec table_info(db(), table_id()) -> table_info(). table_info(Db, Tbl) -> gen_server:call(Db, {table_info, Tbl}). %%-------------------------------------------------------------------- %% @doc %% Returns table schema for Tbl in Db. %% @end %%-------------------------------------------------------------------- -spec table_info_timeout(db(), table_id(), timeout()) -> table_info(). table_info_timeout(Db, Tbl, Timeout) -> gen_server:call(Db, {table_info, Tbl}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Write Data into Tbl table. Value must be of the same type as %% determined from table_info/2. %% @end %%-------------------------------------------------------------------- -spec write(table_id(), [{column_id(), sql_value()}]) -> sql_non_query_result(). write(Tbl, Data) -> write(?MODULE, Tbl, Data). %%-------------------------------------------------------------------- %% @doc %% Write Data into Tbl table in Db database. Value must be of the %% same type as determined from table_info/3. %% @end %%-------------------------------------------------------------------- -spec write(db(), table_id(), [{column_id(), sql_value()}]) -> sql_non_query_result(). write(Db, Tbl, Data) -> gen_server:call(Db, {write, Tbl, Data}). %%-------------------------------------------------------------------- %% @doc %% Write Data into Tbl table in Db database. Value must be of the %% same type as determined from table_info/3. %% @end %%-------------------------------------------------------------------- -spec write_timeout(db(), table_id(), [{column_id(), sql_value()}], timeout()) -> sql_non_query_result(). write_timeout(Db, Tbl, Data, Timeout) -> gen_server:call(Db, {write, Tbl, Data}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Write all records in Data into table Tbl. Value must be of the %% same type as determined from table_info/2. %% @end %%-------------------------------------------------------------------- -spec write_many(table_id(), [[{column_id(), sql_value()}]]) -> [sql_result()]. write_many(Tbl, Data) -> write_many(?MODULE, Tbl, Data). %%-------------------------------------------------------------------- %% @doc %% Write all records in Data into table Tbl in database Db. Value %% must be of the same type as determined from table_info/3. %% @end %%-------------------------------------------------------------------- -spec write_many(db(), table_id(), [[{column_id(), sql_value()}]]) -> [sql_result()]. write_many(Db, Tbl, Data) -> gen_server:call(Db, {write_many, Tbl, Data}). %%-------------------------------------------------------------------- %% @doc %% Write all records in Data into table Tbl in database Db. Value %% must be of the same type as determined from table_info/3. %% @end %%-------------------------------------------------------------------- -spec write_many_timeout(db(), table_id(), [[{column_id(), sql_value()}]], timeout()) -> [sql_result()]. write_many_timeout(Db, Tbl, Data, Timeout) -> gen_server:call(Db, {write_many, Tbl, Data}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Updates rows into Tbl table such that the Value matches the %% value in Key with Data. %% @end %%-------------------------------------------------------------------- -spec update(table_id(), {column_id(), sql_value()}, [{column_id(), sql_value()}]) -> sql_non_query_result(). update(Tbl, {Key, Value}, Data) -> update(?MODULE, Tbl, {Key, Value}, Data). %%-------------------------------------------------------------------- %% @doc %% Updates rows into Tbl table in Db database such that the Value %% matches the value in Key with Data. %% @end %%-------------------------------------------------------------------- -spec update(db(), table_id(), {column_id(), sql_value()}, [{column_id(), sql_value()}]) -> sql_non_query_result(). update(Db, Tbl, {Key, Value}, Data) -> gen_server:call(Db, {update, Tbl, Key, Value, Data}). %%-------------------------------------------------------------------- %% @doc %% Updates rows into Tbl table in Db database such that the Value %% matches the value in Key with Data. %% @end %%-------------------------------------------------------------------- -spec update_timeout(db(), table_id(), {column_id(), sql_value()}, [{column_id(), sql_value()}], timeout()) -> sql_non_query_result(). update_timeout(Db, Tbl, {Key, Value}, Data, Timeout) -> gen_server:call(Db, {update, Tbl, Key, Value, Data}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Reads all rows from Table in Db. %% @end %%-------------------------------------------------------------------- -spec read_all(db(), table_id()) -> sql_result(). read_all(Db, Tbl) -> gen_server:call(Db, {read, Tbl}). %%-------------------------------------------------------------------- %% @doc %% Reads all rows from Table in Db. %% @end %%-------------------------------------------------------------------- -spec read_all_timeout(db(), table_id(), timeout()) -> sql_result(). read_all_timeout(Db, Tbl, Timeout) -> gen_server:call(Db, {read, Tbl}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Reads Columns in all rows from Table in Db. %% @end %%-------------------------------------------------------------------- -spec read_all(db(), table_id(), [column_id()]) -> sql_result(). read_all(Db, Tbl, Columns) -> gen_server:call(Db, {read, Tbl, Columns}). %%-------------------------------------------------------------------- %% @doc %% Reads Columns in all rows from Table in Db. %% @end %%-------------------------------------------------------------------- -spec read_all_timeout(db(), table_id(), [column_id()], timeout()) -> sql_result(). read_all_timeout(Db, Tbl, Columns, Timeout) -> gen_server:call(Db, {read, Tbl, Columns}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Reads a row from Tbl table such that the Value matches the %% value in Column. Value must have the same type as determined %% from table_info/2. %% @end %%-------------------------------------------------------------------- -spec read(table_id(), {column_id(), sql_value()}) -> sql_result(). read(Tbl, Key) -> read(?MODULE, Tbl, Key). %%-------------------------------------------------------------------- %% @doc %% Reads a row from Tbl table in Db database such that the Value %% matches the value in Column. ColValue must have the same type %% as determined from table_info/3. %% @end %%-------------------------------------------------------------------- -spec read(db(), table_id(), {column_id(), sql_value()}) -> sql_result(). read(Db, Tbl, {Column, Value}) -> gen_server:call(Db, {read, Tbl, Column, Value}). %%-------------------------------------------------------------------- %% @doc %% Reads a row from Tbl table in Db database such that the Value %% matches the value in Column. Value must have the same type as %% determined from table_info/3. %% @end %%-------------------------------------------------------------------- -spec read(db(), table_id(), {column_id(), sql_value()}, [column_id()]) -> sql_result(). read(Db, Tbl, {Key, Value}, Columns) -> gen_server:call(Db, {read, Tbl, Key, Value, Columns}). %%-------------------------------------------------------------------- %% @doc %% Reads a row from Tbl table in Db database such that the Value %% matches the value in Column. ColValue must have the same type %% as determined from table_info/3. %% @end %%-------------------------------------------------------------------- -spec read_timeout(db(), table_id(), {column_id(), sql_value()}, timeout()) -> sql_result(). read_timeout(Db, Tbl, {Column, Value}, Timeout) -> gen_server:call(Db, {read, Tbl, Column, Value}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Reads a row from Tbl table in Db database such that the Value %% matches the value in Column. Value must have the same type as %% determined from table_info/3. %% @end %%-------------------------------------------------------------------- -spec read_timeout(db(), table_id(), {column_id(), sql_value()}, [column_id()], timeout()) -> sql_result(). read_timeout(Db, Tbl, {Key, Value}, Columns, Timeout) -> gen_server:call(Db, {read, Tbl, Key, Value, Columns}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Delete a row from Tbl table in Db database such that the Value %% matches the value in Column. %% Value must have the same type as determined from table_info/3. %% @end %%-------------------------------------------------------------------- -spec delete(table_id(), {column_id(), sql_value()}) -> sql_non_query_result(). delete(Tbl, Key) -> delete(?MODULE, Tbl, Key). %%-------------------------------------------------------------------- %% @doc %% Delete a row from Tbl table in Db database such that the Value %% matches the value in Column. %% Value must have the same type as determined from table_info/3. %% @end %%-------------------------------------------------------------------- -spec delete_timeout(db(), table_id(), {column_id(), sql_value()}, timeout()) -> sql_non_query_result(). delete_timeout(Db, Tbl, Key, Timeout) -> gen_server:call(Db, {delete, Tbl, Key}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Delete a row from Tbl table in Db database such that the Value %% matches the value in Column. %% Value must have the same type as determined from table_info/3. %% @end %%-------------------------------------------------------------------- -spec delete(db(), table_id(), {column_id(), sql_value()}) -> sql_non_query_result(). delete(Db, Tbl, Key) -> gen_server:call(Db, {delete, Tbl, Key}). %%-------------------------------------------------------------------- %% @doc %% Drop the table Tbl. %% @end %%-------------------------------------------------------------------- -spec drop_table(table_id()) -> sql_non_query_result(). drop_table(Tbl) -> drop_table(?MODULE, Tbl). %%-------------------------------------------------------------------- %% @doc %% Drop the table Tbl from Db database. %% @end %%-------------------------------------------------------------------- -spec drop_table(db(), table_id()) -> sql_non_query_result(). drop_table(Db, Tbl) -> gen_server:call(Db, {drop_table, Tbl}). %%-------------------------------------------------------------------- %% @doc %% Drop the table Tbl from Db database. %% @end %%-------------------------------------------------------------------- -spec drop_table_timeout(db(), table_id(), timeout()) -> sql_non_query_result(). drop_table_timeout(Db, Tbl, Timeout) -> gen_server:call(Db, {drop_table, Tbl}, Timeout). %%-------------------------------------------------------------------- %% @doc %% Vacuum the default database. %% @end %%-------------------------------------------------------------------- -spec vacuum() -> sql_non_query_result(). vacuum() -> gen_server:call(?MODULE, vacuum). %%-------------------------------------------------------------------- %% @doc %% Vacuum the Db database. %% @end %%-------------------------------------------------------------------- -spec vacuum(db()) -> sql_non_query_result(). vacuum(Db) -> gen_server:call(Db, vacuum). %%-------------------------------------------------------------------- %% @doc %% Vacuum the Db database. %% @end %%-------------------------------------------------------------------- -spec vacuum_timeout(db(), timeout()) -> sql_non_query_result(). vacuum_timeout(Db, Timeout) -> gen_server:call(Db, vacuum, Timeout). %% %%-------------------------------------------------------------------- %% %% @doc %% %% Creates function under name FunctionName. %% %% %% %% @end %% %%-------------------------------------------------------------------- %% -spec create_function(db(), atom(), function()) -> any(). %% create_function(Db, FunctionName, Function) -> %% gen_server:call(Db, {create_function, FunctionName, Function}). %%-------------------------------------------------------------------- %% @doc %% Converts an Erlang term to an SQL string. %% Currently supports integers, floats, 'null' atom, and iodata %% (binaries and iolists) which are treated as SQL strings. %% %% Note that it opens opportunity for injection if an iolist includes %% single quotes! Replace all single quotes (') with '' manually, or %% use value_to_sql/1 if you are not sure if your strings contain %% single quotes (e.g. can be entered by users). %% %% Reexported from sqlite3_lib:value_to_sql/1 for user convenience. %% @end %%-------------------------------------------------------------------- -spec value_to_sql_unsafe(sql_value()) -> iolist(). value_to_sql_unsafe(X) -> sqlite3_lib:value_to_sql_unsafe(X). %%-------------------------------------------------------------------- %% @doc %% Converts an Erlang term to an SQL string. %% Currently supports integers, floats, 'null' atom, and iodata %% (binaries and iolists) which are treated as SQL strings. %% %% All single quotes (') will be replaced with ''. %% %% Reexported from sqlite3_lib:value_to_sql/1 for user convenience. %% @end %%-------------------------------------------------------------------- -spec value_to_sql(sql_value()) -> iolist(). value_to_sql(X) -> sqlite3_lib:value_to_sql(X). %%==================================================================== %% gen_server callbacks %%==================================================================== %%-------------------------------------------------------------------- %% @doc Initiates the server %% @end %% @hidden %%-------------------------------------------------------------------- % -type init_return() :: {'ok', tuple()} | {'ok', tuple(), integer()} | 'ignore' | {'stop', any()}. -spec init([any()]) -> {'ok', #state{}} | {'stop', string()}. init(Options) -> DbFile = proplists:get_value(file, Options), PrivDir = get_priv_dir(), case erl_ddll:load(PrivDir, atom_to_list(?DRIVER_NAME)) of ok -> Port = open_port({spawn, create_port_cmd(DbFile)}, [binary]), {ok, #state{port = Port, ops = Options}}; {error, permanent} -> %% already loaded! Port = open_port({spawn, create_port_cmd(DbFile)}, [binary]), {ok, #state{port = Port, ops = Options}}; {error, Error} -> Msg = io_lib:format("Error loading ~p: ~s", [?DRIVER_NAME, erl_ddll:format_error(Error)]), {stop, lists:flatten(Msg)} end. %%-------------------------------------------------------------------- %% @doc Handling call messages %% @end %% @hidden %%-------------------------------------------------------------------- %% -type handle_call_return() :: {reply, any(), tuple()} | {reply, any(), tuple(), integer()} | %% {noreply, tuple()} | {noreply, tuple(), integer()} | %% {stop, any(), any(), tuple()} | {stop, any(), tuple()}. -spec handle_call(any(), pid(), #state{}) -> {'reply', any(), #state{}} | {'stop', 'normal', 'ok', #state{}}. handle_call(close, _From, State) -> Reply = ok, {stop, normal, Reply, State}; handle_call(list_tables, _From, State) -> SQL = "select name, sql from sqlite_master where type='table';", Data = do_sql_exec(SQL, State), TableList = proplists:get_value(rows, Data), TableNames = [cast_table_name(Name, SQLx) || {Name,SQLx} <- TableList], {reply, TableNames, State}; handle_call({table_info, Tbl}, _From, State) when is_atom(Tbl) -> % make sure we only get table info. SQL = io_lib:format("select sql from sqlite_master where tbl_name = '~p' and type='table';", [Tbl]), Data = do_sql_exec(SQL, State), TableSql = proplists:get_value(rows, Data), case TableSql of [{Info}] -> ColumnList = parse_table_info(binary_to_list(Info)), {reply, ColumnList, State}; [] -> {reply, table_does_not_exist, State} end; handle_call({table_info, _NotAnAtom}, _From, State) -> {reply, {error, badarg}, State}; handle_call({create_function, FunctionName, Function}, _From, #state{port = Port} = State) -> Reply = exec(Port, {create_function, FunctionName, Function}), {reply, Reply, State}; handle_call({sql_exec, SQL}, _From, State) -> do_handle_call_sql_exec(SQL, State); handle_call({sql_bind_and_exec, SQL, Params}, _From, State) -> Reply = do_sql_bind_and_exec(SQL, Params, State), {reply, Reply, State}; handle_call({sql_exec_script, SQL}, _From, State) -> Reply = do_sql_exec_script(SQL, State), {reply, Reply, State}; handle_call({add_columns, Tbl, Columns}, _From, State) -> try sqlite3_lib:add_columns_sql(Tbl, Columns) of SQL -> do_handle_call_sql_exec(SQL, State) catch _:Exception -> {reply, {error, Exception}, State} end; handle_call({create_table, Tbl, Columns}, _From, State) -> try sqlite3_lib:create_table_sql(Tbl, Columns) of SQL -> do_handle_call_sql_exec(SQL, State) catch _:Exception -> {reply, {error, Exception}, State} end; handle_call({create_table, Tbl, Columns, Constraints}, _From, State) -> try sqlite3_lib:create_table_sql(Tbl, Columns, Constraints) of SQL -> do_handle_call_sql_exec(SQL, State) catch _:Exception -> {reply, {error, Exception}, State} end; handle_call({update, Tbl, Key, Value, Data}, _From, State) -> try sqlite3_lib:update_sql(Tbl, Key, Value, Data) of SQL -> do_handle_call_sql_exec(SQL, State) catch _:Exception -> {reply, {error, Exception}, State} end; handle_call({write, Tbl, Data}, _From, State) -> % insert into t1 (data,num) values ('This is sample data',3); try sqlite3_lib:write_sql(Tbl, Data) of SQL -> do_handle_call_sql_exec(SQL, State) catch _:Exception -> {reply, {error, Exception}, State} end; handle_call({write_many, Tbl, DataList}, _From, State) -> SQLScript = ["SAVEPOINT 'erlang-sqlite3-write_many';", [sqlite3_lib:write_sql(Tbl, Data) || Data <- DataList], "RELEASE SAVEPOINT 'erlang-sqlite3-write_many';"], Reply = do_sql_exec_script(SQLScript, State), {reply, Reply, State}; handle_call({read, Tbl}, _From, State) -> % select * from Tbl where Key = Value; try sqlite3_lib:read_sql(Tbl) of SQL -> do_handle_call_sql_exec(SQL, State) catch _:Exception -> {reply, {error, Exception}, State} end; handle_call({read, Tbl, Columns}, _From, State) -> try sqlite3_lib:read_sql(Tbl, Columns) of SQL -> do_handle_call_sql_exec(SQL, State) catch _:Exception -> {reply, {error, Exception}, State} end; handle_call({read, Tbl, Key, Value}, _From, State) -> % select * from Tbl where Key = Value; try sqlite3_lib:read_sql(Tbl, Key, Value) of SQL -> do_handle_call_sql_exec(SQL, State) catch _:Exception -> {reply, {error, Exception}, State} end; handle_call({read, Tbl, Key, Value, Columns}, _From, State) -> try sqlite3_lib:read_sql(Tbl, Key, Value, Columns) of SQL -> do_handle_call_sql_exec(SQL, State) catch _:Exception -> {reply, {error, Exception}, State} end; handle_call({delete, Tbl, {Key, Value}}, _From, State) -> % delete from Tbl where Key = Value; try sqlite3_lib:delete_sql(Tbl, Key, Value) of SQL -> do_handle_call_sql_exec(SQL, State) catch _:Exception -> {reply, {error, Exception}, State} end; handle_call({drop_table, Tbl}, _From, State) -> try sqlite3_lib:drop_table_sql(Tbl) of SQL -> do_handle_call_sql_exec(SQL, State) catch _:Exception -> {reply, {error, Exception}, State} end; handle_call({prepare, SQL}, _From, State = #state{port = Port, refs = Refs}) -> case exec(Port, {prepare, SQL}) of Index when is_integer(Index) -> Ref = erlang:make_ref(), Reply = {ok, Ref}, NewState = State#state{refs = dict:store(Ref, Index, Refs)}; Error -> Reply = Error, NewState = State end, {reply, Reply, NewState}; handle_call({bind, Ref, Params}, _From, State = #state{port = Port, refs = Refs}) -> Reply = case dict:find(Ref, Refs) of {ok, Index} -> exec(Port, {bind, Index, Params}); error -> {error, badarg} end, {reply, Reply, State}; handle_call({finalize, Ref}, _From, State = #state{port = Port, refs = Refs}) -> case dict:find(Ref, Refs) of {ok, Index} -> case exec(Port, {finalize, Index}) of ok -> Reply = ok, NewState = State#state{refs = dict:erase(Ref, Refs)}; Error -> Reply = Error, NewState = State end; error -> Reply = {error, badarg}, NewState = State end, {reply, Reply, NewState}; handle_call({enable_load_extension, _Value} = Payload, _From, State = #state{port = Port, refs = _Refs}) -> Reply = exec(Port, Payload), {reply, Reply, State}; handle_call({Cmd, Ref}, _From, State = #state{port = Port, refs = Refs}) -> Reply = case dict:find(Ref, Refs) of {ok, Index} -> exec(Port, {Cmd, Index}); error -> {error, badarg} end, {reply, Reply, State}; handle_call(vacuum, _From, State) -> SQL = "VACUUM;", do_handle_call_sql_exec(SQL, State); handle_call(_Request, _From, State) -> Reply = unknown_request, {reply, Reply, State}. %%-------------------------------------------------------------------- %% @doc Handling cast messages %% @end %% @hidden %%-------------------------------------------------------------------- %% -type handle_cast_return() :: {noreply, tuple()} | {noreply, tuple(), integer()} | %% {stop, any(), tuple()}. -spec handle_cast(any(), #state{}) -> {'noreply', #state{}}. handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% @doc Handling all non call/cast messages %% @end %% @hidden %%-------------------------------------------------------------------- -spec handle_info(any(), #state{}) -> {'noreply', #state{}}. handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% @doc This function is called by a gen_server when it is about to %% terminate. It should be the opposite of Module:init/1 and do any necessary %% cleaning up. When it returns, the gen_server terminates with Reason. %% The return value is ignored. %% @end %% @hidden %%-------------------------------------------------------------------- -spec terminate(atom(), tuple()) -> term(). terminate(_Reason, #state{port = Port}) -> case Port of undefined -> pass; _ -> port_close(Port) end, case erl_ddll:unload(?DRIVER_NAME) of ok -> ok; {error, permanent} -> %% Older Erlang versions mark any driver using driver_async %% as permanent ok; {error, ErrorDesc} -> error_logger:error_msg("Error unloading sqlite3 driver: ~s~n", [erl_ddll:format_error(ErrorDesc)]) end, ok. %%-------------------------------------------------------------------- %% @doc Convert process state when code is changed %% @end %% @hidden %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- get_priv_dir() -> case code:priv_dir(sqlite3) of {error, bad_name} -> %% application isn't in path, fall back {?MODULE, _, FileName} = code:get_object_code(?MODULE), filename:join(filename:dirname(FileName), "../priv"); Dir -> Dir end. -define(SQL_EXEC_COMMAND, 2). -define(SQL_CREATE_FUNCTION, 3). -define(SQL_BIND_AND_EXEC_COMMAND, 4). -define(PREPARE, 5). -define(PREPARED_BIND, 6). -define(PREPARED_STEP, 7). -define(PREPARED_RESET, 8). -define(PREPARED_CLEAR_BINDINGS, 9). -define(PREPARED_FINALIZE, 10). -define(PREPARED_COLUMNS, 11). -define(SQL_EXEC_SCRIPT, 12). -define(ENABLE_LOAD_EXTENSION, 13). create_port_cmd(DbFile) -> atom_to_list(?DRIVER_NAME) ++ " " ++ DbFile. do_handle_call_sql_exec(SQL, State) -> Reply = do_sql_exec(SQL, State), {reply, Reply, State}. do_sql_exec(SQL, #state{port = Port}) -> ?dbgF("SQL: ~s~n", [SQL]), exec(Port, {sql_exec, SQL}). do_sql_bind_and_exec(SQL, Params, #state{port = Port}) -> ?dbgF("SQL: ~s; Parameters: ~p~n", [SQL, Params]), exec(Port, {sql_bind_and_exec, SQL, Params}). do_sql_exec_script(SQL, #state{port = Port}) -> ?dbgF("SQL: ~s~n", [SQL]), exec(Port, {sql_exec_script, SQL}). exec(_Port, {create_function, _FunctionName, _Function}) -> error_logger:error_report([{application, sqlite3}, "NOT IMPL YET"]); %port_control(Port, ?SQL_CREATE_FUNCTION, list_to_binary(Cmd)), %wait_result(Port); exec(Port, {sql_exec, SQL}) -> port_control(Port, ?SQL_EXEC_COMMAND, SQL), wait_result(Port); exec(Port, {sql_bind_and_exec, SQL, Params}) -> Bin = term_to_binary({iolist_to_binary(SQL), Params}), port_control(Port, ?SQL_BIND_AND_EXEC_COMMAND, Bin), wait_result(Port); exec(Port, {sql_exec_script, SQL}) -> port_control(Port, ?SQL_EXEC_SCRIPT, SQL), wait_result(Port); exec(Port, {prepare, SQL}) -> port_control(Port, ?PREPARE, SQL), wait_result(Port); exec(Port, {bind, Index, Params}) -> Bin = term_to_binary({Index, Params}), port_control(Port, ?PREPARED_BIND, Bin), wait_result(Port); exec(Port, {enable_load_extension, Value}) -> % Payload is 1 if enabling extension loading, % 0 if disabling Payload = case Value of true -> 1; _ when is_integer(Value) -> Value; false -> 0; _ -> 0 end, port_control(Port, ?ENABLE_LOAD_EXTENSION, <>), wait_result(Port); exec(Port, {Cmd, Index}) when is_integer(Index) -> CmdCode = case Cmd of next -> ?PREPARED_STEP; reset -> ?PREPARED_RESET; clear_bindings -> ?PREPARED_CLEAR_BINDINGS; finalize -> ?PREPARED_FINALIZE; columns -> ?PREPARED_COLUMNS end, Bin = term_to_binary(Index), port_control(Port, CmdCode, Bin), wait_result(Port). wait_result(Port) -> receive {Port, Reply} -> Reply; {'EXIT', Port, Reason} -> {error, {port_exit, Reason}}; Other when is_tuple(Other), element(1, Other) =/= '$gen_call', element(1, Other) =/= '$gen_cast' -> Other end. parse_table_info(Info) -> Info1 = re:replace(Info, <<"CHECK \\('(bin|lst|am)'='(bin|lst|am)'\\)\\)">>, "", [{return, list}]), {_, [$(|Rest]} = lists:splitwith(fun(C) -> C =/= $( end, Info1), %% remove ) at the end Rest1 = list_init(Rest), Cols = string:tokens(Rest1, ","), build_table_info(lists:map(fun(X) -> string:tokens(X, " ") end, Cols), []). build_table_info([], Acc) -> lists:reverse(Acc); build_table_info([[ColName, ColType] | Tl], Acc) -> build_table_info(Tl, [{list_to_atom(ColName), sqlite3_lib:col_type_to_atom(ColType)}| Acc]); build_table_info([[ColName, ColType | Constraints] | Tl], Acc) -> build_table_info(Tl, [{list_to_atom(ColName), sqlite3_lib:col_type_to_atom(ColType), build_constraints(Constraints)} | Acc]). %% TODO conflict-clause parsing build_constraints([]) -> []; build_constraints(["PRIMARY", "KEY" | Tail]) -> {Constraint, Rest} = build_primary_key_constraint(Tail), [Constraint | build_constraints(Rest)]; build_constraints(["UNIQUE" | Tail]) -> [unique | build_constraints(Tail)]; build_constraints(["NOT", "NULL" | Tail]) -> [not_null | build_constraints(Tail)]; build_constraints(["DEFAULT", DefaultValue | Tail]) -> [{default, sqlite3_lib:sql_to_value(DefaultValue)} | build_constraints(Tail)]; build_constraints(["CHECK", _ | Tail]) -> %% currently ignored build_constraints(Tail). % build_constraints(["REFERENCES", Check | Tail]) -> ... build_primary_key_constraint(Tokens) -> build_primary_key_constraint(Tokens, []). build_primary_key_constraint(["ASC" | Rest], Acc) -> build_primary_key_constraint(Rest, [asc | Acc]); build_primary_key_constraint(["DESC" | Rest], Acc) -> build_primary_key_constraint(Rest, [desc | Acc]); build_primary_key_constraint(["AUTOINCREMENT" | Rest], Acc) -> build_primary_key_constraint(Rest, [autoincrement | Acc]); build_primary_key_constraint(Tail, []) -> {primary_key, Tail}; build_primary_key_constraint(Tail, Acc) -> {{primary_key, lists:reverse(Acc)}, Tail}. cast_table_name(Bin, SQL) -> case re:run(SQL,<<"CHECK \\('(bin|lst|am)'='(bin|lst|am)'\\)\\)">>,[{capture,all_but_first,binary}]) of {match, [<<"bin">>, <<"bin">>]} -> Bin; {match, [<<"lst">>, <<"lst">>]} -> unicode:characters_to_list(Bin, latin1); {match, [<<"am">>, <<"am">>]} -> binary_to_atom(Bin, latin1); _ -> %% backwards compatible binary_to_atom(Bin, latin1) end. list_init([_]) -> []; list_init([H|T]) -> [H|list_init(T)]. %% conflict_clause(["ON", "CONFLICT", ResolutionString | Tail]) -> %% Resolution = case ResolutionString of %% "ROLLBACK" -> rollback; %% "ABORT" -> abort; %% "FAIL" -> fail; %% "IGNORE" -> ignore; %% "REPLACE" -> replace %% end, %% {{on_conflict, Resolution}, Tail}; %% conflict_clause(NoOnConflictClause) -> %% {no_on_conflict, NoOnConflictClause}. %%-------------------------------------------------------------------- %% @type db() = atom() | pid() %% Functions which take databases accept either the name the database is registered under %% or the PID. %% @end %% @type sql_value() = null | number() | iodata() | {blob, binary()}. %% %% Values accepted in SQL statements are atom 'null', numbers, %% strings (represented as iodata()) and blobs. %% @end %% @type sql_type() = integer | text | double | blob | atom() | string(). %% %% Types of SQLite columns are represented by atoms 'integer', 'text', 'double', %% 'blob'. Other atoms and strings may also be used (e.g. "VARCHAR(20)", 'smallint', etc.) %% See [http://www.sqlite.org/datatype3.html]. %% @end %% @type pk_constraint() = autoincrement | desc | asc. %% See {@link pk_constraints()}. %% @type pk_constraints() = pk_constraint() | [pk_constraint()]. %% See {@link column_constraint()}. %% @type column_constraint() = non_null | primary_key | {primary_key, pk_constraints()} %% | unique | {default, sql_value()} | {raw, string()}. %% See {@link column_constraints()}. %% @type column_constraints() = column_constraint() | [column_constraint()]. %% See {@link table_info()}. %% @type table_info() = [{atom(), sql_type()} | {atom(), sql_type(), column_constraints()}]. %% %% Describes the columns of an SQLite table: each tuple contains name, type and constraints (if any) %% of one column. %% @end %% @type table_constraint() = {primary_key, [atom()]} | {unique, [atom()]} | {raw, string()}. %% @type table_constraints() = table_constraint() | [table_constraint()]. %% %% Currently supported constraints for {@link table_info()} and {@link sqlite3:create_table/4}. %% @end %% @type sqlite_error() = {'error', integer(), string()} | {'error', any()}. %% %% Errors reported by SQLite side are represented by 3-element tuples containing %% atom 'error', SQLite result code ([http://www.sqlite.org/c3ref/c_abort.html], %% [http://www.sqlite.org/c3ref/c_busy_recovery.html]) and an English-language error %% message. %% %% Errors occuring on the Erlang side are represented by 2-element tuples with %% first element 'error'. %% @end %% @type sql_non_query_result() = ok | sqlite_error() | {rowid, integer()}. %% The result returned by functions which call the database but don't return %% any records. %% @end %% @type sql_result() = sql_non_query_result() | [{columns, [string()]} | {rows, [tuple()]} | sqlite_error()]. %% The result returned by functions which query the database. If there are errors, %% list of three tuples is returned: [{columns, ListOfColumnNames}, {rows, ListOfResults}, ErrorTuple]. %% If there are no errors, the list has two elements. %% @end %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- %% Tests %%-------------------------------------------------------------------- -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. erlang-sqlite3-1.1.4~dfsg0/rebar.cross_compile.config.sample0000644000175000017500000000161512560407355024463 0ustar debalancedebalance% -*- mode: erlang -*- {port_envs, [{"HOST", "arm-none-linux-gnueabi"}, {"CROSS_COMPILE_ROOT", "/home/aromanov"}, {"SQLITE_CROSS_COMPILED", "$CROSS_COMPILE_ROOT/sqlite-arm"}, {"CROSS_COMPILE_TOOLCHAIN", "$CROSS_COMPILE_ROOT/sbctools/arm-2007q3"}, {"OTPROOT_CROSS_COMPILED", "$CROSS_COMPILE_ROOT/otp_arm_compiled"}, {"ERL_INTERFACE_VERSION", "3.7.1"}, {"ERL_INTERFACE_LIB", "$OTPROOT_CROSS_COMPILED/lib/erl_interface-$ERL_INTERFACE_VERSION/lib"}, {"CC", "$CROSS_COMPILE_TOOLCHAIN/bin/$HOST-gcc"}, {"LD", "$CROSS_COMPILE_TOOLCHAIN/bin/$HOST-ld"}, {"ERL_LDFLAGS", " -L$ERL_INTERFACE_LIB -lerl_interface -lei"}, {"DRV_LDFLAGS", "$DRV_LDFLAGS -L$SQLITE_CROSS_COMPILED/usr/local/lib -lsqlite3"}, {"DRV_CFLAGS", "$DRV_CFLAGS -I$SQLITE_CROSS_COMPILED/usr/local/include"}]}. erlang-sqlite3-1.1.4~dfsg0/AUTHORS0000644000175000017500000000052712560407355017132 0ustar debalancedebalanceOriginal Author: ttyerl - http://github.com/ttyerl/sqlite-erlang Forkers: Max Lapshin - http://github.com/maxlapshin/erlang-sqlite3 Sergey Miryanov - http://github.com/sergey-miryanov/erlang-sqlite3 Alexey Romanov - http://github.com/alexeyr/erlang-sqlite3 David Reid - http://github.com/dreid/erlang-sqlite3 erlang-sqlite3-1.1.4~dfsg0/test.sh0000755000175000017500000000007012560407355017371 0ustar debalancedebalance# separate file to simplify valgrind use ./rebar eunit erlang-sqlite3-1.1.4~dfsg0/.settings/0000755000175000017500000000000012560407355017774 5ustar debalancedebalanceerlang-sqlite3-1.1.4~dfsg0/.settings/org.erlide.core.prefs0000644000175000017500000000024712560407355024021 0ustar debalancedebalancebackend_version=R14B eclipse.preferences.version=1 external_includes= external_modules= include_dirs=include; nukeOutputOnClean=false output_dir=ebin source_dirs=src; erlang-sqlite3-1.1.4~dfsg0/.travis.yml0000644000175000017500000000013512560407355020166 0ustar debalancedebalancelanguage: erlang script: "make test" otp_release: - 17.3 - R16B03-1 - R15B03 - R14B04erlang-sqlite3-1.1.4~dfsg0/c_src/0000755000175000017500000000000012560407355017147 5ustar debalancedebalanceerlang-sqlite3-1.1.4~dfsg0/c_src/sqlite3_drv.h0000644000175000017500000000726212560407355021566 0ustar debalancedebalance// cl.exe defines macro _WIN32, but erl_interface.h checks for __WIN32__ #ifdef _WIN32 #define __WIN32__ #endif #define _CRT_SECURE_NO_WARNINGS // secure functions aren't cross-platform #define ERLANG_SQLITE3_LOAD_EXTENSION // comment out if SQLite is built with SQLITE_OMIT_LOAD_EXTENSION #include #include #include #include #include #include #include #include #if SQLITE_VERSION_NUMBER < 3006001 #error "SQLite3 of version 3.6.1 minumum required" #endif // pre-R15B #if ERL_DRV_EXTENDED_MAJOR_VERSION < 2 typedef int ErlDrvSizeT; typedef int ErlDrvSSizeT; #endif // pre-R16B #if (ERL_DRV_EXTENDED_MAJOR_VERSION < 2) || ((ERL_DRV_EXTENDED_MAJOR_VERSION == 2) && (ERL_DRV_EXTENDED_MINOR_VERSION == 0)) #define PRE_R16B #endif #if defined(_MSC_VER) #pragma warning(disable: 4201) #pragma warning(disable: 4127) #pragma warning(disable: 4820) #endif // Binary commands between Erlang VM and Driver #define CMD_SQL_EXEC 2 // #define CMD_DEL 3 #define CMD_SQL_BIND_AND_EXEC 4 #define CMD_PREPARE 5 #define CMD_PREPARED_BIND 6 #define CMD_PREPARED_STEP 7 #define CMD_PREPARED_RESET 8 #define CMD_PREPARED_CLEAR_BINDINGS 9 #define CMD_PREPARED_FINALIZE 10 #define CMD_PREPARED_COLUMNS 11 #define CMD_SQL_EXEC_SCRIPT 12 #define CMD_ENABLE_LOAD_EXTENSION 13 typedef struct ptr_list { void *head; struct ptr_list *tail; } ptr_list; // Define struct to hold state across calls typedef struct sqlite3_drv_t { ErlDrvPort port; unsigned int key; struct sqlite3 *db; char* db_name; FILE *log; sqlite3_stmt **prepared_stmts; unsigned int prepared_count; unsigned int prepared_alloc; ErlDrvTermData atom_blob; ErlDrvTermData atom_error; ErlDrvTermData atom_columns; ErlDrvTermData atom_rows; ErlDrvTermData atom_null; ErlDrvTermData atom_rowid; ErlDrvTermData atom_ok; ErlDrvTermData atom_done; ErlDrvTermData atom_unknown_cmd; } sqlite3_drv_t; typedef enum async_sqlite3_command_type {t_stmt, t_script} async_sqlite3_command_type; typedef struct async_sqlite3_command { sqlite3_drv_t *driver_data; async_sqlite3_command_type type; union { sqlite3_stmt *statement; struct { char *script; char *end; }; }; ErlDrvTermData *dataset; int term_count; int term_allocated; int row_count; ptr_list *ptrs; ptr_list *binaries; int finalize_statement_on_free; int error_code; } async_sqlite3_command; static ErlDrvData start(ErlDrvPort port, char* cmd); static void stop(ErlDrvData handle); static ErlDrvSSizeT control(ErlDrvData drv_data, unsigned int command, char *buf, ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen); static int sql_exec(sqlite3_drv_t *drv, char *buf, int len); static int sql_bind_and_exec(sqlite3_drv_t *drv, char *buf, int len); static int sql_exec_script(sqlite3_drv_t *drv, char *buf, int len); static int prepare(sqlite3_drv_t *drv, char *buf, int len); static int prepared_bind(sqlite3_drv_t *drv, char *buf, int len); static int prepared_step(sqlite3_drv_t *drv, char *buf, int len); static int prepared_reset(sqlite3_drv_t *drv, char *buf, int len); static int prepared_clear_bindings(sqlite3_drv_t *drv, char *buf, int len); static int prepared_finalize(sqlite3_drv_t *drv, char *buf, int len); static int prepared_columns(sqlite3_drv_t *drv, char *buf, int len); static void sql_exec_async(void *async_command); static void sql_free_async(void *async_command); static void ready_async(ErlDrvData drv_data, ErlDrvThreadData thread_data); static int unknown(sqlite3_drv_t *bdb_drv, char *buf, int len); static int enable_load_extension(sqlite3_drv_t *drv, char *buf, int len); #if defined(_MSC_VER) #pragma warning(default: 4201) #endif erlang-sqlite3-1.1.4~dfsg0/c_src/sqlite3_drv.c0000644000175000017500000013267612560407355021571 0ustar debalancedebalance#include "sqlite3_drv.h" #include #include // MSVC needs "__inline" instead of "inline" in C-source files. #if defined(_MSC_VER) #define inline __inline #endif #ifdef DEBUG static int DEBUG = 1; #else static int DEBUG = 0; #endif #define LOG_DEBUG(M, ...) do { \ if (DEBUG && drv->log) \ fprintf(drv->log, "[DEBUG] (%s:%d) " M "\n", __FILE__, __LINE__, __VA_ARGS__); \ } while (0) #define LOG_ERROR(M, ...) do { \ if (drv->log) \ fprintf(drv->log, "[ERROR] (%s:%d) " M "\n", __FILE__, __LINE__, __VA_ARGS__); \ if (drv->log != stderr) \ fprintf(stderr, "[ERROR] (%s:%d) " M "\n", __FILE__, __LINE__, __VA_ARGS__); \ } while(0) #define EXTEND_DATASET(n, term_count, term_allocated, dataset) \ term_count += n; \ if (term_count > term_allocated) { \ term_allocated = max(term_count, term_allocated*2); \ dataset = driver_realloc(dataset, sizeof(ErlDrvTermData) * term_allocated); \ } #define EXTEND_DATASET_DIRECT(n) EXTEND_DATASET(n, term_count, term_allocated, dataset) #define EXTEND_DATASET_PTR(n) EXTEND_DATASET(n, *term_count_p, *term_allocated_p, *dataset_p) static void append_to_dataset(int n, ErlDrvTermData* dataset, int term_count, ...) { int i; va_list new_terms; va_start(new_terms, term_count); for (i = -n; i < 0; i++) { dataset[term_count + i] = va_arg(new_terms, ErlDrvTermData); } va_end(new_terms); } static inline ptr_list *add_to_ptr_list(ptr_list *list, void *value_ptr) { ptr_list* new_node = driver_alloc(sizeof(ptr_list)); new_node->head = value_ptr; new_node->tail = list; return new_node; } static inline void free_ptr_list(ptr_list *list, void(* free_head)(void *)) { ptr_list* tail; while (list) { tail = list->tail; (*free_head)(list->head); driver_free(list); list = tail; } } #ifndef max // macro in Windows static inline int max(int a, int b) { return a >= b ? a : b; } #endif static inline int sql_is_insert(const char *sql) { // neither strcasestr nor strnicmp are portable, so have to do this int i; char *insert = "insert"; for (i = 0; i < 6; i++) { if ((tolower(sql[i]) != insert[i]) && (sql[i] != ' ')) { return 0; } } return 1; } #ifdef DEBUG static void fprint_dataset(FILE* log, ErlDrvTermData* dataset, int term_count); #endif // required because driver_free(_binary) are macros in Windows static void driver_free_fun(void *ptr) { driver_free(ptr); } static void driver_free_binary_fun(void *ptr) { driver_free_binary((ErlDrvBinary *) ptr); } // sdbm from http://www.cse.yorku.ca/~oz/hash.html unsigned int hash(const char *str) { unsigned int hash = 0; unsigned int c = 0; do { hash = c + (hash << 6) + (hash << 16) - hash; c = *str++; } while (c); return hash; } // Returns a key determined by the file name for an on-disk database, // determined by the port for a private database. // This way all access to a single DB will go through one async thread. static inline unsigned int sql_async_key(char *db_name, ErlDrvPort port) { const char *memory_db_name = ":memory:"; if (strcmp(db_name, memory_db_name)) { return hash(db_name); } else { #if ERL_DRV_EXTENDED_MAJOR_VERSION > 2 || \ (ERL_DRV_EXTENDED_MAJOR_VERSION == 2 && ERL_DRV_EXTENDED_MINOR_VERSION >= 2) return driver_async_port_key(port); #else return (unsigned int) (uintptr_t) port; #endif } } static ErlDrvEntry sqlite3_driver_entry = { NULL, /* init */ start, /* startup (defined below) */ stop, /* shutdown (defined below) */ NULL, /* output */ NULL, /* ready_input */ NULL, /* ready_output */ "sqlite3_drv", /* the name of the driver */ NULL, /* finish */ NULL, /* handle */ control, /* control */ NULL, /* timeout */ NULL, /* outputv */ ready_async, /* ready_async (defined below) */ NULL, /* flush */ NULL, /* call */ NULL, /* event */ ERL_DRV_EXTENDED_MARKER, /* ERL_DRV_EXTENDED_MARKER */ ERL_DRV_EXTENDED_MAJOR_VERSION, /* ERL_DRV_EXTENDED_MAJOR_VERSION */ ERL_DRV_EXTENDED_MINOR_VERSION, /* ERL_DRV_EXTENDED_MINOR_VERSION */ ERL_DRV_FLAG_USE_PORT_LOCKING, /* ERL_DRV_FLAGs */ NULL /* handle2 */, NULL /* process_exit */, NULL /* stop_select */ }; DRIVER_INIT(sqlite3_driver) { return &sqlite3_driver_entry; } // Driver Start static ErlDrvData start(ErlDrvPort port, char* cmd) { sqlite3_drv_t* drv = (sqlite3_drv_t*) driver_alloc(sizeof(sqlite3_drv_t)); struct sqlite3 *db = NULL; int status = 0; char *db_name = strstr(cmd, " "); size_t db_name_len; char *db_name_copy; #ifdef DEBUG errno_t file_open_errno; #ifdef _MSC_VER const char *log_file = _tempnam(NULL, "erlang-sqlite3-log-"); file_open_errno = fopen_s(drv->log, log_file, "a+"); #else const char *log_file = tempnam(NULL, "erlang-sqlite3-log-"); drv->log = fopen(log_file, "ax"); file_open_errno = errno; #endif if (file_open_errno) { fprintf(stderr, "Error creating log file %s; reason %s\n", log_file, strerror(file_open_errno)); // if we can't open the log file we shouldn't hide the data or the problem drv->log = stderr; // noisy } free(log_file); #else drv->log = NULL; #endif #if defined(_MSC_VER) #pragma warning(disable: 4306) #endif if (!db_name) { driver_free(drv); return ERL_DRV_ERROR_BADARG; } else { ++db_name; // move to first character after ' ' } // Create and open the database status = sqlite3_open(db_name, &db); if (status != SQLITE_OK) { LOG_ERROR("Unable to open file: %s because %s\n\n", db_name, sqlite3_errmsg(db)); // We don't do this because there's no way to pass the error to Erlang // sqlite3_close(db); // driver_free(drv); // return ERL_DRV_ERROR_GENERAL; } else { LOG_DEBUG("Opened file %s\n", db_name); } #if defined(_MSC_VER) #pragma warning(default: 4306) #endif db_name_len = strlen(db_name) + 1; // include terminator db_name_copy = driver_alloc(sizeof(char) * db_name_len); strcpy(db_name_copy, db_name); // Set the state for the driver drv->port = port; drv->db = db; drv->db_name = db_name_copy; drv->key = sql_async_key(db_name_copy, port); drv->prepared_stmts = NULL; drv->prepared_count = 0; drv->prepared_alloc = 0; drv->atom_blob = driver_mk_atom("blob"); drv->atom_error = driver_mk_atom("error"); drv->atom_columns = driver_mk_atom("columns"); drv->atom_rows = driver_mk_atom("rows"); drv->atom_null = driver_mk_atom("null"); drv->atom_rowid = driver_mk_atom("rowid"); drv->atom_ok = driver_mk_atom("ok"); drv->atom_done = driver_mk_atom("done"); drv->atom_unknown_cmd = driver_mk_atom("unknown_command"); return (ErlDrvData) drv; } // Driver Stop static void stop(ErlDrvData handle) { sqlite3_drv_t* drv = (sqlite3_drv_t*) handle; unsigned int i; int close_result; if (drv->prepared_stmts) { for (i = 0; i < drv->prepared_count; i++) { sqlite3_finalize(drv->prepared_stmts[i]); } driver_free(drv->prepared_stmts); } close_result = sqlite3_close(drv->db); if (close_result != SQLITE_OK) { LOG_ERROR("Failed to close DB %s, some resources aren't finalized!", drv->db_name); } if (drv->log && (drv->log != stderr)) { fclose(drv->log); } driver_free(drv->db_name); driver_free(drv); } static inline int output_error(sqlite3_drv_t *drv, int error_code, const char *error); // Handle input from Erlang VM static ErlDrvSSizeT control( ErlDrvData drv_data, unsigned int command, char *buf, ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen) { sqlite3_drv_t* drv = (sqlite3_drv_t*) drv_data; if (len > INT_MAX) { output_error(drv, SQLITE_MISUSE, "Command size doesn't fit into int type"); } else { switch (command) { case CMD_SQL_EXEC: sql_exec(drv, buf, (int) len); break; case CMD_SQL_BIND_AND_EXEC: sql_bind_and_exec(drv, buf, (int) len); break; case CMD_PREPARE: prepare(drv, buf, (int) len); break; case CMD_PREPARED_BIND: prepared_bind(drv, buf, (int) len); break; case CMD_PREPARED_STEP: prepared_step(drv, buf, (int) len); break; case CMD_PREPARED_RESET: prepared_reset(drv, buf, (int) len); break; case CMD_PREPARED_CLEAR_BINDINGS: prepared_clear_bindings(drv, buf, (int) len); break; case CMD_PREPARED_FINALIZE: prepared_finalize(drv, buf, (int) len); break; case CMD_PREPARED_COLUMNS: prepared_columns(drv, buf, (int) len); break; case CMD_SQL_EXEC_SCRIPT: sql_exec_script(drv, buf, (int) len); break; case CMD_ENABLE_LOAD_EXTENSION: enable_load_extension(drv, buf, (int) len); break; default: unknown(drv, buf, (int) len); } } return 0; } static inline int return_error( sqlite3_drv_t *drv, int error_code, const char *error, ErlDrvTermData **dataset_p, int *term_count_p, int *term_allocated_p, int* error_code_p) { if (error_code_p) { *error_code_p = error_code; } EXTEND_DATASET_PTR(9); append_to_dataset(9, *dataset_p, *term_count_p, ERL_DRV_ATOM, drv->atom_error, ERL_DRV_INT, (ErlDrvTermData) error_code, ERL_DRV_STRING, (ErlDrvTermData) error, (ErlDrvTermData) strlen(error), ERL_DRV_TUPLE, (ErlDrvTermData) 3); // int i; // for (i = 0; i < *term_count_p; i++) { // printf("%d\n", (*dataset_p)[i]); // } return 0; } static inline int output_error( sqlite3_drv_t *drv, int error_code, const char *error) { int term_count = 2, term_allocated = 13; // for some reason breaks if allocated as an array on stack // even though it shouldn't be extended ErlDrvTermData *dataset = driver_alloc(sizeof(ErlDrvTermData) * term_allocated); dataset[0] = ERL_DRV_PORT; dataset[1] = driver_mk_port(drv->port); return_error(drv, error_code, error, &dataset, &term_count, &term_allocated, NULL); term_count += 2; dataset[11] = ERL_DRV_TUPLE; dataset[12] = 2; #ifdef PRE_R16B driver_output_term(drv->port, #else erl_drv_output_term(dataset[1], #endif dataset, term_count); driver_free(dataset); return 0; } static inline int output_db_error(sqlite3_drv_t *drv) { return output_error(drv, sqlite3_errcode(drv->db), sqlite3_errmsg(drv->db)); } static inline int output_ok(sqlite3_drv_t *drv) { // Return {Port, ok} ErlDrvTermData spec[] = { ERL_DRV_PORT, driver_mk_port(drv->port), ERL_DRV_ATOM, drv->atom_ok, ERL_DRV_TUPLE, 2 }; return #ifdef PRE_R16B driver_output_term(drv->port, #else erl_drv_output_term(spec[1], #endif spec, sizeof(spec) / sizeof(spec[0])); } static int enable_load_extension(sqlite3_drv_t* drv, char *buf, int len) { #ifdef ERLANG_SQLITE3_LOAD_EXTENSION char enable = buf[0]; int result = sqlite3_enable_load_extension(drv->db, (int) enable); if (result) { output_db_error(drv); return result; } else { output_ok(drv); return 0; } #else output_error(drv, SQLITE_MISUSE, "extension loading not enabled, recompile erlang-sqlite3 with ERLANG_SQLITE3_LOAD_EXTENSION defined"); return -1; #endif } static inline async_sqlite3_command *make_async_command_statement( sqlite3_drv_t *drv, sqlite3_stmt *statement, int finalize) { async_sqlite3_command *result = (async_sqlite3_command *) driver_alloc(sizeof(async_sqlite3_command)); memset(result, 0, sizeof(async_sqlite3_command)); result->driver_data = drv; result->type = t_stmt; result->statement = statement; result->finalize_statement_on_free = finalize; return result; } static inline async_sqlite3_command *make_async_command_script( sqlite3_drv_t *drv, char *script, int script_length) { async_sqlite3_command *result = (async_sqlite3_command *) driver_alloc(sizeof(async_sqlite3_command)); char *script_copy = driver_alloc(sizeof(char) * script_length); memset(result, 0, sizeof(async_sqlite3_command)); memcpy(script_copy, script, sizeof(char) * script_length); result->driver_data = drv; result->type = t_script; result->script = script_copy; result->end = script_copy + script_length; return result; } static inline void exec_async_command( sqlite3_drv_t *drv, void (*async_invoke)(void*), async_sqlite3_command *async_command) { long status = driver_async(drv->port, &drv->key, async_invoke, async_command, sql_free_async); // see https://groups.google.com/d/msg/erlang-programming/XiFR6xxhGos/B6ARBIlvpMUJ if (status < 0) { LOG_ERROR("driver_async call failed", 0); output_error(drv, SQLITE_ERROR, "driver_async call failed"); } } static inline int sql_exec_statement( sqlite3_drv_t *drv, sqlite3_stmt *statement) { async_sqlite3_command *async_command = make_async_command_statement(drv, statement, 1); LOG_DEBUG("Driver async: %d %p\n", SQLITE_VERSION_NUMBER, async_command->statement); exec_async_command(drv, sql_exec_async, async_command); return 0; } static int sql_exec(sqlite3_drv_t *drv, char *command, int command_size) { int result; const char *rest; sqlite3_stmt *statement; LOG_DEBUG("Preexec: %.*s\n", command_size, command); result = sqlite3_prepare_v2(drv->db, command, command_size, &statement, &rest); if (result != SQLITE_OK) { return output_db_error(drv); } else if (statement == NULL) { return output_error(drv, SQLITE_MISUSE, "empty statement"); } return sql_exec_statement(drv, statement); } static int sql_exec_script(sqlite3_drv_t *drv, char *command, int command_size) { async_sqlite3_command *async_command = make_async_command_script(drv, command, command_size); LOG_DEBUG("Driver async: %d %p\n", SQLITE_VERSION_NUMBER, async_command->statement); exec_async_command(drv, sql_exec_async, async_command); return 0; } static inline int decode_and_bind_param( sqlite3_drv_t *drv, char *buffer, int *p_index, sqlite3_stmt *statement, int param_index, int *p_type, int *p_size) { int result; sqlite3_int64 int64_val; double double_val; char* char_buf_val; long bin_size; ei_get_type(buffer, p_index, p_type, p_size); switch (*p_type) { case ERL_SMALL_INTEGER_EXT: case ERL_INTEGER_EXT: case ERL_SMALL_BIG_EXT: case ERL_LARGE_BIG_EXT: ei_decode_longlong(buffer, p_index, &int64_val); result = sqlite3_bind_int64(statement, param_index, int64_val); break; case ERL_FLOAT_EXT: #ifdef NEW_FLOAT_EXT case NEW_FLOAT_EXT: // what's the difference? #endif ei_decode_double(buffer, p_index, &double_val); result = sqlite3_bind_double(statement, param_index, double_val); break; case ERL_ATOM_EXT: // include space for null separator char_buf_val = driver_alloc((*p_size + 1) * sizeof(char)); ei_decode_atom(buffer, p_index, char_buf_val); if (strncmp(char_buf_val, "null", 5) == 0) { result = sqlite3_bind_null(statement, param_index); } else { output_error(drv, SQLITE_MISUSE, "Non-null atom as parameter"); return 1; } break; case ERL_STRING_EXT: // include space for null separator char_buf_val = driver_alloc((*p_size + 1) * sizeof(char)); ei_decode_string(buffer, p_index, char_buf_val); result = sqlite3_bind_text(statement, param_index, char_buf_val, *p_size, &driver_free_fun); break; case ERL_BINARY_EXT: char_buf_val = driver_alloc(*p_size * sizeof(char)); ei_decode_binary(buffer, p_index, char_buf_val, &bin_size); result = sqlite3_bind_text(statement, param_index, char_buf_val, *p_size, &driver_free_fun); break; case ERL_SMALL_TUPLE_EXT: // assume this is {blob, Blob} ei_get_type(buffer, p_index, p_type, p_size); ei_decode_tuple_header(buffer, p_index, p_size); if (*p_size != 2) { output_error(drv, SQLITE_MISUSE, "bad parameter type"); return 1; } ei_skip_term(buffer, p_index); // skipped the atom 'blob' ei_get_type(buffer, p_index, p_type, p_size); if (*p_type != ERL_BINARY_EXT) { output_error(drv, SQLITE_MISUSE, "bad parameter type"); return 1; } char_buf_val = driver_alloc(*p_size * sizeof(char)); ei_decode_binary(buffer, p_index, char_buf_val, &bin_size); result = sqlite3_bind_blob(statement, param_index, char_buf_val, *p_size, &driver_free_fun); break; default: output_error(drv, SQLITE_MISUSE, "bad parameter type"); return 1; } if (result != SQLITE_OK) { output_db_error(drv); return result; } return SQLITE_OK; } static int bind_parameters( sqlite3_drv_t *drv, char *buffer, int buffer_size, int *p_index, sqlite3_stmt *statement, int *p_type, int *p_size) { // decoding parameters int i, cur_list_size = -1, param_index = 1, param_indices_are_explicit = 0, result = 0; long param_index_long; char param_name[MAXATOMLEN + 1]; // parameter names shouldn't be longer than 256! char *acc_string; result = ei_decode_list_header(buffer, p_index, &cur_list_size); if (result) { // probably all parameters are integers between 0 and 255 // and the list was encoded as string (see ei documentation) ei_get_type(buffer, p_index, p_type, p_size); if (*p_type != ERL_STRING_EXT) { return output_error(drv, SQLITE_ERROR, "error while binding parameters"); } acc_string = driver_alloc(sizeof(char) * (*p_size + 1)); ei_decode_string(buffer, p_index, acc_string); for (param_index = 1; param_index <= *p_size; param_index++) { sqlite3_bind_int(statement, param_index, (int) (unsigned char) acc_string[param_index - 1]); } driver_free(acc_string); return 0; } for (i = 0; i < cur_list_size; i++) { if (*p_index >= buffer_size) { return output_error(drv, SQLITE_ERROR, "error while binding parameters"); } ei_get_type(buffer, p_index, p_type, p_size); if (*p_type == ERL_SMALL_TUPLE_EXT) { int old_index = *p_index; // param with name or explicit index param_indices_are_explicit = 1; if (*p_size != 2) { return output_error(drv, SQLITE_MISUSE, "tuple should contain index or name, and value"); } ei_decode_tuple_header(buffer, p_index, p_size); ei_get_type(buffer, p_index, p_type, p_size); // first element of tuple is int (index), atom, or string (name) switch (*p_type) { case ERL_SMALL_INTEGER_EXT: case ERL_INTEGER_EXT: ei_decode_long(buffer, p_index, ¶m_index_long); param_index = param_index_long; break; case ERL_ATOM_EXT: ei_decode_atom(buffer, p_index, param_name); // insert zero terminator param_name[*p_size] = '\0'; if (strncmp(param_name, "blob", 5) == 0) { // this isn't really a parameter name! *p_index = old_index; param_indices_are_explicit = 0; goto IMPLICIT_INDEX; // yuck } else { param_index = sqlite3_bind_parameter_index(statement, param_name); } break; case ERL_STRING_EXT: if (*p_size >= MAXATOMLEN) { return output_error(drv, SQLITE_TOOBIG, "parameter name too long"); } ei_decode_string(buffer, p_index, param_name); // insert zero terminator param_name[*p_size] = '\0'; param_index = sqlite3_bind_parameter_index(statement, param_name); break; default: return output_error(drv, SQLITE_MISMATCH, "parameter index must be given as integer, atom, or string"); } result = decode_and_bind_param( drv, buffer, p_index, statement, param_index, p_type, p_size); if (result != SQLITE_OK) { return result; // error has already been output } } else { IMPLICIT_INDEX: if (param_indices_are_explicit) { return output_error(drv, SQLITE_MISUSE, "parameters without indices shouldn't follow indexed or named parameters"); } result = decode_and_bind_param( drv, buffer, p_index, statement, param_index, p_type, p_size); if (result != SQLITE_OK) { return result; // error has already been output } ++param_index; } } return result; } static void get_columns( sqlite3_drv_t *drv, sqlite3_stmt *statement, int column_count, int base, int *term_count_p, int *term_allocated_p, ptr_list** p_ptrs, ErlDrvTermData **dataset_p) { int i; EXTEND_DATASET_PTR(column_count * 3 + 3); for (i = 0; i < column_count; i++) { const char *column_name = sqlite3_column_name(statement, i); size_t column_name_length = strlen(column_name); char *column_name_copy = driver_alloc(sizeof(char) * (column_name_length + 1)); strcpy(column_name_copy, column_name); *p_ptrs = add_to_ptr_list(*p_ptrs, column_name_copy); LOG_DEBUG("Column: %s\n", column_name_copy); (*dataset_p)[base + (i * 3)] = ERL_DRV_STRING; (*dataset_p)[base + (i * 3) + 1] = (ErlDrvTermData) column_name_copy; (*dataset_p)[base + (i * 3) + 2] = column_name_length; } (*dataset_p)[base + column_count * 3 + 0] = ERL_DRV_NIL; (*dataset_p)[base + column_count * 3 + 1] = ERL_DRV_LIST; (*dataset_p)[base + column_count * 3 + 2] = column_count + 1; } static int sql_bind_and_exec(sqlite3_drv_t *drv, char *buffer, int buffer_size) { int result; int index = 0; int type, size; const char *rest; sqlite3_stmt *statement; long bin_size; char *command; LOG_DEBUG("Preexec: %.*s\n", buffer_size, buffer); ei_decode_version(buffer, &index, NULL); result = ei_decode_tuple_header(buffer, &index, &size); if (result || (size != 2)) { return output_error(drv, SQLITE_MISUSE, "Expected a tuple of SQL command and params"); } // decode SQL statement ei_get_type(buffer, &index, &type, &size); // TODO support any iolists if (type != ERL_BINARY_EXT) { return output_error(drv, SQLITE_MISUSE, "SQL should be sent as an Erlang binary"); } command = driver_alloc(size * sizeof(char)); ei_decode_binary(buffer, &index, command, &bin_size); // assert(bin_size == size) result = sqlite3_prepare_v2(drv->db, command, size, &statement, &rest); driver_free(command); if (result != SQLITE_OK) { return output_db_error(drv); } else if (statement == NULL) { return output_error(drv, SQLITE_MISUSE, "empty statement"); } result = bind_parameters(drv, buffer, buffer_size, &index, statement, &type, &size); if (result == SQLITE_OK) { return sql_exec_statement(drv, statement); } else { sqlite3_finalize(statement); return result; // error has already been output } } static void sql_free_async(void *_async_command) { async_sqlite3_command *async_command = (async_sqlite3_command *) _async_command; driver_free(async_command->dataset); free_ptr_list(async_command->ptrs, &driver_free_fun); free_ptr_list(async_command->binaries, &driver_free_binary_fun); if ((async_command->type == t_stmt) && async_command->finalize_statement_on_free && async_command->statement) { sqlite3_finalize(async_command->statement); async_command->statement = NULL; } else if (async_command->type == t_script) { driver_free(async_command->script); } driver_free(async_command); } static int sql_exec_one_statement( sqlite3_stmt *statement, async_sqlite3_command *async_command, int *term_count_p, int *term_allocated_p, ErlDrvTermData **dataset_p) { int column_count = sqlite3_column_count(statement); int row_count = 0, next_row; int base_term_count; int has_error = 0; // bool sqlite3_drv_t *drv = async_command->driver_data; ptr_list **ptrs_p = &(async_command->ptrs); ptr_list **binaries_p = &(async_command->binaries); // printf("\nsql_exec_one_statement. SQL:\n%s\n Term count: %d, terms alloc: %d\n", sqlite3_sql(statement), *term_count_p, *term_allocated_p); int i; if (column_count > 0) { EXTEND_DATASET_PTR(2); append_to_dataset(2, *dataset_p, *term_count_p, ERL_DRV_ATOM, drv->atom_columns); base_term_count = *term_count_p; get_columns( drv, statement, column_count, base_term_count, term_count_p, term_allocated_p, ptrs_p, dataset_p); EXTEND_DATASET_PTR(4); append_to_dataset(4, *dataset_p, base_term_count + column_count * 3 + 7, ERL_DRV_TUPLE, (ErlDrvTermData) 2, ERL_DRV_ATOM, drv->atom_rows); } LOG_DEBUG("Exec: %s\n", sqlite3_sql(statement)); while ((next_row = sqlite3_step(statement)) == SQLITE_ROW) { for (i = 0; i < column_count; i++) { LOG_DEBUG("Column %d type: %d\n", i, sqlite3_column_type(statement, i)); switch (sqlite3_column_type(statement, i)) { case SQLITE_INTEGER: { ErlDrvSInt64 *int64_ptr = driver_alloc(sizeof(ErlDrvSInt64)); *int64_ptr = (ErlDrvSInt64) sqlite3_column_int64(statement, i); *ptrs_p = add_to_ptr_list(*ptrs_p, int64_ptr); EXTEND_DATASET_PTR(2); append_to_dataset(2, *dataset_p, *term_count_p, ERL_DRV_INT64, (ErlDrvTermData) int64_ptr); break; } case SQLITE_FLOAT: { double *float_ptr = driver_alloc(sizeof(double)); *float_ptr = sqlite3_column_double(statement, i); *ptrs_p = add_to_ptr_list(*ptrs_p, float_ptr); EXTEND_DATASET_PTR(2); append_to_dataset(2, *dataset_p, *term_count_p, ERL_DRV_FLOAT, (ErlDrvTermData) float_ptr); break; } case SQLITE_BLOB: { int bytes = sqlite3_column_bytes(statement, i); ErlDrvBinary* binary = driver_alloc_binary(bytes); binary->orig_size = bytes; memcpy(binary->orig_bytes, sqlite3_column_blob(statement, i), bytes); *binaries_p = add_to_ptr_list(*binaries_p, binary); EXTEND_DATASET_PTR(8); append_to_dataset(8, *dataset_p, *term_count_p, ERL_DRV_ATOM, drv->atom_blob, ERL_DRV_BINARY, (ErlDrvTermData) binary, (ErlDrvTermData) bytes, (ErlDrvTermData) 0, ERL_DRV_TUPLE, (ErlDrvTermData) 2); break; } case SQLITE_TEXT: { int bytes = sqlite3_column_bytes(statement, i); ErlDrvBinary* binary = driver_alloc_binary(bytes); binary->orig_size = bytes; memcpy(binary->orig_bytes, sqlite3_column_blob(statement, i), bytes); *binaries_p = add_to_ptr_list(*binaries_p, binary); EXTEND_DATASET_PTR(4); append_to_dataset(4, *dataset_p, *term_count_p, ERL_DRV_BINARY, (ErlDrvTermData) binary, (ErlDrvTermData) bytes, (ErlDrvTermData) 0); break; } case SQLITE_NULL: { EXTEND_DATASET_PTR(2); append_to_dataset(2, *dataset_p, *term_count_p, ERL_DRV_ATOM, drv->atom_null); break; } } } EXTEND_DATASET_PTR(2); append_to_dataset(2, *dataset_p, *term_count_p, ERL_DRV_TUPLE, (ErlDrvTermData) column_count); row_count++; } if (next_row != SQLITE_DONE) { if (column_count == 0) { return_error(drv, next_row, sqlite3_errmsg(drv->db), dataset_p, term_count_p, term_allocated_p, &async_command->error_code); async_command->finalize_statement_on_free = 1; return 1; } else { has_error = 1; } } if (column_count > 0) { EXTEND_DATASET_PTR(5); append_to_dataset(5, *dataset_p, *term_count_p, ERL_DRV_NIL, ERL_DRV_LIST, (ErlDrvTermData) (row_count + 1), ERL_DRV_TUPLE, (ErlDrvTermData) 2); if (has_error) { return_error(drv, next_row, sqlite3_errmsg(drv->db), dataset_p, term_count_p, term_allocated_p, &async_command->error_code); } EXTEND_DATASET_PTR(3); append_to_dataset(3, *dataset_p, *term_count_p, ERL_DRV_NIL, ERL_DRV_LIST, (ErlDrvTermData) (3 + has_error)); } else if (sql_is_insert(sqlite3_sql(statement))) { ErlDrvSInt64 *rowid_ptr = driver_alloc(sizeof(ErlDrvSInt64)); *rowid_ptr = (ErlDrvSInt64) sqlite3_last_insert_rowid(drv->db); *ptrs_p = add_to_ptr_list(*ptrs_p, rowid_ptr); EXTEND_DATASET_PTR(6); append_to_dataset(6, *dataset_p, *term_count_p, ERL_DRV_ATOM, drv->atom_rowid, ERL_DRV_INT64, (ErlDrvTermData) rowid_ptr, ERL_DRV_TUPLE, (ErlDrvTermData) 2); } else { EXTEND_DATASET_PTR(2); append_to_dataset(2, *dataset_p, *term_count_p, ERL_DRV_ATOM, drv->atom_ok); } LOG_DEBUG("Total term count: %p %d, rows count: %dx%d\n", statement, *term_count_p, column_count, row_count); async_command->finalize_statement_on_free = 1; return has_error; } static void sql_exec_async(void *_async_command) { async_sqlite3_command *async_command = (async_sqlite3_command *) _async_command; sqlite3_stmt *statement = NULL; int result; const char *rest; const char *end; int num_statements = 0; int term_count = 0, term_allocated = 0; ErlDrvTermData *dataset = NULL; sqlite3_drv_t *drv = async_command->driver_data; EXTEND_DATASET_DIRECT(2); append_to_dataset(2, dataset, term_count, ERL_DRV_PORT, driver_mk_port(drv->port)); switch (async_command->type) { case t_stmt: statement = async_command->statement; sql_exec_one_statement(statement, async_command, &term_count, &term_allocated, &dataset); break; case t_script: rest = async_command->script; end = async_command->end; while ((rest < end) && !(async_command->error_code)) { result = sqlite3_prepare_v2(drv->db, rest, (int) (end - rest), &statement, &rest); if (result != SQLITE_OK) { // sqlite doc says statement will be NULL here, so no need to finalize it num_statements++; return_error(drv, result, sqlite3_errmsg(drv->db), &dataset, &term_count, &term_allocated, &async_command->error_code); break; } else if (statement == NULL) { // the script has completed break; } else { num_statements++; result = sql_exec_one_statement(statement, async_command, &term_count, &term_allocated, &dataset); sqlite3_finalize(statement); if (result) { break; } } } EXTEND_DATASET_DIRECT(3); append_to_dataset(3, dataset, term_count, ERL_DRV_NIL, ERL_DRV_LIST, (ErlDrvTermData) (num_statements + 1)); } EXTEND_DATASET_DIRECT(2); append_to_dataset(2, dataset, term_count, ERL_DRV_TUPLE, (ErlDrvTermData) 2); // print_dataset(dataset, term_count); async_command->term_count = term_count; async_command->term_allocated = term_allocated; async_command->dataset = dataset; } static void sql_step_async(void *_async_command) { async_sqlite3_command *async_command = (async_sqlite3_command *) _async_command; int term_count = 0; int term_allocated = 0; ErlDrvTermData *dataset = NULL; sqlite3_drv_t *drv = async_command->driver_data; int column_count = 0; sqlite3_stmt *statement = async_command->statement; ptr_list *ptrs = NULL; ptr_list *binaries = NULL; int i; int result; switch(result = sqlite3_step(statement)) { case SQLITE_ROW: column_count = sqlite3_column_count(statement); EXTEND_DATASET_DIRECT(2); append_to_dataset(2, dataset, term_count, ERL_DRV_PORT, driver_mk_port(drv->port)); for (i = 0; i < column_count; i++) { LOG_DEBUG("Column %d type: %d\n", i, sqlite3_column_type(statement, i)); switch (sqlite3_column_type(statement, i)) { case SQLITE_INTEGER: { ErlDrvSInt64 *int64_ptr = driver_alloc(sizeof(ErlDrvSInt64)); *int64_ptr = (ErlDrvSInt64) sqlite3_column_int64(statement, i); ptrs = add_to_ptr_list(ptrs, int64_ptr); EXTEND_DATASET_DIRECT(2); append_to_dataset(2, dataset, term_count, ERL_DRV_INT64, (ErlDrvTermData) int64_ptr); break; } case SQLITE_FLOAT: { double *float_ptr = driver_alloc(sizeof(double)); *float_ptr = sqlite3_column_double(statement, i); ptrs = add_to_ptr_list(ptrs, float_ptr); EXTEND_DATASET_DIRECT(2); append_to_dataset(2, dataset, term_count, ERL_DRV_FLOAT, (ErlDrvTermData) float_ptr); break; } case SQLITE_BLOB: { int bytes = sqlite3_column_bytes(statement, i); ErlDrvBinary* binary = driver_alloc_binary(bytes); binary->orig_size = bytes; memcpy(binary->orig_bytes, sqlite3_column_blob(statement, i), bytes); binaries = add_to_ptr_list(binaries, binary); EXTEND_DATASET_DIRECT(8); append_to_dataset(8, dataset, term_count, ERL_DRV_ATOM, drv->atom_blob, ERL_DRV_BINARY, (ErlDrvTermData) binary, (ErlDrvTermData) bytes, (ErlDrvTermData) 0, ERL_DRV_TUPLE, (ErlDrvTermData) 2); break; } case SQLITE_TEXT: { int bytes = sqlite3_column_bytes(statement, i); ErlDrvBinary* binary = driver_alloc_binary(bytes); binary->orig_size = bytes; memcpy(binary->orig_bytes, sqlite3_column_blob(statement, i), bytes); binaries = add_to_ptr_list(binaries, binary); EXTEND_DATASET_DIRECT(4); append_to_dataset(4, dataset, term_count, ERL_DRV_BINARY, (ErlDrvTermData) binary, (ErlDrvTermData) bytes, (ErlDrvTermData) 0); break; } case SQLITE_NULL: { EXTEND_DATASET_DIRECT(2); append_to_dataset(2, dataset, term_count, ERL_DRV_ATOM, drv->atom_null); break; } } } EXTEND_DATASET_DIRECT(2); append_to_dataset(2, dataset, term_count, ERL_DRV_TUPLE, (ErlDrvTermData) column_count); async_command->ptrs = ptrs; async_command->binaries = binaries; break; case SQLITE_DONE: EXTEND_DATASET_DIRECT(4); append_to_dataset(4, dataset, term_count, ERL_DRV_PORT, driver_mk_port(drv->port), ERL_DRV_ATOM, drv->atom_done); sqlite3_reset(statement); break; case SQLITE_BUSY: return_error(drv, SQLITE_BUSY, "SQLite3 database is busy", &dataset, &term_count, &term_allocated, &async_command->error_code); sqlite3_reset(statement); goto POPULATE_COMMAND; break; default: return_error(drv, result, sqlite3_errmsg(drv->db), &dataset, &term_count, &term_allocated, &async_command->error_code); sqlite3_reset(statement); goto POPULATE_COMMAND; } EXTEND_DATASET_DIRECT(2); append_to_dataset(2, dataset, term_count, ERL_DRV_TUPLE, (ErlDrvTermData) 2); POPULATE_COMMAND: async_command->dataset = dataset; async_command->term_count = term_count; async_command->ptrs = ptrs; async_command->binaries = binaries; async_command->row_count = 1; LOG_DEBUG("Total term count: %p %d, columns count: %d\n", statement, term_count, column_count); } static void ready_async(ErlDrvData drv_data, ErlDrvThreadData thread_data) { async_sqlite3_command *async_command = (async_sqlite3_command *) thread_data; sqlite3_drv_t *drv = async_command->driver_data; int res = #ifdef PRE_R16B driver_output_term(drv->port, #else erl_drv_output_term(driver_mk_port(drv->port), #endif async_command->dataset, async_command->term_count); if (res != 1) { LOG_DEBUG("driver_output_term returned %d\n", res); #ifdef DEBUG fprint_dataset(drv->log, async_command->dataset, async_command->term_count); #endif } LOG_DEBUG("Total term count: %p %d, rows count: %d (%d)\n", async_command->statement, async_command->term_count, async_command->row_count, res); sql_free_async(async_command); } static int prepare(sqlite3_drv_t *drv, char *command, int command_size) { int result; const char *rest; sqlite3_stmt *statement; ErlDrvTermData spec[6]; LOG_DEBUG("Preparing statement: %.*s\n", command_size, command); result = sqlite3_prepare_v2(drv->db, command, command_size, &statement, &rest); if (result != SQLITE_OK) { return output_db_error(drv); } else if (statement == NULL) { return output_error(drv, SQLITE_MISUSE, "empty statement"); } if (drv->prepared_count >= drv->prepared_alloc) { drv->prepared_alloc = (drv->prepared_alloc != 0) ? 2*drv->prepared_alloc : 4; drv->prepared_stmts = driver_realloc(drv->prepared_stmts, drv->prepared_alloc * sizeof(sqlite3_stmt *)); } drv->prepared_stmts[drv->prepared_count] = statement; drv->prepared_count++; spec[0] = ERL_DRV_PORT; spec[1] = driver_mk_port(drv->port); spec[2] = ERL_DRV_UINT; spec[3] = drv->prepared_count - 1; spec[4] = ERL_DRV_TUPLE; spec[5] = 2; return #ifdef PRE_R16B driver_output_term(drv->port, #else erl_drv_output_term(spec[1], #endif spec, sizeof(spec) / sizeof(spec[0])); } static int prepared_bind(sqlite3_drv_t *drv, char *buffer, int buffer_size) { int result; unsigned int prepared_index; long long_prepared_index; int index = 0, type, size; sqlite3_stmt *statement; LOG_DEBUG("Finalizing prepared statement: %.*s\n", buffer_size, buffer); ei_decode_version(buffer, &index, NULL); ei_decode_tuple_header(buffer, &index, &size); // assert(size == 2); ei_decode_long(buffer, &index, &long_prepared_index); prepared_index = (unsigned int) long_prepared_index; if (prepared_index >= drv->prepared_count) { return output_error(drv, SQLITE_MISUSE, "Trying to bind non-existent prepared statement"); } statement = drv->prepared_stmts[prepared_index]; result = bind_parameters(drv, buffer, buffer_size, &index, statement, &type, &size); if (result == SQLITE_OK) { return output_ok(drv); } else { return result; // error has already been output } } static int prepared_columns(sqlite3_drv_t *drv, char *buffer, int buffer_size) { unsigned int prepared_index; long long_prepared_index; int index = 0, term_count = 0, term_allocated = 0, column_count; sqlite3_stmt *statement; ErlDrvTermData *dataset = NULL, port; ptr_list* ptrs = NULL; ei_decode_version(buffer, &index, NULL); ei_decode_long(buffer, &index, &long_prepared_index); prepared_index = (unsigned int) long_prepared_index; if (prepared_index >= drv->prepared_count) { LOG_DEBUG("Tried to get columns for prepared statement #%d, but maximum possible is #%d\n", prepared_index, drv->prepared_count - 1); return output_error(drv, SQLITE_MISUSE, "Trying to reset non-existent prepared statement"); } LOG_DEBUG("Getting the columns for prepared statement #%d\n", prepared_index); statement = drv->prepared_stmts[prepared_index]; port = driver_mk_port(drv->port); EXTEND_DATASET_DIRECT(2); append_to_dataset(2, dataset, term_count, ERL_DRV_PORT, port); column_count = sqlite3_column_count(statement); get_columns( drv, statement, column_count, 2, &term_count, &term_allocated, &ptrs, &dataset); EXTEND_DATASET_DIRECT(2); append_to_dataset(2, dataset, term_count, ERL_DRV_TUPLE, (ErlDrvTermData) 2); #ifdef PRE_R16B driver_output_term(drv->port, #else erl_drv_output_term(port, #endif dataset, term_count); free_ptr_list(ptrs, driver_free_fun); driver_free(dataset); return 0; } static int prepared_step(sqlite3_drv_t *drv, char *buffer, int buffer_size) { unsigned int prepared_index; long long_prepared_index; int index = 0; sqlite3_stmt *statement; async_sqlite3_command *async_command; ei_decode_version(buffer, &index, NULL); ei_decode_long(buffer, &index, &long_prepared_index); prepared_index = (unsigned int) long_prepared_index; if (prepared_index >= drv->prepared_count) { LOG_DEBUG("Tried to make a step in prepared statement #%d, but maximum possible is #%d\n", prepared_index, drv->prepared_count - 1); return output_error(drv, SQLITE_MISUSE, "Trying to evaluate non-existent prepared statement"); } LOG_DEBUG("Making a step in prepared statement #%d\n", prepared_index); statement = drv->prepared_stmts[prepared_index]; async_command = make_async_command_statement(drv, statement, 0); exec_async_command(drv, sql_step_async, async_command); return 0; } static int prepared_reset(sqlite3_drv_t *drv, char *buffer, int buffer_size) { unsigned int prepared_index; long long_prepared_index; int index = 0; sqlite3_stmt *statement; ei_decode_version(buffer, &index, NULL); ei_decode_long(buffer, &index, &long_prepared_index); prepared_index = (unsigned int) long_prepared_index; if (prepared_index >= drv->prepared_count) { LOG_DEBUG("Tried to reset prepared statement #%d, but maximum possible is #%d\n", prepared_index, drv->prepared_count - 1); return output_error(drv, SQLITE_MISUSE, "Trying to reset non-existent prepared statement"); } LOG_DEBUG("Resetting prepared statement #%d\n", prepared_index); // don't bother about error code, any errors should already be shown by step statement = drv->prepared_stmts[prepared_index]; sqlite3_reset(statement); return output_ok(drv); } static int prepared_clear_bindings(sqlite3_drv_t *drv, char *buffer, int buffer_size) { unsigned int prepared_index; long long_prepared_index; int index = 0; sqlite3_stmt *statement; ei_decode_version(buffer, &index, NULL); ei_decode_long(buffer, &index, &long_prepared_index); prepared_index = (unsigned int) long_prepared_index; if (prepared_index >= drv->prepared_count) { LOG_DEBUG("Tried to clear bindings of prepared statement #%d, but maximum possible is #%d\n", prepared_index, drv->prepared_count - 1); return output_error(drv, SQLITE_MISUSE, "Trying to clear bindings of non-existent prepared statement"); } LOG_DEBUG("Clearing bindings of prepared statement #%d\n", prepared_index); statement = drv->prepared_stmts[prepared_index]; sqlite3_clear_bindings(statement); return output_ok(drv); } static int prepared_finalize(sqlite3_drv_t *drv, char *buffer, int buffer_size) { unsigned int prepared_index; long long_prepared_index; int index = 0; ei_decode_version(buffer, &index, NULL); ei_decode_long(buffer, &index, &long_prepared_index); prepared_index = (unsigned int) long_prepared_index; if (prepared_index >= drv->prepared_count) { LOG_DEBUG("Tried to finalize prepared statement #%d, but maximum possible is #%d\n", prepared_index, drv->prepared_count - 1); return output_error(drv, SQLITE_MISUSE, "Trying to finalize non-existent prepared statement"); } LOG_DEBUG("Finalizing prepared statement #%d\n", prepared_index); // finalize the statement and make sure it isn't accidentally executed again sqlite3_finalize(drv->prepared_stmts[prepared_index]); drv->prepared_stmts[prepared_index] = NULL; // if the statement is at the end of the array, space can be reused; // otherwise don't bother if (prepared_index == drv->prepared_count - 1) { drv->prepared_count--; } return output_ok(drv); } // Unknown Command static int unknown(sqlite3_drv_t *drv, char *command, int command_size) { // Return {Port, error, -1, unknown_command} ErlDrvTermData spec[] = { ERL_DRV_PORT, driver_mk_port(drv->port), ERL_DRV_ATOM, drv->atom_error, ERL_DRV_INT, (ErlDrvTermData) ((ErlDrvSInt) -1), ERL_DRV_ATOM, drv->atom_unknown_cmd, ERL_DRV_TUPLE, 4 }; return #ifdef PRE_R16B driver_output_term(drv->port, #else erl_drv_output_term(spec[1], #endif spec, sizeof(spec) / sizeof(spec[0])); } #ifdef DEBUG static void fprint_dataset(FILE* log, ErlDrvTermData *dataset, int term_count) { int i = 0, stack_size = 0; ErlDrvUInt length; fprintf(log, "\nPrinting dataset\n"); while(i < term_count) { switch (dataset[i]) { case ERL_DRV_NIL: fprintf(log, "%d: []", i); i++; stack_size++; break; case ERL_DRV_ATOM: fprintf(log, "%d-%d: an atom", i, i+1); i += 2; stack_size++; break; case ERL_DRV_INT: fprintf(log, "%d-%d: int %ld", i, i+1, (ErlDrvSInt) dataset[i+1]); i += 2; stack_size++; break; case ERL_DRV_PORT: fprintf(log, "%d-%d: a port", i, i+1); i += 2; stack_size++; break; case ERL_DRV_BINARY: fprintf(log, "%d-%d: a binary (length %lu, offset %lu)", i, i+3, (ErlDrvUInt) dataset[i+2], (ErlDrvUInt) dataset[i+3]); i += 4; stack_size++; break; case ERL_DRV_BUF2BINARY: fprintf(log, "%d-%d: a string used as binary (length %lu)", i, i+2, (ErlDrvUInt) dataset[i+2]); i += 3; stack_size++; break; case ERL_DRV_STRING: fprintf(log, "%d-%d: a string (length %lu)", i, i+2, (ErlDrvUInt) dataset[i+2]); i += 3; stack_size++; break; case ERL_DRV_TUPLE: length = (ErlDrvUInt) dataset[i+1]; fprintf(log, "%d-%d: a tuple (size %lu)", i, i+1, length); i += 2; stack_size -= length - 1; break; case ERL_DRV_LIST: length = (ErlDrvUInt) dataset[i+1]; fprintf(log, "%d-%d: a list (length %lu)", i, i+1, length); i += 2; stack_size -= length - 1; break; case ERL_DRV_PID: fprintf(log, "%d-%d: a pid", i, i+1); i += 2; stack_size++; break; case ERL_DRV_STRING_CONS: length = (ErlDrvUInt) dataset[i+2]; fprintf(log, "%d-%d: a string inside surrounding list (length %lu)", i, i+2, length); i += 3; break; case ERL_DRV_FLOAT: fprintf(log, "%d-%d: float %f", i, i+1, (double) dataset[i+1]); i += 2; stack_size++; break; case ERL_DRV_EXT2TERM: fprintf(log, "%d-%d: a term in external format of length %lu", i, i+1, (ErlDrvUInt) dataset[i+1]); i += 2; stack_size++; break; case ERL_DRV_INT64: #if defined(_MSC_VER) fprintf(log, "%d-%d: int %I64d", i, i+1, (ErlDrvSInt64) dataset[i+1]); #else fprintf(log, "%d-%d: int %lld", i, i+1, (ErlDrvSInt64) dataset[i+1]); #endif i += 2; stack_size++; break; case ERL_DRV_UINT64: #if defined(_MSC_VER) fprintf(log, "%d-%d: int %I64lu", i, i+1, (ErlDrvUInt64) dataset[i+1]); #else fprintf(log, "%d-%d: int %llu", i, i+1, (ErlDrvUInt64) dataset[i+1]); #endif i += 2; stack_size++; break; default: fprintf(log, "%d: unexpected type", i); i++; break; } fprintf(log, ".\tStack size: %d\n", stack_size); fflush(log); } } #endif