2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
otp_version: [21,22,23,24]
|
otp_version: [22,23,24]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
container:
|
container:
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,3 +11,7 @@ _build
|
|||||||
c_src/**/*.d
|
c_src/**/*.d
|
||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
tags
|
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
|
VM. If you do not want to take this risk, it is always possible to
|
||||||
access the sqlite nif from a separate erlang node.
|
access the sqlite nif from a separate erlang node.
|
||||||
|
|
||||||
Special care has been taken not to block the scheduler of the calling
|
Special care has been taken not to block the normal erlang scheduler
|
||||||
process. This is done by handling all commands from erlang within a
|
of the calling process. This is done by handling neccesary commands
|
||||||
lightweight thread. The erlang scheduler will get control back when
|
from erlang by using a dirty scheduler.
|
||||||
the command has been added to the command-queue of the thread.
|
|
||||||
|
|
||||||
SQLite Compile Options
|
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
|
Will not work, because it sees the value 'abcd' as a SQL object
|
||||||
value, and not a string literal.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2617
c_src/esqlite3_nif.c
2617
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>
|
@author Maas-Maarten Zeeman <mmzeeman@xs4all.nl>
|
||||||
|
|
||||||
@title eSqlite Documentation
|
@title ESQLite Documentation
|
||||||
|
|
||||||
@doc
|
@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.
|
as a NIF, which means that the sqlite database engine is linked to the erlang virtual machine.
|
||||||
|
|
||||||
<hr />
|
<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"].
|
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".
|
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".
|
DrvLdFlagsDefault = "-shared -lsqlite3".
|
||||||
@@ -23,9 +23,9 @@ CFlags =
|
|||||||
end.
|
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,
|
{xref_checks, [undefined_function_calls,
|
||||||
undefined_functions,
|
undefined_functions,
|
||||||
@@ -68,6 +68,7 @@ CFlags =
|
|||||||
]},
|
]},
|
||||||
|
|
||||||
{edoc_opts, [{preprocess, true},
|
{edoc_opts, [{preprocess, true},
|
||||||
|
{stylesheet, "style.css"},
|
||||||
{sort_functions, false}]},
|
{sort_functions, false}]},
|
||||||
|
|
||||||
{hex, [{doc, edoc}]}
|
{hex, [{doc, edoc}]}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{application, esqlite,
|
{application, esqlite,
|
||||||
[
|
[
|
||||||
{description, "sqlite nif interface"},
|
{description, "sqlite nif interface"},
|
||||||
{vsn, "0.7.3"},
|
{vsn, "0.8.0"},
|
||||||
{modules, [esqlite3, esqlite3_nif]},
|
{modules, [esqlite3, esqlite3_nif]},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{licenses, ["Apache"]},
|
{licenses, ["Apache"]},
|
||||||
|
|||||||
911
src/esqlite3.erl
911
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");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
%% you may not use this file except in compliance with the License.
|
%% you may not use this file except in compliance with the License.
|
||||||
%% You may obtain a copy of the License at
|
%% You may obtain a copy of the License at
|
||||||
@@ -9,47 +15,67 @@
|
|||||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% 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).
|
-module(esqlite3_nif).
|
||||||
-author("Maas-Maarten Zeeman <mmzeeman@xs4all.nl>").
|
-author("Maas-Maarten Zeeman <mmzeeman@xs4all.nl>").
|
||||||
|
|
||||||
%% low-level exports
|
%% low-level exports
|
||||||
-export([
|
-export([
|
||||||
start/0,
|
open/1,
|
||||||
open/4,
|
close/1,
|
||||||
set_update_hook/4,
|
error_info/1,
|
||||||
exec/4,
|
|
||||||
changes/3,
|
set_update_hook/2,
|
||||||
insert/4,
|
|
||||||
last_insert_rowid/3,
|
get_autocommit/1,
|
||||||
get_autocommit/3,
|
last_insert_rowid/1,
|
||||||
prepare/4,
|
changes/1,
|
||||||
multi_step/5,
|
exec/2,
|
||||||
reset/4,
|
prepare/3,
|
||||||
finalize/4,
|
|
||||||
bind/5,
|
column_names/1,
|
||||||
column_names/4,
|
column_decltypes/1,
|
||||||
column_types/4,
|
|
||||||
backup_init/6,
|
bind_int/3,
|
||||||
backup_step/5,
|
bind_int64/3,
|
||||||
backup_remaining/4,
|
bind_double/3,
|
||||||
backup_pagecount/4,
|
bind_text/3,
|
||||||
backup_finish/4,
|
bind_blob/3,
|
||||||
|
bind_null/2,
|
||||||
|
|
||||||
|
step/1,
|
||||||
|
|
||||||
|
reset/1,
|
||||||
|
|
||||||
interrupt/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 esqlite3_ref() :: reference(). % Reference to a database connection handle. See [https://sqlite.org/c3ref/sqlite3.html] for more details.
|
||||||
-type raw_statement() :: reference().
|
-type esqlite3_stmt_ref() :: reference(). % Reference to a prepared statement object. See [https://sqlite.org/c3ref/stmt.html] for more details.
|
||||||
-type raw_backup() :: reference().
|
-type esqlite3_backup_ref() :: reference(). % Reference to a online backup object. See [https://sqlite.org/c3ref/backup.html] for more details.
|
||||||
-type sql() :: iodata().
|
-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).
|
-on_load(init/0).
|
||||||
|
|
||||||
@@ -61,138 +87,225 @@ init() ->
|
|||||||
end,
|
end,
|
||||||
ok = erlang:load_nif(NifFileName, 0).
|
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.
|
%% @doc Open the specified sqlite3 database.
|
||||||
%%
|
%% It is possible to use sqlite's uri filenames to open files.
|
||||||
%% Sends an asynchronous open command over the connection and returns
|
%% See: [https://sqlite.org/uri.html] for more information.
|
||||||
%% ok immediately. When the database is opened
|
-spec open(Filename) -> OpenResult when
|
||||||
%%
|
Filename :: string(),
|
||||||
-spec open(raw_connection(), reference(), pid(), string()) -> ok | {error, _}.
|
OpenResult :: {ok, esqlite3_ref()} | error().
|
||||||
open(_Db, _Ref, _Dest, _Filename) ->
|
open(_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) ->
|
|
||||||
erlang:nif_error(nif_library_not_loaded).
|
erlang:nif_error(nif_library_not_loaded).
|
||||||
|
|
||||||
%% @doc Close the connection.
|
%% @doc Close the connection.
|
||||||
%%
|
%%
|
||||||
-spec close(raw_connection(), reference(), pid()) -> ok | {error, _}.
|
-spec close(Connection) -> CloseResult
|
||||||
close(_Db, _Ref, _Dest) ->
|
when Connection :: esqlite3_ref(),
|
||||||
|
CloseResult :: ok | {error, _}.
|
||||||
|
close(_Db) ->
|
||||||
erlang:nif_error(nif_library_not_loaded).
|
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, _}.
|
-spec error_info(Connection) -> ErrorInfo
|
||||||
insert(_Db, _Ref, _Dest, _Sql) ->
|
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).
|
erlang:nif_error(nif_library_not_loaded).
|
||||||
|
|
||||||
%% @doc Get the last insert rowid.
|
%% @doc Get the last insert rowid.
|
||||||
%%
|
%%
|
||||||
-spec last_insert_rowid(raw_connection(), reference(), pid()) -> ok | {error, _}.
|
-spec last_insert_rowid(esqlite3_ref()) -> rowid().
|
||||||
last_insert_rowid(_Db, _Ref, _Dest) ->
|
last_insert_rowid(_Connection) ->
|
||||||
erlang:nif_error(nif_library_not_loaded).
|
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, _}.
|
-spec changes(esqlite3_ref()) -> integer().
|
||||||
get_autocommit(_Db, _Ref, _Dest) ->
|
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).
|
erlang:nif_error(nif_library_not_loaded).
|
||||||
|
|
||||||
|
|||||||
@@ -19,27 +19,82 @@ close_test() ->
|
|||||||
{ok, C} = esqlite3:open(":memory:"),
|
{ok, C} = esqlite3:open(":memory:"),
|
||||||
ok = esqlite3:close(C),
|
ok = esqlite3:close(C),
|
||||||
|
|
||||||
%% Check if functions still return sensible values.
|
%% Double close should also work.
|
||||||
{error, closed} = esqlite3:set_update_hook(self(), C),
|
ok = esqlite3:close(C),
|
||||||
{error, closed} = esqlite3:changes(C),
|
|
||||||
{error, closed} = esqlite3:get_autocommit(C),
|
|
||||||
{error, closed} = esqlite3:last_insert_rowid(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.
|
ok.
|
||||||
|
|
||||||
iodata_test() ->
|
iodata_test() ->
|
||||||
{ok, C} = esqlite3:open(":memory:"),
|
{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.
|
ok.
|
||||||
|
|
||||||
|
|
||||||
open_multiple_same_databases_test() ->
|
open_multiple_same_databases_test() ->
|
||||||
cleanup(),
|
cleanup(),
|
||||||
|
|
||||||
|
%% Sqlite allows opening the same file multiple
|
||||||
|
%% times
|
||||||
{ok, _C1} = esqlite3:open(?DB1),
|
{ok, _C1} = esqlite3:open(?DB1),
|
||||||
{ok, _C2} = esqlite3:open(?DB1),
|
{ok, _C2} = esqlite3:open(?DB1),
|
||||||
|
|
||||||
@@ -55,71 +110,91 @@ open_multiple_different_databases_test() ->
|
|||||||
|
|
||||||
get_autocommit_test() ->
|
get_autocommit_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{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),
|
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),
|
false = esqlite3:get_autocommit(Db),
|
||||||
ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db),
|
ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is a test');"),
|
||||||
ok = esqlite3:exec("COMMIT;", Db),
|
ok = esqlite3:exec(Db, "COMMIT;"),
|
||||||
|
|
||||||
|
%% After a commit statement, the connection will be in autocommit mode
|
||||||
true = esqlite3:get_autocommit(Db),
|
true = esqlite3:get_autocommit(Db),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
last_insert_rowid_test() ->
|
last_insert_rowid_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
ok = esqlite3:exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);", Db),
|
ok = esqlite3:exec(Db, "CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);"),
|
||||||
ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is a test');", Db),
|
ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is a test');"),
|
||||||
{ok, 1} = esqlite3:last_insert_rowid(Db),
|
1 = esqlite3:last_insert_rowid(Db),
|
||||||
ok = esqlite3:exec("INSERT INTO test (val) VALUES ('this is another test');", Db),
|
ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is another test');"),
|
||||||
{ok, 2} = esqlite3:last_insert_rowid(Db),
|
2 = esqlite3:last_insert_rowid(Db),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
update_hook_test() ->
|
update_hook_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
ok = esqlite3:set_update_hook(self(), Db),
|
ok = esqlite3:set_update_hook(Db, self()),
|
||||||
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 = esqlite3:exec(Db, "CREATE TABLE test (id INTEGER PRIMARY KEY, val STRING);"),
|
||||||
ok = receive {insert, "test", 1} -> ok after 150 -> no_message end,
|
ok = esqlite3:exec(Db, "INSERT INTO test (val) VALUES ('this is a test');"),
|
||||||
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 = receive {insert, <<"main">>, <<"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: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.
|
ok.
|
||||||
|
|
||||||
simple_query_test() ->
|
simple_query_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
ok = esqlite3:exec("begin;", Db),
|
ok = esqlite3:exec(Db, "begin;"),
|
||||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"),
|
||||||
ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db),
|
ok = esqlite3:exec(Db, "insert into test_table values('hello1', 10);"),
|
||||||
{ok, 1} = esqlite3:changes(Db),
|
?assertEqual(1, esqlite3:changes(Db)),
|
||||||
|
|
||||||
ok = esqlite3:exec("insert into test_table values('hello2', 11);", Db),
|
ok = esqlite3:exec(Db, "insert into test_table values('hello2', 11);"),
|
||||||
{ok, 1} = esqlite3:changes(Db),
|
?assertEqual(1, esqlite3:changes(Db)),
|
||||||
ok = esqlite3:exec("insert into test_table values('hello3', 12);", Db),
|
ok = esqlite3:exec(Db, "insert into test_table values('hello3', 12);"),
|
||||||
{ok, 1} = esqlite3:changes(Db),
|
?assertEqual(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);"),
|
||||||
{ok, 1} = esqlite3:changes(Db),
|
?assertEqual(1, esqlite3:changes(Db)),
|
||||||
ok = esqlite3:exec("commit;", Db),
|
ok = esqlite3:exec(Db, "commit;"),
|
||||||
ok = esqlite3:exec("select * from test_table;", Db),
|
ok = esqlite3:exec(Db, "select * from test_table;"),
|
||||||
|
|
||||||
ok = esqlite3:exec("delete from test_table;", Db),
|
ok = esqlite3:exec(Db, "delete from test_table;"),
|
||||||
{ok, 4} = esqlite3:changes(Db),
|
?assertEqual(4, esqlite3:changes(Db)),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
prepare_test() ->
|
prepare2_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
esqlite3:exec("begin;", Db),
|
esqlite3:exec(Db, "begin;"),
|
||||||
esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"),
|
||||||
{ok, Statement} = esqlite3:prepare("insert into test_table values('one', 2)", Db),
|
{ok, Statement} = esqlite3:prepare(Db, "insert into test_table values('one', 2)"),
|
||||||
|
|
||||||
'$done' = esqlite3:step(Statement),
|
'$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.
|
%% Check if the values are there.
|
||||||
[{<<"one">>, 2}, {<<"hello4">>, 13}] = esqlite3:q("select * from test_table order by two", Db),
|
[[<<"one">>, 2], [<<"hello4">>, 13]] = esqlite3:q(Db, "select * from test_table order by two"),
|
||||||
esqlite3:exec("commit;", Db),
|
esqlite3:exec(Db, "commit;"),
|
||||||
esqlite3:close(Db),
|
esqlite3:close(Db),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
@@ -127,178 +202,176 @@ prepare_test() ->
|
|||||||
bind_test() ->
|
bind_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
|
|
||||||
ok = esqlite3:exec("begin;", Db),
|
ok = esqlite3:exec(Db, "begin;"),
|
||||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"),
|
||||||
ok = esqlite3:exec("commit;", Db),
|
ok = esqlite3:exec(Db, "commit;"),
|
||||||
|
|
||||||
%% Create a prepared statement
|
%% Create a prepared statement
|
||||||
{ok, Statement} = esqlite3:prepare("insert into test_table values(?1, ?2)", Db),
|
{ok, Statement} = esqlite3:prepare(Db, "insert into test_table values(?1, ?2)"),
|
||||||
esqlite3:bind(Statement, [one, 2]),
|
ok = esqlite3:bind(Statement, [one, 2]),
|
||||||
|
'$done' = esqlite3:step(Statement),
|
||||||
|
ok = esqlite3:bind(Statement, ["three", 4]),
|
||||||
esqlite3:step(Statement),
|
esqlite3:step(Statement),
|
||||||
esqlite3:bind(Statement, ["three", 4]),
|
ok = esqlite3:bind(Statement, ["five", 6]),
|
||||||
esqlite3:step(Statement),
|
esqlite3:step(Statement),
|
||||||
esqlite3:bind(Statement, ["five", 6]),
|
ok = esqlite3:bind(Statement, [[<<"se">>, $v, "en"], 8]), % iolist bound as text
|
||||||
esqlite3:step(Statement),
|
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: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:step(Statement),
|
||||||
esqlite3:bind(Statement, [{blob, [<<"eleven">>, 0]}, 12]), % iolist bound as blob with trailing eos.
|
ok = esqlite3:bind(Statement, ["empty", undefined]), % 'undefined' is converted to SQL null
|
||||||
esqlite3:step(Statement),
|
|
||||||
esqlite3:bind(Statement, ["empty", undefined]), % 'undefined' is converted to SQL null
|
|
||||||
esqlite3:step(Statement),
|
esqlite3:step(Statement),
|
||||||
|
|
||||||
%% int64
|
%% int64
|
||||||
esqlite3:bind(Statement, [int64, 308553449069486081]),
|
ok = esqlite3:bind(Statement, [int64, 308553449069486081]),
|
||||||
esqlite3:step(Statement),
|
esqlite3:step(Statement),
|
||||||
|
%
|
||||||
%% negative int64
|
%% negative int64
|
||||||
esqlite3:bind(Statement, [negative_int64, -308553449069486081]),
|
ok = esqlite3:bind(Statement, [negative_int64, -308553449069486081]),
|
||||||
esqlite3:step(Statement),
|
esqlite3:step(Statement),
|
||||||
|
|
||||||
|
|
||||||
%% utf-8
|
%% 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),
|
esqlite3:step(Statement),
|
||||||
|
|
||||||
?assertEqual([{<<"one">>, 2}],
|
?assertEqual([[<<"one">>, 2]],
|
||||||
esqlite3:q("select one, two from test_table where two = '2'", Db)),
|
esqlite3:q(Db, "select one, two from test_table where two = '2'")),
|
||||||
?assertEqual([{<<"three">>, 4}],
|
?assertEqual([[<<"three">>, 4]],
|
||||||
esqlite3:q("select one, two from test_table where two = 4", Db)),
|
esqlite3:q(Db, "select one, two from test_table where two = 4")),
|
||||||
?assertEqual([{<<"five">>, 6}],
|
?assertEqual([[<<"five">>, 6]],
|
||||||
esqlite3:q("select one, two from test_table where two = 6", Db)),
|
esqlite3:q(Db, "select one, two from test_table where two = 6")),
|
||||||
?assertEqual([{<<"seven">>, 8}],
|
?assertEqual([[<<"seven">>, 8]],
|
||||||
esqlite3:q("select one, two from test_table where two = 8", Db)),
|
esqlite3:q(Db, "select one, two from test_table where two = 8")),
|
||||||
?assertEqual([{<<"nine">>, 10}],
|
?assertEqual([[<<"nine">>, 10]],
|
||||||
esqlite3:q("select one, two from test_table where two = 10", Db)),
|
esqlite3:q(Db, "select one, two from test_table where two = 10")),
|
||||||
?assertEqual([{{blob, <<$e,$l,$e,$v,$e,$n,0>>}, 12}],
|
?assertEqual([[<<$e,$l,$e,$v,$e,$n,0>>, 12]],
|
||||||
esqlite3:q("select one, two from test_table where two = 12", Db)),
|
esqlite3:q(Db, "select one, two from test_table where two = 12")),
|
||||||
?assertEqual([{<<"empty">>, undefined}],
|
?assertEqual([[<<"empty">>, undefined]],
|
||||||
esqlite3:q("select one, two from test_table where two is null", Db)),
|
esqlite3:q(Db, "select one, two from test_table where two is null")),
|
||||||
|
|
||||||
?assertEqual([{<<"int64">>, 308553449069486081}],
|
?assertEqual([[<<"int64">>, 308553449069486081]],
|
||||||
esqlite3:q("select one, two from test_table where one = 'int64';", Db)),
|
esqlite3:q(Db, "select one, two from test_table where one = 'int64';")),
|
||||||
?assertEqual([{<<"negative_int64">>, -308553449069486081}],
|
?assertEqual([[<<"negative_int64">>, -308553449069486081]],
|
||||||
esqlite3:q("select one, two from test_table where one = 'negative_int64';", Db)),
|
esqlite3:q(Db, "select one, two from test_table where one = 'negative_int64';")),
|
||||||
|
|
||||||
%% utf-8
|
%% utf-8
|
||||||
?assertEqual([{<<228,184,138,230,181,183>>, 100}],
|
?assertEqual([[<<228,184,138,230,181,183>>, 100]],
|
||||||
esqlite3:q("select one, two from test_table where two = 100", Db)),
|
esqlite3:q(Db, "select one, two from test_table where two = 100")),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
bind_for_queries_test() ->
|
bind_for_queries_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
|
|
||||||
ok = esqlite3:exec("begin;", Db),
|
ok = esqlite3:exec(Db, "begin;"),
|
||||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"),
|
||||||
ok = esqlite3:exec("commit;", Db),
|
ok = esqlite3:exec(Db, "commit;"),
|
||||||
|
|
||||||
?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
?assertEqual([[1]], esqlite3:q(Db, <<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
||||||
[test_table], Db)),
|
[test_table])),
|
||||||
?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
?assertEqual([[1]], esqlite3:q(Db, <<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
||||||
["test_table"], Db)),
|
["test_table"])),
|
||||||
?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
?assertEqual([[1]], esqlite3:q(Db, <<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
||||||
[<<"test_table">>], Db)),
|
[<<"test_table">>])),
|
||||||
?assertEqual([{1}], esqlite3:q(<<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
?assertEqual([[1]], esqlite3:q(Db, <<"SELECT count(type) FROM sqlite_master WHERE type='table' AND name=?;">>,
|
||||||
[[<<"test_table">>]], Db)),
|
[[<<"test_table">>]])),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
column_names_test() ->
|
column_names2_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
ok = esqlite3:exec("begin;", Db),
|
ok = esqlite3:exec(Db, "begin;"),
|
||||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"),
|
||||||
ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db),
|
ok = esqlite3:exec(Db, "insert into test_table values('hello1', 10);"),
|
||||||
ok = esqlite3:exec("insert into test_table values('hello2', 20);", Db),
|
ok = esqlite3:exec(Db, "insert into test_table values('hello2', 20);"),
|
||||||
ok = esqlite3:exec("commit;", Db),
|
ok = esqlite3:exec(Db, "commit;"),
|
||||||
|
|
||||||
%% All columns
|
%% All columns
|
||||||
{ok, Stmt} = esqlite3:prepare("select * from test_table", Db),
|
{ok, Stmt} = esqlite3:prepare(Db, "select * from test_table"),
|
||||||
{one, two} = esqlite3:column_names(Stmt),
|
[<<"one">>, <<"two">>] = esqlite3:column_names(Stmt),
|
||||||
{row, {<<"hello1">>, 10}} = esqlite3:step(Stmt),
|
[<<"hello1">>, 10] = esqlite3:step(Stmt),
|
||||||
{one, two} = esqlite3:column_names(Stmt),
|
[<<"one">>, <<"two">>] = esqlite3:column_names(Stmt),
|
||||||
{row, {<<"hello2">>, 20}} = esqlite3:step(Stmt),
|
[<<"hello2">>, 20] = esqlite3:step(Stmt),
|
||||||
{one, two} = esqlite3:column_names(Stmt),
|
[<<"one">>, <<"two">>] = esqlite3:column_names(Stmt),
|
||||||
'$done' = esqlite3:step(Stmt),
|
'$done' = esqlite3:step(Stmt),
|
||||||
{one, two} = esqlite3:column_names(Stmt),
|
[<<"one">>, <<"two">>] = esqlite3:column_names(Stmt),
|
||||||
|
|
||||||
%% One column
|
%% One column
|
||||||
{ok, Stmt2} = esqlite3:prepare("select two from test_table", Db),
|
{ok, Stmt2} = esqlite3:prepare(Db, "select two from test_table"),
|
||||||
{two} = esqlite3:column_names(Stmt2),
|
[<<"two">>] = esqlite3:column_names(Stmt2),
|
||||||
{row, {10}} = esqlite3:step(Stmt2),
|
[10] = esqlite3:step(Stmt2),
|
||||||
{two} = esqlite3:column_names(Stmt2),
|
[<<"two">>] = esqlite3:column_names(Stmt2),
|
||||||
{row, {20}} = esqlite3:step(Stmt2),
|
[20] = esqlite3:step(Stmt2),
|
||||||
{two} = esqlite3:column_names(Stmt2),
|
[<<"two">>] = esqlite3:column_names(Stmt2),
|
||||||
'$done' = esqlite3:step(Stmt2),
|
'$done' = esqlite3:step(Stmt2),
|
||||||
{two} = esqlite3:column_names(Stmt2),
|
[<<"two">>] = esqlite3:column_names(Stmt2),
|
||||||
|
|
||||||
%% No columns
|
%% No columns
|
||||||
{ok, Stmt3} = esqlite3:prepare("values(1);", Db),
|
{ok, Stmt3} = esqlite3:prepare(Db, "values(1);"),
|
||||||
{column1} = esqlite3:column_names(Stmt3),
|
[<<"column1">>] = esqlite3:column_names(Stmt3),
|
||||||
{row, {1}} = esqlite3:step(Stmt3),
|
[1] = esqlite3:step(Stmt3),
|
||||||
{column1} = esqlite3:column_names(Stmt3),
|
[<<"column1">>] = esqlite3:column_names(Stmt3),
|
||||||
|
|
||||||
%% Things get a bit weird when you retrieve the column name
|
%% Things get a bit weird when you retrieve the column name
|
||||||
%% when calling an aggragage function.
|
%% when calling an aggragage function.
|
||||||
{ok, Stmt4} = esqlite3:prepare("select date('now');", Db),
|
{ok, Stmt4} = esqlite3:prepare(Db, "select date('now');"),
|
||||||
{'date(\'now\')'} = esqlite3:column_names(Stmt4),
|
[<<"date(\'now\')">>] = esqlite3:column_names(Stmt4),
|
||||||
{row, {Date}} = esqlite3:step(Stmt4),
|
[Date] = esqlite3:step(Stmt4),
|
||||||
true = is_binary(Date),
|
true = is_binary(Date),
|
||||||
|
|
||||||
%% Some statements have no column names
|
%% Some statements have no column names
|
||||||
{ok, Stmt5} = esqlite3:prepare("create table dummy(a, b, c);", Db),
|
{ok, Stmt5} = esqlite3:prepare(Db, "create table dummy(a, b, c);"),
|
||||||
{} = esqlite3:column_names(Stmt5),
|
[] = esqlite3:column_names(Stmt5),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
column_types_test() ->
|
column_types_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
ok = esqlite3:exec("begin;", Db),
|
ok = esqlite3:exec(Db, "begin;"),
|
||||||
ok = esqlite3:exec("create table test_table(one varchar(10), two int);", Db),
|
ok = esqlite3:exec(Db, "create table test_table(one varchar(10), two int);"),
|
||||||
ok = esqlite3:exec("insert into test_table values('hello1', 10);", Db),
|
ok = esqlite3:exec(Db, "insert into test_table values('hello1', 10);"),
|
||||||
ok = esqlite3:exec("insert into test_table values('hello2', 20);", Db),
|
ok = esqlite3:exec(Db, "insert into test_table values('hello2', 20);"),
|
||||||
ok = esqlite3:exec("commit;", Db),
|
ok = esqlite3:exec(Db, "commit;"),
|
||||||
|
|
||||||
%% All columns
|
%% All columns
|
||||||
{ok, Stmt} = esqlite3:prepare("select * from test_table", Db),
|
{ok, Stmt} = esqlite3:prepare(Db, "select * from test_table"),
|
||||||
?assertEqual({'varchar(10)', 'INT'}, esqlite3:column_types(Stmt)),
|
?assertEqual([<<"varchar(10)">>, <<"INT">>], esqlite3:column_decltypes(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),
|
|
||||||
|
|
||||||
%% Some statements have no column types
|
%% Some statements have no column types
|
||||||
{ok, Stmt2} = esqlite3:prepare("create table dummy(a, b, c);", Db),
|
{ok, Stmt2} = esqlite3:prepare(Db, "create table dummy(a, b, c);"),
|
||||||
{} = esqlite3:column_types(Stmt2),
|
[] = esqlite3:column_decltypes(Stmt2),
|
||||||
|
|
||||||
|
{ok, Stmt3} = esqlite3:prepare(Db, "select 1, 2, 3;"),
|
||||||
|
[undefined, undefined, undefined] = esqlite3:column_decltypes(Stmt3),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
nil_column_types_test() ->
|
nil_column_decltypes_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
ok = esqlite3:exec("begin;", Db),
|
ok = esqlite3:exec(Db, "begin;"),
|
||||||
ok = esqlite3:exec("create table t1(c1 variant);", Db),
|
ok = esqlite3:exec(Db, "create table t1(c1 variant);"),
|
||||||
ok = esqlite3:exec("commit;", Db),
|
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.
|
ok.
|
||||||
|
|
||||||
reset_test() ->
|
reset_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
|
|
||||||
{ok, Stmt} = esqlite3:prepare("select * from (values (1), (2));", Db),
|
{ok, Stmt} = esqlite3:prepare(Db, "select * from (values (1), (2));"),
|
||||||
{row, {1}} = esqlite3:step(Stmt),
|
[1] = esqlite3:step(Stmt),
|
||||||
|
|
||||||
ok = esqlite3:reset(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),
|
'$done' = esqlite3:step(Stmt),
|
||||||
|
|
||||||
% After a done the statement is automatically reset.
|
% After a done the statement is automatically reset.
|
||||||
{row, {1}} = esqlite3:step(Stmt),
|
[1] = esqlite3:step(Stmt),
|
||||||
|
|
||||||
% Calling reset multiple times...
|
% Calling reset multiple times...
|
||||||
ok = esqlite3:reset(Stmt),
|
ok = esqlite3:reset(Stmt),
|
||||||
@@ -307,155 +380,41 @@ reset_test() ->
|
|||||||
ok = esqlite3:reset(Stmt),
|
ok = esqlite3:reset(Stmt),
|
||||||
|
|
||||||
% The statement should still be reset.
|
% The statement should still be reset.
|
||||||
{row, {1}} = esqlite3:step(Stmt),
|
[1] = esqlite3:step(Stmt),
|
||||||
|
|
||||||
ok.
|
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() ->
|
error1_msg_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
|
|
||||||
%% Not sql.
|
%% 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.
|
%% 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.
|
%% Opening non-existant database.
|
||||||
{error, {cantopen, _Msg3}} = esqlite3:open("/dit/bestaat/niet"),
|
{error, 14} = esqlite3:open("/dit/bestaat/niet"),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
prepare_and_close_connection_test() ->
|
prepare_and_close_connection_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
|
|
||||||
[] = esqlite3:q("create table test(one, two, three)", Db),
|
[] = esqlite3:q(Db, "create table test(one, two, three)"),
|
||||||
ok = esqlite3:exec(["insert into test values(1,2,3);"], Db),
|
ok = esqlite3:exec(Db, ["insert into test values(1,2,3);"]),
|
||||||
{ok, Stmt} = esqlite3:prepare("select * from test", Db),
|
{ok, Stmt} = esqlite3:prepare(Db, "select * from test"),
|
||||||
|
|
||||||
%% The prepated statment works.
|
%% The prepated statment works.
|
||||||
{row, {1,2,3}} = esqlite3:step(Stmt),
|
[1,2,3] = esqlite3:step(Stmt),
|
||||||
'$done' = esqlite3:step(Stmt),
|
'$done' = esqlite3:step(Stmt),
|
||||||
|
|
||||||
ok = esqlite3:close(Db),
|
ok = esqlite3:close(Db),
|
||||||
|
|
||||||
ok = esqlite3:reset(Stmt),
|
ok = esqlite3:reset(Stmt),
|
||||||
|
|
||||||
%% Internally sqlite3_close_v2 is used by the nif. This will destruct the
|
%% Internally sqlite3_close_v2 is used by the nif. This will destruct the
|
||||||
%% connection when the last perpared statement is finalized
|
%% 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),
|
'$done' = esqlite3:step(Stmt),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
@@ -466,10 +425,12 @@ backup_test() ->
|
|||||||
{ok, Dest} = esqlite3:open(?DB1),
|
{ok, Dest} = esqlite3:open(?DB1),
|
||||||
{ok, Source} = esqlite3:open(?DB2),
|
{ok, Source} = esqlite3:open(?DB2),
|
||||||
|
|
||||||
{ok, Backup} = esqlite3:backup_init(Dest, "main", Source, "main"),
|
{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),
|
||||||
done = esqlite3:backup_step(Backup, 1),
|
0 = esqlite3:backup_pagecount(Backup),
|
||||||
|
|
||||||
|
'$done' = esqlite3:backup_step(Backup, 1),
|
||||||
|
|
||||||
cleanup(),
|
cleanup(),
|
||||||
|
|
||||||
@@ -481,38 +442,39 @@ backup1_test() ->
|
|||||||
{ok, Dest} = esqlite3:open(?DB1),
|
{ok, Dest} = esqlite3:open(?DB1),
|
||||||
{ok, Source} = esqlite3:open(?DB2),
|
{ok, Source} = esqlite3:open(?DB2),
|
||||||
|
|
||||||
[] = esqlite3:q("create table test(one, two)", Source),
|
[] = esqlite3:q(Source, "create table test(one, two)"),
|
||||||
[] = esqlite3:q("begin;", Source),
|
[] = esqlite3:q(Source, "begin;"),
|
||||||
[] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source),
|
[] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"),
|
||||||
[] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source),
|
[] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"),
|
||||||
[] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source),
|
[] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"),
|
||||||
[] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source),
|
[] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"),
|
||||||
[] = esqlite3:q("insert into test values(randomblob(10000), randomblob(10000));", Source),
|
[] = esqlite3:q(Source, "insert into test values(randomblob(10000), randomblob(10000));"),
|
||||||
[] = esqlite3:q("commit;", Source),
|
[] = esqlite3:q(Source, "commit;"),
|
||||||
|
|
||||||
[{5}] = esqlite3:q("select count(*) from test", Source),
|
[[5]] = esqlite3:q(Source, "select count(*) from test"),
|
||||||
{error, {sqlite_error, "no such table: test"}} = esqlite3:q("select count(*) from test", Dest),
|
{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, Backup} = esqlite3:backup_init(Dest, "main", Source, "main"),
|
||||||
|
|
||||||
{ok, 0} = esqlite3:backup_remaining(Backup),
|
0 = esqlite3:backup_remaining(Backup),
|
||||||
{ok, 0} = esqlite3:backup_pagecount(Backup),
|
0 = esqlite3:backup_pagecount(Backup),
|
||||||
|
|
||||||
%% Backup 1 page.
|
%% Backup 1 page.
|
||||||
ok = esqlite3:backup_step(Backup, 1),
|
ok = esqlite3:backup_step(Backup, 1),
|
||||||
|
|
||||||
{ok, 26} = esqlite3:backup_remaining(Backup),
|
26 = esqlite3:backup_remaining(Backup),
|
||||||
{ok, 27} = esqlite3:backup_pagecount(Backup),
|
27 = esqlite3:backup_pagecount(Backup),
|
||||||
|
|
||||||
%% Do all the remaining pages.
|
%% Do all the remaining pages.
|
||||||
done = esqlite3:backup_step(Backup, -1),
|
'$done' = esqlite3:backup_step(Backup, -1),
|
||||||
|
|
||||||
{ok, 0} = esqlite3:backup_remaining(Backup),
|
0 = esqlite3:backup_remaining(Backup),
|
||||||
{ok, 27} = esqlite3:backup_pagecount(Backup),
|
27 = esqlite3:backup_pagecount(Backup),
|
||||||
|
|
||||||
ok = esqlite3:backup_finish(Backup),
|
ok = esqlite3:backup_finish(Backup),
|
||||||
|
|
||||||
[{5}] = esqlite3:q("select count(*) from test", Dest),
|
[[5]] = esqlite3:q(Dest, "select count(*) from test"),
|
||||||
|
|
||||||
cleanup(),
|
cleanup(),
|
||||||
|
|
||||||
@@ -521,47 +483,58 @@ backup1_test() ->
|
|||||||
|
|
||||||
sqlite_version_test() ->
|
sqlite_version_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
{ok, Stmt} = esqlite3:prepare("select sqlite_version() as sqlite_version;", Db),
|
{ok, Stmt} = esqlite3:prepare(Db, "select sqlite_version() as sqlite_version;"),
|
||||||
{sqlite_version} = esqlite3:column_names(Stmt),
|
[<<"sqlite_version">>] = esqlite3:column_names(Stmt),
|
||||||
?assertEqual({row, {<<"3.38.0">>}}, esqlite3:step(Stmt)),
|
?assertEqual([<<"3.38.0">>], esqlite3:step(Stmt)),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
sqlite_source_id_test() ->
|
sqlite_source_id_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
{ok, Stmt} = esqlite3:prepare("select sqlite_source_id() as sqlite_source_id;", Db),
|
{ok, Stmt} = esqlite3:prepare(Db, "select sqlite_source_id() as sqlite_source_id;"),
|
||||||
{sqlite_source_id} = esqlite3:column_names(Stmt),
|
[<<"sqlite_source_id">>] = esqlite3:column_names(Stmt),
|
||||||
?assertEqual({row, {<<"2022-02-22 18:58:40 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab">>}},
|
?assertEqual([<<"2022-02-22 18:58:40 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab">>],
|
||||||
esqlite3:step(Stmt)),
|
esqlite3:step(Stmt)),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
interrupt_on_timeout_test() ->
|
interrupt_on_timeout_test() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db1} = esqlite3:open("file:memdb1?mode=memory&cache=shared"),
|
||||||
CreateTableQuery = "CREATE TABLE all_numbers_in_the_world (number int not null);",
|
% {ok, Db2} = esqlite3:open("file:memdb1?mode=memory&cache=shared"),
|
||||||
ok = esqlite3:exec(CreateTableQuery, Db),
|
|
||||||
VeryLongQuery = "
|
Self = self(),
|
||||||
WITH RECURSIVE
|
|
||||||
for(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM for WHERE i < 10000000)
|
F = fun() ->
|
||||||
INSERT INTO all_numbers_in_the_world SELECT i FROM for;
|
CreateTableQuery = "CREATE TABLE all_numbers_in_the_world (number int not null);",
|
||||||
",
|
ok = esqlite3:exec(Db1, CreateTableQuery),
|
||||||
try
|
VeryLongQuery = "
|
||||||
ok = esqlite3:exec(VeryLongQuery, [], Db, 10)
|
WITH RECURSIVE
|
||||||
catch
|
for(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM for WHERE i < 10000000)
|
||||||
{error, timeout, _} ->
|
INSERT INTO all_numbers_in_the_world SELECT i FROM for;
|
||||||
?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
|
%% The query was interrupted
|
||||||
{esqlite3, _, {error, {interrupt, "interrupted"}}} ->
|
Self ! {msg, esqlite3:exec(Db1, VeryLongQuery)}
|
||||||
ok
|
end,
|
||||||
end
|
|
||||||
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() ->
|
garbage_collect_test() ->
|
||||||
F = fun() ->
|
F = fun() ->
|
||||||
{ok, Db} = esqlite3:open(":memory:"),
|
{ok, Db} = esqlite3:open(":memory:"),
|
||||||
[] = esqlite3:q("create table test(one, two, three)", Db),
|
[] = esqlite3:q(Db, "create table test(one, two, three)"),
|
||||||
[] = esqlite3:q("insert into test values(1, '2', 3.0)", Db),
|
[] = esqlite3:q(Db, "insert into test values(1, '2', 3.0)"),
|
||||||
{ok, Stmt} = esqlite3:prepare("select * from test", Db),
|
{ok, Stmt} = esqlite3:prepare(Db, "select * from test"),
|
||||||
{row, {1, <<"2">>, 3.0}} = esqlite3:step(Stmt),
|
[1, <<"2">>, 3.0] = esqlite3:step(Stmt),
|
||||||
'$done' = esqlite3:step(Stmt),
|
'$done' = esqlite3:step(Stmt),
|
||||||
ok = esqlite3:close(Db)
|
ok = esqlite3:close(Db)
|
||||||
end,
|
end,
|
||||||
@@ -574,7 +547,6 @@ garbage_collect_test() ->
|
|||||||
receive after 500 -> ok end,
|
receive after 500 -> ok end,
|
||||||
erlang:garbage_collect(),
|
erlang:garbage_collect(),
|
||||||
|
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|||||||
Reference in New Issue
Block a user