From 7a4ae6f5397d24f3970ec4b652c7a80e4dbe1a00 Mon Sep 17 00:00:00 2001 From: Tee Teoh Date: Wed, 4 Jun 2008 23:14:51 -0400 Subject: [PATCH] Working version of sqlite --- priv/Makefile | 3 +- priv/sqlite_port.c | 144 ++++++++++++++++++++++++++++++++++++--------- src/sqlite.erl | 95 ++++++++++++++++++++++++++++-- 3 files changed, 209 insertions(+), 33 deletions(-) diff --git a/priv/Makefile b/priv/Makefile index 47c8650..4ec8355 100644 --- a/priv/Makefile +++ b/priv/Makefile @@ -1,7 +1,8 @@ OTP_DIR=/opt/local/lib/erlang/lib/erl_interface-3.5.5.3 LIB=-lsqlite3 -lerl_interface -lei FLAGS=-I$(OTP_DIR)/include -L$(OTP_DIR)/lib -GCC=/opt/local/bin/gcc-apple-4.2 +#GCC=/opt/local/bin/gcc-apple-4.2 +GCC=/usr/bin/gcc all: erl_comm.c sqlite_port.c $(GCC) -Wall -o sqlite_port erl_comm.c sqlite_port.c $(FLAGS) $(LIB) diff --git a/priv/sqlite_port.c b/priv/sqlite_port.c index de385ce..2de93ee 100644 --- a/priv/sqlite_port.c +++ b/priv/sqlite_port.c @@ -7,18 +7,61 @@ #include "erl_interface.h" #include "ei.h" +static FILE *log; + +typedef struct { + ETERM *result; +} Result; + +static Result r; + +void send_error(char *err_msg); +void send_result(); +void send_ok(); + +static int callback(void *notUsed, int argc, char **argv, char **azColName) { + ETERM **record_list; + int i; + + if (r.result == 0) { + r.result = erl_mk_empty_list(); + } + + record_list = malloc(argc * sizeof(ETERM *)); + + fprintf(log, "runs %d\n", argc); + for (i = 0; i < argc; i++) { + fprintf(log, "%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL"); + if (argv[i]) { + record_list[i] = erl_mk_string(argv[i]); + } + else { + record_list[i] = erl_mk_empty_list(); + } + } + fprintf(log, "\n"); + fflush(log); + + r.result = erl_cons(erl_mk_tuple(record_list, argc), r.result); + + free(record_list); + return 0; +} + int main(int argc, char **argv) { - ETERM *tuplep, *intp; + ETERM *tuplep; ETERM *fnp, *argp; - int res; - byte buf[100]; - long allocated, freed; + byte buf[1024]; sqlite3 *db; char *zErrMsg = 0; int rc; + log = fopen("/tmp/sqlite_port.log", "a+"); + fprintf(log, "******start log (%s)******\n", argv[1]); + fflush(log); + rc = sqlite3_open(argv[1], &db); if (rc) { sqlite3_close(db); @@ -33,42 +76,89 @@ int main(int argc, char **argv) argp = erl_element(2, tuplep); if (strncmp((const char *)ERL_ATOM_PTR(fnp), "close", 5) == 0) { + fprintf(log, "closing sqlite3_close\n"); + fflush(log); + sqlite3_close(db); break; - } else if (strncmp((const char *)ERL_ATOM_PTR(fnp), "bar", 3) == 0) { - res = ERL_INT_VALUE(argp); - } + } else if (strncmp((const char *)ERL_ATOM_PTR(fnp), "sql_exec", 8) == 0) { + fprintf(log, "calling sqlite3_exec %s\n", erl_iolist_to_string(argp)); - intp = erl_mk_int(res); - erl_encode(intp, buf); - write_cmd(buf, erl_term_len(intp)); + r.result = 0; + + rc = sqlite3_exec(db, erl_iolist_to_string(argp), callback, 0, &zErrMsg); + if (rc != SQLITE_OK) { + send_error(zErrMsg); + sqlite3_free(zErrMsg); + } + else if (r.result != 0) { + send_result(); + } + else { + // not an error and no results. still need to return something + send_ok(); + } + + fflush(log); + } erl_free_compound(tuplep); erl_free_term(fnp); erl_free_term(argp); - erl_free_term(intp); } + fprintf(log, "******end log******\n"); + fclose(log); return 0; } +void send_error(char *err_msg) { + ETERM **tup_list; + ETERM *result; + byte buf[1024]; + tup_list = malloc(sizeof(ETERM *) * 2); + + tup_list[0] = erl_mk_atom("sql_error"); + tup_list[1] = erl_mk_string(err_msg); + result = erl_mk_tuple(tup_list, 2); -static int callback(void *notUsed, int argc, char **argv, char **azColName) { - int i; - printf("runs %d\n", argc); - for (i = 1; i < argc; i++) { - printf("%s = %s\n", azColName[i], argv[1] ? argv[1] : "NULL"); - } - printf("\n"); - return 0; + bzero(buf, 1024); + erl_encode(result, buf); + write_cmd(buf, erl_term_len(result)); + + fprintf(log, "SQL Error: %s\n", err_msg); + + erl_free_term(tup_list[0]); + erl_free_term(tup_list[1]); + free(tup_list); + erl_free_compound(result); +} + +void send_result() { + byte buf[2048]; + + bzero(buf, 2048); + erl_encode(r.result, buf); + write_cmd(buf, erl_term_len(r.result)); + + fprintf(log, "returning at len %d\n", erl_term_len(r.result)); + + erl_free_compound(r.result); + r.result = 0; +} + +void send_ok() { + ETERM *result; + byte buf[48]; + + result = erl_mk_atom("ok"); + bzero(buf, 48); + erl_encode(result, buf); + write_cmd(buf, erl_term_len(result)); + + fprintf(log, "returning ok at len %d\n", erl_term_len(result)); + + erl_free_term(result); } -/* - rc = sqlite3_exec(db, "SELECT * from t1;", callback, 0, &zErrMsg); - if (rc != SQLITE_OK) { - fprintf(stderr, "SQL Error: %s\n", zErrMsg); - sqlite3_free(zErrMsg); - } - sqlite3_close(db); -*/ diff --git a/src/sqlite.erl b/src/sqlite.erl index 6d7de13..4e080b4 100644 --- a/src/sqlite.erl +++ b/src/sqlite.erl @@ -10,7 +10,8 @@ -behaviour(gen_server). %% API --export([start_link/1]). +-export([open/1, open/2]). +-export([start_link/1, start_link/2]). -export([stop/1, close/1]). -export([sql_exec/2]). @@ -36,16 +37,23 @@ start_link(Db) -> %% {db, DbFile :: String()} %%-------------------------------------------------------------------- start_link(Db, Options) -> - gen_server:start_link({local, Db}, ?MODULE, [], Options). + gen_server:start_link({local, Db}, ?MODULE, Options, []). + +open(Db) -> + ?MODULE:start_link(Db). + +open(Db, Options) -> + ?MODULE:start_link(Db, Options). close(Db) -> gen_server:call(Db, close). stop(Db) -> - close(Db). + ?MODULE:close(Db). sql_exec(Db, SQL) -> gen_server:call(Db, {sql_exec, SQL}). + %%==================================================================== %% gen_server callbacks %%==================================================================== @@ -58,6 +66,83 @@ sql_exec(Db, SQL) -> %% Description: Initiates the server %%-------------------------------------------------------------------- init(Options) -> - Dbase = proplist:get_value(db, Options), - Port = open_port({spawn, create_cmd(Dbase) + Dbase = proplists:get_value(db, Options), + Port = open_port({spawn, create_cmd(Dbase)}, [{packet, 2}, binary]), + {ok, #state{port = Port, ops = Options}}. + +%%-------------------------------------------------------------------- +%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% Description: Handling call messages +%%-------------------------------------------------------------------- +handle_call(close, _From, State) -> + Reply = ok, + {stop, normal, Reply, State}; +handle_call({sql_exec, SQL}, _From, #state{port = Port} = State) -> + port_command(Port, term_to_binary({sql_exec, SQL})), + Reply = receive + {Port, {data, Data}} when is_binary(Data) -> + List = binary_to_term(Data), + if is_list(List) -> + lists:reverse(List); + true -> List + end; + _ -> + ok + end, + {reply, Reply, State}; +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast(_Msg, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling all non call/cast messages +%%-------------------------------------------------------------------- +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> void() +%% Description: 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. +%%-------------------------------------------------------------------- +terminate(normal, #state{port = Port}) -> + port_command(Port, term_to_binary({close, nop})), + port_close(Port), + ok; +terminate(_Reason, _State) -> + ok. + +%%-------------------------------------------------------------------- +%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +create_cmd(Dbase) -> "sqlite_port " ++ Dbase. + +