2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
otp_version: [21,22,23,24]
|
||||
otp_version: [22,23,24]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
container:
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,3 +11,7 @@ _build
|
||||
c_src/**/*.d
|
||||
erl_crash.dump
|
||||
tags
|
||||
doc/*
|
||||
!doc/style.css
|
||||
!doc/overview.edoc
|
||||
!doc/SQLite370.svg
|
||||
|
||||
24
README.md
24
README.md
@@ -13,10 +13,9 @@ in the nif library or the sqlite database can crash the entire Erlang
|
||||
VM. If you do not want to take this risk, it is always possible to
|
||||
access the sqlite nif from a separate erlang node.
|
||||
|
||||
Special care has been taken not to block the scheduler of the calling
|
||||
process. This is done by handling all commands from erlang within a
|
||||
lightweight thread. The erlang scheduler will get control back when
|
||||
the command has been added to the command-queue of the thread.
|
||||
Special care has been taken not to block the normal erlang scheduler
|
||||
of the calling process. This is done by handling neccesary commands
|
||||
from erlang by using a dirty scheduler.
|
||||
|
||||
SQLite Compile Options
|
||||
----------------------
|
||||
@@ -66,3 +65,20 @@ INSERT INTO table VALUES("abcd", 1234);
|
||||
Will not work, because it sees the value 'abcd' as a SQL object
|
||||
value, and not a string literal.
|
||||
|
||||
Version 0.8.0
|
||||
-------------
|
||||
|
||||
This version is a major derivation from previous versions. When
|
||||
I started with this library it was implemented by using a separate
|
||||
os level thread per connection. At the time this was the only way
|
||||
to use functions in C which take longer to process than 1ms.
|
||||
A lot has changed since then. The VM now has dirty schedulers
|
||||
which make it possible to remove the thread per connection.
|
||||
This makes it possible to open a lot more connections. On the
|
||||
SQLite side some things have also changed. Extended error codes,
|
||||
introspection into the internals. This release modernizes the
|
||||
integration. In some places the API is no longer compatible and
|
||||
will require small changes. In order to ease this process the
|
||||
library now has typespecs, and the documentation was extended.
|
||||
|
||||
|
||||
|
||||
2597
c_src/esqlite3_nif.c
2597
c_src/esqlite3_nif.c
File diff suppressed because it is too large
Load Diff
195
c_src/queue.c
195
c_src/queue.c
@@ -1,195 +0,0 @@
|
||||
// This file is part of Emonk released under the MIT license.
|
||||
// See the LICENSE file for more information.
|
||||
|
||||
/* Adapted by: Maas-Maarten Zeeman <mmzeeman@xs4all.nl */
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "queue.h"
|
||||
|
||||
struct qitem_t
|
||||
{
|
||||
struct qitem_t* next;
|
||||
void* data;
|
||||
};
|
||||
|
||||
typedef struct qitem_t qitem;
|
||||
|
||||
struct queue_t
|
||||
{
|
||||
ErlNifMutex *lock;
|
||||
ErlNifCond *cond;
|
||||
qitem *head;
|
||||
qitem *tail;
|
||||
void *message;
|
||||
int length;
|
||||
};
|
||||
|
||||
queue *
|
||||
queue_create()
|
||||
{
|
||||
queue *ret;
|
||||
|
||||
ret = (queue *) enif_alloc(sizeof(struct queue_t));
|
||||
if(ret == NULL) goto error;
|
||||
|
||||
ret->lock = NULL;
|
||||
ret->cond = NULL;
|
||||
ret->head = NULL;
|
||||
ret->tail = NULL;
|
||||
ret->message = NULL;
|
||||
ret->length = 0;
|
||||
|
||||
ret->lock = enif_mutex_create("queue_lock");
|
||||
if(ret->lock == NULL) goto error;
|
||||
|
||||
ret->cond = enif_cond_create("queue_cond");
|
||||
if(ret->cond == NULL) goto error;
|
||||
|
||||
return ret;
|
||||
|
||||
error:
|
||||
if(ret->lock != NULL)
|
||||
enif_mutex_destroy(ret->lock);
|
||||
if(ret->cond != NULL)
|
||||
enif_cond_destroy(ret->cond);
|
||||
if(ret != NULL)
|
||||
enif_free(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
queue_destroy(queue *queue)
|
||||
{
|
||||
ErlNifMutex *lock;
|
||||
ErlNifCond *cond;
|
||||
int length;
|
||||
|
||||
enif_mutex_lock(queue->lock);
|
||||
lock = queue->lock;
|
||||
cond = queue->cond;
|
||||
length = queue->length;
|
||||
|
||||
queue->lock = NULL;
|
||||
queue->cond = NULL;
|
||||
queue->head = NULL;
|
||||
queue->tail = NULL;
|
||||
queue->length = -1;
|
||||
enif_mutex_unlock(lock);
|
||||
|
||||
assert(length == 0 && "Attempting to destroy a non-empty queue.");
|
||||
enif_cond_destroy(cond);
|
||||
enif_mutex_destroy(lock);
|
||||
enif_free(queue);
|
||||
}
|
||||
|
||||
int
|
||||
queue_has_item(queue *queue)
|
||||
{
|
||||
int ret;
|
||||
|
||||
enif_mutex_lock(queue->lock);
|
||||
ret = (queue->head != NULL);
|
||||
enif_mutex_unlock(queue->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
queue_push(queue *queue, void *item)
|
||||
{
|
||||
qitem * entry = (qitem *) enif_alloc(sizeof(struct qitem_t));
|
||||
if(entry == NULL)
|
||||
return 0;
|
||||
|
||||
entry->data = item;
|
||||
entry->next = NULL;
|
||||
|
||||
enif_mutex_lock(queue->lock);
|
||||
|
||||
assert(queue->length >= 0 && "Invalid queue size at push");
|
||||
|
||||
if(queue->tail != NULL)
|
||||
queue->tail->next = entry;
|
||||
|
||||
queue->tail = entry;
|
||||
|
||||
if(queue->head == NULL)
|
||||
queue->head = queue->tail;
|
||||
|
||||
queue->length += 1;
|
||||
|
||||
enif_cond_signal(queue->cond);
|
||||
enif_mutex_unlock(queue->lock);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void*
|
||||
queue_pop(queue *queue)
|
||||
{
|
||||
qitem *entry;
|
||||
void* item;
|
||||
|
||||
enif_mutex_lock(queue->lock);
|
||||
|
||||
/* Wait for an item to become available.
|
||||
*/
|
||||
while(queue->head == NULL)
|
||||
enif_cond_wait(queue->cond, queue->lock);
|
||||
|
||||
assert(queue->length >= 0 && "Invalid queue size at pop.");
|
||||
|
||||
/* Woke up because queue->head != NULL
|
||||
* Remove the entry and return the payload.
|
||||
*/
|
||||
entry = queue->head;
|
||||
queue->head = entry->next;
|
||||
entry->next = NULL;
|
||||
|
||||
if(queue->head == NULL) {
|
||||
assert(queue->tail == entry && "Invalid queue state: Bad tail.");
|
||||
queue->tail = NULL;
|
||||
}
|
||||
|
||||
queue->length -= 1;
|
||||
|
||||
enif_mutex_unlock(queue->lock);
|
||||
|
||||
item = entry->data;
|
||||
enif_free(entry);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
int
|
||||
queue_send(queue *queue, void *item)
|
||||
{
|
||||
enif_mutex_lock(queue->lock);
|
||||
assert(queue->message == NULL && "Attempting to send multiple messages.");
|
||||
queue->message = item;
|
||||
enif_cond_signal(queue->cond);
|
||||
enif_mutex_unlock(queue->lock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void *
|
||||
queue_receive(queue *queue)
|
||||
{
|
||||
void *item;
|
||||
|
||||
enif_mutex_lock(queue->lock);
|
||||
|
||||
/* Wait for an item to become available.
|
||||
*/
|
||||
while(queue->message == NULL)
|
||||
enif_cond_wait(queue->cond, queue->lock);
|
||||
|
||||
item = queue->message;
|
||||
queue->message = NULL;
|
||||
|
||||
enif_mutex_unlock(queue->lock);
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// This file is part of Emonk released under the MIT license.
|
||||
// See the LICENSE file for more information.
|
||||
|
||||
/* adapted by: Maas-Maarten Zeeman <mmzeeman@xs4all.nl */
|
||||
|
||||
#ifndef ESQLITE_QUEUE_H
|
||||
#define ESQLITE_QUEUE_H
|
||||
|
||||
#include "erl_nif.h"
|
||||
|
||||
typedef struct queue_t queue;
|
||||
|
||||
queue * queue_create();
|
||||
void queue_destroy(queue *queue);
|
||||
|
||||
int queue_has_item(queue *queue);
|
||||
|
||||
int queue_push(queue *queue, void* item);
|
||||
void* queue_pop(queue *queue);
|
||||
|
||||
int queue_send(queue *queue, void* item);
|
||||
void* queue_receive(queue *);
|
||||
|
||||
#endif
|
||||
67
doc/SQLite370.svg
Normal file
67
doc/SQLite370.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.7 KiB |
@@ -1,10 +1,10 @@
|
||||
@author Maas-Maarten Zeeman <mmzeeman@xs4all.nl>
|
||||
|
||||
@title eSqlite Documentation
|
||||
@title ESQLite Documentation
|
||||
|
||||
@doc
|
||||
|
||||
eSqlite is a library which makes it possible to use sqlite databases in erlang. It is implemented
|
||||
ESQLite is a library which makes it possible to use sqlite databases in erlang. It is implemented
|
||||
as a NIF, which means that the sqlite database engine is linked to the erlang virtual machine.
|
||||
|
||||
<hr />
|
||||
|
||||
76
doc/style.css
Normal file
76
doc/style.css
Normal file
@@ -0,0 +1,76 @@
|
||||
/* standard EDoc style sheet */
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
margin-left: .25in;
|
||||
margin-right: .2in;
|
||||
margin-top: 0.2in;
|
||||
margin-bottom: 0.2in;
|
||||
color: #000000;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
h1,h2 {
|
||||
margin-left: -0.2in;
|
||||
}
|
||||
div.navbar {
|
||||
background-color: #add8e6;
|
||||
padding: 0.2em;
|
||||
}
|
||||
h2.indextitle {
|
||||
padding: 0.4em;
|
||||
background-color: #add8e6;
|
||||
}
|
||||
h3.function,h3.typedecl {
|
||||
background-color: #add8e6;
|
||||
padding-left: 1em;
|
||||
}
|
||||
div.spec {
|
||||
margin-left: 2em;
|
||||
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
a.module {
|
||||
text-decoration:none
|
||||
}
|
||||
a.module:hover {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
ul.definitions {
|
||||
list-style-type: none;
|
||||
}
|
||||
ul.index {
|
||||
list-style-type: none;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
/*
|
||||
* Minor style tweaks
|
||||
*/
|
||||
ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
td {
|
||||
padding: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/*
|
||||
Tune styles
|
||||
*/
|
||||
|
||||
table[summary="navigation bar"] {
|
||||
background-image: url('SQLite370.svg');
|
||||
background-size: 110px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
code, p>tt, a>tt {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
NifSharedSources = ["c_src/esqlite3_nif.c", "c_src/queue.c"].
|
||||
NifSharedSources = ["c_src/esqlite3_nif.c"].
|
||||
NifStaticSources = NifSharedSources ++ ["c_src/sqlite3/sqlite3.c"].
|
||||
CFlagsDefault = "$CFLAGS -Os -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=1 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_USE_ALLOCA -DSQLITE_OMIT_AUTOINIT -DSQLITE_USE_URI -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_MATH_FUNCTIONS -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_GEOPOLY".
|
||||
DrvLdFlagsDefault = "-shared -lsqlite3".
|
||||
@@ -23,9 +23,9 @@ CFlags =
|
||||
end.
|
||||
|
||||
[
|
||||
{minimum_otp_vsn, "21.0"},
|
||||
{minimum_otp_vsn, "22.0"},
|
||||
|
||||
{erl_opts, [debug_info, warnings_as_errors]},
|
||||
{erl_opts, [debug_info]},
|
||||
|
||||
{xref_checks, [undefined_function_calls,
|
||||
undefined_functions,
|
||||
@@ -68,6 +68,7 @@ CFlags =
|
||||
]},
|
||||
|
||||
{edoc_opts, [{preprocess, true},
|
||||
{stylesheet, "style.css"},
|
||||
{sort_functions, false}]},
|
||||
|
||||
{hex, [{doc, edoc}]}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{application, esqlite,
|
||||
[
|
||||
{description, "sqlite nif interface"},
|
||||
{vsn, "0.7.3"},
|
||||
{vsn, "0.8.0"},
|
||||
{modules, [esqlite3, esqlite3_nif]},
|
||||
{registered, []},
|
||||
{licenses, ["Apache"]},
|
||||
|
||||
907
src/esqlite3.erl
907
src/esqlite3.erl
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,9 @@
|
||||
%% @author Maas-Maarten Zeeman <mmzeeman@xs4all.nl>
|
||||
%% @copyright 2011 - 2022 Maas-Maarten Zeeman
|
||||
%%
|
||||
%% @doc Low level Erlang API for sqlite3 databases.
|
||||
%% @end
|
||||
|
||||
%% 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
|
||||
@@ -9,47 +15,67 @@
|
||||
%% 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.
|
||||
%%
|
||||
%% @author Maas-Maarten Zeeman <mmzeeman@xs4all.nl>
|
||||
%% @copyright 2011 - 2022 Maas-Maarten Zeeman
|
||||
%%
|
||||
%% @doc Low level erlang API for sqlite3 databases.
|
||||
|
||||
-module(esqlite3_nif).
|
||||
-author("Maas-Maarten Zeeman <mmzeeman@xs4all.nl>").
|
||||
|
||||
%% low-level exports
|
||||
-export([
|
||||
start/0,
|
||||
open/4,
|
||||
set_update_hook/4,
|
||||
exec/4,
|
||||
changes/3,
|
||||
insert/4,
|
||||
last_insert_rowid/3,
|
||||
get_autocommit/3,
|
||||
prepare/4,
|
||||
multi_step/5,
|
||||
reset/4,
|
||||
finalize/4,
|
||||
bind/5,
|
||||
column_names/4,
|
||||
column_types/4,
|
||||
backup_init/6,
|
||||
backup_step/5,
|
||||
backup_remaining/4,
|
||||
backup_pagecount/4,
|
||||
backup_finish/4,
|
||||
open/1,
|
||||
close/1,
|
||||
error_info/1,
|
||||
|
||||
set_update_hook/2,
|
||||
|
||||
get_autocommit/1,
|
||||
last_insert_rowid/1,
|
||||
changes/1,
|
||||
exec/2,
|
||||
prepare/3,
|
||||
|
||||
column_names/1,
|
||||
column_decltypes/1,
|
||||
|
||||
bind_int/3,
|
||||
bind_int64/3,
|
||||
bind_double/3,
|
||||
bind_text/3,
|
||||
bind_blob/3,
|
||||
bind_null/2,
|
||||
|
||||
step/1,
|
||||
|
||||
reset/1,
|
||||
|
||||
interrupt/1,
|
||||
close/3
|
||||
|
||||
backup_init/4,
|
||||
backup_remaining/1,
|
||||
backup_pagecount/1,
|
||||
backup_step/2,
|
||||
backup_finish/1,
|
||||
|
||||
memory_stats/1,
|
||||
status/2
|
||||
]).
|
||||
|
||||
-type raw_connection() :: reference().
|
||||
-type raw_statement() :: reference().
|
||||
-type raw_backup() :: reference().
|
||||
-type sql() :: iodata().
|
||||
-type esqlite3_ref() :: reference(). % Reference to a database connection handle. See [https://sqlite.org/c3ref/sqlite3.html] for more details.
|
||||
-type esqlite3_stmt_ref() :: reference(). % Reference to a prepared statement object. See [https://sqlite.org/c3ref/stmt.html] for more details.
|
||||
-type esqlite3_backup_ref() :: reference(). % Reference to a online backup object. See [https://sqlite.org/c3ref/backup.html] for more details.
|
||||
-type sql() :: iodata(). % Make sure the iodata contains utf-8 encoded data.
|
||||
-type rowid() :: integer().
|
||||
-type cell() :: undefined | integer() | float() | binary().
|
||||
-type row() :: list(cell()).
|
||||
-type extended_errcode() :: integer(). % Extended sqlite3 error code. See [https://sqlite.org/rescode.html] for more details.
|
||||
-type error() :: {error, extended_errcode()}.
|
||||
-type error_info() :: #{ errcode := integer(),
|
||||
extended_errcode := extended_errcode(),
|
||||
errstr := unicode:unicode_binary(), % English-language text that describes the result code, as UTF-8
|
||||
errmsg := unicode:unicode_binary(), % English-language text that describes the error, as UTF-8
|
||||
error_offset := integer() % The byte offset to the token in the input sql.
|
||||
}. % See: [https://sqlite.org/c3ref/errcode.html] for more information.
|
||||
|
||||
-export_type([raw_connection/0, raw_statement/0, raw_backup/0, sql/0]).
|
||||
-export_type([esqlite3_ref/0, esqlite3_stmt_ref/0, esqlite3_backup_ref/0, sql/0, rowid/0, cell/0, row/0, error/0, error_info/0]).
|
||||
|
||||
-on_load(init/0).
|
||||
|
||||
@@ -61,138 +87,225 @@ init() ->
|
||||
end,
|
||||
ok = erlang:load_nif(NifFileName, 0).
|
||||
|
||||
%% @doc Start a low level thread which will can handle sqlite3 calls.
|
||||
%%
|
||||
-spec start() -> {ok, raw_connection()} | {error, _}.
|
||||
start() ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Open the specified sqlite3 database.
|
||||
%%
|
||||
%% Sends an asynchronous open command over the connection and returns
|
||||
%% ok immediately. When the database is opened
|
||||
%%
|
||||
-spec open(raw_connection(), reference(), pid(), string()) -> ok | {error, _}.
|
||||
open(_Db, _Ref, _Dest, _Filename) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
-spec set_update_hook(raw_connection(), reference(), pid(), pid()) -> ok | {error, _}.
|
||||
set_update_hook(_Db, _Ref, _Dest, _Pid) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Exec the query.
|
||||
%%
|
||||
%% Sends an asynchronous exec command over the connection and returns
|
||||
%% ok immediately.
|
||||
%%
|
||||
%% When the statement is executed Dest will receive message {Ref, answer()}
|
||||
%% with answer() integer | {error, reason()}
|
||||
%%
|
||||
-spec exec(raw_connection(), reference(), pid(), sql()) -> ok | {error, _}.
|
||||
exec(_Db, _Ref, _Dest, _Sql) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Get the number of affected rows of last statement
|
||||
%%
|
||||
%% When the statement is executed Dest will receive message {Ref, answer()}
|
||||
%% with answer() integer | {error, reason()}
|
||||
-spec changes(raw_connection(), reference(), pid()) -> ok | {error, _}.
|
||||
changes(_Db, _Ref, _Dest) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc
|
||||
%%
|
||||
-spec prepare(raw_connection(), reference(), pid(), sql()) -> ok | {error, _}.
|
||||
prepare(_Db, _Ref, _Dest, _Sql) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc
|
||||
%%
|
||||
-spec multi_step(raw_connection(), raw_statement(), pos_integer(), reference(), pid()) -> ok | {error, _}.
|
||||
multi_step(_Db, _Stmt, _Chunk_Size, _Ref, _Dest) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc
|
||||
%%
|
||||
-spec reset(raw_connection(), raw_statement(), reference(), pid()) -> ok | {error, _}.
|
||||
reset(_Db, _Stmt, _Ref, _Dest) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc
|
||||
%%
|
||||
-spec finalize(raw_connection(), raw_statement(), reference(), pid()) -> ok | {error, _}.
|
||||
finalize(_Db, _Stmt, _Ref, _Dest) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Bind parameters to a prepared statement.
|
||||
%%
|
||||
-spec bind(raw_connection(), raw_statement(), reference(), pid(), list(any())) -> ok | {error, _}.
|
||||
bind(_Db, _Stmt, _Ref, _Dest, _Args) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Retrieve the column names of the prepared statement
|
||||
%%
|
||||
-spec column_names(raw_connection(), raw_statement(), reference(), pid()) -> ok | {error, _}.
|
||||
column_names(_Db, _Stmt, _Ref, _Dest) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Retrieve the column types of the prepared statement
|
||||
%%
|
||||
-spec column_types(raw_connection(), raw_statement(), reference(), pid()) -> ok | {error, _}.
|
||||
column_types(_Db, _Stmt, _Ref, _Dest) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Initialize a backup procedure of a database.
|
||||
-spec backup_init(raw_connection(), string(), raw_connection(), string(), reference(), pid()) -> ok | {error, _}.
|
||||
backup_init(_DestDb, _DestName, _SourceDb, _SourceName, _Ref, _Dest) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Do a backup step.
|
||||
-spec backup_step(raw_connection(), raw_backup(), integer(), reference(), pid()) -> ok | {error, _}.
|
||||
backup_step(_Db, _Backup, _NPages, _Ref, _Dest) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Get the amount of remaining pages which need to be backed up.
|
||||
-spec backup_remaining(raw_connection(), raw_backup(), reference(), pid()) -> ok | {error, _}.
|
||||
backup_remaining(_Db, _Backup, _Ref, _Dest) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Get the total number of pages which need to be backed up.
|
||||
-spec backup_pagecount(raw_connection(), raw_backup(), reference(), pid()) -> ok | {error, _}.
|
||||
backup_pagecount(_Db, _Backup, _Ref, _Dest) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Finish the backup.
|
||||
-spec backup_finish(raw_connection(), raw_backup(), reference(), pid()) -> ok | {error, _}.
|
||||
backup_finish(_Db, _Backup, _Ref, _Dest) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Interrupt all active queries.
|
||||
-spec interrupt(raw_connection()) -> ok.
|
||||
interrupt(_Db) ->
|
||||
%% It is possible to use sqlite's uri filenames to open files.
|
||||
%% See: [https://sqlite.org/uri.html] for more information.
|
||||
-spec open(Filename) -> OpenResult when
|
||||
Filename :: string(),
|
||||
OpenResult :: {ok, esqlite3_ref()} | error().
|
||||
open(_Filename) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Close the connection.
|
||||
%%
|
||||
-spec close(raw_connection(), reference(), pid()) -> ok | {error, _}.
|
||||
close(_Db, _Ref, _Dest) ->
|
||||
-spec close(Connection) -> CloseResult
|
||||
when Connection :: esqlite3_ref(),
|
||||
CloseResult :: ok | {error, _}.
|
||||
close(_Db) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Insert record
|
||||
%% @doc Get an error messages for the last occurred error.
|
||||
%%
|
||||
-spec insert(raw_connection(), reference(), pid(), sql()) -> ok | {error, _}.
|
||||
insert(_Db, _Ref, _Dest, _Sql) ->
|
||||
-spec error_info(Connection) -> ErrorInfo
|
||||
when Connection :: esqlite3_ref(),
|
||||
ErrorInfo :: error_info().
|
||||
error_info(_Db) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
|
||||
%% @doc Set an update hook
|
||||
-spec set_update_hook(Connection, Pid) -> Result
|
||||
when Connection :: esqlite3_ref(),
|
||||
Pid :: pid(),
|
||||
Result :: ok.
|
||||
set_update_hook(_Db, _Pid) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
|
||||
%% @doc Execute a sql statement
|
||||
-spec exec(Connection, Sql) -> ExecResult
|
||||
when Connection :: esqlite3_ref(),
|
||||
Sql :: sql(),
|
||||
ExecResult :: ok | error().
|
||||
exec(_Connection, _Sql) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
|
||||
%% @doc Compile a sql statement.
|
||||
-spec prepare(Connection, Sql, PrepareFlags) -> PrepareResult
|
||||
when Connection :: esqlite3_ref(),
|
||||
Sql :: sql(),
|
||||
PrepareFlags :: non_neg_integer(),
|
||||
PrepareResult :: {ok, esqlite3_stmt_ref()} | error().
|
||||
prepare(_Connection, _Sql, _PrepareFlags) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
% @doc Bind an integer to a position in a prepared statement.
|
||||
-spec bind_int(Statement, Index, Value) -> Result when
|
||||
Statement :: esqlite3_stmt_ref(),
|
||||
Index :: integer(),
|
||||
Value :: integer(), %% [todo] Should be a 32 bit integer range.
|
||||
Result :: ok | error().
|
||||
bind_int(_Statement, _Index, _Value) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
% @doc Bind a 64 bit integer to a position in a prepared statement.
|
||||
-spec bind_int64(Statement, Index, Value) -> Result when
|
||||
Statement :: esqlite3_stmt_ref(),
|
||||
Index :: integer(),
|
||||
Value :: integer(), %% [todo] Should be a 64 bit integer range.
|
||||
Result :: ok | error().
|
||||
bind_int64(_Statement, _Index, _Value) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
% @doc Bind an double/float to a position in a prepared statement.
|
||||
-spec bind_double(Statement, Index, Value) -> Result when
|
||||
Statement :: esqlite3_stmt_ref(),
|
||||
Index :: integer(),
|
||||
Value :: float(), %% [todo] Should be a 64 bit integer range.
|
||||
Result :: ok | error().
|
||||
bind_double(_Statement, _Index, _Value) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
% @doc Bind a utf-8 string to a position in a prepared statement.
|
||||
-spec bind_text(Statement, Index, Value) -> Result when
|
||||
Statement :: esqlite3_stmt_ref(),
|
||||
Index :: integer(),
|
||||
Value :: iodata(), %% [todo] Should be a utf-8 iodata.
|
||||
Result :: ok | error().
|
||||
bind_text(_Statement, _Index, _Value) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
% @doc Bind a blob to a position in a prepared statement.
|
||||
-spec bind_blob(Statement, Index, Value) -> Result when
|
||||
Statement :: esqlite3_stmt_ref(),
|
||||
Index :: integer(),
|
||||
Value :: iodata(),
|
||||
Result :: ok | error().
|
||||
bind_blob(_Statement, _Index, _Value) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
% @doc Bind a null to a position in a prepared statement.
|
||||
-spec bind_null(Statement, Index) -> Result when
|
||||
Statement :: esqlite3_stmt_ref(),
|
||||
Index :: integer(),
|
||||
Result :: ok | error().
|
||||
bind_null(_Statement, _Index) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
-spec step(Statement) -> StepResult when
|
||||
Statement :: esqlite3_stmt_ref(),
|
||||
StepResult :: row() | '$done' | error().
|
||||
step(_Statement) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
-spec reset(Statement) -> ResetResult when
|
||||
Statement :: esqlite3_stmt_ref(),
|
||||
ResetResult :: ok | error().
|
||||
reset(_Statement) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Retrieve the column names of the prepared statement
|
||||
%%
|
||||
-spec column_names(Statement) -> Names when
|
||||
Statement :: esqlite3_stmt_ref(),
|
||||
Names :: list(unicode:unicode_binary()).
|
||||
column_names(_Stmt) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Retrieve the declared datatypes of all columns.
|
||||
%%
|
||||
-spec column_decltypes(Statement) -> Types when
|
||||
Statement :: esqlite3_stmt_ref(),
|
||||
Types :: list(undefined | unicode:unicode_binary()).
|
||||
column_decltypes(_Stmt) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
|
||||
%% @doc Initialize a backup procedure of a database.
|
||||
-spec backup_init(Destination, DestinationName, Source, SourceName) -> InitResult when
|
||||
Destination :: esqlite3_ref(),
|
||||
DestinationName :: iodata(),
|
||||
Source :: esqlite3_ref(),
|
||||
SourceName :: iodata(),
|
||||
InitResult :: {ok, esqlite3_backup_ref()} | error().
|
||||
backup_init(_Dest, _DestName, _Src, _SrcName) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
-spec backup_remaining(Backup) -> Remaining when
|
||||
Backup :: esqlite3_backup_ref(),
|
||||
Remaining :: integer().
|
||||
backup_remaining(_Backup) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
-spec backup_pagecount(Backup) -> Pagecount when
|
||||
Backup :: esqlite3_backup_ref(),
|
||||
Pagecount :: integer().
|
||||
backup_pagecount(_Backup) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
-spec backup_step(Backup, NPage) -> Result when
|
||||
Backup :: esqlite3_backup_ref(),
|
||||
NPage :: integer(),
|
||||
Result :: ok | '$done' | error().
|
||||
backup_step(_Backup, _PageCount) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
-spec backup_finish(Backup) -> Result when
|
||||
Backup :: esqlite3_backup_ref(),
|
||||
Result :: ok | error().
|
||||
backup_finish(_Backup) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Interrupt all active queries.
|
||||
-spec interrupt(esqlite3_ref()) -> ok.
|
||||
interrupt(_Db) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Get the last insert rowid.
|
||||
%%
|
||||
-spec last_insert_rowid(raw_connection(), reference(), pid()) -> ok | {error, _}.
|
||||
last_insert_rowid(_Db, _Ref, _Dest) ->
|
||||
-spec last_insert_rowid(esqlite3_ref()) -> rowid().
|
||||
last_insert_rowid(_Connection) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Get automcommit
|
||||
%% @doc Get number of changes insert, delete of the most recent completed
|
||||
%% INSERT, DELETE or UPDATE statement.
|
||||
%%
|
||||
-spec get_autocommit(raw_connection(), reference(), pid()) -> ok | {error, _}.
|
||||
get_autocommit(_Db, _Ref, _Dest) ->
|
||||
-spec changes(esqlite3_ref()) -> integer().
|
||||
changes(_Connection) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Get autocommit
|
||||
%%
|
||||
-spec get_autocommit(esqlite3_ref()) -> true | false.
|
||||
get_autocommit(_Connection) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Get memory statistics
|
||||
%%
|
||||
-spec memory_stats(HighwaterResetFlag) -> Stats when
|
||||
HighwaterResetFlag :: integer(),
|
||||
Stats :: #{ used := integer(), highwater := integer() }.
|
||||
memory_stats(_Flag) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
%% @doc Get sqlite status information.
|
||||
%%
|
||||
%% <code>
|
||||
%% MEMORY_USED 0
|
||||
%% PAGECACHE_USED 1
|
||||
%% PAGECACHE_OVERFLOW 2
|
||||
%% MALLOC_SIZE 5
|
||||
%% PARSER_STACK 6
|
||||
%% PAGECACHE_SIZE 7
|
||||
%% MALLOC_COUNT 8
|
||||
%% </code>
|
||||
%%
|
||||
-spec status(Op, HighwaterResetFlag) -> Stats when
|
||||
Op :: integer(),
|
||||
HighwaterResetFlag :: integer(),
|
||||
Stats :: #{ used := non_neg_integer(), highwater := non_neg_integer() }.
|
||||
status(_Op, _Flag) ->
|
||||
erlang:nif_error(nif_library_not_loaded).
|
||||
|
||||
|
||||
@@ -19,27 +19,82 @@ close_test() ->
|
||||
{ok, C} = esqlite3:open(":memory:"),
|
||||
ok = esqlite3:close(C),
|
||||
|
||||
%% Check if functions still return sensible values.
|
||||
{error, closed} = esqlite3:set_update_hook(self(), C),
|
||||
{error, closed} = esqlite3:changes(C),
|
||||
{error, closed} = esqlite3:get_autocommit(C),
|
||||
{error, closed} = esqlite3:last_insert_rowid(C),
|
||||
%% Double close should also work.
|
||||
ok = esqlite3:close(C),
|
||||
|
||||
{error, _} = esqlite3:exec("create table test(one, two, three)", C),
|
||||
%% Check if functions still return sensible values.
|
||||
?assertError(closed, esqlite3:set_update_hook(C, self())),
|
||||
?assertError(closed, esqlite3:changes(C)),
|
||||
?assertError(closed, esqlite3:get_autocommit(C)),
|
||||
?assertError(closed, esqlite3:last_insert_rowid(C)),
|
||||
|
||||
?assertEqual({error, 21},
|
||||
esqlite3:exec(C, "create table test(one, two, three)")),
|
||||
|
||||
ok.
|
||||
|
||||
prepare_test() ->
|
||||
{ok, C} = esqlite3:open(":memory:"),
|
||||
?assertMatch({ok, {esqlite3_stmt, _}}, esqlite3:prepare(C, "select 1")),
|
||||
ok = esqlite3:close(C),
|
||||
ok.
|
||||
|
||||
prepare_after_close_test() ->
|
||||
{ok, C} = esqlite3:open(":memory:"),
|
||||
?assertEqual(ok, esqlite3:close(C)),
|
||||
?assertMatch({error, 21}, esqlite3:prepare(C, "select 1")),
|
||||
ok.
|
||||
|
||||
column_names_test() ->
|
||||
{ok, C} = esqlite3:open(":memory:"),
|
||||
|
||||
{ok, Stmt} = esqlite3:prepare(C, "select 1 as one"),
|
||||
?assertEqual([<<"one">>], esqlite3:column_names(Stmt)),
|
||||
|
||||
{ok, Stmt1} = esqlite3:prepare(C, <<"select 1 as 😀"/utf8>>),
|
||||
?assertEqual([<<"😀"/utf8>>], esqlite3:column_names(Stmt1)),
|
||||
|
||||
{ok, Stmt2} = esqlite3:prepare(C, <<"select 1">>),
|
||||
?assertEqual([<<"1">>], esqlite3:column_names(Stmt2)),
|
||||
|
||||
{ok, Stmt3} = esqlite3:prepare(C, <<"select 1, 2, 3">>),
|
||||
?assertEqual([<<"1">>, <<"2">>, <<"3">>], esqlite3:column_names(Stmt3)),
|
||||
|
||||
ok.
|
||||
|
||||
column_decltypes_test() ->
|
||||
{ok, C} = esqlite3:open(":memory:"),
|
||||
{ok, Stmt} = esqlite3:prepare(C, "select 1, 2, 3"),
|
||||
?assertEqual([undefined, undefined, undefined], esqlite3:column_decltypes(Stmt)),
|
||||
|
||||
ok.
|
||||
|
||||
step_test() ->
|
||||
{ok, C} = esqlite3:open(":memory:"),
|
||||
{ok, Stmt} = esqlite3:prepare(C, "select 1, 2, 3;" ),
|
||||
|
||||
?assertEqual([1,2,3], esqlite3:step(Stmt)),
|
||||
?assertEqual('$done', esqlite3:step(Stmt)),
|
||||
|
||||
%% After the done, the statement is reset and
|
||||
?assertEqual([1,2,3], esqlite3:step(Stmt)),
|
||||
?assertEqual('$done', esqlite3:step(Stmt)),
|
||||
|
||||
ok.
|
||||
|
||||
iodata_test() ->
|
||||
{ok, C} = esqlite3:open(":memory:"),
|
||||
{error, no_iodata} = esqlite3:exec(1000, C),
|
||||
{error, no_iodata} = esqlite3:insert(1000, C),
|
||||
|
||||
?assertError(badarg, esqlite3:exec(C, 1000)),
|
||||
?assertError(badarg, esqlite3:exec(C, 1000)),
|
||||
|
||||
ok.
|
||||
|
||||
|
||||
open_multiple_same_databases_test() ->
|
||||
cleanup(),
|
||||
|
||||
%% Sqlite allows opening the same file multiple
|
||||
%% times
|
||||
{ok, _C1} = esqlite3:open(?DB1),
|
||||
{ok, _C2} = esqlite3:open(?DB1),
|
||||
|
||||
@@ -55,71 +110,91 @@ open_multiple_different_databases_test() ->
|
||||
|
||||
get_autocommit_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db),
|
||||
|
||||
%% By default, the database is in autocommit mode
|
||||
true = esqlite3:get_autocommit(Db),
|
||||
ok = esqlite3:exec("BEGIN;", Db),
|
||||
ok = esqlite3:exec(Db, "CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);"),
|
||||
true = esqlite3:get_autocommit(Db),
|
||||
|
||||
%% After a begin statement, the connection will not be in autocommit mode anymore
|
||||
ok = esqlite3:exec(Db, "BEGIN;"),
|
||||
false = esqlite3:get_autocommit(Db),
|
||||
ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db),
|
||||
ok = esqlite3:exec("COMMIT;", Db),
|
||||
ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is a test');"),
|
||||
ok = esqlite3:exec(Db, "COMMIT;"),
|
||||
|
||||
%% After a commit statement, the connection will be in autocommit mode
|
||||
true = esqlite3:get_autocommit(Db),
|
||||
|
||||
ok.
|
||||
|
||||
last_insert_rowid_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db),
|
||||
ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db),
|
||||
{ok, 1} = esqlite3:last_insert_rowid(Db),
|
||||
ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is another test');", Db),
|
||||
{ok, 2} = esqlite3:last_insert_rowid(Db),
|
||||
ok = esqlite3:exec(Db, "CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);"),
|
||||
ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is a test');"),
|
||||
1 = esqlite3:last_insert_rowid(Db),
|
||||
ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is another test');"),
|
||||
2 = esqlite3:last_insert_rowid(Db),
|
||||
ok.
|
||||
|
||||
update_hook_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
ok = esqlite3:set_update_hook(self(), Db),
|
||||
ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db),
|
||||
ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db),
|
||||
ok = receive {insert, "test", 1} -> ok after 150 -> no_message end,
|
||||
ok = esqlite3:exec("UPDATE test SET val = 'a new test' WHERE id = 1;", Db),
|
||||
ok = receive {update, "test", 1} -> ok after 150 -> no_message end,
|
||||
ok = esqlite3:exec("DELETE FROM test WHERE id = 1;", Db),
|
||||
ok = receive {delete, "test", 1} -> ok after 150 -> no_message end,
|
||||
ok = esqlite3:set_update_hook(Db, self()),
|
||||
|
||||
ok = esqlite3:exec(Db, "CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);"),
|
||||
ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is a test');"),
|
||||
|
||||
ok = receive {insert, <<"main">>, <<"test">>, 1} -> ok after 150 -> no_message end,
|
||||
|
||||
ok = esqlite3:exec(Db, "UPDATE test SET val = 'a new test' WHERE id = 1;"),
|
||||
|
||||
ok = receive {update, <<"main">>, <<"test">>, 1} -> ok after 150 -> no_message end,
|
||||
|
||||
ok = esqlite3:exec(Db, "DELETE FROM test WHERE id = 1;"),
|
||||
|
||||
ok = receive {delete, <<"main">>, <<"test">>, 1} -> ok after 150 -> no_message end,
|
||||
|
||||
ok = esqlite3:set_update_hook(Db, undefined),
|
||||
|
||||
ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is a test');"),
|
||||
no_message = receive {insert, <<"main">>, <<"test">>, 1} -> ok after 150 -> no_message end,
|
||||
|
||||
ok.
|
||||
|
||||
simple_query_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
ok = esqlite3:exec("begin;", Db),
|
||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db),
|
||||
{ok, 1} = esqlite3:changes(Db),
|
||||
ok = esqlite3:exec(Db, "begin;"),
|
||||
ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"),
|
||||
ok = esqlite3:exec(Db, "insert into test_table values('hello1', 10);"),
|
||||
?assertEqual(1, esqlite3:changes(Db)),
|
||||
|
||||
ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db),
|
||||
{ok, 1} = esqlite3:changes(Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db),
|
||||
{ok, 1} = esqlite3:changes(Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db),
|
||||
{ok, 1} = esqlite3:changes(Db),
|
||||
ok = esqlite3:exec("commit;", Db),
|
||||
ok = esqlite3:exec("select * from test_table;", Db),
|
||||
ok = esqlite3:exec(Db, "insert into test_table values('hello2', 11);"),
|
||||
?assertEqual(1, esqlite3:changes(Db)),
|
||||
ok = esqlite3:exec(Db, "insert into test_table values('hello3', 12);"),
|
||||
?assertEqual(1, esqlite3:changes(Db)),
|
||||
ok = esqlite3:exec(Db, "insert into test_table values('hello4', 13);"),
|
||||
?assertEqual(1, esqlite3:changes(Db)),
|
||||
ok = esqlite3:exec(Db, "commit;"),
|
||||
ok = esqlite3:exec(Db, "select * from test_table;"),
|
||||
|
||||
ok = esqlite3:exec("delete from test_table;", Db),
|
||||
{ok, 4} = esqlite3:changes(Db),
|
||||
ok = esqlite3:exec(Db, "delete from test_table;"),
|
||||
?assertEqual(4, esqlite3:changes(Db)),
|
||||
|
||||
ok.
|
||||
|
||||
prepare_test() ->
|
||||
prepare2_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
esqlite3:exec("begin;", Db),
|
||||
esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
||||
{ok, Statement} = esqlite3:prepare("insert into test_table values('one', 2)", Db),
|
||||
esqlite3:exec(Db, "begin;"),
|
||||
esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"),
|
||||
{ok, Statement} = esqlite3:prepare(Db, "insert into test_table values('one', 2)"),
|
||||
|
||||
'$done' = esqlite3:step(Statement),
|
||||
{ok, 1} = esqlite3:changes(Db),
|
||||
1 = esqlite3:changes(Db),
|
||||
|
||||
ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db),
|
||||
ok = esqlite3:exec(Db, "insert into test_table values('hello4', 13);"),
|
||||
|
||||
%% Check if the values are there.
|
||||
[{<<"one">>, 2}, {<<"hello4">>, 13}] = esqlite3:q("select * from test_table order by two", Db),
|
||||
esqlite3:exec("commit;", Db),
|
||||
[[<<"one">>, 2], [<<"hello4">>, 13]] = esqlite3:q(Db, "select * from test_table order by two"),
|
||||
esqlite3:exec(Db, "commit;"),
|
||||
esqlite3:close(Db),
|
||||
|
||||
ok.
|
||||
@@ -127,178 +202,176 @@ prepare_test() ->
|
||||
bind_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
|
||||
ok = esqlite3:exec("begin;", Db),
|
||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
||||
ok = esqlite3:exec("commit;", Db),
|
||||
ok = esqlite3:exec(Db, "begin;"),
|
||||
ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"),
|
||||
ok = esqlite3:exec(Db, "commit;"),
|
||||
|
||||
%% Create a prepared statement
|
||||
{ok, Statement} = esqlite3:prepare("insert into test_table values(?1, ?2)", Db),
|
||||
esqlite3:bind(Statement, [one, 2]),
|
||||
{ok, Statement} = esqlite3:prepare(Db, "insert into test_table values(?1, ?2)"),
|
||||
ok = esqlite3:bind(Statement, [one, 2]),
|
||||
'$done' = esqlite3:step(Statement),
|
||||
ok = esqlite3:bind(Statement, ["three", 4]),
|
||||
esqlite3:step(Statement),
|
||||
esqlite3:bind(Statement, ["three", 4]),
|
||||
ok = esqlite3:bind(Statement, ["five", 6]),
|
||||
esqlite3:step(Statement),
|
||||
esqlite3:bind(Statement, ["five", 6]),
|
||||
ok = esqlite3:bind(Statement, [[<<"se">>, $v, "en"], 8]), % iolist bound as text
|
||||
esqlite3:step(Statement),
|
||||
esqlite3:bind(Statement, [[<<"se">>, $v, "en"], 8]), % iolist bound as text
|
||||
ok = esqlite3:bind(Statement, [<<"nine">>, 10]), % iolist bound as text
|
||||
esqlite3:step(Statement),
|
||||
esqlite3:bind(Statement, [<<"nine">>, 10]), % iolist bound as text
|
||||
ok = esqlite3:bind(Statement, [{blob, [<<"eleven">>, 0]}, 12]), % iolist bound as blob with trailing eos.
|
||||
esqlite3:step(Statement),
|
||||
esqlite3:bind(Statement, [{blob, [<<"eleven">>, 0]}, 12]), % iolist bound as blob with trailing eos.
|
||||
esqlite3:step(Statement),
|
||||
esqlite3:bind(Statement, ["empty", undefined]), % 'undefined' is converted to SQL null
|
||||
ok = esqlite3:bind(Statement, ["empty", undefined]), % 'undefined' is converted to SQL null
|
||||
esqlite3:step(Statement),
|
||||
|
||||
%% int64
|
||||
esqlite3:bind(Statement, [int64, 308553449069486081]),
|
||||
ok = esqlite3:bind(Statement, [int64, 308553449069486081]),
|
||||
esqlite3:step(Statement),
|
||||
|
||||
%
|
||||
%% negative int64
|
||||
esqlite3:bind(Statement, [negative_int64, -308553449069486081]),
|
||||
ok = esqlite3:bind(Statement, [negative_int64, -308553449069486081]),
|
||||
esqlite3:step(Statement),
|
||||
|
||||
|
||||
%% utf-8
|
||||
esqlite3:bind(Statement, [[<<228,184,138,230,181,183>>], 100]),
|
||||
ok = esqlite3:bind(Statement, [[<<228,184,138,230,181,183>>], 100]),
|
||||
esqlite3:step(Statement),
|
||||
|
||||
?assertEqual([{<<"one">>, 2}],
|
||||
esqlite3:q("select one, two from test_table where two = '2'", Db)),
|
||||
?assertEqual([{<<"three">>, 4}],
|
||||
esqlite3:q("select one, two from test_table where two = 4", Db)),
|
||||
?assertEqual([{<<"five">>, 6}],
|
||||
esqlite3:q("select one, two from test_table where two = 6", Db)),
|
||||
?assertEqual([{<<"seven">>, 8}],
|
||||
esqlite3:q("select one, two from test_table where two = 8", Db)),
|
||||
?assertEqual([{<<"nine">>, 10}],
|
||||
esqlite3:q("select one, two from test_table where two = 10", Db)),
|
||||
?assertEqual([{{blob, <<$e,$l,$e,$v,$e,$n,0>>}, 12}],
|
||||
esqlite3:q("select one, two from test_table where two = 12", Db)),
|
||||
?assertEqual([{<<"empty">>, undefined}],
|
||||
esqlite3:q("select one, two from test_table where two is null", Db)),
|
||||
?assertEqual([[<<"one">>, 2]],
|
||||
esqlite3:q(Db, "select one, two from test_table where two = '2'")),
|
||||
?assertEqual([[<<"three">>, 4]],
|
||||
esqlite3:q(Db, "select one, two from test_table where two = 4")),
|
||||
?assertEqual([[<<"five">>, 6]],
|
||||
esqlite3:q(Db, "select one, two from test_table where two = 6")),
|
||||
?assertEqual([[<<"seven">>, 8]],
|
||||
esqlite3:q(Db, "select one, two from test_table where two = 8")),
|
||||
?assertEqual([[<<"nine">>, 10]],
|
||||
esqlite3:q(Db, "select one, two from test_table where two = 10")),
|
||||
?assertEqual([[<<$e,$l,$e,$v,$e,$n,0>>, 12]],
|
||||
esqlite3:q(Db, "select one, two from test_table where two = 12")),
|
||||
?assertEqual([[<<"empty">>, undefined]],
|
||||
esqlite3:q(Db, "select one, two from test_table where two is null")),
|
||||
|
||||
?assertEqual([{<<"int64">>, 308553449069486081}],
|
||||
esqlite3:q("select one, two from test_table where one = 'int64';", Db)),
|
||||
?assertEqual([{<<"negative_int64">>, -308553449069486081}],
|
||||
esqlite3:q("select one, two from test_table where one = 'negative_int64';", Db)),
|
||||
?assertEqual([[<<"int64">>, 308553449069486081]],
|
||||
esqlite3:q(Db, "select one, two from test_table where one = 'int64';")),
|
||||
?assertEqual([[<<"negative_int64">>, -308553449069486081]],
|
||||
esqlite3:q(Db, "select one, two from test_table where one = 'negative_int64';")),
|
||||
|
||||
%% utf-8
|
||||
?assertEqual([{<<228,184,138,230,181,183>>, 100}],
|
||||
esqlite3:q("select one, two from test_table where two = 100", Db)),
|
||||
?assertEqual([[<<228,184,138,230,181,183>>, 100]],
|
||||
esqlite3:q(Db, "select one, two from test_table where two = 100")),
|
||||
|
||||
ok.
|
||||
|
||||
bind_for_queries_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
|
||||
ok = esqlite3:exec("begin;", Db),
|
||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
||||
ok = esqlite3:exec("commit;", Db),
|
||||
ok = esqlite3:exec(Db, "begin;"),
|
||||
ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"),
|
||||
ok = esqlite3:exec(Db, "commit;"),
|
||||
|
||||
?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
||||
[test_table], Db)),
|
||||
?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
||||
["test_table"], Db)),
|
||||
?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
||||
[<<"test_table">>], Db)),
|
||||
?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
||||
[[<<"test_table">>]], Db)),
|
||||
?assertEqual([[1]], esqlite3:q(Db, <<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
||||
[test_table])),
|
||||
?assertEqual([[1]], esqlite3:q(Db, <<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
||||
["test_table"])),
|
||||
?assertEqual([[1]], esqlite3:q(Db, <<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
||||
[<<"test_table">>])),
|
||||
?assertEqual([[1]], esqlite3:q(Db, <<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
||||
[[<<"test_table">>]])),
|
||||
|
||||
ok.
|
||||
|
||||
column_names_test() ->
|
||||
column_names2_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
ok = esqlite3:exec("begin;", Db),
|
||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello2', 20);", Db),
|
||||
ok = esqlite3:exec("commit;", Db),
|
||||
ok = esqlite3:exec(Db, "begin;"),
|
||||
ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"),
|
||||
ok = esqlite3:exec(Db, "insert into test_table values('hello1', 10);"),
|
||||
ok = esqlite3:exec(Db, "insert into test_table values('hello2', 20);"),
|
||||
ok = esqlite3:exec(Db, "commit;"),
|
||||
|
||||
%% All columns
|
||||
{ok, Stmt} = esqlite3:prepare("select * from test_table", Db),
|
||||
{one, two} = esqlite3:column_names(Stmt),
|
||||
{row, {<<"hello1">>, 10}} = esqlite3:step(Stmt),
|
||||
{one, two} = esqlite3:column_names(Stmt),
|
||||
{row, {<<"hello2">>, 20}} = esqlite3:step(Stmt),
|
||||
{one, two} = esqlite3:column_names(Stmt),
|
||||
{ok, Stmt} = esqlite3:prepare(Db, "select * from test_table"),
|
||||
[<<"one">>, <<"two">>] = esqlite3:column_names(Stmt),
|
||||
[<<"hello1">>, 10] = esqlite3:step(Stmt),
|
||||
[<<"one">>, <<"two">>] = esqlite3:column_names(Stmt),
|
||||
[<<"hello2">>, 20] = esqlite3:step(Stmt),
|
||||
[<<"one">>, <<"two">>] = esqlite3:column_names(Stmt),
|
||||
'$done' = esqlite3:step(Stmt),
|
||||
{one, two} = esqlite3:column_names(Stmt),
|
||||
[<<"one">>, <<"two">>] = esqlite3:column_names(Stmt),
|
||||
|
||||
%% One column
|
||||
{ok, Stmt2} = esqlite3:prepare("select two from test_table", Db),
|
||||
{two} = esqlite3:column_names(Stmt2),
|
||||
{row, {10}} = esqlite3:step(Stmt2),
|
||||
{two} = esqlite3:column_names(Stmt2),
|
||||
{row, {20}} = esqlite3:step(Stmt2),
|
||||
{two} = esqlite3:column_names(Stmt2),
|
||||
{ok, Stmt2} = esqlite3:prepare(Db, "select two from test_table"),
|
||||
[<<"two">>] = esqlite3:column_names(Stmt2),
|
||||
[10] = esqlite3:step(Stmt2),
|
||||
[<<"two">>] = esqlite3:column_names(Stmt2),
|
||||
[20] = esqlite3:step(Stmt2),
|
||||
[<<"two">>] = esqlite3:column_names(Stmt2),
|
||||
'$done' = esqlite3:step(Stmt2),
|
||||
{two} = esqlite3:column_names(Stmt2),
|
||||
[<<"two">>] = esqlite3:column_names(Stmt2),
|
||||
|
||||
%% No columns
|
||||
{ok, Stmt3} = esqlite3:prepare("values(1);", Db),
|
||||
{column1} = esqlite3:column_names(Stmt3),
|
||||
{row, {1}} = esqlite3:step(Stmt3),
|
||||
{column1} = esqlite3:column_names(Stmt3),
|
||||
{ok, Stmt3} = esqlite3:prepare(Db, "values(1);"),
|
||||
[<<"column1">>] = esqlite3:column_names(Stmt3),
|
||||
[1] = esqlite3:step(Stmt3),
|
||||
[<<"column1">>] = esqlite3:column_names(Stmt3),
|
||||
|
||||
%% Things get a bit weird when you retrieve the column name
|
||||
%% when calling an aggragage function.
|
||||
{ok, Stmt4} = esqlite3:prepare("select date('now');", Db),
|
||||
{'date(\'now\')'} = esqlite3:column_names(Stmt4),
|
||||
{row, {Date}} = esqlite3:step(Stmt4),
|
||||
{ok, Stmt4} = esqlite3:prepare(Db, "select date('now');"),
|
||||
[<<"date(\'now\')">>] = esqlite3:column_names(Stmt4),
|
||||
[Date] = esqlite3:step(Stmt4),
|
||||
true = is_binary(Date),
|
||||
|
||||
%% Some statements have no column names
|
||||
{ok, Stmt5} = esqlite3:prepare("create table dummy(a, b, c);", Db),
|
||||
{} = esqlite3:column_names(Stmt5),
|
||||
{ok, Stmt5} = esqlite3:prepare(Db, "create table dummy(a, b, c);"),
|
||||
[] = esqlite3:column_names(Stmt5),
|
||||
|
||||
ok.
|
||||
|
||||
column_types_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
ok = esqlite3:exec("begin;", Db),
|
||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello2', 20);", Db),
|
||||
ok = esqlite3:exec("commit;", Db),
|
||||
ok = esqlite3:exec(Db, "begin;"),
|
||||
ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"),
|
||||
ok = esqlite3:exec(Db, "insert into test_table values('hello1', 10);"),
|
||||
ok = esqlite3:exec(Db, "insert into test_table values('hello2', 20);"),
|
||||
ok = esqlite3:exec(Db, "commit;"),
|
||||
|
||||
%% All columns
|
||||
{ok, Stmt} = esqlite3:prepare("select * from test_table", Db),
|
||||
?assertEqual({'varchar(10)', 'INT'}, esqlite3:column_types(Stmt)),
|
||||
{row, {<<"hello1">>, 10}} = esqlite3:step(Stmt),
|
||||
{'varchar(10)', 'INT'} = esqlite3:column_types(Stmt),
|
||||
{row, {<<"hello2">>, 20}} = esqlite3:step(Stmt),
|
||||
{'varchar(10)', 'INT'} = esqlite3:column_types(Stmt),
|
||||
'$done' = esqlite3:step(Stmt),
|
||||
{'varchar(10)', 'INT'} = esqlite3:column_types(Stmt),
|
||||
{ok, Stmt} = esqlite3:prepare(Db, "select * from test_table"),
|
||||
?assertEqual([<<"varchar(10)">>, <<"INT">>], esqlite3:column_decltypes(Stmt)),
|
||||
|
||||
%% Some statements have no column types
|
||||
{ok, Stmt2} = esqlite3:prepare("create table dummy(a, b, c);", Db),
|
||||
{} = esqlite3:column_types(Stmt2),
|
||||
{ok, Stmt2} = esqlite3:prepare(Db, "create table dummy(a, b, c);"),
|
||||
[] = esqlite3:column_decltypes(Stmt2),
|
||||
|
||||
{ok, Stmt3} = esqlite3:prepare(Db, "select 1, 2, 3;"),
|
||||
[undefined, undefined, undefined] = esqlite3:column_decltypes(Stmt3),
|
||||
|
||||
ok.
|
||||
|
||||
nil_column_types_test() ->
|
||||
nil_column_decltypes_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
ok = esqlite3:exec("begin;", Db),
|
||||
ok = esqlite3:exec("create table t1(c1 variant);", Db),
|
||||
ok = esqlite3:exec("commit;", Db),
|
||||
ok = esqlite3:exec(Db, "begin;"),
|
||||
ok = esqlite3:exec(Db, "create table t1(c1 variant);"),
|
||||
ok = esqlite3:exec(Db, "commit;"),
|
||||
|
||||
{ok, Stmt} = esqlite3:prepare(Db, "select c1 + 1, c1 from t1"),
|
||||
?assertEqual([undefined, <<"variant">>], esqlite3:column_decltypes(Stmt)),
|
||||
|
||||
{ok, Stmt} = esqlite3:prepare("select c1 + 1, c1 from t1", Db),
|
||||
{nil, variant} = esqlite3:column_types(Stmt),
|
||||
ok.
|
||||
|
||||
reset_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
|
||||
{ok, Stmt} = esqlite3:prepare("select * from (values (1), (2));", Db),
|
||||
{row, {1}} = esqlite3:step(Stmt),
|
||||
{ok, Stmt} = esqlite3:prepare(Db, "select * from (values (1), (2));"),
|
||||
[1] = esqlite3:step(Stmt),
|
||||
|
||||
ok = esqlite3:reset(Stmt),
|
||||
{row, {1}} = esqlite3:step(Stmt),
|
||||
{row, {2}} = esqlite3:step(Stmt),
|
||||
|
||||
[1] = esqlite3:step(Stmt),
|
||||
[2] = esqlite3:step(Stmt),
|
||||
'$done' = esqlite3:step(Stmt),
|
||||
|
||||
% After a done the statement is automatically reset.
|
||||
{row, {1}} = esqlite3:step(Stmt),
|
||||
[1] = esqlite3:step(Stmt),
|
||||
|
||||
% Calling reset multiple times...
|
||||
ok = esqlite3:reset(Stmt),
|
||||
@@ -307,155 +380,41 @@ reset_test() ->
|
||||
ok = esqlite3:reset(Stmt),
|
||||
|
||||
% The statement should still be reset.
|
||||
{row, {1}} = esqlite3:step(Stmt),
|
||||
[1] = esqlite3:step(Stmt),
|
||||
|
||||
ok.
|
||||
|
||||
foreach_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
|
||||
ok = esqlite3:exec("begin;", Db),
|
||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
||||
|
||||
ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db),
|
||||
|
||||
ok = esqlite3:exec("commit;", Db),
|
||||
|
||||
F = fun(Row) ->
|
||||
case Row of
|
||||
{Key, Value} ->
|
||||
put(Key, Value);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
|
||||
esqlite3:foreach(F, "select * from test_table;", Db),
|
||||
|
||||
10 = get(<<"hello1">>),
|
||||
11 = get(<<"hello2">>),
|
||||
12 = get(<<"hello3">>),
|
||||
13 = get(<<"hello4">>),
|
||||
|
||||
ok.
|
||||
|
||||
bind_for_foreach_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
|
||||
ok = esqlite3:exec("begin;", Db),
|
||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db),
|
||||
ok = esqlite3:exec("commit;", Db),
|
||||
|
||||
F = fun(Row) ->
|
||||
case Row of
|
||||
{Key, Value} ->
|
||||
put(Key, Value);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
|
||||
esqlite3:foreach(F, "select * from test_table where one = ?;", ["hello1"], Db),
|
||||
|
||||
10 = get(<<"hello1">>),
|
||||
|
||||
ok.
|
||||
|
||||
map_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
|
||||
ok = esqlite3:exec("begin;", Db),
|
||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db),
|
||||
ok = esqlite3:exec("commit;", Db),
|
||||
|
||||
F = fun(Row) -> Row end,
|
||||
|
||||
[{<<"hello1">>,10},
|
||||
{<<"hello2">>,11},
|
||||
{<<"hello3">>,12},
|
||||
{<<"hello4">>,13}] = esqlite3:map(F, "select * from test_table", Db),
|
||||
|
||||
%% Test that when the row-names are added..
|
||||
Assoc = fun(Names, Row) ->
|
||||
lists:zip(tuple_to_list(Names), tuple_to_list(Row))
|
||||
end,
|
||||
|
||||
[[{one,<<"hello1">>},{two,10}],
|
||||
[{one,<<"hello2">>},{two,11}],
|
||||
[{one,<<"hello3">>},{two,12}],
|
||||
[{one,<<"hello4">>},{two,13}]] = esqlite3:map(Assoc, "select * from test_table", Db),
|
||||
|
||||
ok.
|
||||
|
||||
bind_for_map_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
|
||||
ok = esqlite3:exec("begin;", Db),
|
||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db),
|
||||
ok = esqlite3:exec("insert into test_table values('hello4', 13);", Db),
|
||||
ok = esqlite3:exec("commit;", Db),
|
||||
|
||||
F = fun(Row) -> Row end,
|
||||
|
||||
[{<<"hello1">>,10}]
|
||||
= esqlite3:map(F, "select * from test_table where one = ?", ["hello1"], Db),
|
||||
|
||||
%% Test that when the row-names are added..
|
||||
Assoc = fun(Names, Row) ->
|
||||
lists:zip(tuple_to_list(Names), tuple_to_list(Row))
|
||||
end,
|
||||
|
||||
[[{one,<<"hello1">>},{two,10}]] = esqlite3:map(Assoc, "select * from test_table where one = ?", ["hello1"], Db),
|
||||
|
||||
ok.
|
||||
|
||||
|
||||
error1_msg_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
|
||||
%% Not sql.
|
||||
{error, {sqlite_error, _Msg1}} = esqlite3:exec("dit is geen sql", Db),
|
||||
{error, 1} = esqlite3:exec(Db, "dit is geen sql"),
|
||||
|
||||
%% Database test does not exist.
|
||||
{error, {sqlite_error, _Msg2}} = esqlite3:exec("select * from test;", Db),
|
||||
{error, 1} = esqlite3:exec(Db, "select * from test;"),
|
||||
|
||||
%% Opening non-existant database.
|
||||
{error, {cantopen, _Msg3}} = esqlite3:open("/dit/bestaat/niet"),
|
||||
{error, 14} = esqlite3:open("/dit/bestaat/niet"),
|
||||
|
||||
ok.
|
||||
|
||||
prepare_and_close_connection_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
|
||||
[] = esqlite3:q("create table test(one, two, three)", Db),
|
||||
ok = esqlite3:exec(["insert into test values(1,2,3);"], Db),
|
||||
{ok, Stmt} = esqlite3:prepare("select * from test", Db),
|
||||
[] = esqlite3:q(Db, "create table test(one, two, three)"),
|
||||
ok = esqlite3:exec(Db, ["insert into test values(1,2,3);"]),
|
||||
{ok, Stmt} = esqlite3:prepare(Db, "select * from test"),
|
||||
|
||||
%% The prepated statment works.
|
||||
{row, {1,2,3}} = esqlite3:step(Stmt),
|
||||
[1,2,3] = esqlite3:step(Stmt),
|
||||
'$done' = esqlite3:step(Stmt),
|
||||
|
||||
ok = esqlite3:close(Db),
|
||||
|
||||
ok = esqlite3:reset(Stmt),
|
||||
|
||||
%% Internally sqlite3_close_v2 is used by the nif. This will destruct the
|
||||
%% connection when the last perpared statement is finalized
|
||||
{row, {1,2,3}} = esqlite3:step(Stmt),
|
||||
[1,2,3] = esqlite3:step(Stmt),
|
||||
'$done' = esqlite3:step(Stmt),
|
||||
|
||||
ok.
|
||||
@@ -466,10 +425,12 @@ backup_test() ->
|
||||
{ok, Dest} = esqlite3:open(?DB1),
|
||||
{ok, Source} = esqlite3:open(?DB2),
|
||||
|
||||
{ok, Backup} = esqlite3:backup_init(Dest, "main", Source, "main"),
|
||||
{ok, 0} = esqlite3:backup_remaining(Backup),
|
||||
{ok, 0} = esqlite3:backup_pagecount(Backup),
|
||||
done = esqlite3:backup_step(Backup, 1),
|
||||
{ok, Backup} = esqlite3:backup_init(Dest, <<"main">>, Source, <<"main">>),
|
||||
|
||||
0 = esqlite3:backup_remaining(Backup),
|
||||
0 = esqlite3:backup_pagecount(Backup),
|
||||
|
||||
'$done' = esqlite3:backup_step(Backup, 1),
|
||||
|
||||
cleanup(),
|
||||
|
||||
@@ -481,38 +442,39 @@ backup1_test() ->
|
||||
{ok, Dest} = esqlite3:open(?DB1),
|
||||
{ok, Source} = esqlite3:open(?DB2),
|
||||
|
||||
[] = esqlite3:q("create table test(one, two)", Source),
|
||||
[] = esqlite3:q("begin;", Source),
|
||||
[] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source),
|
||||
[] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source),
|
||||
[] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source),
|
||||
[] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source),
|
||||
[] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source),
|
||||
[] = esqlite3:q("commit;", Source),
|
||||
[] = esqlite3:q(Source, "create table test(one, two)"),
|
||||
[] = esqlite3:q(Source, "begin;"),
|
||||
[] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"),
|
||||
[] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"),
|
||||
[] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"),
|
||||
[] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"),
|
||||
[] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"),
|
||||
[] = esqlite3:q(Source, "commit;"),
|
||||
|
||||
[{5}] = esqlite3:q("select count(*) from test", Source),
|
||||
{error, {sqlite_error, "no such table: test"}} = esqlite3:q("select count(*) from test", Dest),
|
||||
[[5]] = esqlite3:q(Source, "select count(*) from test"),
|
||||
{error, 1} = esqlite3:q(Dest, "select count(*) from test"),
|
||||
#{ errmsg := <<"no such table: test">> } = esqlite3:error_info(Dest),
|
||||
|
||||
{ok, Backup} = esqlite3:backup_init(Dest, "main", Source, "main"),
|
||||
|
||||
{ok, 0} = esqlite3:backup_remaining(Backup),
|
||||
{ok, 0} = esqlite3:backup_pagecount(Backup),
|
||||
0 = esqlite3:backup_remaining(Backup),
|
||||
0 = esqlite3:backup_pagecount(Backup),
|
||||
|
||||
%% Backup 1 page.
|
||||
ok = esqlite3:backup_step(Backup, 1),
|
||||
|
||||
{ok, 26} = esqlite3:backup_remaining(Backup),
|
||||
{ok, 27} = esqlite3:backup_pagecount(Backup),
|
||||
26 = esqlite3:backup_remaining(Backup),
|
||||
27 = esqlite3:backup_pagecount(Backup),
|
||||
|
||||
%% Do all the remaining pages.
|
||||
done = esqlite3:backup_step(Backup, -1),
|
||||
'$done' = esqlite3:backup_step(Backup, -1),
|
||||
|
||||
{ok, 0} = esqlite3:backup_remaining(Backup),
|
||||
{ok, 27} = esqlite3:backup_pagecount(Backup),
|
||||
0 = esqlite3:backup_remaining(Backup),
|
||||
27 = esqlite3:backup_pagecount(Backup),
|
||||
|
||||
ok = esqlite3:backup_finish(Backup),
|
||||
|
||||
[{5}] = esqlite3:q("select count(*) from test", Dest),
|
||||
[[5]] = esqlite3:q(Dest, "select count(*) from test"),
|
||||
|
||||
cleanup(),
|
||||
|
||||
@@ -521,47 +483,58 @@ backup1_test() ->
|
||||
|
||||
sqlite_version_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
{ok, Stmt} = esqlite3:prepare("select sqlite_version() as sqlite_version;", Db),
|
||||
{sqlite_version} = esqlite3:column_names(Stmt),
|
||||
?assertEqual({row, {<<"3.38.0">>}}, esqlite3:step(Stmt)),
|
||||
{ok, Stmt} = esqlite3:prepare(Db, "select sqlite_version() as sqlite_version;"),
|
||||
[<<"sqlite_version">>] = esqlite3:column_names(Stmt),
|
||||
?assertEqual([<<"3.38.0">>], esqlite3:step(Stmt)),
|
||||
ok.
|
||||
|
||||
sqlite_source_id_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
{ok, Stmt} = esqlite3:prepare("select sqlite_source_id() as sqlite_source_id;", Db),
|
||||
{sqlite_source_id} = esqlite3:column_names(Stmt),
|
||||
?assertEqual({row, {<<"2022-02-22 18:58:40 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab">>}},
|
||||
{ok, Stmt} = esqlite3:prepare(Db, "select sqlite_source_id() as sqlite_source_id;"),
|
||||
[<<"sqlite_source_id">>] = esqlite3:column_names(Stmt),
|
||||
?assertEqual([<<"2022-02-22 18:58:40 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab">>],
|
||||
esqlite3:step(Stmt)),
|
||||
ok.
|
||||
|
||||
interrupt_on_timeout_test() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
{ok, Db1} = esqlite3:open("file:memdb1?mode=memory&cache=shared"),
|
||||
% {ok, Db2} = esqlite3:open("file:memdb1?mode=memory&cache=shared"),
|
||||
|
||||
Self = self(),
|
||||
|
||||
F = fun() ->
|
||||
CreateTableQuery = "CREATE TABLE all_numbers_in_the_world (number int not null);",
|
||||
ok = esqlite3:exec(CreateTableQuery, Db),
|
||||
ok = esqlite3:exec(Db1, CreateTableQuery),
|
||||
VeryLongQuery = "
|
||||
WITH RECURSIVE
|
||||
for(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM for WHERE i < 10000000)
|
||||
INSERT INTO all_numbers_in_the_world SELECT i FROM for;
|
||||
",
|
||||
try
|
||||
ok = esqlite3:exec(VeryLongQuery, [], Db, 10)
|
||||
catch
|
||||
{error, timeout, _} ->
|
||||
?assertMatch([{0}], esqlite3:q("SELECT COUNT(*) FROM all_numbers_in_the_world", Db)),
|
||||
%% There is now a stale answer, because the recursive query was interrupted.
|
||||
receive
|
||||
{esqlite3, _, {error, {interrupt, "interrupted"}}} ->
|
||||
ok
|
||||
end
|
||||
end.
|
||||
|
||||
%% The query was interrupted
|
||||
Self ! {msg, esqlite3:exec(Db1, VeryLongQuery)}
|
||||
end,
|
||||
|
||||
spawn(F),
|
||||
timer:sleep(10),
|
||||
ok = esqlite3:interrupt(Db1),
|
||||
|
||||
%% The query was interrupted, so no result
|
||||
?assertEqual([[0]], esqlite3:q(Db1, "SELECT COUNT(*) FROM all_numbers_in_the_world")),
|
||||
|
||||
%% We should have gotten an interrupt error.
|
||||
Msg = receive {msg, M} -> M end,
|
||||
?assertEqual({error, 9}, Msg),
|
||||
|
||||
ok.
|
||||
|
||||
garbage_collect_test() ->
|
||||
F = fun() ->
|
||||
{ok, Db} = esqlite3:open(":memory:"),
|
||||
[] = esqlite3:q("create table test(one, two, three)", Db),
|
||||
[] = esqlite3:q("insert into test values(1, '2', 3.0)", Db),
|
||||
{ok, Stmt} = esqlite3:prepare("select * from test", Db),
|
||||
{row, {1, <<"2">>, 3.0}} = esqlite3:step(Stmt),
|
||||
[] = esqlite3:q(Db, "create table test(one, two, three)"),
|
||||
[] = esqlite3:q(Db, "insert into test values(1, '2', 3.0)"),
|
||||
{ok, Stmt} = esqlite3:prepare(Db, "select * from test"),
|
||||
[1, <<"2">>, 3.0] = esqlite3:step(Stmt),
|
||||
'$done' = esqlite3:step(Stmt),
|
||||
ok = esqlite3:close(Db)
|
||||
end,
|
||||
@@ -574,7 +547,6 @@ garbage_collect_test() ->
|
||||
receive after 500 -> ok end,
|
||||
erlang:garbage_collect(),
|
||||
|
||||
|
||||
ok.
|
||||
|
||||
%%
|
||||
|
||||
Reference in New Issue
Block a user