erlang-sqlite3-1.1.15/ 0000775 0001750 0001750 00000000000 14637223250 015015 5 ustar debalance debalance erlang-sqlite3-1.1.15/test.sh 0000775 0001750 0001750 00000000070 14637223250 016330 0 ustar debalance debalance # separate file to simplify valgrind use ./rebar eunit erlang-sqlite3-1.1.15/.project 0000664 0001750 0001750 00000000734 14637223250 016470 0 ustar debalance debalance erlang-sqlite3 org.erlide.core.erlbuilder org.erlide.core.builder.dialyzer org.erlide.core.erlnature erlang-sqlite3-1.1.15/rebar.config 0000664 0001750 0001750 00000002041 14637223250 017274 0 ustar debalance debalance % -*- mode: erlang -*- {port_specs, [{"^((?!darwin).)*$", "priv/sqlite3_drv.so", ["c_src/*.c"]}, {"darwin", "priv/sqlite3_drv.so", ["c_src/*.c", "sqlite3_amalgamation/*.c"]}]}. {port_env, [{"DRV_CFLAGS", "$DRV_CFLAGS -Wall -Wextra -Wno-unused-parameter -Wstrict-prototypes"}, {"^((?!darwin|win32).)*$", "DRV_LDFLAGS", "$DRV_LDFLAGS -lsqlite3"}, {"ERL_LDFLAGS", " -L$ERL_EI_LIBDIR -lei"}, {"darwin", "DRV_CFLAGS", "$DRV_CFLAGS -Isqlite3_amalgamation"}, {".*win32.*", "DRV_CFLAGS", "$DRV_CFLAGS /O2 /Isqlite3_amalgamation /Ic_src /W4 /wd4100 /wd4204 /wd4820 /wd4255 /wd4668 /wd4710 /wd4711"}, {".*win32.*", "DRV_LDFLAGS", "$DRV_LDFLAGS sqlite3.lib"}]}. {cover_enabled, true}. {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}. {cover_export_enabled, true}. {coveralls_coverdata , "_build/test/cover/eunit.coverdata"}. {coveralls_service_name , "github"}. {xref_checks, [undefined_function_calls, undefined_functions, deprecated_function_calls, deprecated_functions]}. erlang-sqlite3-1.1.15/src/ 0000775 0001750 0001750 00000000000 14637223250 015604 5 ustar debalance debalance erlang-sqlite3-1.1.15/src/sqlite3_lib.erl 0000664 0001750 0001750 00000044723 14637223250 020534 0 ustar debalance debalance %%%------------------------------------------------------------------- %%% 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, 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) -> if is_binary(X) orelse is_list(X) -> [$', binary_or_unicode_to_binary(X), $']; true -> value_to_sql(X) 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), $']; _ when is_binary(X) orelse is_list(X) -> Bin = binary_or_unicode_to_binary(X), [$', binary:replace(Bin, <<"'">>, <<"''">>, [global]), $'] 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 binary_or_unicode_to_binary(binary() | unicode:charlist()) -> binary(). binary_or_unicode_to_binary(Bin) when is_binary(Bin) -> Bin; binary_or_unicode_to_binary(CharList) when is_list(CharList) -> unicode:characters_to_binary(CharList). %%-------------------------------------------------------------------- %% @doc Converts a plain binary to its hexadecimal encoding, to be %% passed as a blob literal. %% @end %%-------------------------------------------------------------------- -spec bin_to_hex(binary()) -> 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, ", "), ")"]; {foreign_key, {Columns, Parent, ParentColumns, Action}} -> ["FOREIGN KEY(", map_intersperse(fun indexed_column_sql/1, Columns, ", "), ") REFERENCES ", atom_to_list(Parent), "(", map_intersperse(fun indexed_column_sql/1, ParentColumns, ", "), ")", Action]; {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.15/src/sqlite3.app.src 0000664 0001750 0001750 00000001076 14637223250 020464 0 ustar debalance debalance {application, sqlite3, [ {description, "SQLite3 Interface"}, {vsn, "1.1.15"}, {modules, []}, {registered, []}, {applications, [kernel, stdlib]}, {env, []}, %% hex.pm packaging: {files, ["src/", "include/", "c_src/sqlite3_drv.c", "c_src/sqlite3_drv.h", "sqlite3_amalgamation/shell.c", "sqlite3_amalgamation/sqlite3.c", "sqlite3_amalgamation/sqlite3.h", "sqlite3_amalgamation/sqlite3ext.h", "rebar.config", "rebar.config.script", "README.md", "LICENSE"]}, {licenses, ["EPL 1.1"]}, {links, [{"Github", "https://github.com/processone/erlang-sqlite3"}]}]}. erlang-sqlite3-1.1.15/src/sqlite3.erl 0000664 0001750 0001750 00000146333 14637223250 017706 0 ustar debalance debalance %%%------------------------------------------------------------------- %%% 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([changes/1, changes/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 %% Get affected rows. %% @end %%-------------------------------------------------------------------- changes(Db) -> gen_server:call(Db, changes). changes(Db, Timeout) -> gen_server:call(Db, changes, Timeout). %%-------------------------------------------------------------------- %% @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) -> PrivDir = get_priv_dir(), case erl_ddll:load(PrivDir, atom_to_list(?DRIVER_NAME)) of ok -> do_init(Options); {error, permanent} -> %% already loaded! do_init(Options); {error, Error} -> Msg = io_lib:format("Error loading ~p: ~s", [?DRIVER_NAME, erl_ddll:format_error(Error)]), {stop, lists:flatten(Msg)} end. -spec do_init([any()]) -> {'ok', #state{}} | {'stop', string()}. do_init(Options) -> DbFile = proplists:get_value(file, Options), Port = open_port({spawn, create_port_cmd(DbFile)}, [binary]), receive {Port, ok} -> {ok, #state{port = Port, ops = Options}}; {Port, {error, Code, Message}} -> Msg = io_lib:format("Error opening DB file ~p: code ~B, message '~s'", [DbFile, Code, Message]), {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(), term()}, #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(changes = 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). -define(CHANGES, 14). 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, changes) -> port_control(Port, ?CHANGES, <<"">>), 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, " \n\t") 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(["NULL" | Tail]) -> [null | build_constraints(Tail)]; build_constraints(["DEFAULT", DefaultValue | Tail]) -> [{default, sqlite3_lib:sql_to_value(DefaultValue)} | build_constraints(Tail)]; % build_constraints(["CHECK", _ | Tail]) -> % build_constraints(Tail); % build_constraints(["REFERENCES", Check | Tail]) -> ... build_constraints(UnknownConstraints) -> [{cant_parse_constraints, string:join(UnknownConstraints, " ")}]. 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.15/Makefile 0000664 0001750 0001750 00000002025 14637223250 016454 0 ustar debalance debalance REBAR=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.obj erlang-sqlite3-1.1.15/LICENSE 0000664 0001750 0001750 00000000205 14637223250 016017 0 ustar debalance debalance This 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.15/rebar.config.script 0000664 0001750 0001750 00000012676 14637223250 020616 0 ustar debalance debalance %%%---------------------------------------------------------------------- %%% File : rebar.config %%% Author : Mickael Remond %%% Purpose : Rebar build script. Compliant with rebar and rebar3. %%% Created : 15 Dec 2015 by Mickael Remond %%% %%% Copyright (C) 2002-2021 ProcessOne, SARL. All Rights Reserved. %%% %%% Licensed under the Apache License, Version 2.0 (the "License"); %%% you may not use this file except in compliance with the License. %%% You may obtain a copy of the License at %%% %%% http://www.apache.org/licenses/LICENSE-2.0 %%% %%% Unless required by applicable law or agreed to in writing, software %%% distributed under the License is distributed on an "AS IS" BASIS, %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% See the License for the specific language governing permissions and %%% limitations under the License. %%% %%%---------------------------------------------------------------------- Cfg = case file:consult(filename:join([filename:dirname(SCRIPT),"vars.config"])) of {ok, Terms} -> Terms; _Err -> [] end ++ [{cflags, "-g -O2 -Wall"}, {ldflags, ""}, {with_gcov, "false"}], {cflags, CfgCFlags} = lists:keyfind(cflags, 1, Cfg), {ldflags, CfgLDFlags} = lists:keyfind(ldflags, 1, Cfg), {with_gcov, CfgWithGCov} = lists:keyfind(with_gcov, 1, Cfg), SysVersion = lists:map(fun erlang:list_to_integer/1, string:tokens(erlang:system_info(version), ".")), IsRebar3 = case application:get_key(rebar, vsn) of {ok, VSN} -> [VSN1 | _] = string:tokens(VSN, "-"), [Maj|_] = string:tokens(VSN1, "."), (list_to_integer(Maj) >= 3); undefined -> lists:keymember(mix, 1, application:loaded_applications()) end, ModCfg0 = fun(F, Cfg, [Key|Tail], Op, Default) -> {OldVal,PartCfg} = case lists:keytake(Key, 1, Cfg) of {value, {_, V1}, V2} -> {V1, V2}; false -> {if Tail == [] -> Default; true -> [] end, Cfg} end, case Tail of [] -> [{Key, Op(OldVal)} | PartCfg]; _ -> [{Key, F(F, OldVal, Tail, Op, Default)} | PartCfg] end end, ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op, Default) end, ModCfgS = fun(Cfg, Keys, Val) -> ModCfg0(ModCfg0, Cfg, Keys, fun(_V) -> Val end, "") end, FilterConfig = fun(F, Cfg, [{Path, true, ModFun, Default} | Tail]) -> F(F, ModCfg0(ModCfg0, Cfg, Path, ModFun, Default), Tail); (F, Cfg, [_ | Tail]) -> F(F, Cfg, Tail); (F, Cfg, []) -> Cfg end, AppendStr = fun(Append) -> fun("") -> Append; (Val) -> Val ++ " " ++ Append end end, AppendList = fun(Append) -> fun(Val) -> Val ++ Append end end, Rebar3DepsFilter = fun(DepsList) -> lists:map(fun({DepName,_, {git,_, {tag,Version}}}) -> {DepName, Version}; (Dep) -> Dep end, DepsList) end, GlobalDepsFilter = fun(Deps) -> DepNames = lists:map(fun({DepName, _, _}) -> DepName; ({DepName, _}) -> DepName end, Deps), lists:filtermap(fun(Dep) -> case code:lib_dir(Dep) of {error, _} -> {true,"Unable to locate dep '"++atom_to_list(Dep)++"' in system deps."}; _ -> false end end, DepNames) end, GithubConfig = case {os:getenv("GITHUB_ACTIONS"), os:getenv("GITHUB_TOKEN")} of {"true", Token} when is_list(Token) -> CONFIG1 = [{coveralls_repo_token, Token}, {coveralls_service_job_id, os:getenv("GITHUB_RUN_ID")}, {coveralls_commit_sha, os:getenv("GITHUB_SHA")}, {coveralls_service_number, os:getenv("GITHUB_RUN_NUMBER")}], case os:getenv("GITHUB_EVENT_NAME") =:= "pull_request" andalso string:tokens(os:getenv("GITHUB_REF"), "/") of [_, "pull", PRNO, _] -> [{coveralls_service_pull_request, PRNO} | CONFIG1]; _ -> CONFIG1 end; _ -> [] end, Rules = [ {[port_env, "CFLAGS"], SysVersion >= [7, 3], AppendStr("-DHAS_ERTS_EXIT"), "$CFLAGS"}, {[port_env, "CFLAGS"], true, AppendStr(CfgCFlags), "$CFLAGS"}, {[port_env, "LDFLAGS"], true, AppendStr(CfgLDFlags), "$LDFLAGS"}, {[post_hooks], (not IsRebar3) and (CfgWithGCov == "true"), AppendList([{eunit, "gcov -o c_src sqlite3_drv"}, {eunit, "mv *.gcov .eunit/"}]), []}, {[post_hooks], IsRebar3 and (CfgWithGCov == "true"), AppendList([{eunit, "gcov -o c_src sqlite3_drv"}, {eunit, "mv *.gcov _build/test/cover/"}]), []}, {[port_env, "LDFLAGS"], CfgWithGCov == "true", AppendStr("--coverage"), ""}, {[port_env, "CFLAGS"], CfgWithGCov == "true", AppendStr("--coverage"), ""}, {[deps], IsRebar3, Rebar3DepsFilter, []}, {[plugins], IsRebar3, AppendList([pc]), []}, {[provider_hooks], IsRebar3, AppendList([{pre, [ {compile, {pc, compile}}, {clean, {pc, clean}} ]}]), []}, {[plugins], os:getenv("COVERALLS") == "true", AppendList([{coveralls, {git, "https://github.com/processone/coveralls-erl.git", {branch, "addjsonfile"}}} ]), []}, {[deps], os:getenv("USE_GLOBAL_DEPS") /= false, GlobalDepsFilter, []} ], Config = FilterConfig(FilterConfig, CONFIG, Rules) ++ GithubConfig, %io:format("Rules:~n~p~n~nCONFIG:~n~p~n~nConfig:~n~p~n", [Rules, CONFIG, Config]), Config. %% Local Variables: %% mode: erlang %% End: %% vim: set filetype=erlang tabstop=8: erlang-sqlite3-1.1.15/.settings/ 0000775 0001750 0001750 00000000000 14637223250 016733 5 ustar debalance debalance erlang-sqlite3-1.1.15/.settings/org.erlide.core.prefs 0000664 0001750 0001750 00000000247 14637223250 022760 0 ustar debalance debalance backend_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.15/AUTHORS 0000664 0001750 0001750 00000000527 14637223250 016071 0 ustar debalance debalance Original 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.15/c_src/ 0000775 0001750 0001750 00000000000 14637223250 016106 5 ustar debalance debalance erlang-sqlite3-1.1.15/c_src/sqlite3_drv.c 0000664 0001750 0001750 00000134463 14637223250 020524 0 ustar debalance debalance #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 do_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 do_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 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 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 */, #if ERL_DRV_EXTENDED_MAJOR_VERSION > 3 || \ (ERL_DRV_EXTENDED_MAJOR_VERSION == 3 && ERL_DRV_EXTENDED_MINOR_VERSION >= 2) NULL /* stop_select */, NULL /* emergency_close */ #else NULL /* stop_select */ #endif }; 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 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"); if (status != SQLITE_OK) { LOG_ERROR("Unable to open file %s: \"%s\"\n\n", db_name, sqlite3_errmsg(db)); output_db_error(drv); } else { LOG_DEBUG("Opened file %s\n", db_name); output_ok(drv); } 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); } // 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; case CMD_CHANGES: changes(drv, buf, (int) len); break; default: unknown(drv, buf, (int) len); } } return 0; } static int changes(sqlite3_drv_t *drv, char *buf, int len) { int changes = sqlite3_changes(drv->db); ErlDrvTermData spec[6]; spec[0] = ERL_DRV_PORT; spec[1] = driver_mk_port(drv->port); spec[2] = ERL_DRV_UINT; spec[3] = changes; 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 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) { // Check is required because we are sometimes accessing // sqlite3 from the emulator thread. Could also be fixed // by making _all_ access except start/stop go through driver_async if (sqlite3_threadsafe()) { 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: %ld", status); output_error(drv, SQLITE_ERROR, "driver_async call failed"); } } else { async_invoke(async_command); ready_async((ErlDrvData) drv, (ErlDrvThreadData) async_command); } } 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 erlang-sqlite3-1.1.15/c_src/sqlite3_drv.h 0000664 0001750 0001750 00000007352 14637223250 020525 0 ustar debalance debalance // 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 #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 #define CMD_CHANGES 14 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); static int changes(sqlite3_drv_t *drv, char *buf, int len); #if defined(_MSC_VER) #pragma warning(default: 4201) #endif erlang-sqlite3-1.1.15/vars.config.in 0000664 0001750 0001750 00000000153 14637223250 017563 0 ustar debalance debalance {with_gcov, "@gcov@"}. %% Local Variables: %% mode: erlang %% End: %% vim: set filetype=erlang tabstop=8: erlang-sqlite3-1.1.15/sqlite3_amalgamation/ 0000775 0001750 0001750 00000000000 14637223250 021113 5 ustar debalance debalance erlang-sqlite3-1.1.15/sqlite3_amalgamation/shell.c 0000664 0001750 0001750 00000450314 14637223250 022375 0 ustar debalance debalance /* ** 2001 September 15 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains code to implement the "sqlite" command line ** utility for accessing SQLite databases. */ #if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) /* This needs to come before any includes for MSVC compiler */ #define _CRT_SECURE_NO_WARNINGS #endif /* ** If requested, include the SQLite compiler options file for MSVC. */ #if defined(INCLUDE_MSVC_H) #include "msvc.h" #endif /* ** No support for loadable extensions in VxWorks. */ #if (defined(__RTP__) || defined(_WRS_KERNEL)) && !SQLITE_OMIT_LOAD_EXTENSION # define SQLITE_OMIT_LOAD_EXTENSION 1 #endif /* ** Enable large-file support for fopen() and friends on unix. */ #ifndef SQLITE_DISABLE_LFS # define _LARGE_FILE 1 # ifndef _FILE_OFFSET_BITS # define _FILE_OFFSET_BITS 64 # endif # define _LARGEFILE_SOURCE 1 #endif #include #include #include #include #include "sqlite3.h" #if SQLITE_USER_AUTHENTICATION # include "sqlite3userauth.h" #endif #include #include #if !defined(_WIN32) && !defined(WIN32) # include # if !defined(__RTP__) && !defined(_WRS_KERNEL) # include # endif # include # include #endif #if HAVE_READLINE # include # include #endif #if HAVE_EDITLINE # include #endif #if HAVE_EDITLINE || HAVE_READLINE # define shell_add_history(X) add_history(X) # define shell_read_history(X) read_history(X) # define shell_write_history(X) write_history(X) # define shell_stifle_history(X) stifle_history(X) # define shell_readline(X) readline(X) #elif HAVE_LINENOISE # include "linenoise.h" # define shell_add_history(X) linenoiseHistoryAdd(X) # define shell_read_history(X) linenoiseHistoryLoad(X) # define shell_write_history(X) linenoiseHistorySave(X) # define shell_stifle_history(X) linenoiseHistorySetMaxLen(X) # define shell_readline(X) linenoise(X) #else # define shell_read_history(X) # define shell_write_history(X) # define shell_stifle_history(X) # define SHELL_USE_LOCAL_GETLINE 1 #endif #if defined(_WIN32) || defined(WIN32) # include # include # define isatty(h) _isatty(h) # ifndef access # define access(f,m) _access((f),(m)) # endif # undef popen # define popen _popen # undef pclose # define pclose _pclose #else /* Make sure isatty() has a prototype. */ extern int isatty(int); # if !defined(__RTP__) && !defined(_WRS_KERNEL) /* popen and pclose are not C89 functions and so are ** sometimes omitted from the header */ extern FILE *popen(const char*,const char*); extern int pclose(FILE*); # else # define SQLITE_OMIT_POPEN 1 # endif #endif #if defined(_WIN32_WCE) /* Windows CE (arm-wince-mingw32ce-gcc) does not provide isatty() * thus we always assume that we have a console. That can be * overridden with the -batch command line option. */ #define isatty(x) 1 #endif /* ctype macros that work with signed characters */ #define IsSpace(X) isspace((unsigned char)X) #define IsDigit(X) isdigit((unsigned char)X) #define ToLower(X) (char)tolower((unsigned char)X) /* On Windows, we normally run with output mode of TEXT so that \n characters ** are automatically translated into \r\n. However, this behavior needs ** to be disabled in some cases (ex: when generating CSV output and when ** rendering quoted strings that contain \n characters). The following ** routines take care of that. */ #if defined(_WIN32) || defined(WIN32) static void setBinaryMode(FILE *out){ fflush(out); _setmode(_fileno(out), _O_BINARY); } static void setTextMode(FILE *out){ fflush(out); _setmode(_fileno(out), _O_TEXT); } #else # define setBinaryMode(X) # define setTextMode(X) #endif /* True if the timer is enabled */ static int enableTimer = 0; /* Return the current wall-clock time */ static sqlite3_int64 timeOfDay(void){ static sqlite3_vfs *clockVfs = 0; sqlite3_int64 t; if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); if( clockVfs->iVersion>=1 && clockVfs->xCurrentTimeInt64!=0 ){ clockVfs->xCurrentTimeInt64(clockVfs, &t); }else{ double r; clockVfs->xCurrentTime(clockVfs, &r); t = (sqlite3_int64)(r*86400000.0); } return t; } #if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) #include #include /* VxWorks does not support getrusage() as far as we can determine */ #if defined(_WRS_KERNEL) || defined(__RTP__) struct rusage { struct timeval ru_utime; /* user CPU time used */ struct timeval ru_stime; /* system CPU time used */ }; #define getrusage(A,B) memset(B,0,sizeof(*B)) #endif /* Saved resource information for the beginning of an operation */ static struct rusage sBegin; /* CPU time at start */ static sqlite3_int64 iBegin; /* Wall-clock time at start */ /* ** Begin timing an operation */ static void beginTimer(void){ if( enableTimer ){ getrusage(RUSAGE_SELF, &sBegin); iBegin = timeOfDay(); } } /* Return the difference of two time_structs in seconds */ static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + (double)(pEnd->tv_sec - pStart->tv_sec); } /* ** Print the timing results. */ static void endTimer(void){ if( enableTimer ){ sqlite3_int64 iEnd = timeOfDay(); struct rusage sEnd; getrusage(RUSAGE_SELF, &sEnd); printf("Run Time: real %.3f user %f sys %f\n", (iEnd - iBegin)*0.001, timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); } } #define BEGIN_TIMER beginTimer() #define END_TIMER endTimer() #define HAS_TIMER 1 #elif (defined(_WIN32) || defined(WIN32)) #include /* Saved resource information for the beginning of an operation */ static HANDLE hProcess; static FILETIME ftKernelBegin; static FILETIME ftUserBegin; static sqlite3_int64 ftWallBegin; typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME); static GETPROCTIMES getProcessTimesAddr = NULL; /* ** Check to see if we have timer support. Return 1 if necessary ** support found (or found previously). */ static int hasTimer(void){ if( getProcessTimesAddr ){ return 1; } else { /* GetProcessTimes() isn't supported in WIN95 and some other Windows ** versions. See if the version we are running on has it, and if it ** does, save off a pointer to it and the current process handle. */ hProcess = GetCurrentProcess(); if( hProcess ){ HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); if( NULL != hinstLib ){ getProcessTimesAddr = (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); if( NULL != getProcessTimesAddr ){ return 1; } FreeLibrary(hinstLib); } } } return 0; } /* ** Begin timing an operation */ static void beginTimer(void){ if( enableTimer && getProcessTimesAddr ){ FILETIME ftCreation, ftExit; getProcessTimesAddr(hProcess,&ftCreation,&ftExit, &ftKernelBegin,&ftUserBegin); ftWallBegin = timeOfDay(); } } /* Return the difference of two FILETIME structs in seconds */ static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ sqlite_int64 i64Start = *((sqlite_int64 *) pStart); sqlite_int64 i64End = *((sqlite_int64 *) pEnd); return (double) ((i64End - i64Start) / 10000000.0); } /* ** Print the timing results. */ static void endTimer(void){ if( enableTimer && getProcessTimesAddr){ FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; sqlite3_int64 ftWallEnd = timeOfDay(); getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); printf("Run Time: real %.3f user %f sys %f\n", (ftWallEnd - ftWallBegin)*0.001, timeDiff(&ftUserBegin, &ftUserEnd), timeDiff(&ftKernelBegin, &ftKernelEnd)); } } #define BEGIN_TIMER beginTimer() #define END_TIMER endTimer() #define HAS_TIMER hasTimer() #else #define BEGIN_TIMER #define END_TIMER #define HAS_TIMER 0 #endif /* ** Used to prevent warnings about unused parameters */ #define UNUSED_PARAMETER(x) (void)(x) /* ** If the following flag is set, then command execution stops ** at an error if we are not interactive. */ static int bail_on_error = 0; /* ** Threat stdin as an interactive input if the following variable ** is true. Otherwise, assume stdin is connected to a file or pipe. */ static int stdin_is_interactive = 1; /* ** The following is the open SQLite database. We make a pointer ** to this database a static variable so that it can be accessed ** by the SIGINT handler to interrupt database processing. */ static sqlite3 *globalDb = 0; /* ** True if an interrupt (Control-C) has been received. */ static volatile int seenInterrupt = 0; /* ** This is the name of our program. It is set in main(), used ** in a number of other places, mostly for error messages. */ static char *Argv0; /* ** Prompt strings. Initialized in main. Settable with ** .prompt main continue */ static char mainPrompt[20]; /* First line prompt. default: "sqlite> "*/ static char continuePrompt[20]; /* Continuation prompt. default: " ...> " */ /* ** Write I/O traces to the following stream. */ #ifdef SQLITE_ENABLE_IOTRACE static FILE *iotrace = 0; #endif /* ** This routine works like printf in that its first argument is a ** format string and subsequent arguments are values to be substituted ** in place of % fields. The result of formatting this string ** is written to iotrace. */ #ifdef SQLITE_ENABLE_IOTRACE static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){ va_list ap; char *z; if( iotrace==0 ) return; va_start(ap, zFormat); z = sqlite3_vmprintf(zFormat, ap); va_end(ap); fprintf(iotrace, "%s", z); sqlite3_free(z); } #endif /* ** Determines if a string is a number of not. */ static int isNumber(const char *z, int *realnum){ if( *z=='-' || *z=='+' ) z++; if( !IsDigit(*z) ){ return 0; } z++; if( realnum ) *realnum = 0; while( IsDigit(*z) ){ z++; } if( *z=='.' ){ z++; if( !IsDigit(*z) ) return 0; while( IsDigit(*z) ){ z++; } if( realnum ) *realnum = 1; } if( *z=='e' || *z=='E' ){ z++; if( *z=='+' || *z=='-' ) z++; if( !IsDigit(*z) ) return 0; while( IsDigit(*z) ){ z++; } if( realnum ) *realnum = 1; } return *z==0; } /* ** A global char* and an SQL function to access its current value ** from within an SQL statement. This program used to use the ** sqlite_exec_printf() API to substitue a string into an SQL statement. ** The correct way to do this with sqlite3 is to use the bind API, but ** since the shell is built around the callback paradigm it would be a lot ** of work. Instead just use this hack, which is quite harmless. */ static const char *zShellStatic = 0; static void shellstaticFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ assert( 0==argc ); assert( zShellStatic ); UNUSED_PARAMETER(argc); UNUSED_PARAMETER(argv); sqlite3_result_text(context, zShellStatic, -1, SQLITE_STATIC); } /* ** This routine reads a line of text from FILE in, stores ** the text in memory obtained from malloc() and returns a pointer ** to the text. NULL is returned at end of file, or if malloc() ** fails. ** ** If zLine is not NULL then it is a malloced buffer returned from ** a previous call to this routine that may be reused. */ static char *local_getline(char *zLine, FILE *in){ int nLine = zLine==0 ? 0 : 100; int n = 0; while( 1 ){ if( n+100>nLine ){ nLine = nLine*2 + 100; zLine = realloc(zLine, nLine); if( zLine==0 ) return 0; } if( fgets(&zLine[n], nLine - n, in)==0 ){ if( n==0 ){ free(zLine); return 0; } zLine[n] = 0; break; } while( zLine[n] ) n++; if( n>0 && zLine[n-1]=='\n' ){ n--; if( n>0 && zLine[n-1]=='\r' ) n--; zLine[n] = 0; break; } } return zLine; } /* ** Retrieve a single line of input text. ** ** If in==0 then read from standard input and prompt before each line. ** If isContinuation is true, then a continuation prompt is appropriate. ** If isContinuation is zero, then the main prompt should be used. ** ** If zPrior is not NULL then it is a buffer from a prior call to this ** routine that can be reused. ** ** The result is stored in space obtained from malloc() and must either ** be freed by the caller or else passed back into this routine via the ** zPrior argument for reuse. */ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ char *zPrompt; char *zResult; if( in!=0 ){ zResult = local_getline(zPrior, in); }else{ zPrompt = isContinuation ? continuePrompt : mainPrompt; #if SHELL_USE_LOCAL_GETLINE printf("%s", zPrompt); fflush(stdout); zResult = local_getline(zPrior, stdin); #else free(zPrior); zResult = shell_readline(zPrompt); if( zResult && *zResult ) shell_add_history(zResult); #endif } return zResult; } /* ** Shell output mode information from before ".explain on", ** saved so that it can be restored by ".explain off" */ typedef struct SavedModeInfo SavedModeInfo; struct SavedModeInfo { int valid; /* Is there legit data in here? */ int mode; /* Mode prior to ".explain on" */ int showHeader; /* The ".header" setting prior to ".explain on" */ int colWidth[100]; /* Column widths prior to ".explain on" */ }; /* ** State information about the database connection is contained in an ** instance of the following structure. */ typedef struct ShellState ShellState; struct ShellState { sqlite3 *db; /* The database */ int echoOn; /* True to echo input commands */ int autoEQP; /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */ int statsOn; /* True to display memory stats before each finalize */ int scanstatsOn; /* True to display scan stats before each finalize */ int backslashOn; /* Resolve C-style \x escapes in SQL input text */ int outCount; /* Revert to stdout when reaching zero */ int cnt; /* Number of records displayed so far */ FILE *out; /* Write results here */ FILE *traceOut; /* Output for sqlite3_trace() */ int nErr; /* Number of errors seen */ int mode; /* An output mode setting */ int writableSchema; /* True if PRAGMA writable_schema=ON */ int showHeader; /* True to show column names in List or Column mode */ unsigned shellFlgs; /* Various flags */ char *zDestTable; /* Name of destination table when MODE_Insert */ char colSeparator[20]; /* Column separator character for several modes */ char rowSeparator[20]; /* Row separator character for MODE_Ascii */ int colWidth[100]; /* Requested width of each column when in column mode*/ int actualWidth[100]; /* Actual width of each column */ char nullValue[20]; /* The text to print when a NULL comes back from ** the database */ SavedModeInfo normalMode;/* Holds the mode just before .explain ON */ char outfile[FILENAME_MAX]; /* Filename for *out */ const char *zDbFilename; /* name of the database file */ char *zFreeOnClose; /* Filename to free when closing */ const char *zVfs; /* Name of VFS to use */ sqlite3_stmt *pStmt; /* Current statement if any. */ FILE *pLog; /* Write log output here */ int *aiIndent; /* Array of indents used in MODE_Explain */ int nIndent; /* Size of array aiIndent[] */ int iIndent; /* Index of current op in aiIndent[] */ }; /* ** These are the allowed shellFlgs values */ #define SHFLG_Scratch 0x00001 /* The --scratch option is used */ #define SHFLG_Pagecache 0x00002 /* The --pagecache option is used */ #define SHFLG_Lookaside 0x00004 /* Lookaside memory is used */ /* ** These are the allowed modes. */ #define MODE_Line 0 /* One column per line. Blank line between records */ #define MODE_Column 1 /* One record per line in neat columns */ #define MODE_List 2 /* One record per line with a separator */ #define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */ #define MODE_Html 4 /* Generate an XHTML table */ #define MODE_Insert 5 /* Generate SQL "insert" statements */ #define MODE_Tcl 6 /* Generate ANSI-C or TCL quoted elements */ #define MODE_Csv 7 /* Quote strings, numbers are plain */ #define MODE_Explain 8 /* Like MODE_Column, but do not truncate data */ #define MODE_Ascii 9 /* Use ASCII unit and record separators (0x1F/0x1E) */ static const char *modeDescr[] = { "line", "column", "list", "semi", "html", "insert", "tcl", "csv", "explain", "ascii", }; /* ** These are the column/row/line separators used by the various ** import/export modes. */ #define SEP_Column "|" #define SEP_Row "\n" #define SEP_Tab "\t" #define SEP_Space " " #define SEP_Comma "," #define SEP_CrLf "\r\n" #define SEP_Unit "\x1F" #define SEP_Record "\x1E" /* ** Number of elements in an array */ #define ArraySize(X) (int)(sizeof(X)/sizeof(X[0])) /* ** Compute a string length that is limited to what can be stored in ** lower 30 bits of a 32-bit signed integer. */ static int strlen30(const char *z){ const char *z2 = z; while( *z2 ){ z2++; } return 0x3fffffff & (int)(z2 - z); } /* ** A callback for the sqlite3_log() interface. */ static void shellLog(void *pArg, int iErrCode, const char *zMsg){ ShellState *p = (ShellState*)pArg; if( p->pLog==0 ) return; fprintf(p->pLog, "(%d) %s\n", iErrCode, zMsg); fflush(p->pLog); } /* ** Output the given string as a hex-encoded blob (eg. X'1234' ) */ static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ int i; char *zBlob = (char *)pBlob; fprintf(out,"X'"); for(i=0; i' && z[i]!='\"' && z[i]!='\''; i++){} if( i>0 ){ fprintf(out,"%.*s",i,z); } if( z[i]=='<' ){ fprintf(out,"<"); }else if( z[i]=='&' ){ fprintf(out,"&"); }else if( z[i]=='>' ){ fprintf(out,">"); }else if( z[i]=='\"' ){ fprintf(out,"""); }else if( z[i]=='\'' ){ fprintf(out,"'"); }else{ break; } z += i + 1; } } /* ** If a field contains any character identified by a 1 in the following ** array, then the string must be quoted for CSV. */ static const char needCsvQuote[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; /* ** Output a single term of CSV. Actually, p->colSeparator is used for ** the separator, which may or may not be a comma. p->nullValue is ** the null value. Strings are quoted if necessary. The separator ** is only issued if bSep is true. */ static void output_csv(ShellState *p, const char *z, int bSep){ FILE *out = p->out; if( z==0 ){ fprintf(out,"%s",p->nullValue); }else{ int i; int nSep = strlen30(p->colSeparator); for(i=0; z[i]; i++){ if( needCsvQuote[((unsigned char*)z)[i]] || (z[i]==p->colSeparator[0] && (nSep==1 || memcmp(z, p->colSeparator, nSep)==0)) ){ i = 0; break; } } if( i==0 ){ putc('"', out); for(i=0; z[i]; i++){ if( z[i]=='"' ) putc('"', out); putc(z[i], out); } putc('"', out); }else{ fprintf(out, "%s", z); } } if( bSep ){ fprintf(p->out, "%s", p->colSeparator); } } #ifdef SIGINT /* ** This routine runs when the user presses Ctrl-C */ static void interrupt_handler(int NotUsed){ UNUSED_PARAMETER(NotUsed); seenInterrupt++; if( seenInterrupt>2 ) exit(1); if( globalDb ) sqlite3_interrupt(globalDb); } #endif /* ** This is the callback routine that the shell ** invokes for each row of a query result. */ static int shell_callback( void *pArg, int nArg, /* Number of result columns */ char **azArg, /* Text of each result column */ char **azCol, /* Column names */ int *aiType /* Column types */ ){ int i; ShellState *p = (ShellState*)pArg; switch( p->mode ){ case MODE_Line: { int w = 5; if( azArg==0 ) break; for(i=0; iw ) w = len; } if( p->cnt++>0 ) fprintf(p->out, "%s", p->rowSeparator); for(i=0; iout,"%*s = %s%s", w, azCol[i], azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator); } break; } case MODE_Explain: case MODE_Column: { if( p->cnt++==0 ){ for(i=0; icolWidth) ){ w = p->colWidth[i]; }else{ w = 0; } if( w==0 ){ w = strlen30(azCol[i] ? azCol[i] : ""); if( w<10 ) w = 10; n = strlen30(azArg && azArg[i] ? azArg[i] : p->nullValue); if( wactualWidth) ){ p->actualWidth[i] = w; } if( p->showHeader ){ if( w<0 ){ fprintf(p->out,"%*.*s%s",-w,-w,azCol[i], i==nArg-1 ? p->rowSeparator : " "); }else{ fprintf(p->out,"%-*.*s%s",w,w,azCol[i], i==nArg-1 ? p->rowSeparator : " "); } } } if( p->showHeader ){ for(i=0; iactualWidth) ){ w = p->actualWidth[i]; if( w<0 ) w = -w; }else{ w = 10; } fprintf(p->out,"%-*.*s%s",w,w,"-----------------------------------" "----------------------------------------------------------", i==nArg-1 ? p->rowSeparator : " "); } } } if( azArg==0 ) break; for(i=0; iactualWidth) ){ w = p->actualWidth[i]; }else{ w = 10; } if( p->mode==MODE_Explain && azArg[i] && strlen30(azArg[i])>w ){ w = strlen30(azArg[i]); } if( i==1 && p->aiIndent && p->pStmt ){ if( p->iIndentnIndent ){ fprintf(p->out, "%*.s", p->aiIndent[p->iIndent], ""); } p->iIndent++; } if( w<0 ){ fprintf(p->out,"%*.*s%s",-w,-w, azArg[i] ? azArg[i] : p->nullValue, i==nArg-1 ? p->rowSeparator : " "); }else{ fprintf(p->out,"%-*.*s%s",w,w, azArg[i] ? azArg[i] : p->nullValue, i==nArg-1 ? p->rowSeparator : " "); } } break; } case MODE_Semi: case MODE_List: { if( p->cnt++==0 && p->showHeader ){ for(i=0; iout,"%s%s",azCol[i], i==nArg-1 ? p->rowSeparator : p->colSeparator); } } if( azArg==0 ) break; for(i=0; inullValue; fprintf(p->out, "%s", z); if( iout, "%s", p->colSeparator); }else if( p->mode==MODE_Semi ){ fprintf(p->out, ";%s", p->rowSeparator); }else{ fprintf(p->out, "%s", p->rowSeparator); } } break; } case MODE_Html: { if( p->cnt++==0 && p->showHeader ){ fprintf(p->out,""); for(i=0; iout,""); output_html_string(p->out, azCol[i]); fprintf(p->out,"\n"); } fprintf(p->out,"\n"); } if( azArg==0 ) break; fprintf(p->out,""); for(i=0; iout,""); output_html_string(p->out, azArg[i] ? azArg[i] : p->nullValue); fprintf(p->out,"\n"); } fprintf(p->out,"\n"); break; } case MODE_Tcl: { if( p->cnt++==0 && p->showHeader ){ for(i=0; iout,azCol[i] ? azCol[i] : ""); if(iout, "%s", p->colSeparator); } fprintf(p->out, "%s", p->rowSeparator); } if( azArg==0 ) break; for(i=0; iout, azArg[i] ? azArg[i] : p->nullValue); if(iout, "%s", p->colSeparator); } fprintf(p->out, "%s", p->rowSeparator); break; } case MODE_Csv: { setBinaryMode(p->out); if( p->cnt++==0 && p->showHeader ){ for(i=0; iout, "%s", p->rowSeparator); } if( nArg>0 ){ for(i=0; iout, "%s", p->rowSeparator); } setTextMode(p->out); break; } case MODE_Insert: { p->cnt++; if( azArg==0 ) break; fprintf(p->out,"INSERT INTO %s",p->zDestTable); if( p->showHeader ){ fprintf(p->out,"("); for(i=0; i0 ? ",": ""; fprintf(p->out, "%s%s", zSep, azCol[i]); } fprintf(p->out,")"); } fprintf(p->out," VALUES("); for(i=0; i0 ? ",": ""; if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ fprintf(p->out,"%sNULL",zSep); }else if( aiType && aiType[i]==SQLITE_TEXT ){ if( zSep[0] ) fprintf(p->out,"%s",zSep); output_quoted_string(p->out, azArg[i]); }else if( aiType && (aiType[i]==SQLITE_INTEGER || aiType[i]==SQLITE_FLOAT) ){ fprintf(p->out,"%s%s",zSep, azArg[i]); }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ const void *pBlob = sqlite3_column_blob(p->pStmt, i); int nBlob = sqlite3_column_bytes(p->pStmt, i); if( zSep[0] ) fprintf(p->out,"%s",zSep); output_hex_blob(p->out, pBlob, nBlob); }else if( isNumber(azArg[i], 0) ){ fprintf(p->out,"%s%s",zSep, azArg[i]); }else{ if( zSep[0] ) fprintf(p->out,"%s",zSep); output_quoted_string(p->out, azArg[i]); } } fprintf(p->out,");\n"); break; } case MODE_Ascii: { if( p->cnt++==0 && p->showHeader ){ for(i=0; i0 ) fprintf(p->out, "%s", p->colSeparator); fprintf(p->out,"%s",azCol[i] ? azCol[i] : ""); } fprintf(p->out, "%s", p->rowSeparator); } if( azArg==0 ) break; for(i=0; i0 ) fprintf(p->out, "%s", p->colSeparator); fprintf(p->out,"%s",azArg[i] ? azArg[i] : p->nullValue); } fprintf(p->out, "%s", p->rowSeparator); break; } } return 0; } /* ** This is the callback routine that the SQLite library ** invokes for each row of a query result. */ static int callback(void *pArg, int nArg, char **azArg, char **azCol){ /* since we don't have type info, call the shell_callback with a NULL value */ return shell_callback(pArg, nArg, azArg, azCol, NULL); } /* ** Set the destination table field of the ShellState structure to ** the name of the table given. Escape any quote characters in the ** table name. */ static void set_table_name(ShellState *p, const char *zName){ int i, n; int needQuote; char *z; if( p->zDestTable ){ free(p->zDestTable); p->zDestTable = 0; } if( zName==0 ) return; needQuote = !isalpha((unsigned char)*zName) && *zName!='_'; for(i=n=0; zName[i]; i++, n++){ if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ){ needQuote = 1; if( zName[i]=='\'' ) n++; } } if( needQuote ) n += 2; z = p->zDestTable = malloc( n+1 ); if( z==0 ){ fprintf(stderr,"Error: out of memory\n"); exit(1); } n = 0; if( needQuote ) z[n++] = '\''; for(i=0; zName[i]; i++){ z[n++] = zName[i]; if( zName[i]=='\'' ) z[n++] = '\''; } if( needQuote ) z[n++] = '\''; z[n] = 0; } /* zIn is either a pointer to a NULL-terminated string in memory obtained ** from malloc(), or a NULL pointer. The string pointed to by zAppend is ** added to zIn, and the result returned in memory obtained from malloc(). ** zIn, if it was not NULL, is freed. ** ** If the third argument, quote, is not '\0', then it is used as a ** quote character for zAppend. */ static char *appendText(char *zIn, char const *zAppend, char quote){ int len; int i; int nAppend = strlen30(zAppend); int nIn = (zIn?strlen30(zIn):0); len = nAppend+nIn+1; if( quote ){ len += 2; for(i=0; idb, zSelect, -1, &pSelect, 0); if( rc!=SQLITE_OK || !pSelect ){ fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; return rc; } rc = sqlite3_step(pSelect); nResult = sqlite3_column_count(pSelect); while( rc==SQLITE_ROW ){ if( zFirstRow ){ fprintf(p->out, "%s", zFirstRow); zFirstRow = 0; } z = (const char*)sqlite3_column_text(pSelect, 0); fprintf(p->out, "%s", z); for(i=1; iout, ",%s", sqlite3_column_text(pSelect, i)); } if( z==0 ) z = ""; while( z[0] && (z[0]!='-' || z[1]!='-') ) z++; if( z[0] ){ fprintf(p->out, "\n;\n"); }else{ fprintf(p->out, ";\n"); } rc = sqlite3_step(pSelect); } rc = sqlite3_finalize(pSelect); if( rc!=SQLITE_OK ){ fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; } return rc; } /* ** Allocate space and save off current error string. */ static char *save_err_msg( sqlite3 *db /* Database to query */ ){ int nErrMsg = 1+strlen30(sqlite3_errmsg(db)); char *zErrMsg = sqlite3_malloc64(nErrMsg); if( zErrMsg ){ memcpy(zErrMsg, sqlite3_errmsg(db), nErrMsg); } return zErrMsg; } /* ** Display memory stats. */ static int display_stats( sqlite3 *db, /* Database to query */ ShellState *pArg, /* Pointer to ShellState */ int bReset /* True to reset the stats */ ){ int iCur; int iHiwtr; if( pArg && pArg->out ){ iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_MEMORY_USED, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Memory Used: %d (max %d) bytes\n", iCur, iHiwtr); iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_MALLOC_COUNT, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Number of Outstanding Allocations: %d (max %d)\n", iCur, iHiwtr); if( pArg->shellFlgs & SHFLG_Pagecache ){ iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_PAGECACHE_USED, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Number of Pcache Pages Used: %d (max %d) pages\n", iCur, iHiwtr); } iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Number of Pcache Overflow Bytes: %d (max %d) bytes\n", iCur, iHiwtr); if( pArg->shellFlgs & SHFLG_Scratch ){ iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_SCRATCH_USED, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Number of Scratch Allocations Used: %d (max %d)\n", iCur, iHiwtr); } iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_SCRATCH_OVERFLOW, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Number of Scratch Overflow Bytes: %d (max %d) bytes\n", iCur, iHiwtr); iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_MALLOC_SIZE, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Largest Allocation: %d bytes\n", iHiwtr); iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_PAGECACHE_SIZE, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Largest Pcache Allocation: %d bytes\n", iHiwtr); iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_SCRATCH_SIZE, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Largest Scratch Allocation: %d bytes\n", iHiwtr); #ifdef YYTRACKMAXSTACKDEPTH iHiwtr = iCur = -1; sqlite3_status(SQLITE_STATUS_PARSER_STACK, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Deepest Parser Stack: %d (max %d)\n", iCur, iHiwtr); #endif } if( pArg && pArg->out && db ){ if( pArg->shellFlgs & SHFLG_Lookaside ){ iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Lookaside Slots Used: %d (max %d)\n", iCur, iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Successful lookaside attempts: %d\n", iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Lookaside failures due to size: %d\n", iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Lookaside failures due to OOM: %d\n", iHiwtr); } iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Pager Heap Usage: %d bytes\n",iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1); fprintf(pArg->out, "Page cache hits: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); fprintf(pArg->out, "Page cache misses: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1); fprintf(pArg->out, "Page cache writes: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Schema Heap Usage: %d bytes\n",iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset); fprintf(pArg->out, "Statement Heap/Lookaside Usage: %d bytes\n",iCur); } if( pArg && pArg->out && db && pArg->pStmt ){ iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, bReset); fprintf(pArg->out, "Fullscan Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset); fprintf(pArg->out, "Sort Operations: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset); fprintf(pArg->out, "Autoindex Inserts: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset); fprintf(pArg->out, "Virtual Machine Steps: %d\n", iCur); } return 0; } /* ** Display scan stats. */ static void display_scanstats( sqlite3 *db, /* Database to query */ ShellState *pArg /* Pointer to ShellState */ ){ #ifndef SQLITE_ENABLE_STMT_SCANSTATUS UNUSED_PARAMETER(db); UNUSED_PARAMETER(pArg); #else int i, k, n, mx; fprintf(pArg->out, "-------- scanstats --------\n"); mx = 0; for(k=0; k<=mx; k++){ double rEstLoop = 1.0; for(i=n=0; 1; i++){ sqlite3_stmt *p = pArg->pStmt; sqlite3_int64 nLoop, nVisit; double rEst; int iSid; const char *zExplain; if( sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_NLOOP, (void*)&nLoop) ){ break; } sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_SELECTID, (void*)&iSid); if( iSid>mx ) mx = iSid; if( iSid!=k ) continue; if( n==0 ){ rEstLoop = (double)nLoop; if( k>0 ) fprintf(pArg->out, "-------- subquery %d -------\n", k); } n++; sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_NVISIT, (void*)&nVisit); sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_EST, (void*)&rEst); sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_EXPLAIN, (void*)&zExplain); fprintf(pArg->out, "Loop %2d: %s\n", n, zExplain); rEstLoop *= rEst; fprintf(pArg->out, " nLoop=%-8lld nRow=%-8lld estRow=%-8lld estRow/Loop=%-8g\n", nLoop, nVisit, (sqlite3_int64)(rEstLoop+0.5), rEst ); } } fprintf(pArg->out, "---------------------------\n"); #endif } /* ** Parameter azArray points to a zero-terminated array of strings. zStr ** points to a single nul-terminated string. Return non-zero if zStr ** is equal, according to strcmp(), to any of the strings in the array. ** Otherwise, return zero. */ static int str_in_array(const char *zStr, const char **azArray){ int i; for(i=0; azArray[i]; i++){ if( 0==strcmp(zStr, azArray[i]) ) return 1; } return 0; } /* ** If compiled statement pSql appears to be an EXPLAIN statement, allocate ** and populate the ShellState.aiIndent[] array with the number of ** spaces each opcode should be indented before it is output. ** ** The indenting rules are: ** ** * For each "Next", "Prev", "VNext" or "VPrev" instruction, indent ** all opcodes that occur between the p2 jump destination and the opcode ** itself by 2 spaces. ** ** * For each "Goto", if the jump destination is earlier in the program ** and ends on one of: ** Yield SeekGt SeekLt RowSetRead Rewind ** or if the P1 parameter is one instead of zero, ** then indent all opcodes between the earlier instruction ** and "Goto" by 2 spaces. */ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ const char *zSql; /* The text of the SQL statement */ const char *z; /* Used to check if this is an EXPLAIN */ int *abYield = 0; /* True if op is an OP_Yield */ int nAlloc = 0; /* Allocated size of p->aiIndent[], abYield */ int iOp; /* Index of operation in p->aiIndent[] */ const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext", "NextIfOpen", "PrevIfOpen", 0 }; const char *azYield[] = { "Yield", "SeekLT", "SeekGT", "RowSetRead", "Rewind", 0 }; const char *azGoto[] = { "Goto", 0 }; /* Try to figure out if this is really an EXPLAIN statement. If this ** cannot be verified, return early. */ zSql = sqlite3_sql(pSql); if( zSql==0 ) return; for(z=zSql; *z==' ' || *z=='\t' || *z=='\n' || *z=='\f' || *z=='\r'; z++); if( sqlite3_strnicmp(z, "explain", 7) ) return; for(iOp=0; SQLITE_ROW==sqlite3_step(pSql); iOp++){ int i; int iAddr = sqlite3_column_int(pSql, 0); const char *zOp = (const char*)sqlite3_column_text(pSql, 1); /* Set p2 to the P2 field of the current opcode. Then, assuming that ** p2 is an instruction address, set variable p2op to the index of that ** instruction in the aiIndent[] array. p2 and p2op may be different if ** the current instruction is part of a sub-program generated by an ** SQL trigger or foreign key. */ int p2 = sqlite3_column_int(pSql, 3); int p2op = (p2 + (iOp-iAddr)); /* Grow the p->aiIndent array as required */ if( iOp>=nAlloc ){ nAlloc += 100; p->aiIndent = (int*)sqlite3_realloc64(p->aiIndent, nAlloc*sizeof(int)); abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int)); } abYield[iOp] = str_in_array(zOp, azYield); p->aiIndent[iOp] = 0; p->nIndent = iOp+1; if( str_in_array(zOp, azNext) ){ for(i=p2op; iaiIndent[i] += 2; } if( str_in_array(zOp, azGoto) && p2opnIndent && (abYield[p2op] || sqlite3_column_int(pSql, 2)) ){ for(i=p2op+1; iaiIndent[i] += 2; } } p->iIndent = 0; sqlite3_free(abYield); sqlite3_reset(pSql); } /* ** Free the array allocated by explain_data_prepare(). */ static void explain_data_delete(ShellState *p){ sqlite3_free(p->aiIndent); p->aiIndent = 0; p->nIndent = 0; p->iIndent = 0; } /* ** Execute a statement or set of statements. Print ** any result rows/columns depending on the current mode ** set via the supplied callback. ** ** This is very similar to SQLite's built-in sqlite3_exec() ** function except it takes a slightly different callback ** and callback data argument. */ static int shell_exec( sqlite3 *db, /* An open database */ const char *zSql, /* SQL to be evaluated */ int (*xCallback)(void*,int,char**,char**,int*), /* Callback function */ /* (not the same as sqlite3_exec) */ ShellState *pArg, /* Pointer to ShellState */ char **pzErrMsg /* Error msg written here */ ){ sqlite3_stmt *pStmt = NULL; /* Statement to execute. */ int rc = SQLITE_OK; /* Return Code */ int rc2; const char *zLeftover; /* Tail of unprocessed SQL */ if( pzErrMsg ){ *pzErrMsg = NULL; } while( zSql[0] && (SQLITE_OK == rc) ){ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); if( SQLITE_OK != rc ){ if( pzErrMsg ){ *pzErrMsg = save_err_msg(db); } }else{ if( !pStmt ){ /* this happens for a comment or white-space */ zSql = zLeftover; while( IsSpace(zSql[0]) ) zSql++; continue; } /* save off the prepared statment handle and reset row count */ if( pArg ){ pArg->pStmt = pStmt; pArg->cnt = 0; } /* echo the sql statement if echo on */ if( pArg && pArg->echoOn ){ const char *zStmtSql = sqlite3_sql(pStmt); fprintf(pArg->out, "%s\n", zStmtSql ? zStmtSql : zSql); } /* Show the EXPLAIN QUERY PLAN if .eqp is on */ if( pArg && pArg->autoEQP ){ sqlite3_stmt *pExplain; char *zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", sqlite3_sql(pStmt)); rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0); if( rc==SQLITE_OK ){ while( sqlite3_step(pExplain)==SQLITE_ROW ){ fprintf(pArg->out,"--EQP-- %d,", sqlite3_column_int(pExplain, 0)); fprintf(pArg->out,"%d,", sqlite3_column_int(pExplain, 1)); fprintf(pArg->out,"%d,", sqlite3_column_int(pExplain, 2)); fprintf(pArg->out,"%s\n", sqlite3_column_text(pExplain, 3)); } } sqlite3_finalize(pExplain); sqlite3_free(zEQP); } /* If the shell is currently in ".explain" mode, gather the extra ** data required to add indents to the output.*/ if( pArg && pArg->mode==MODE_Explain ){ explain_data_prepare(pArg, pStmt); } /* perform the first step. this will tell us if we ** have a result set or not and how wide it is. */ rc = sqlite3_step(pStmt); /* if we have a result set... */ if( SQLITE_ROW == rc ){ /* if we have a callback... */ if( xCallback ){ /* allocate space for col name ptr, value ptr, and type */ int nCol = sqlite3_column_count(pStmt); void *pData = sqlite3_malloc64(3*nCol*sizeof(const char*) + 1); if( !pData ){ rc = SQLITE_NOMEM; }else{ char **azCols = (char **)pData; /* Names of result columns */ char **azVals = &azCols[nCol]; /* Results */ int *aiTypes = (int *)&azVals[nCol]; /* Result types */ int i, x; assert(sizeof(int) <= sizeof(char *)); /* save off ptrs to column names */ for(i=0; imode==MODE_Insert ){ azVals[i] = ""; }else{ azVals[i] = (char*)sqlite3_column_text(pStmt, i); } if( !azVals[i] && (aiTypes[i]!=SQLITE_NULL) ){ rc = SQLITE_NOMEM; break; /* from for */ } } /* end for */ /* if data and types extracted successfully... */ if( SQLITE_ROW == rc ){ /* call the supplied callback with the result row data */ if( xCallback(pArg, nCol, azVals, azCols, aiTypes) ){ rc = SQLITE_ABORT; }else{ rc = sqlite3_step(pStmt); } } } while( SQLITE_ROW == rc ); sqlite3_free(pData); } }else{ do{ rc = sqlite3_step(pStmt); } while( rc == SQLITE_ROW ); } } explain_data_delete(pArg); /* print usage stats if stats on */ if( pArg && pArg->statsOn ){ display_stats(db, pArg, 0); } /* print loop-counters if required */ if( pArg && pArg->scanstatsOn ){ display_scanstats(db, pArg); } /* Finalize the statement just executed. If this fails, save a ** copy of the error message. Otherwise, set zSql to point to the ** next statement to execute. */ rc2 = sqlite3_finalize(pStmt); if( rc!=SQLITE_NOMEM ) rc = rc2; if( rc==SQLITE_OK ){ zSql = zLeftover; while( IsSpace(zSql[0]) ) zSql++; }else if( pzErrMsg ){ *pzErrMsg = save_err_msg(db); } /* clear saved stmt handle */ if( pArg ){ pArg->pStmt = NULL; } } } /* end while */ return rc; } /* ** This is a different callback routine used for dumping the database. ** Each row received by this callback consists of a table name, ** the table type ("index" or "table") and SQL to create the table. ** This routine should print text sufficient to recreate the table. */ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){ int rc; const char *zTable; const char *zType; const char *zSql; const char *zPrepStmt = 0; ShellState *p = (ShellState *)pArg; UNUSED_PARAMETER(azCol); if( nArg!=3 ) return 1; zTable = azArg[0]; zType = azArg[1]; zSql = azArg[2]; if( strcmp(zTable, "sqlite_sequence")==0 ){ zPrepStmt = "DELETE FROM sqlite_sequence;\n"; }else if( sqlite3_strglob("sqlite_stat?", zTable)==0 ){ fprintf(p->out, "ANALYZE sqlite_master;\n"); }else if( strncmp(zTable, "sqlite_", 7)==0 ){ return 0; }else if( strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){ char *zIns; if( !p->writableSchema ){ fprintf(p->out, "PRAGMA writable_schema=ON;\n"); p->writableSchema = 1; } zIns = sqlite3_mprintf( "INSERT INTO sqlite_master(type,name,tbl_name,rootpage,sql)" "VALUES('table','%q','%q',0,'%q');", zTable, zTable, zSql); fprintf(p->out, "%s\n", zIns); sqlite3_free(zIns); return 0; }else{ fprintf(p->out, "%s;\n", zSql); } if( strcmp(zType, "table")==0 ){ sqlite3_stmt *pTableInfo = 0; char *zSelect = 0; char *zTableInfo = 0; char *zTmp = 0; int nRow = 0; zTableInfo = appendText(zTableInfo, "PRAGMA table_info(", 0); zTableInfo = appendText(zTableInfo, zTable, '"'); zTableInfo = appendText(zTableInfo, ");", 0); rc = sqlite3_prepare_v2(p->db, zTableInfo, -1, &pTableInfo, 0); free(zTableInfo); if( rc!=SQLITE_OK || !pTableInfo ){ return 1; } zSelect = appendText(zSelect, "SELECT 'INSERT INTO ' || ", 0); /* Always quote the table name, even if it appears to be pure ascii, ** in case it is a keyword. Ex: INSERT INTO "table" ... */ zTmp = appendText(zTmp, zTable, '"'); if( zTmp ){ zSelect = appendText(zSelect, zTmp, '\''); free(zTmp); } zSelect = appendText(zSelect, " || ' VALUES(' || ", 0); rc = sqlite3_step(pTableInfo); while( rc==SQLITE_ROW ){ const char *zText = (const char *)sqlite3_column_text(pTableInfo, 1); zSelect = appendText(zSelect, "quote(", 0); zSelect = appendText(zSelect, zText, '"'); rc = sqlite3_step(pTableInfo); if( rc==SQLITE_ROW ){ zSelect = appendText(zSelect, "), ", 0); }else{ zSelect = appendText(zSelect, ") ", 0); } nRow++; } rc = sqlite3_finalize(pTableInfo); if( rc!=SQLITE_OK || nRow==0 ){ free(zSelect); return 1; } zSelect = appendText(zSelect, "|| ')' FROM ", 0); zSelect = appendText(zSelect, zTable, '"'); rc = run_table_dump_query(p, zSelect, zPrepStmt); if( rc==SQLITE_CORRUPT ){ zSelect = appendText(zSelect, " ORDER BY rowid DESC", 0); run_table_dump_query(p, zSelect, 0); } free(zSelect); } return 0; } /* ** Run zQuery. Use dump_callback() as the callback routine so that ** the contents of the query are output as SQL statements. ** ** If we get a SQLITE_CORRUPT error, rerun the query after appending ** "ORDER BY rowid DESC" to the end. */ static int run_schema_dump_query( ShellState *p, const char *zQuery ){ int rc; char *zErr = 0; rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr); if( rc==SQLITE_CORRUPT ){ char *zQ2; int len = strlen30(zQuery); fprintf(p->out, "/****** CORRUPTION ERROR *******/\n"); if( zErr ){ fprintf(p->out, "/****** %s ******/\n", zErr); sqlite3_free(zErr); zErr = 0; } zQ2 = malloc( len+100 ); if( zQ2==0 ) return rc; sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery); rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr); if( rc ){ fprintf(p->out, "/****** ERROR: %s ******/\n", zErr); }else{ rc = SQLITE_CORRUPT; } sqlite3_free(zErr); free(zQ2); } return rc; } /* ** Text of a help message */ static char zHelp[] = ".backup ?DB? FILE Backup DB (default \"main\") to FILE\n" ".bail on|off Stop after hitting an error. Default OFF\n" ".binary on|off Turn binary output on or off. Default OFF\n" ".clone NEWDB Clone data into NEWDB from the existing database\n" ".databases List names and files of attached databases\n" ".dbinfo ?DB? Show status information about the database\n" ".dump ?TABLE? ... Dump the database in an SQL text format\n" " If TABLE specified, only dump tables matching\n" " LIKE pattern TABLE.\n" ".echo on|off Turn command echo on or off\n" ".eqp on|off Enable or disable automatic EXPLAIN QUERY PLAN\n" ".exit Exit this program\n" ".explain ?on|off? Turn output mode suitable for EXPLAIN on or off.\n" " With no args, it turns EXPLAIN on.\n" ".fullschema Show schema and the content of sqlite_stat tables\n" ".headers on|off Turn display of headers on or off\n" ".help Show this message\n" ".import FILE TABLE Import data from FILE into TABLE\n" ".indexes ?TABLE? Show names of all indexes\n" " If TABLE specified, only show indexes for tables\n" " matching LIKE pattern TABLE.\n" #ifdef SQLITE_ENABLE_IOTRACE ".iotrace FILE Enable I/O diagnostic logging to FILE\n" #endif ".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT\n" #ifndef SQLITE_OMIT_LOAD_EXTENSION ".load FILE ?ENTRY? Load an extension library\n" #endif ".log FILE|off Turn logging on or off. FILE can be stderr/stdout\n" ".mode MODE ?TABLE? Set output mode where MODE is one of:\n" " ascii Columns/rows delimited by 0x1F and 0x1E\n" " csv Comma-separated values\n" " column Left-aligned columns. (See .width)\n" " html HTML code\n" " insert SQL insert statements for TABLE\n" " line One value per line\n" " list Values delimited by .separator strings\n" " tabs Tab-separated values\n" " tcl TCL list elements\n" ".nullvalue STRING Use STRING in place of NULL values\n" ".once FILENAME Output for the next SQL command only to FILENAME\n" ".open ?FILENAME? Close existing database and reopen FILENAME\n" ".output ?FILENAME? Send output to FILENAME or stdout\n" ".print STRING... Print literal STRING\n" ".prompt MAIN CONTINUE Replace the standard prompts\n" ".quit Exit this program\n" ".read FILENAME Execute SQL in FILENAME\n" ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE\n" ".save FILE Write in-memory database into FILE\n" ".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off\n" ".schema ?TABLE? Show the CREATE statements\n" " If TABLE specified, only show tables matching\n" " LIKE pattern TABLE.\n" ".separator COL ?ROW? Change the column separator and optionally the row\n" " separator for both the output mode and .import\n" ".shell CMD ARGS... Run CMD ARGS... in a system shell\n" ".show Show the current values for various settings\n" ".stats on|off Turn stats on or off\n" ".system CMD ARGS... Run CMD ARGS... in a system shell\n" ".tables ?TABLE? List names of tables\n" " If TABLE specified, only list tables matching\n" " LIKE pattern TABLE.\n" ".timeout MS Try opening locked tables for MS milliseconds\n" ".timer on|off Turn SQL timer on or off\n" ".trace FILE|off Output each SQL statement as it is run\n" ".vfsname ?AUX? Print the name of the VFS stack\n" ".width NUM1 NUM2 ... Set column widths for \"column\" mode\n" " Negative values right-justify\n" ; /* Forward reference */ static int process_input(ShellState *p, FILE *in); /* ** Implementation of the "readfile(X)" SQL function. The entire content ** of the file named X is read and returned as a BLOB. NULL is returned ** if the file does not exist or is unreadable. */ static void readfileFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zName; FILE *in; long nIn; void *pBuf; UNUSED_PARAMETER(argc); zName = (const char*)sqlite3_value_text(argv[0]); if( zName==0 ) return; in = fopen(zName, "rb"); if( in==0 ) return; fseek(in, 0, SEEK_END); nIn = ftell(in); rewind(in); pBuf = sqlite3_malloc64( nIn ); if( pBuf && 1==fread(pBuf, nIn, 1, in) ){ sqlite3_result_blob(context, pBuf, nIn, sqlite3_free); }else{ sqlite3_free(pBuf); } fclose(in); } /* ** Implementation of the "writefile(X,Y)" SQL function. The argument Y ** is written into file X. The number of bytes written is returned. Or ** NULL is returned if something goes wrong, such as being unable to open ** file X for writing. */ static void writefileFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ FILE *out; const char *z; sqlite3_int64 rc; const char *zFile; UNUSED_PARAMETER(argc); zFile = (const char*)sqlite3_value_text(argv[0]); if( zFile==0 ) return; out = fopen(zFile, "wb"); if( out==0 ) return; z = (const char*)sqlite3_value_blob(argv[1]); if( z==0 ){ rc = 0; }else{ rc = fwrite(z, 1, sqlite3_value_bytes(argv[1]), out); } fclose(out); sqlite3_result_int64(context, rc); } /* ** Make sure the database is open. If it is not, then open it. If ** the database fails to open, print an error message and exit. */ static void open_db(ShellState *p, int keepAlive){ if( p->db==0 ){ sqlite3_initialize(); sqlite3_open(p->zDbFilename, &p->db); globalDb = p->db; if( p->db && sqlite3_errcode(p->db)==SQLITE_OK ){ sqlite3_create_function(p->db, "shellstatic", 0, SQLITE_UTF8, 0, shellstaticFunc, 0, 0); } if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ fprintf(stderr,"Error: unable to open database \"%s\": %s\n", p->zDbFilename, sqlite3_errmsg(p->db)); if( keepAlive ) return; exit(1); } #ifndef SQLITE_OMIT_LOAD_EXTENSION sqlite3_enable_load_extension(p->db, 1); #endif sqlite3_create_function(p->db, "readfile", 1, SQLITE_UTF8, 0, readfileFunc, 0, 0); sqlite3_create_function(p->db, "writefile", 2, SQLITE_UTF8, 0, writefileFunc, 0, 0); } } /* ** Do C-language style dequoting. ** ** \a -> alarm ** \b -> backspace ** \t -> tab ** \n -> newline ** \v -> vertical tab ** \f -> form feed ** \r -> carriage return ** \s -> space ** \" -> " ** \' -> ' ** \\ -> backslash ** \NNN -> ascii character NNN in octal */ static void resolve_backslashes(char *z){ int i, j; char c; while( *z && *z!='\\' ) z++; for(i=j=0; (c = z[i])!=0; i++, j++){ if( c=='\\' && z[i+1]!=0 ){ c = z[++i]; if( c=='a' ){ c = '\a'; }else if( c=='b' ){ c = '\b'; }else if( c=='t' ){ c = '\t'; }else if( c=='n' ){ c = '\n'; }else if( c=='v' ){ c = '\v'; }else if( c=='f' ){ c = '\f'; }else if( c=='r' ){ c = '\r'; }else if( c=='"' ){ c = '"'; }else if( c=='\'' ){ c = '\''; }else if( c=='\\' ){ c = '\\'; }else if( c>='0' && c<='7' ){ c -= '0'; if( z[i+1]>='0' && z[i+1]<='7' ){ i++; c = (c<<3) + z[i] - '0'; if( z[i+1]>='0' && z[i+1]<='7' ){ i++; c = (c<<3) + z[i] - '0'; } } } } z[j] = c; } if( j='0' && c<='9' ) return c - '0'; if( c>='a' && c<='f' ) return c - 'a' + 10; if( c>='A' && c<='F' ) return c - 'A' + 10; return -1; } /* ** Interpret zArg as an integer value, possibly with suffixes. */ static sqlite3_int64 integerValue(const char *zArg){ sqlite3_int64 v = 0; static const struct { char *zSuffix; int iMult; } aMult[] = { { "KiB", 1024 }, { "MiB", 1024*1024 }, { "GiB", 1024*1024*1024 }, { "KB", 1000 }, { "MB", 1000000 }, { "GB", 1000000000 }, { "K", 1000 }, { "M", 1000000 }, { "G", 1000000000 }, }; int i; int isNeg = 0; if( zArg[0]=='-' ){ isNeg = 1; zArg++; }else if( zArg[0]=='+' ){ zArg++; } if( zArg[0]=='0' && zArg[1]=='x' ){ int x; zArg += 2; while( (x = hexDigitValue(zArg[0]))>=0 ){ v = (v<<4) + x; zArg++; } }else{ while( IsDigit(zArg[0]) ){ v = v*10 + zArg[0] - '0'; zArg++; } } for(i=0; i=0; i++){} }else{ for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){} } if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff); if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){ return 1; } if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){ return 0; } fprintf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", zArg); return 0; } /* ** Close an output file, assuming it is not stderr or stdout */ static void output_file_close(FILE *f){ if( f && f!=stdout && f!=stderr ) fclose(f); } /* ** Try to open an output file. The names "stdout" and "stderr" are ** recognized and do the right thing. NULL is returned if the output ** filename is "off". */ static FILE *output_file_open(const char *zFile){ FILE *f; if( strcmp(zFile,"stdout")==0 ){ f = stdout; }else if( strcmp(zFile, "stderr")==0 ){ f = stderr; }else if( strcmp(zFile, "off")==0 ){ f = 0; }else{ f = fopen(zFile, "wb"); if( f==0 ){ fprintf(stderr, "Error: cannot open \"%s\"\n", zFile); } } return f; } /* ** A routine for handling output from sqlite3_trace(). */ static void sql_trace_callback(void *pArg, const char *z){ FILE *f = (FILE*)pArg; if( f ){ int i = (int)strlen(z); while( i>0 && z[i-1]==';' ){ i--; } fprintf(f, "%.*s;\n", i, z); } } /* ** A no-op routine that runs with the ".breakpoint" doc-command. This is ** a useful spot to set a debugger breakpoint. */ static void test_breakpoint(void){ static int nCall = 0; nCall++; } /* ** An object used to read a CSV and other files for import. */ typedef struct ImportCtx ImportCtx; struct ImportCtx { const char *zFile; /* Name of the input file */ FILE *in; /* Read the CSV text from this input stream */ char *z; /* Accumulated text for a field */ int n; /* Number of bytes in z */ int nAlloc; /* Space allocated for z[] */ int nLine; /* Current line number */ int cTerm; /* Character that terminated the most recent field */ int cColSep; /* The column separator character. (Usually ",") */ int cRowSep; /* The row separator character. (Usually "\n") */ }; /* Append a single byte to z[] */ static void import_append_char(ImportCtx *p, int c){ if( p->n+1>=p->nAlloc ){ p->nAlloc += p->nAlloc + 100; p->z = sqlite3_realloc64(p->z, p->nAlloc); if( p->z==0 ){ fprintf(stderr, "out of memory\n"); exit(1); } } p->z[p->n++] = (char)c; } /* Read a single field of CSV text. Compatible with rfc4180 and extended ** with the option of having a separator other than ",". ** ** + Input comes from p->in. ** + Store results in p->z of length p->n. Space to hold p->z comes ** from sqlite3_malloc64(). ** + Use p->cSep as the column separator. The default is ",". ** + Use p->rSep as the row separator. The default is "\n". ** + Keep track of the line number in p->nLine. ** + Store the character that terminates the field in p->cTerm. Store ** EOF on end-of-file. ** + Report syntax errors on stderr */ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ int c; int cSep = p->cColSep; int rSep = p->cRowSep; p->n = 0; c = fgetc(p->in); if( c==EOF || seenInterrupt ){ p->cTerm = EOF; return 0; } if( c=='"' ){ int pc, ppc; int startLine = p->nLine; int cQuote = c; pc = ppc = 0; while( 1 ){ c = fgetc(p->in); if( c==rSep ) p->nLine++; if( c==cQuote ){ if( pc==cQuote ){ pc = 0; continue; } } if( (c==cSep && pc==cQuote) || (c==rSep && pc==cQuote) || (c==rSep && pc=='\r' && ppc==cQuote) || (c==EOF && pc==cQuote) ){ do{ p->n--; }while( p->z[p->n]!=cQuote ); p->cTerm = c; break; } if( pc==cQuote && c!='\r' ){ fprintf(stderr, "%s:%d: unescaped %c character\n", p->zFile, p->nLine, cQuote); } if( c==EOF ){ fprintf(stderr, "%s:%d: unterminated %c-quoted field\n", p->zFile, startLine, cQuote); p->cTerm = c; break; } import_append_char(p, c); ppc = pc; pc = c; } }else{ while( c!=EOF && c!=cSep && c!=rSep ){ import_append_char(p, c); c = fgetc(p->in); } if( c==rSep ){ p->nLine++; if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--; } p->cTerm = c; } if( p->z ) p->z[p->n] = 0; return p->z; } /* Read a single field of ASCII delimited text. ** ** + Input comes from p->in. ** + Store results in p->z of length p->n. Space to hold p->z comes ** from sqlite3_malloc64(). ** + Use p->cSep as the column separator. The default is "\x1F". ** + Use p->rSep as the row separator. The default is "\x1E". ** + Keep track of the row number in p->nLine. ** + Store the character that terminates the field in p->cTerm. Store ** EOF on end-of-file. ** + Report syntax errors on stderr */ static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){ int c; int cSep = p->cColSep; int rSep = p->cRowSep; p->n = 0; c = fgetc(p->in); if( c==EOF || seenInterrupt ){ p->cTerm = EOF; return 0; } while( c!=EOF && c!=cSep && c!=rSep ){ import_append_char(p, c); c = fgetc(p->in); } if( c==rSep ){ p->nLine++; } p->cTerm = c; if( p->z ) p->z[p->n] = 0; return p->z; } /* ** Try to transfer data for table zTable. If an error is seen while ** moving forward, try to go backwards. The backwards movement won't ** work for WITHOUT ROWID tables. */ static void tryToCloneData( ShellState *p, sqlite3 *newDb, const char *zTable ){ sqlite3_stmt *pQuery = 0; sqlite3_stmt *pInsert = 0; char *zQuery = 0; char *zInsert = 0; int rc; int i, j, n; int nTable = (int)strlen(zTable); int k = 0; int cnt = 0; const int spinRate = 10000; zQuery = sqlite3_mprintf("SELECT * FROM \"%w\"", zTable); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ fprintf(stderr, "Error %d: %s on [%s]\n", sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery); goto end_data_xfer; } n = sqlite3_column_count(pQuery); zInsert = sqlite3_malloc64(200 + nTable + n*3); if( zInsert==0 ){ fprintf(stderr, "out of memory\n"); goto end_data_xfer; } sqlite3_snprintf(200+nTable,zInsert, "INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable); i = (int)strlen(zInsert); for(j=1; jdb, zQuery, -1, &pQuery, 0); if( rc ){ fprintf(stderr, "Warning: cannot step \"%s\" backwards", zTable); break; } } /* End for(k=0...) */ end_data_xfer: sqlite3_finalize(pQuery); sqlite3_finalize(pInsert); sqlite3_free(zQuery); sqlite3_free(zInsert); } /* ** Try to transfer all rows of the schema that match zWhere. For ** each row, invoke xForEach() on the object defined by that row. ** If an error is encountered while moving forward through the ** sqlite_master table, try again moving backwards. */ static void tryToCloneSchema( ShellState *p, sqlite3 *newDb, const char *zWhere, void (*xForEach)(ShellState*,sqlite3*,const char*) ){ sqlite3_stmt *pQuery = 0; char *zQuery = 0; int rc; const unsigned char *zName; const unsigned char *zSql; char *zErrMsg = 0; zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_master" " WHERE %s", zWhere); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ fprintf(stderr, "Error: (%d) %s on [%s]\n", sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery); goto end_schema_xfer; } while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){ zName = sqlite3_column_text(pQuery, 0); zSql = sqlite3_column_text(pQuery, 1); printf("%s... ", zName); fflush(stdout); sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); if( zErrMsg ){ fprintf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); sqlite3_free(zErrMsg); zErrMsg = 0; } if( xForEach ){ xForEach(p, newDb, (const char*)zName); } printf("done\n"); } if( rc!=SQLITE_DONE ){ sqlite3_finalize(pQuery); sqlite3_free(zQuery); zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_master" " WHERE %s ORDER BY rowid DESC", zWhere); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ fprintf(stderr, "Error: (%d) %s on [%s]\n", sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery); goto end_schema_xfer; } while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){ zName = sqlite3_column_text(pQuery, 0); zSql = sqlite3_column_text(pQuery, 1); printf("%s... ", zName); fflush(stdout); sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); if( zErrMsg ){ fprintf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); sqlite3_free(zErrMsg); zErrMsg = 0; } if( xForEach ){ xForEach(p, newDb, (const char*)zName); } printf("done\n"); } } end_schema_xfer: sqlite3_finalize(pQuery); sqlite3_free(zQuery); } /* ** Open a new database file named "zNewDb". Try to recover as much information ** as possible out of the main database (which might be corrupt) and write it ** into zNewDb. */ static void tryToClone(ShellState *p, const char *zNewDb){ int rc; sqlite3 *newDb = 0; if( access(zNewDb,0)==0 ){ fprintf(stderr, "File \"%s\" already exists.\n", zNewDb); return; } rc = sqlite3_open(zNewDb, &newDb); if( rc ){ fprintf(stderr, "Cannot create output database: %s\n", sqlite3_errmsg(newDb)); }else{ sqlite3_exec(p->db, "PRAGMA writable_schema=ON;", 0, 0, 0); sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0); tryToCloneSchema(p, newDb, "type='table'", tryToCloneData); tryToCloneSchema(p, newDb, "type!='table'", 0); sqlite3_exec(newDb, "COMMIT;", 0, 0, 0); sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); } sqlite3_close(newDb); } /* ** Change the output file back to stdout */ static void output_reset(ShellState *p){ if( p->outfile[0]=='|' ){ #ifndef SQLITE_OMIT_POPEN pclose(p->out); #endif }else{ output_file_close(p->out); } p->outfile[0] = 0; p->out = stdout; } /* ** Run an SQL command and return the single integer result. */ static int db_int(ShellState *p, const char *zSql){ sqlite3_stmt *pStmt; int res = 0; sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ res = sqlite3_column_int(pStmt,0); } sqlite3_finalize(pStmt); return res; } /* ** Convert a 2-byte or 4-byte big-endian integer into a native integer */ unsigned int get2byteInt(unsigned char *a){ return (a[0]<<8) + a[1]; } unsigned int get4byteInt(unsigned char *a){ return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3]; } /* ** Implementation of the ".info" command. ** ** Return 1 on error, 2 to exit, and 0 otherwise. */ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ static const struct { const char *zName; int ofst; } aField[] = { { "file change counter:", 24 }, { "database page count:", 28 }, { "freelist page count:", 36 }, { "schema cookie:", 40 }, { "schema format:", 44 }, { "default cache size:", 48 }, { "autovacuum top root:", 52 }, { "incremental vacuum:", 64 }, { "text encoding:", 56 }, { "user version:", 60 }, { "application id:", 68 }, { "software version:", 96 }, }; static const struct { const char *zName; const char *zSql; } aQuery[] = { { "number of tables:", "SELECT count(*) FROM %s WHERE type='table'" }, { "number of indexes:", "SELECT count(*) FROM %s WHERE type='index'" }, { "number of triggers:", "SELECT count(*) FROM %s WHERE type='trigger'" }, { "number of views:", "SELECT count(*) FROM %s WHERE type='view'" }, { "schema size:", "SELECT total(length(sql)) FROM %s" }, }; sqlite3_file *pFile; int i; char *zSchemaTab; char *zDb = nArg>=2 ? azArg[1] : "main"; unsigned char aHdr[100]; open_db(p, 0); if( p->db==0 ) return 1; sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_FILE_POINTER, &pFile); if( pFile==0 || pFile->pMethods==0 || pFile->pMethods->xRead==0 ){ return 1; } i = pFile->pMethods->xRead(pFile, aHdr, 100, 0); if( i!=SQLITE_OK ){ fprintf(stderr, "unable to read database header\n"); return 1; } i = get2byteInt(aHdr+16); if( i==1 ) i = 65536; fprintf(p->out, "%-20s %d\n", "database page size:", i); fprintf(p->out, "%-20s %d\n", "write format:", aHdr[18]); fprintf(p->out, "%-20s %d\n", "read format:", aHdr[19]); fprintf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]); for(i=0; iout, "%-20s %u", aField[i].zName, val); switch( ofst ){ case 56: { if( val==1 ) fprintf(p->out, " (utf8)"); if( val==2 ) fprintf(p->out, " (utf16le)"); if( val==3 ) fprintf(p->out, " (utf16be)"); } } fprintf(p->out, "\n"); } if( zDb==0 ){ zSchemaTab = sqlite3_mprintf("main.sqlite_master"); }else if( strcmp(zDb,"temp")==0 ){ zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_master"); }else{ zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_master", zDb); } for(i=0; iout, "%-20s %d\n", aQuery[i].zName, val); } sqlite3_free(zSchemaTab); return 0; } /* ** If an input line begins with "." then invoke this routine to ** process that line. ** ** Return 1 on error, 2 to exit, and 0 otherwise. */ static int do_meta_command(char *zLine, ShellState *p){ int h = 1; int nArg = 0; int n, c; int rc = 0; char *azArg[50]; /* Parse the input line into tokens. */ while( zLine[h] && nArg=3 && strncmp(azArg[0], "backup", n)==0) || (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0) ){ const char *zDestFile = 0; const char *zDb = 0; sqlite3 *pDest; sqlite3_backup *pBackup; int j; for(j=1; jdb, zDb); if( pBackup==0 ){ fprintf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); sqlite3_close(pDest); return 1; } while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){} sqlite3_backup_finish(pBackup); if( rc==SQLITE_DONE ){ rc = 0; }else{ fprintf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); rc = 1; } sqlite3_close(pDest); }else if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 ){ if( nArg==2 ){ bail_on_error = booleanValue(azArg[1]); }else{ fprintf(stderr, "Usage: .bail on|off\n"); rc = 1; } }else if( c=='b' && n>=3 && strncmp(azArg[0], "binary", n)==0 ){ if( nArg==2 ){ if( booleanValue(azArg[1]) ){ setBinaryMode(p->out); }else{ setTextMode(p->out); } }else{ fprintf(stderr, "Usage: .binary on|off\n"); rc = 1; } }else /* The undocumented ".breakpoint" command causes a call to the no-op ** routine named test_breakpoint(). */ if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){ test_breakpoint(); }else if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){ if( nArg==2 ){ tryToClone(p, azArg[1]); }else{ fprintf(stderr, "Usage: .clone FILENAME\n"); rc = 1; } }else if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 ){ ShellState data; char *zErrMsg = 0; open_db(p, 0); memcpy(&data, p, sizeof(data)); data.showHeader = 1; data.mode = MODE_Column; data.colWidth[0] = 3; data.colWidth[1] = 15; data.colWidth[2] = 58; data.cnt = 0; sqlite3_exec(p->db, "PRAGMA database_list; ", callback, &data, &zErrMsg); if( zErrMsg ){ fprintf(stderr,"Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); rc = 1; } }else if( c=='d' && strncmp(azArg[0], "dbinfo", n)==0 ){ rc = shell_dbinfo_command(p, nArg, azArg); }else if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ open_db(p, 0); /* When playing back a "dump", the content might appear in an order ** which causes immediate foreign key constraints to be violated. ** So disable foreign-key constraint enforcement to prevent problems. */ if( nArg!=1 && nArg!=2 ){ fprintf(stderr, "Usage: .dump ?LIKE-PATTERN?\n"); rc = 1; goto meta_command_exit; } fprintf(p->out, "PRAGMA foreign_keys=OFF;\n"); fprintf(p->out, "BEGIN TRANSACTION;\n"); p->writableSchema = 0; sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); p->nErr = 0; if( nArg==1 ){ run_schema_dump_query(p, "SELECT name, type, sql FROM sqlite_master " "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'" ); run_schema_dump_query(p, "SELECT name, type, sql FROM sqlite_master " "WHERE name=='sqlite_sequence'" ); run_table_dump_query(p, "SELECT sql FROM sqlite_master " "WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0 ); }else{ int i; for(i=1; iwritableSchema ){ fprintf(p->out, "PRAGMA writable_schema=OFF;\n"); p->writableSchema = 0; } sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); fprintf(p->out, p->nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n"); }else if( c=='e' && strncmp(azArg[0], "echo", n)==0 ){ if( nArg==2 ){ p->echoOn = booleanValue(azArg[1]); }else{ fprintf(stderr, "Usage: .echo on|off\n"); rc = 1; } }else if( c=='e' && strncmp(azArg[0], "eqp", n)==0 ){ if( nArg==2 ){ p->autoEQP = booleanValue(azArg[1]); }else{ fprintf(stderr, "Usage: .eqp on|off\n"); rc = 1; } }else if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); rc = 2; }else if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){ int val = nArg>=2 ? booleanValue(azArg[1]) : 1; if(val == 1) { if(!p->normalMode.valid) { p->normalMode.valid = 1; p->normalMode.mode = p->mode; p->normalMode.showHeader = p->showHeader; memcpy(p->normalMode.colWidth,p->colWidth,sizeof(p->colWidth)); } /* We could put this code under the !p->explainValid ** condition so that it does not execute if we are already in ** explain mode. However, always executing it allows us an easy ** was to reset to explain mode in case the user previously ** did an .explain followed by a .width, .mode or .header ** command. */ p->mode = MODE_Explain; p->showHeader = 1; memset(p->colWidth,0,sizeof(p->colWidth)); p->colWidth[0] = 4; /* addr */ p->colWidth[1] = 13; /* opcode */ p->colWidth[2] = 4; /* P1 */ p->colWidth[3] = 4; /* P2 */ p->colWidth[4] = 4; /* P3 */ p->colWidth[5] = 13; /* P4 */ p->colWidth[6] = 2; /* P5 */ p->colWidth[7] = 13; /* Comment */ }else if (p->normalMode.valid) { p->normalMode.valid = 0; p->mode = p->normalMode.mode; p->showHeader = p->normalMode.showHeader; memcpy(p->colWidth,p->normalMode.colWidth,sizeof(p->colWidth)); } }else if( c=='f' && strncmp(azArg[0], "fullschema", n)==0 ){ ShellState data; char *zErrMsg = 0; int doStats = 0; if( nArg!=1 ){ fprintf(stderr, "Usage: .fullschema\n"); rc = 1; goto meta_command_exit; } open_db(p, 0); memcpy(&data, p, sizeof(data)); data.showHeader = 0; data.mode = MODE_Semi; rc = sqlite3_exec(p->db, "SELECT sql FROM" " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" " FROM sqlite_master UNION ALL" " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_master) " "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' " "ORDER BY rowid", callback, &data, &zErrMsg ); if( rc==SQLITE_OK ){ sqlite3_stmt *pStmt; rc = sqlite3_prepare_v2(p->db, "SELECT rowid FROM sqlite_master" " WHERE name GLOB 'sqlite_stat[134]'", -1, &pStmt, 0); doStats = sqlite3_step(pStmt)==SQLITE_ROW; sqlite3_finalize(pStmt); } if( doStats==0 ){ fprintf(p->out, "/* No STAT tables available */\n"); }else{ fprintf(p->out, "ANALYZE sqlite_master;\n"); sqlite3_exec(p->db, "SELECT 'ANALYZE sqlite_master'", callback, &data, &zErrMsg); data.mode = MODE_Insert; data.zDestTable = "sqlite_stat1"; shell_exec(p->db, "SELECT * FROM sqlite_stat1", shell_callback, &data,&zErrMsg); data.zDestTable = "sqlite_stat3"; shell_exec(p->db, "SELECT * FROM sqlite_stat3", shell_callback, &data,&zErrMsg); data.zDestTable = "sqlite_stat4"; shell_exec(p->db, "SELECT * FROM sqlite_stat4", shell_callback, &data, &zErrMsg); fprintf(p->out, "ANALYZE sqlite_master;\n"); } }else if( c=='h' && strncmp(azArg[0], "headers", n)==0 ){ if( nArg==2 ){ p->showHeader = booleanValue(azArg[1]); }else{ fprintf(stderr, "Usage: .headers on|off\n"); rc = 1; } }else if( c=='h' && strncmp(azArg[0], "help", n)==0 ){ fprintf(p->out, "%s", zHelp); }else if( c=='i' && strncmp(azArg[0], "import", n)==0 ){ char *zTable; /* Insert data into this table */ char *zFile; /* Name of file to extra content from */ sqlite3_stmt *pStmt = NULL; /* A statement */ int nCol; /* Number of columns in the table */ int nByte; /* Number of bytes in an SQL string */ int i, j; /* Loop counters */ int needCommit; /* True to COMMIT or ROLLBACK at end */ int nSep; /* Number of bytes in p->colSeparator[] */ char *zSql; /* An SQL statement */ ImportCtx sCtx; /* Reader context */ char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */ int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close file */ if( nArg!=3 ){ fprintf(stderr, "Usage: .import FILE TABLE\n"); goto meta_command_exit; } zFile = azArg[1]; zTable = azArg[2]; seenInterrupt = 0; memset(&sCtx, 0, sizeof(sCtx)); open_db(p, 0); nSep = strlen30(p->colSeparator); if( nSep==0 ){ fprintf(stderr, "Error: non-null column separator required for import\n"); return 1; } if( nSep>1 ){ fprintf(stderr, "Error: multi-character column separators not allowed" " for import\n"); return 1; } nSep = strlen30(p->rowSeparator); if( nSep==0 ){ fprintf(stderr, "Error: non-null row separator required for import\n"); return 1; } if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator, SEP_CrLf)==0 ){ /* When importing CSV (only), if the row separator is set to the ** default output row separator, change it to the default input ** row separator. This avoids having to maintain different input ** and output row separators. */ sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row); nSep = strlen30(p->rowSeparator); } if( nSep>1 ){ fprintf(stderr, "Error: multi-character row separators not allowed" " for import\n"); return 1; } sCtx.zFile = zFile; sCtx.nLine = 1; if( sCtx.zFile[0]=='|' ){ #ifdef SQLITE_OMIT_POPEN fprintf(stderr, "Error: pipes are not supported in this OS\n"); return 1; #else sCtx.in = popen(sCtx.zFile+1, "r"); sCtx.zFile = ""; xCloser = pclose; #endif }else{ sCtx.in = fopen(sCtx.zFile, "rb"); xCloser = fclose; } if( p->mode==MODE_Ascii ){ xRead = ascii_read_one_field; }else{ xRead = csv_read_one_field; } if( sCtx.in==0 ){ fprintf(stderr, "Error: cannot open \"%s\"\n", zFile); return 1; } sCtx.cColSep = p->colSeparator[0]; sCtx.cRowSep = p->rowSeparator[0]; zSql = sqlite3_mprintf("SELECT * FROM %s", zTable); if( zSql==0 ){ fprintf(stderr, "Error: out of memory\n"); xCloser(sCtx.in); return 1; } nByte = strlen30(zSql); rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){ char *zCreate = sqlite3_mprintf("CREATE TABLE %s", zTable); char cSep = '('; while( xRead(&sCtx) ){ zCreate = sqlite3_mprintf("%z%c\n \"%s\" TEXT", zCreate, cSep, sCtx.z); cSep = ','; if( sCtx.cTerm!=sCtx.cColSep ) break; } if( cSep=='(' ){ sqlite3_free(zCreate); sqlite3_free(sCtx.z); xCloser(sCtx.in); fprintf(stderr,"%s: empty file\n", sCtx.zFile); return 1; } zCreate = sqlite3_mprintf("%z\n)", zCreate); rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); sqlite3_free(zCreate); if( rc ){ fprintf(stderr, "CREATE TABLE %s(...) failed: %s\n", zTable, sqlite3_errmsg(p->db)); sqlite3_free(sCtx.z); xCloser(sCtx.in); return 1; } rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); } sqlite3_free(zSql); if( rc ){ if (pStmt) sqlite3_finalize(pStmt); fprintf(stderr,"Error: %s\n", sqlite3_errmsg(p->db)); xCloser(sCtx.in); return 1; } nCol = sqlite3_column_count(pStmt); sqlite3_finalize(pStmt); pStmt = 0; if( nCol==0 ) return 0; /* no columns, no error */ zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 ); if( zSql==0 ){ fprintf(stderr, "Error: out of memory\n"); xCloser(sCtx.in); return 1; } sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable); j = strlen30(zSql); for(i=1; idb, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rc ){ fprintf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); if (pStmt) sqlite3_finalize(pStmt); xCloser(sCtx.in); return 1; } needCommit = sqlite3_get_autocommit(p->db); if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0); do{ int startLine = sCtx.nLine; for(i=0; imode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); if( i=nCol ){ sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); if( rc!=SQLITE_OK ){ fprintf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile, startLine, sqlite3_errmsg(p->db)); } } }while( sCtx.cTerm!=EOF ); xCloser(sCtx.in); sqlite3_free(sCtx.z); sqlite3_finalize(pStmt); if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); }else if( c=='i' && (strncmp(azArg[0], "indices", n)==0 || strncmp(azArg[0], "indexes", n)==0) ){ ShellState data; char *zErrMsg = 0; open_db(p, 0); memcpy(&data, p, sizeof(data)); data.showHeader = 0; data.mode = MODE_List; if( nArg==1 ){ rc = sqlite3_exec(p->db, "SELECT name FROM sqlite_master " "WHERE type='index' AND name NOT LIKE 'sqlite_%' " "UNION ALL " "SELECT name FROM sqlite_temp_master " "WHERE type='index' " "ORDER BY 1", callback, &data, &zErrMsg ); }else if( nArg==2 ){ zShellStatic = azArg[1]; rc = sqlite3_exec(p->db, "SELECT name FROM sqlite_master " "WHERE type='index' AND tbl_name LIKE shellstatic() " "UNION ALL " "SELECT name FROM sqlite_temp_master " "WHERE type='index' AND tbl_name LIKE shellstatic() " "ORDER BY 1", callback, &data, &zErrMsg ); zShellStatic = 0; }else{ fprintf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n"); rc = 1; goto meta_command_exit; } if( zErrMsg ){ fprintf(stderr,"Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); rc = 1; }else if( rc != SQLITE_OK ){ fprintf(stderr,"Error: querying sqlite_master and sqlite_temp_master\n"); rc = 1; } }else #ifdef SQLITE_ENABLE_IOTRACE if( c=='i' && strncmp(azArg[0], "iotrace", n)==0 ){ SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...); if( iotrace && iotrace!=stdout ) fclose(iotrace); iotrace = 0; if( nArg<2 ){ sqlite3IoTrace = 0; }else if( strcmp(azArg[1], "-")==0 ){ sqlite3IoTrace = iotracePrintf; iotrace = stdout; }else{ iotrace = fopen(azArg[1], "w"); if( iotrace==0 ){ fprintf(stderr, "Error: cannot open \"%s\"\n", azArg[1]); sqlite3IoTrace = 0; rc = 1; }else{ sqlite3IoTrace = iotracePrintf; } } }else #endif if( c=='l' && n>=5 && strncmp(azArg[0], "limits", n)==0 ){ static const struct { const char *zLimitName; /* Name of a limit */ int limitCode; /* Integer code for that limit */ } aLimit[] = { { "length", SQLITE_LIMIT_LENGTH }, { "sql_length", SQLITE_LIMIT_SQL_LENGTH }, { "column", SQLITE_LIMIT_COLUMN }, { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH }, { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT }, { "vdbe_op", SQLITE_LIMIT_VDBE_OP }, { "function_arg", SQLITE_LIMIT_FUNCTION_ARG }, { "attached", SQLITE_LIMIT_ATTACHED }, { "like_pattern_length", SQLITE_LIMIT_LIKE_PATTERN_LENGTH }, { "variable_number", SQLITE_LIMIT_VARIABLE_NUMBER }, { "trigger_depth", SQLITE_LIMIT_TRIGGER_DEPTH }, { "worker_threads", SQLITE_LIMIT_WORKER_THREADS }, }; int i, n2; open_db(p, 0); if( nArg==1 ){ for(i=0; idb, aLimit[i].limitCode, -1)); } }else if( nArg>3 ){ fprintf(stderr, "Usage: .limit NAME ?NEW-VALUE?\n"); rc = 1; goto meta_command_exit; }else{ int iLimit = -1; n2 = strlen30(azArg[1]); for(i=0; idb, aLimit[iLimit].limitCode, (int)integerValue(azArg[2])); } printf("%20s %d\n", aLimit[iLimit].zLimitName, sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); } }else #ifndef SQLITE_OMIT_LOAD_EXTENSION if( c=='l' && strncmp(azArg[0], "load", n)==0 ){ const char *zFile, *zProc; char *zErrMsg = 0; if( nArg<2 ){ fprintf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n"); rc = 1; goto meta_command_exit; } zFile = azArg[1]; zProc = nArg>=3 ? azArg[2] : 0; open_db(p, 0); rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg); if( rc!=SQLITE_OK ){ fprintf(stderr, "Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); rc = 1; } }else #endif if( c=='l' && strncmp(azArg[0], "log", n)==0 ){ if( nArg!=2 ){ fprintf(stderr, "Usage: .log FILENAME\n"); rc = 1; }else{ const char *zFile = azArg[1]; output_file_close(p->pLog); p->pLog = output_file_open(zFile); } }else if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){ const char *zMode = nArg>=2 ? azArg[1] : ""; int n2 = (int)strlen(zMode); int c2 = zMode[0]; if( c2=='l' && n2>2 && strncmp(azArg[1],"lines",n2)==0 ){ p->mode = MODE_Line; }else if( c2=='c' && strncmp(azArg[1],"columns",n2)==0 ){ p->mode = MODE_Column; }else if( c2=='l' && n2>2 && strncmp(azArg[1],"list",n2)==0 ){ p->mode = MODE_List; }else if( c2=='h' && strncmp(azArg[1],"html",n2)==0 ){ p->mode = MODE_Html; }else if( c2=='t' && strncmp(azArg[1],"tcl",n2)==0 ){ p->mode = MODE_Tcl; sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space); }else if( c2=='c' && strncmp(azArg[1],"csv",n2)==0 ){ p->mode = MODE_Csv; sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma); sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf); }else if( c2=='t' && strncmp(azArg[1],"tabs",n2)==0 ){ p->mode = MODE_List; sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab); }else if( c2=='i' && strncmp(azArg[1],"insert",n2)==0 ){ p->mode = MODE_Insert; set_table_name(p, nArg>=3 ? azArg[2] : "table"); }else if( c2=='a' && strncmp(azArg[1],"ascii",n2)==0 ){ p->mode = MODE_Ascii; sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit); sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record); }else { fprintf(stderr,"Error: mode should be one of: " "ascii column csv html insert line list tabs tcl\n"); rc = 1; } }else if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){ if( nArg==2 ){ sqlite3_snprintf(sizeof(p->nullValue), p->nullValue, "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]); }else{ fprintf(stderr, "Usage: .nullvalue STRING\n"); rc = 1; } }else if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){ sqlite3 *savedDb = p->db; const char *zSavedFilename = p->zDbFilename; char *zNewFilename = 0; p->db = 0; if( nArg>=2 ) zNewFilename = sqlite3_mprintf("%s", azArg[1]); p->zDbFilename = zNewFilename; open_db(p, 1); if( p->db!=0 ){ sqlite3_close(savedDb); sqlite3_free(p->zFreeOnClose); p->zFreeOnClose = zNewFilename; }else{ sqlite3_free(zNewFilename); p->db = savedDb; p->zDbFilename = zSavedFilename; } }else if( c=='o' && (strncmp(azArg[0], "output", n)==0 || strncmp(azArg[0], "once", n)==0) ){ const char *zFile = nArg>=2 ? azArg[1] : "stdout"; if( nArg>2 ){ fprintf(stderr, "Usage: .%s FILE\n", azArg[0]); rc = 1; goto meta_command_exit; } if( n>1 && strncmp(azArg[0], "once", n)==0 ){ if( nArg<2 ){ fprintf(stderr, "Usage: .once FILE\n"); rc = 1; goto meta_command_exit; } p->outCount = 2; }else{ p->outCount = 0; } output_reset(p); if( zFile[0]=='|' ){ #ifdef SQLITE_OMIT_POPEN fprintf(stderr,"Error: pipes are not supported in this OS\n"); rc = 1; p->out = stdout; #else p->out = popen(zFile + 1, "w"); if( p->out==0 ){ fprintf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1); p->out = stdout; rc = 1; }else{ sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); } #endif }else{ p->out = output_file_open(zFile); if( p->out==0 ){ if( strcmp(zFile,"off")!=0 ){ fprintf(stderr,"Error: cannot write to \"%s\"\n", zFile); } p->out = stdout; rc = 1; } else { sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); } } }else if( c=='p' && n>=3 && strncmp(azArg[0], "print", n)==0 ){ int i; for(i=1; i1 ) fprintf(p->out, " "); fprintf(p->out, "%s", azArg[i]); } fprintf(p->out, "\n"); }else if( c=='p' && strncmp(azArg[0], "prompt", n)==0 ){ if( nArg >= 2) { strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1); } if( nArg >= 3) { strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1); } }else if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){ rc = 2; }else if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 ){ FILE *alt; if( nArg!=2 ){ fprintf(stderr, "Usage: .read FILE\n"); rc = 1; goto meta_command_exit; } alt = fopen(azArg[1], "rb"); if( alt==0 ){ fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ rc = process_input(p, alt); fclose(alt); } }else if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){ const char *zSrcFile; const char *zDb; sqlite3 *pSrc; sqlite3_backup *pBackup; int nTimeout = 0; if( nArg==2 ){ zSrcFile = azArg[1]; zDb = "main"; }else if( nArg==3 ){ zSrcFile = azArg[2]; zDb = azArg[1]; }else{ fprintf(stderr, "Usage: .restore ?DB? FILE\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_open(zSrcFile, &pSrc); if( rc!=SQLITE_OK ){ fprintf(stderr, "Error: cannot open \"%s\"\n", zSrcFile); sqlite3_close(pSrc); return 1; } open_db(p, 0); pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main"); if( pBackup==0 ){ fprintf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); sqlite3_close(pSrc); return 1; } while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK || rc==SQLITE_BUSY ){ if( rc==SQLITE_BUSY ){ if( nTimeout++ >= 3 ) break; sqlite3_sleep(100); } } sqlite3_backup_finish(pBackup); if( rc==SQLITE_DONE ){ rc = 0; }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){ fprintf(stderr, "Error: source database is busy\n"); rc = 1; }else{ fprintf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); rc = 1; } sqlite3_close(pSrc); }else if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){ if( nArg==2 ){ p->scanstatsOn = booleanValue(azArg[1]); #ifndef SQLITE_ENABLE_STMT_SCANSTATUS fprintf(stderr, "Warning: .scanstats not available in this build.\n"); #endif }else{ fprintf(stderr, "Usage: .scanstats on|off\n"); rc = 1; } }else if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){ ShellState data; char *zErrMsg = 0; open_db(p, 0); memcpy(&data, p, sizeof(data)); data.showHeader = 0; data.mode = MODE_Semi; if( nArg==2 ){ int i; for(i=0; azArg[1][i]; i++) azArg[1][i] = ToLower(azArg[1][i]); if( strcmp(azArg[1],"sqlite_master")==0 ){ char *new_argv[2], *new_colv[2]; new_argv[0] = "CREATE TABLE sqlite_master (\n" " type text,\n" " name text,\n" " tbl_name text,\n" " rootpage integer,\n" " sql text\n" ")"; new_argv[1] = 0; new_colv[0] = "sql"; new_colv[1] = 0; callback(&data, 1, new_argv, new_colv); rc = SQLITE_OK; }else if( strcmp(azArg[1],"sqlite_temp_master")==0 ){ char *new_argv[2], *new_colv[2]; new_argv[0] = "CREATE TEMP TABLE sqlite_temp_master (\n" " type text,\n" " name text,\n" " tbl_name text,\n" " rootpage integer,\n" " sql text\n" ")"; new_argv[1] = 0; new_colv[0] = "sql"; new_colv[1] = 0; callback(&data, 1, new_argv, new_colv); rc = SQLITE_OK; }else{ zShellStatic = azArg[1]; rc = sqlite3_exec(p->db, "SELECT sql FROM " " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" " FROM sqlite_master UNION ALL" " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_master) " "WHERE lower(tbl_name) LIKE shellstatic()" " AND type!='meta' AND sql NOTNULL " "ORDER BY rowid", callback, &data, &zErrMsg); zShellStatic = 0; } }else if( nArg==1 ){ rc = sqlite3_exec(p->db, "SELECT sql FROM " " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x" " FROM sqlite_master UNION ALL" " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_master) " "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' " "ORDER BY rowid", callback, &data, &zErrMsg ); }else{ fprintf(stderr, "Usage: .schema ?LIKE-PATTERN?\n"); rc = 1; goto meta_command_exit; } if( zErrMsg ){ fprintf(stderr,"Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); rc = 1; }else if( rc != SQLITE_OK ){ fprintf(stderr,"Error: querying schema information\n"); rc = 1; }else{ rc = 0; } }else #if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_SELECTTRACE) if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){ extern int sqlite3SelectTrace; sqlite3SelectTrace = integerValue(azArg[1]); }else #endif #ifdef SQLITE_DEBUG /* Undocumented commands for internal testing. Subject to change ** without notice. */ if( c=='s' && n>=10 && strncmp(azArg[0], "selftest-", 9)==0 ){ if( strncmp(azArg[0]+9, "boolean", n-9)==0 ){ int i, v; for(i=1; iout, "%s: %d 0x%x\n", azArg[i], v, v); } } if( strncmp(azArg[0]+9, "integer", n-9)==0 ){ int i; sqlite3_int64 v; for(i=1; iout, "%s", zBuf); } } }else #endif if( c=='s' && strncmp(azArg[0], "separator", n)==0 ){ if( nArg<2 || nArg>3 ){ fprintf(stderr, "Usage: .separator COL ?ROW?\n"); rc = 1; } if( nArg>=2 ){ sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]); } if( nArg>=3 ){ sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]); } }else if( c=='s' && (strncmp(azArg[0], "shell", n)==0 || strncmp(azArg[0],"system",n)==0) ){ char *zCmd; int i, x; if( nArg<2 ){ fprintf(stderr, "Usage: .system COMMAND\n"); rc = 1; goto meta_command_exit; } zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]); for(i=2; iout,"%12.12s: %s\n","echo", p->echoOn ? "on" : "off"); fprintf(p->out,"%12.12s: %s\n","eqp", p->autoEQP ? "on" : "off"); fprintf(p->out,"%9.9s: %s\n","explain", p->normalMode.valid ? "on" :"off"); fprintf(p->out,"%12.12s: %s\n","headers", p->showHeader ? "on" : "off"); fprintf(p->out,"%12.12s: %s\n","mode", modeDescr[p->mode]); fprintf(p->out,"%12.12s: ", "nullvalue"); output_c_string(p->out, p->nullValue); fprintf(p->out, "\n"); fprintf(p->out,"%12.12s: %s\n","output", strlen30(p->outfile) ? p->outfile : "stdout"); fprintf(p->out,"%12.12s: ", "colseparator"); output_c_string(p->out, p->colSeparator); fprintf(p->out, "\n"); fprintf(p->out,"%12.12s: ", "rowseparator"); output_c_string(p->out, p->rowSeparator); fprintf(p->out, "\n"); fprintf(p->out,"%12.12s: %s\n","stats", p->statsOn ? "on" : "off"); fprintf(p->out,"%12.12s: ","width"); for (i=0;i<(int)ArraySize(p->colWidth) && p->colWidth[i] != 0;i++) { fprintf(p->out,"%d ",p->colWidth[i]); } fprintf(p->out,"\n"); }else if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){ if( nArg==2 ){ p->statsOn = booleanValue(azArg[1]); }else{ fprintf(stderr, "Usage: .stats on|off\n"); rc = 1; } }else if( c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0 ){ sqlite3_stmt *pStmt; char **azResult; int nRow, nAlloc; char *zSql = 0; int ii; open_db(p, 0); rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); if( rc ) return rc; zSql = sqlite3_mprintf( "SELECT name FROM sqlite_master" " WHERE type IN ('table','view')" " AND name NOT LIKE 'sqlite_%%'" " AND name LIKE ?1"); while( sqlite3_step(pStmt)==SQLITE_ROW ){ const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); if( zDbName==0 || strcmp(zDbName,"main")==0 ) continue; if( strcmp(zDbName,"temp")==0 ){ zSql = sqlite3_mprintf( "%z UNION ALL " "SELECT 'temp.' || name FROM sqlite_temp_master" " WHERE type IN ('table','view')" " AND name NOT LIKE 'sqlite_%%'" " AND name LIKE ?1", zSql); }else{ zSql = sqlite3_mprintf( "%z UNION ALL " "SELECT '%q.' || name FROM \"%w\".sqlite_master" " WHERE type IN ('table','view')" " AND name NOT LIKE 'sqlite_%%'" " AND name LIKE ?1", zSql, zDbName, zDbName); } } sqlite3_finalize(pStmt); zSql = sqlite3_mprintf("%z ORDER BY 1", zSql); rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rc ) return rc; nRow = nAlloc = 0; azResult = 0; if( nArg>1 ){ sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT); }else{ sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); } while( sqlite3_step(pStmt)==SQLITE_ROW ){ if( nRow>=nAlloc ){ char **azNew; int n2 = nAlloc*2 + 10; azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2); if( azNew==0 ){ fprintf(stderr, "Error: out of memory\n"); break; } nAlloc = n2; azResult = azNew; } azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); if( azResult[nRow] ) nRow++; } sqlite3_finalize(pStmt); if( nRow>0 ){ int len, maxlen = 0; int i, j; int nPrintCol, nPrintRow; for(i=0; imaxlen ) maxlen = len; } nPrintCol = 80/(maxlen+2); if( nPrintCol<1 ) nPrintCol = 1; nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; for(i=0; iout, "%s%-*s", zSp, maxlen, azResult[j] ? azResult[j]:""); } fprintf(p->out, "\n"); } } for(ii=0; ii=8 && strncmp(azArg[0], "testctrl", n)==0 && nArg>=2 ){ static const struct { const char *zCtrlName; /* Name of a test-control option */ int ctrlCode; /* Integer code for that option */ } aCtrl[] = { { "prng_save", SQLITE_TESTCTRL_PRNG_SAVE }, { "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE }, { "prng_reset", SQLITE_TESTCTRL_PRNG_RESET }, { "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST }, { "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL }, { "benign_malloc_hooks", SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS }, { "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE }, { "assert", SQLITE_TESTCTRL_ASSERT }, { "always", SQLITE_TESTCTRL_ALWAYS }, { "reserve", SQLITE_TESTCTRL_RESERVE }, { "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS }, { "iskeyword", SQLITE_TESTCTRL_ISKEYWORD }, { "scratchmalloc", SQLITE_TESTCTRL_SCRATCHMALLOC }, { "byteorder", SQLITE_TESTCTRL_BYTEORDER }, { "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT }, { "imposter", SQLITE_TESTCTRL_IMPOSTER }, }; int testctrl = -1; int rc2 = 0; int i, n2; open_db(p, 0); /* convert testctrl text option to value. allow any unique prefix ** of the option name, or a numerical value. */ n2 = strlen30(azArg[1]); for(i=0; iSQLITE_TESTCTRL_LAST) ){ fprintf(stderr,"Error: invalid testctrl option: %s\n", azArg[1]); }else{ switch(testctrl){ /* sqlite3_test_control(int, db, int) */ case SQLITE_TESTCTRL_OPTIMIZATIONS: case SQLITE_TESTCTRL_RESERVE: if( nArg==3 ){ int opt = (int)strtol(azArg[2], 0, 0); rc2 = sqlite3_test_control(testctrl, p->db, opt); fprintf(p->out, "%d (0x%08x)\n", rc2, rc2); } else { fprintf(stderr,"Error: testctrl %s takes a single int option\n", azArg[1]); } break; /* sqlite3_test_control(int) */ case SQLITE_TESTCTRL_PRNG_SAVE: case SQLITE_TESTCTRL_PRNG_RESTORE: case SQLITE_TESTCTRL_PRNG_RESET: case SQLITE_TESTCTRL_BYTEORDER: if( nArg==2 ){ rc2 = sqlite3_test_control(testctrl); fprintf(p->out, "%d (0x%08x)\n", rc2, rc2); } else { fprintf(stderr,"Error: testctrl %s takes no options\n", azArg[1]); } break; /* sqlite3_test_control(int, uint) */ case SQLITE_TESTCTRL_PENDING_BYTE: if( nArg==3 ){ unsigned int opt = (unsigned int)integerValue(azArg[2]); rc2 = sqlite3_test_control(testctrl, opt); fprintf(p->out, "%d (0x%08x)\n", rc2, rc2); } else { fprintf(stderr,"Error: testctrl %s takes a single unsigned" " int option\n", azArg[1]); } break; /* sqlite3_test_control(int, int) */ case SQLITE_TESTCTRL_ASSERT: case SQLITE_TESTCTRL_ALWAYS: case SQLITE_TESTCTRL_NEVER_CORRUPT: if( nArg==3 ){ int opt = booleanValue(azArg[2]); rc2 = sqlite3_test_control(testctrl, opt); fprintf(p->out, "%d (0x%08x)\n", rc2, rc2); } else { fprintf(stderr,"Error: testctrl %s takes a single int option\n", azArg[1]); } break; /* sqlite3_test_control(int, char *) */ #ifdef SQLITE_N_KEYWORD case SQLITE_TESTCTRL_ISKEYWORD: if( nArg==3 ){ const char *opt = azArg[2]; rc2 = sqlite3_test_control(testctrl, opt); fprintf(p->out, "%d (0x%08x)\n", rc2, rc2); } else { fprintf(stderr,"Error: testctrl %s takes a single char * option\n", azArg[1]); } break; #endif case SQLITE_TESTCTRL_IMPOSTER: if( nArg==5 ){ rc2 = sqlite3_test_control(testctrl, p->db, azArg[2], integerValue(azArg[3]), integerValue(azArg[4])); fprintf(p->out, "%d (0x%08x)\n", rc2, rc2); }else{ fprintf(stderr,"Usage: .testctrl imposter dbName onoff tnum\n"); } break; case SQLITE_TESTCTRL_BITVEC_TEST: case SQLITE_TESTCTRL_FAULT_INSTALL: case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS: case SQLITE_TESTCTRL_SCRATCHMALLOC: default: fprintf(stderr,"Error: CLI support for testctrl %s not implemented\n", azArg[1]); break; } } }else if( c=='t' && n>4 && strncmp(azArg[0], "timeout", n)==0 ){ open_db(p, 0); sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0); }else if( c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0 ){ if( nArg==2 ){ enableTimer = booleanValue(azArg[1]); if( enableTimer && !HAS_TIMER ){ fprintf(stderr, "Error: timer not available on this system.\n"); enableTimer = 0; } }else{ fprintf(stderr, "Usage: .timer on|off\n"); rc = 1; } }else if( c=='t' && strncmp(azArg[0], "trace", n)==0 ){ open_db(p, 0); if( nArg!=2 ){ fprintf(stderr, "Usage: .trace FILE|off\n"); rc = 1; goto meta_command_exit; } output_file_close(p->traceOut); p->traceOut = output_file_open(azArg[1]); #if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT) if( p->traceOut==0 ){ sqlite3_trace(p->db, 0, 0); }else{ sqlite3_trace(p->db, sql_trace_callback, p->traceOut); } #endif }else #if SQLITE_USER_AUTHENTICATION if( c=='u' && strncmp(azArg[0], "user", n)==0 ){ if( nArg<2 ){ fprintf(stderr, "Usage: .user SUBCOMMAND ...\n"); rc = 1; goto meta_command_exit; } open_db(p, 0); if( strcmp(azArg[1],"login")==0 ){ if( nArg!=4 ){ fprintf(stderr, "Usage: .user login USER PASSWORD\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3], (int)strlen(azArg[3])); if( rc ){ fprintf(stderr, "Authentication failed for user %s\n", azArg[2]); rc = 1; } }else if( strcmp(azArg[1],"add")==0 ){ if( nArg!=5 ){ fprintf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_user_add(p->db, azArg[2], azArg[3], (int)strlen(azArg[3]), booleanValue(azArg[4])); if( rc ){ fprintf(stderr, "User-Add failed: %d\n", rc); rc = 1; } }else if( strcmp(azArg[1],"edit")==0 ){ if( nArg!=5 ){ fprintf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_user_change(p->db, azArg[2], azArg[3], (int)strlen(azArg[3]), booleanValue(azArg[4])); if( rc ){ fprintf(stderr, "User-Edit failed: %d\n", rc); rc = 1; } }else if( strcmp(azArg[1],"delete")==0 ){ if( nArg!=3 ){ fprintf(stderr, "Usage: .user delete USER\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_user_delete(p->db, azArg[2]); if( rc ){ fprintf(stderr, "User-Delete failed: %d\n", rc); rc = 1; } }else{ fprintf(stderr, "Usage: .user login|add|edit|delete ...\n"); rc = 1; goto meta_command_exit; } }else #endif /* SQLITE_USER_AUTHENTICATION */ if( c=='v' && strncmp(azArg[0], "version", n)==0 ){ fprintf(p->out, "SQLite %s %s\n" /*extra-version-info*/, sqlite3_libversion(), sqlite3_sourceid()); }else if( c=='v' && strncmp(azArg[0], "vfsname", n)==0 ){ const char *zDbName = nArg==2 ? azArg[1] : "main"; char *zVfsName = 0; if( p->db ){ sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName); if( zVfsName ){ fprintf(p->out, "%s\n", zVfsName); sqlite3_free(zVfsName); } } }else #if defined(SQLITE_DEBUG) && defined(SQLITE_ENABLE_WHERETRACE) if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){ extern int sqlite3WhereTrace; sqlite3WhereTrace = nArg>=2 ? booleanValue(azArg[1]) : 0xff; }else #endif if( c=='w' && strncmp(azArg[0], "width", n)==0 ){ int j; assert( nArg<=ArraySize(azArg) ); for(j=1; jcolWidth); j++){ p->colWidth[j-1] = (int)integerValue(azArg[j]); } }else { fprintf(stderr, "Error: unknown command or invalid arguments: " " \"%s\". Enter \".help\" for help\n", azArg[0]); rc = 1; } meta_command_exit: if( p->outCount ){ p->outCount--; if( p->outCount==0 ) output_reset(p); } return rc; } /* ** Return TRUE if a semicolon occurs anywhere in the first N characters ** of string z[]. */ static int line_contains_semicolon(const char *z, int N){ int i; for(i=0; iout); zLine = one_input_line(in, zLine, nSql>0); if( zLine==0 ){ /* End of input */ if( stdin_is_interactive ) printf("\n"); break; } if( seenInterrupt ){ if( in!=0 ) break; seenInterrupt = 0; } lineno++; if( nSql==0 && _all_whitespace(zLine) ){ if( p->echoOn ) printf("%s\n", zLine); continue; } if( zLine && zLine[0]=='.' && nSql==0 ){ if( p->echoOn ) printf("%s\n", zLine); rc = do_meta_command(zLine, p); if( rc==2 ){ /* exit requested */ break; }else if( rc ){ errCnt++; } continue; } if( line_is_command_terminator(zLine) && line_is_complete(zSql, nSql) ){ memcpy(zLine,";",2); } nLine = strlen30(zLine); if( nSql+nLine+2>=nAlloc ){ nAlloc = nSql+nLine+100; zSql = realloc(zSql, nAlloc); if( zSql==0 ){ fprintf(stderr, "Error: out of memory\n"); exit(1); } } nSqlPrior = nSql; if( nSql==0 ){ int i; for(i=0; zLine[i] && IsSpace(zLine[i]); i++){} assert( nAlloc>0 && zSql!=0 ); memcpy(zSql, zLine+i, nLine+1-i); startline = lineno; nSql = nLine-i; }else{ zSql[nSql++] = '\n'; memcpy(zSql+nSql, zLine, nLine+1); nSql += nLine; } if( nSql && line_contains_semicolon(&zSql[nSqlPrior], nSql-nSqlPrior) && sqlite3_complete(zSql) ){ p->cnt = 0; open_db(p, 0); if( p->backslashOn ) resolve_backslashes(zSql); BEGIN_TIMER; rc = shell_exec(p->db, zSql, shell_callback, p, &zErrMsg); END_TIMER; if( rc || zErrMsg ){ char zPrefix[100]; if( in!=0 || !stdin_is_interactive ){ sqlite3_snprintf(sizeof(zPrefix), zPrefix, "Error: near line %d:", startline); }else{ sqlite3_snprintf(sizeof(zPrefix), zPrefix, "Error:"); } if( zErrMsg!=0 ){ fprintf(stderr, "%s %s\n", zPrefix, zErrMsg); sqlite3_free(zErrMsg); zErrMsg = 0; }else{ fprintf(stderr, "%s %s\n", zPrefix, sqlite3_errmsg(p->db)); } errCnt++; } nSql = 0; if( p->outCount ){ output_reset(p); p->outCount = 0; } }else if( nSql && _all_whitespace(zSql) ){ if( p->echoOn ) printf("%s\n", zSql); nSql = 0; } } if( nSql ){ if( !_all_whitespace(zSql) ){ fprintf(stderr, "Error: incomplete SQL: %s\n", zSql); errCnt++; } free(zSql); } free(zLine); return errCnt>0; } /* ** Return a pathname which is the user's home directory. A ** 0 return indicates an error of some kind. */ static char *find_home_dir(void){ static char *home_dir = NULL; if( home_dir ) return home_dir; #if !defined(_WIN32) && !defined(WIN32) && !defined(_WIN32_WCE) \ && !defined(__RTP__) && !defined(_WRS_KERNEL) { struct passwd *pwent; uid_t uid = getuid(); if( (pwent=getpwuid(uid)) != NULL) { home_dir = pwent->pw_dir; } } #endif #if defined(_WIN32_WCE) /* Windows CE (arm-wince-mingw32ce-gcc) does not provide getenv() */ home_dir = "/"; #else #if defined(_WIN32) || defined(WIN32) if (!home_dir) { home_dir = getenv("USERPROFILE"); } #endif if (!home_dir) { home_dir = getenv("HOME"); } #if defined(_WIN32) || defined(WIN32) if (!home_dir) { char *zDrive, *zPath; int n; zDrive = getenv("HOMEDRIVE"); zPath = getenv("HOMEPATH"); if( zDrive && zPath ){ n = strlen30(zDrive) + strlen30(zPath) + 1; home_dir = malloc( n ); if( home_dir==0 ) return 0; sqlite3_snprintf(n, home_dir, "%s%s", zDrive, zPath); return home_dir; } home_dir = "c:\\"; } #endif #endif /* !_WIN32_WCE */ if( home_dir ){ int n = strlen30(home_dir) + 1; char *z = malloc( n ); if( z ) memcpy(z, home_dir, n); home_dir = z; } return home_dir; } /* ** Read input from the file given by sqliterc_override. Or if that ** parameter is NULL, take input from ~/.sqliterc ** ** Returns the number of errors. */ static void process_sqliterc( ShellState *p, /* Configuration data */ const char *sqliterc_override /* Name of config file. NULL to use default */ ){ char *home_dir = NULL; const char *sqliterc = sqliterc_override; char *zBuf = 0; FILE *in = NULL; if (sqliterc == NULL) { home_dir = find_home_dir(); if( home_dir==0 ){ fprintf(stderr, "-- warning: cannot find home directory;" " cannot read ~/.sqliterc\n"); return; } sqlite3_initialize(); zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir); sqliterc = zBuf; } in = fopen(sqliterc,"rb"); if( in ){ if( stdin_is_interactive ){ fprintf(stderr,"-- Loading resources from %s\n",sqliterc); } process_input(p,in); fclose(in); } sqlite3_free(zBuf); } /* ** Show available command line options */ static const char zOptions[] = " -ascii set output mode to 'ascii'\n" " -bail stop after hitting an error\n" " -batch force batch I/O\n" " -column set output mode to 'column'\n" " -cmd COMMAND run \"COMMAND\" before reading stdin\n" " -csv set output mode to 'csv'\n" " -echo print commands before execution\n" " -init FILENAME read/process named file\n" " -[no]header turn headers on or off\n" #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) " -heap SIZE Size of heap for memsys3 or memsys5\n" #endif " -help show this message\n" " -html set output mode to HTML\n" " -interactive force interactive I/O\n" " -line set output mode to 'line'\n" " -list set output mode to 'list'\n" " -lookaside SIZE N use N entries of SZ bytes for lookaside memory\n" " -mmap N default mmap size set to N\n" #ifdef SQLITE_ENABLE_MULTIPLEX " -multiplex enable the multiplexor VFS\n" #endif " -newline SEP set output row separator. Default: '\\n'\n" " -nullvalue TEXT set text string for NULL values. Default ''\n" " -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n" " -scratch SIZE N use N slots of SZ bytes each for scratch memory\n" " -separator SEP set output column separator. Default: '|'\n" " -stats print memory stats before each finalize\n" " -version show SQLite version\n" " -vfs NAME use NAME as the default VFS\n" #ifdef SQLITE_ENABLE_VFSTRACE " -vfstrace enable tracing of all VFS calls\n" #endif ; static void usage(int showDetail){ fprintf(stderr, "Usage: %s [OPTIONS] FILENAME [SQL]\n" "FILENAME is the name of an SQLite database. A new database is created\n" "if the file does not previously exist.\n", Argv0); if( showDetail ){ fprintf(stderr, "OPTIONS include:\n%s", zOptions); }else{ fprintf(stderr, "Use the -help option for additional information\n"); } exit(1); } /* ** Initialize the state information in data */ static void main_init(ShellState *data) { memset(data, 0, sizeof(*data)); data->mode = MODE_List; memcpy(data->colSeparator,SEP_Column, 2); memcpy(data->rowSeparator,SEP_Row, 2); data->showHeader = 0; data->shellFlgs = SHFLG_Lookaside; sqlite3_config(SQLITE_CONFIG_URI, 1); sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data); sqlite3_config(SQLITE_CONFIG_MULTITHREAD); sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> "); sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> "); } /* ** Output text to the console in a font that attracts extra attention. */ #ifdef _WIN32 static void printBold(const char *zText){ HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO defaultScreenInfo; GetConsoleScreenBufferInfo(out, &defaultScreenInfo); SetConsoleTextAttribute(out, FOREGROUND_RED|FOREGROUND_INTENSITY ); printf("%s", zText); SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes); } #else static void printBold(const char *zText){ printf("\033[1m%s\033[0m", zText); } #endif /* ** Get the argument to an --option. Throw an error and die if no argument ** is available. */ static char *cmdline_option_value(int argc, char **argv, int i){ if( i==argc ){ fprintf(stderr, "%s: Error: missing argument to %s\n", argv[0], argv[argc-1]); exit(1); } return argv[i]; } int SQLITE_CDECL main(int argc, char **argv){ char *zErrMsg = 0; ShellState data; const char *zInitFile = 0; int i; int rc = 0; int warnInmemoryDb = 0; int readStdin = 1; int nCmd = 0; char **azCmd = 0; #if USE_SYSTEM_SQLITE+0!=1 if( strcmp(sqlite3_sourceid(),SQLITE_SOURCE_ID)!=0 ){ fprintf(stderr, "SQLite header and source version mismatch\n%s\n%s\n", sqlite3_sourceid(), SQLITE_SOURCE_ID); exit(1); } #endif setBinaryMode(stdin); setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */ Argv0 = argv[0]; main_init(&data); stdin_is_interactive = isatty(0); /* Make sure we have a valid signal handler early, before anything ** else is done. */ #ifdef SIGINT signal(SIGINT, interrupt_handler); #endif #ifdef SQLITE_SHELL_DBNAME_PROC { /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name ** of a C-function that will provide the name of the database file. Use ** this compile-time option to embed this shell program in larger ** applications. */ extern void SQLITE_SHELL_DBNAME_PROC(const char**); SQLITE_SHELL_DBNAME_PROC(&data.zDbFilename); warnInmemoryDb = 0; } #endif /* Do an initial pass through the command-line argument to locate ** the name of the database file, the name of the initialization file, ** the size of the alternative malloc heap, ** and the first command to execute. */ for(i=1; i0x7fff0000 ) szHeap = 0x7fff0000; sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64); #endif }else if( strcmp(z,"-scratch")==0 ){ int n, sz; sz = (int)integerValue(cmdline_option_value(argc,argv,++i)); if( sz>400000 ) sz = 400000; if( sz<2500 ) sz = 2500; n = (int)integerValue(cmdline_option_value(argc,argv,++i)); if( n>10 ) n = 10; if( n<1 ) n = 1; sqlite3_config(SQLITE_CONFIG_SCRATCH, malloc(n*sz+1), sz, n); data.shellFlgs |= SHFLG_Scratch; }else if( strcmp(z,"-pagecache")==0 ){ int n, sz; sz = (int)integerValue(cmdline_option_value(argc,argv,++i)); if( sz>70000 ) sz = 70000; if( sz<800 ) sz = 800; n = (int)integerValue(cmdline_option_value(argc,argv,++i)); if( n<10 ) n = 10; sqlite3_config(SQLITE_CONFIG_PAGECACHE, malloc(n*sz+1), sz, n); data.shellFlgs |= SHFLG_Pagecache; }else if( strcmp(z,"-lookaside")==0 ){ int n, sz; sz = (int)integerValue(cmdline_option_value(argc,argv,++i)); if( sz<0 ) sz = 0; n = (int)integerValue(cmdline_option_value(argc,argv,++i)); if( n<0 ) n = 0; sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n); if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside; #ifdef SQLITE_ENABLE_VFSTRACE }else if( strcmp(z,"-vfstrace")==0 ){ extern int vfstrace_register( const char *zTraceName, const char *zOldVfsName, int (*xOut)(const char*,void*), void *pOutArg, int makeDefault ); vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1); #endif #ifdef SQLITE_ENABLE_MULTIPLEX }else if( strcmp(z,"-multiplex")==0 ){ extern int sqlite3_multiple_initialize(const char*,int); sqlite3_multiplex_initialize(0, 1); #endif }else if( strcmp(z,"-mmap")==0 ){ sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz); }else if( strcmp(z,"-vfs")==0 ){ sqlite3_vfs *pVfs = sqlite3_vfs_find(cmdline_option_value(argc,argv,++i)); if( pVfs ){ sqlite3_vfs_register(pVfs, 1); }else{ fprintf(stderr, "no such VFS: \"%s\"\n", argv[i]); exit(1); } } } if( data.zDbFilename==0 ){ #ifndef SQLITE_OMIT_MEMORYDB data.zDbFilename = ":memory:"; warnInmemoryDb = argc==1; #else fprintf(stderr,"%s: Error: no database filename specified\n", Argv0); return 1; #endif } data.out = stdout; /* Go ahead and open the database file if it already exists. If the ** file does not exist, delay opening it. This prevents empty database ** files from being created if a user mistypes the database name argument ** to the sqlite command-line tool. */ if( access(data.zDbFilename, 0)==0 ){ open_db(&data, 0); } /* Process the initialization file if there is one. If no -init option ** is given on the command line, look for a file named ~/.sqliterc and ** try to process it. */ process_sqliterc(&data,zInitFile); /* Make a second pass through the command-line argument and set ** options. This second pass is delayed until after the initialization ** file is processed so that the command-line arguments will override ** settings in the initialization file. */ for(i=1; i