From 1f42c1fe3958b29172720939b556b52ac45053d8 Mon Sep 17 00:00:00 2001 From: Tee Teoh Date: Fri, 13 Jun 2008 00:25:32 -0400 Subject: [PATCH] Listing, info and creating tables now working. --- priv/sqlite_port.c | 130 ++++++++++++++++++++++++++++----------------- src/sqlite.erl | 95 +++++++++++++++++++++++++++++---- 2 files changed, 166 insertions(+), 59 deletions(-) diff --git a/priv/sqlite_port.c b/priv/sqlite_port.c index 2de93ee..764913d 100644 --- a/priv/sqlite_port.c +++ b/priv/sqlite_port.c @@ -7,24 +7,40 @@ #include "erl_interface.h" #include "ei.h" +#define MASTER_QUERY "select * from sqlite_master where type='table';" + static FILE *log; -typedef struct { - ETERM *result; -} Result; - -static Result r; +static ETERM *result; +void respond(ETERM *r); void send_error(char *err_msg); void send_result(); void send_ok(); -static int callback(void *notUsed, int argc, char **argv, char **azColName) { +// 4 sql = CREATE TABLE t1 (t1key INTEGER PRIMARY KEY, data TEXT, num double, timeEnter DATE) +static int list_tables(void *notUsed, int argc, char **argv, char **azColName) +{ + if (result == 0) { + result = erl_mk_empty_list(); + } + + fprintf(log, "%d %s = %s\n", 2, azColName[2], argv[2]); + fprintf(log, "\n"); + fflush(log); + + result = erl_cons(erl_mk_atom(argv[2]), result); + + return 0; +} + +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(); + if (result == 0) { + result = erl_mk_empty_list(); } record_list = malloc(argc * sizeof(ETERM *)); @@ -42,7 +58,7 @@ static int callback(void *notUsed, int argc, char **argv, char **azColName) { fprintf(log, "\n"); fflush(log); - r.result = erl_cons(erl_mk_tuple(record_list, argc), r.result); + result = erl_cons(erl_mk_tuple(record_list, argc), result); free(record_list); return 0; @@ -81,17 +97,39 @@ int main(int argc, char **argv) sqlite3_close(db); break; - } else if (strncmp((const char *)ERL_ATOM_PTR(fnp), "sql_exec", 8) == 0) { + } + else if (strncmp((const char *)ERL_ATOM_PTR(fnp), "list_tables", 11) == 0) { + fprintf(log, "calling list_tables\n"); + + result = 0; + + rc = sqlite3_exec(db, MASTER_QUERY, list_tables, 0, &zErrMsg); + if (rc != SQLITE_OK) { + send_error(zErrMsg); + sqlite3_free(zErrMsg); + } + else if (result != 0) { + send_result(); + } + else { + // not an error and no results. still need to return something + send_ok(); + } + + fflush(log); + + } + 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)); - r.result = 0; + 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) { + else if (result != 0) { send_result(); } else { @@ -112,53 +150,49 @@ int main(int argc, char **argv) return 0; } -void send_error(char *err_msg) { - ETERM **tup_list; - ETERM *result; - byte buf[1024]; - - tup_list = malloc(sizeof(ETERM *) * 2); +void send_error(char *err_msg) +{ + ETERM *tup_list[2]; + ETERM *to_send; tup_list[0] = erl_mk_atom("sql_error"); tup_list[1] = erl_mk_string(err_msg); - result = erl_mk_tuple(tup_list, 2); - - bzero(buf, 1024); - erl_encode(result, buf); - write_cmd(buf, erl_term_len(result)); + to_send = erl_mk_tuple(tup_list, 2); fprintf(log, "SQL Error: %s\n", err_msg); + respond(to_send); erl_free_term(tup_list[0]); erl_free_term(tup_list[1]); - free(tup_list); + erl_free_compound(to_send); +} + +void send_result() +{ + fprintf(log, "returning at len %d\n", erl_term_len(result)); + respond(result); + erl_free_compound(result); + result = 0; } -void send_result() { +void send_ok() +{ + ETERM *to_send; + + to_send = erl_mk_atom("ok"); + fprintf(log, "returning ok at len %d\n", erl_term_len(to_send)); + respond(to_send); + + erl_free_term(to_send); +} + +void respond(ETERM *r) +{ 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; + erl_encode(r, buf); + write_cmd(buf, erl_term_len(r)); + + fprintf(log, "sending response back\n"); } - -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); -} - diff --git a/src/sqlite.erl b/src/sqlite.erl index 4e080b4..a3731ee 100644 --- a/src/sqlite.erl +++ b/src/sqlite.erl @@ -15,6 +15,10 @@ -export([stop/1, close/1]). -export([sql_exec/2]). +-export([create_table/3]). +-export([list_tables/1, table_info/2]). +-export([create_table_sql/2]). + %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -54,6 +58,16 @@ stop(Db) -> sql_exec(Db, SQL) -> gen_server:call(Db, {sql_exec, SQL}). +create_table(Db, Tbl, Options) -> + gen_server:call(Db, {create_table, Tbl, Options}). + +% returns list or ok +list_tables(Db) -> + gen_server:call(Db, list_tables). + +table_info(Db, Tbl) -> + gen_server:call(Db, {table_info, Tbl}). + %%==================================================================== %% gen_server callbacks %%==================================================================== @@ -83,17 +97,22 @@ 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 = exec(Port, {sql_exec, SQL}), + {reply, Reply, State}; +handle_call(list_tables, _From, #state{port = Port} = State) -> + Reply = exec(Port, {list_tables, none}), + {reply, Reply, State}; +handle_call({table_info, Tbl}, _From, #state{port = Port} = State) -> + % make sure we only get table info + SQL = io_lib:format("select sql from sqlite_master where tbl_name = '~p' and type='table';", [Tbl]), + Cmd = {sql_exec, SQL}, + [{Info}] = exec(Port, Cmd), + Reply = parse_table_info(Info), + {reply, Reply, State}; +handle_call({create_table, Tbl, Options}, _From, #state{port = Port} = State) -> + SQL = create_table_sql(Tbl, Options), + Cmd = {sql_exec, SQL}, + Reply = exec(Port, Cmd), {reply, Reply, State}; handle_call(_Request, _From, State) -> Reply = ok, @@ -145,4 +164,58 @@ code_change(_OldVsn, State, _Extra) -> create_cmd(Dbase) -> "sqlite_port " ++ Dbase. +exec(Port, Cmd) -> + port_command(Port, term_to_binary(Cmd)), + 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. + +parse_table_info(Info) -> + [_, Tail] = string:tokens(Info, "()"), + Cols = string:tokens(Tail, ","), + 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), col_type(ColType)}| Acc]); +build_table_info([[ColName, ColType, "PRIMARY", "KEY"] | Tl], Acc) -> + build_table_info(Tl, [{list_to_atom(ColName), col_type(ColType)}| Acc]). + +create_table_sql(Tbl, [{Name, Type} | Tl]) -> + CT = io_lib:format("CREATE TABLE ~p ", [Tbl]), + Start = io_lib:format("(~p ~s PRIMARY KEY, ", [Name, col_type(Type)]), + End = string:join( + lists:map(fun({Name0, Type0}) -> + io_lib:format("~p ~s", [Name0, col_type(Type0)]) + end, Tl), ", ") ++ ");", + lists:flatten(CT ++ Start ++ End). + +col_type("INTEGER") -> + integer; +col_type("TEXT") -> + text; +col_type("double") -> + double; +col_type("DOUBLE") -> + double; +col_type("DATE") -> + date; +col_type(integer) -> + "INTEGER"; +col_type(text) -> + "TEXT"; +col_type(double) -> + "DOUBLE"; +col_type(date) -> + "DATE".