This commit is contained in:
2026-07-14 22:50:46 +04:00
parent 27143319e3
commit bd19e0682b
3116 changed files with 467189 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
---
Language: Cpp
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: false
AlignConsecutiveMacros: true
AlignEscapedNewlinesLeft: false
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: TopLevel
AlwaysBreakAfterReturnType: TopLevelDefinitions
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterClass: true
AfterControlStatement: false
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: true
BeforeCatch: false
BeforeElse: false
IndentBraces: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Mozilla
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true
ColumnLimit: 0
CommentPragmas: '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 2
ContinuationIndentWidth: 2
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|isl|json)/)'
Priority: 3
- Regex: '.*'
Priority: 1
IndentCaseLabels: true
IndentWidth: 4
IndentWrappedFunctionNames: false
IndentPPDirectives: BeforeHash
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: false
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: false
SortIncludes: false
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 4
UseTab: Never
...

View File

@@ -0,0 +1,7 @@
[
inputs: [
"{mix,.formatter}.exs",
"{config,lib,test}/**/*.{ex,exs}"
],
line_length: 88
]

BIN
phoenix/deps/exqlite/.hex Normal file

Binary file not shown.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Matthew A. Johnston
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,160 @@
#
# Makefile for building the NIF
#
# Makefile targets:
#
# all build and install the NIF
# clean clean build products and intermediates
#
# Variables to override:
#
# MIX_APP_PATH path to the build directory
#
# CC The C compiler
# CROSSCOMPILE crosscompiler prefix, if any
# CFLAGS compiler flags for compiling all C files
# LDFLAGS linker flags for linking all binaries
# ERL_CFLAGS additional compiler flags for files using Erlang header files
# ERL_EI_INCLUDE_DIR include path to header files (Possibly required for crosscompile)
#
SRC = c_src/sqlite3_nif.c
CFLAGS = -I"$(ERTS_INCLUDE_DIR)"
ifeq ($(EXQLITE_USE_SYSTEM),)
SRC += c_src/sqlite3.c
CFLAGS += -Ic_src
else
ifneq ($(EXQLITE_SYSTEM_LDFLAGS),)
LDFLAGS += $(EXQLITE_SYSTEM_LDFLAGS)
else
# best attempt to link the system library
# if the user didn't supply it in the environment
LDFLAGS += -lsqlite3
endif
endif
ifneq ($(DEBUG),)
CFLAGS += -g
else
CFLAGS += -DNDEBUG=1 -O2
endif
KERNEL_NAME := $(shell uname -s)
PREFIX = $(MIX_APP_PATH)/priv
BUILD = $(MIX_APP_PATH)/obj
LIB_NAME = $(PREFIX)/sqlite3_nif.so
ARCHIVE_NAME = $(PREFIX)/sqlite3_nif.a
OBJ = $(SRC:c_src/%.c=$(BUILD)/%.o)
ifneq ($(CROSSCOMPILE),)
ifeq ($(CROSSCOMPILE), Android)
CFLAGS:=$(filter-out -O2,$(CFLAGS))
CFLAGS += -fPIC -Os -z global
LDFLAGS += -fPIC -shared -lm
else ifeq ($(findstring linux,$(CROSSCOMPILE)),linux)
CFLAGS:=$(filter-out -O2,$(CFLAGS))
CFLAGS += -fPIC -Os -fvisibility=hidden
LDFLAGS += -fPIC -shared
else
CFLAGS += -fPIC -fvisibility=hidden
LDFLAGS += -fPIC -shared
endif
else
ifeq ($(KERNEL_NAME), Linux)
CFLAGS += -fPIC -fvisibility=hidden
LDFLAGS += -fPIC -shared
endif
ifeq ($(KERNEL_NAME), Darwin)
CFLAGS += -fPIC
LDFLAGS += -dynamiclib -undefined dynamic_lookup
endif
ifeq (MINGW, $(findstring MINGW,$(KERNEL_NAME)))
CFLAGS += -fPIC
LDFLAGS += -fPIC -shared
LIB_NAME = $(PREFIX)/sqlite3_nif.dll
endif
ifeq ($(KERNEL_NAME), $(filter $(KERNEL_NAME),OpenBSD FreeBSD NetBSD SunOS))
CFLAGS += -fPIC
LDFLAGS += -fPIC -shared
endif
endif
# ########################
# COMPILE TIME DEFINITIONS
# ########################
# For more information about these features being enabled, check out
# --> https://sqlite.org/compile.html
CFLAGS += -DSQLITE_THREADSAFE=1
CFLAGS += -DSQLITE_USE_URI=1
CFLAGS += -DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1
CFLAGS += -DSQLITE_DQS=0
CFLAGS += -DHAVE_USLEEP=1
# TODO: The following features should be completely configurable by the person
# installing the nif. Just need to have certain environment variables
# enabled to support them.
CFLAGS += -DALLOW_COVERING_INDEX_SCAN=1
CFLAGS += -DENABLE_FTS3_PARENTHESIS=1
CFLAGS += -DENABLE_LOAD_EXTENSION=1
CFLAGS += -DENABLE_SOUNDEX=1
CFLAGS += -DENABLE_STAT4=1
CFLAGS += -DENABLE_UPDATE_DELETE_LIMIT=1
CFLAGS += -DSQLITE_ENABLE_FTS3=1
CFLAGS += -DSQLITE_ENABLE_FTS4=1
CFLAGS += -DSQLITE_ENABLE_FTS5=1
CFLAGS += -DSQLITE_ENABLE_GEOPOLY=1
CFLAGS += -DSQLITE_ENABLE_MATH_FUNCTIONS=1
CFLAGS += -DSQLITE_ENABLE_RBU=1
CFLAGS += -DSQLITE_ENABLE_RTREE=1
CFLAGS += -DSQLITE_OMIT_DEPRECATED=1
CFLAGS += -DSQLITE_ENABLE_DBSTAT_VTAB=1
# Add any extra flags set in the environment
ifneq ($(EXQLITE_SYSTEM_CFLAGS),)
CFLAGS += $(EXQLITE_SYSTEM_CFLAGS)
endif
# Set Erlang-specific compile flags
ifeq ($(CC_PRECOMPILER_CURRENT_TARGET),armv7l-linux-gnueabihf)
ERL_CFLAGS ?= -I"$(PRECOMPILE_ERL_EI_INCLUDE_DIR)"
else
ERL_CFLAGS ?= -I"$(ERL_EI_INCLUDE_DIR)"
endif
ifneq ($(STATIC_ERLANG_NIF),)
CFLAGS += -DSTATIC_ERLANG_NIF=1
endif
ifeq ($(STATIC_ERLANG_NIF),)
all: $(PREFIX) $(BUILD) $(LIB_NAME)
else
all: $(PREFIX) $(BUILD) $(ARCHIVE_NAME)
endif
$(BUILD)/%.o: c_src/%.c
@echo " CC $(notdir $@)"
$(CC) -c $(ERL_CFLAGS) $(CFLAGS) -o $@ $<
$(LIB_NAME): $(OBJ)
@echo " LD $(notdir $@)"
$(CC) -o $@ $^ $(LDFLAGS)
$(ARCHIVE_NAME): $(OBJ)
@echo " AR $(notdir $@)"
$(AR) -rv $@ $^
$(PREFIX) $(BUILD):
mkdir -p $@
clean:
$(RM) $(LIB_NAME) $(ARCHIVE_NAME) $(OBJ)
.PHONY: all clean
# Don't echo commands unless the caller exports "V=1"
${V}.SILENT:

View File

@@ -0,0 +1,71 @@
!IF [where /q Makefile.auto.win]
# The file doesn't exist, so don't include it.
!ELSE
!INCLUDE Makefile.auto.win
!IF [del /f /q Makefile.auto.win] == 0
!ENDIF
!ENDIF
NMAKE = nmake -$(MAKEFLAGS)
SRC = c_src\sqlite3.c \
c_src\sqlite3_nif.c
CFLAGS = -O2 $(CFLAGS)
CFLAGS = -EHsc $(CFLAGS)
# -Wall will emit a lot of warnings on Windows
# CFLAGS = -Wall $(CFLAGS)
CFLAGS = -Ic_src $(CFLAGS)
# For more information about these features being enabled, check out
# --> https://sqlite.org/compile.html
CFLAGS = -DSQLITE_THREADSAFE=1 $(CFLAGS)
CFLAGS = -DSQLITE_USE_URI=1 $(CFLAGS)
CFLAGS = -DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1 $(CFLAGS)
CFLAGS = -DSQLITE_DQS=0 $(CFLAGS)
CFLAGS = -DHAVE_USLEEP=1 $(CFLAGS)
# TODO: The following features should be completely configurable by the person
# installing the nif. Just need to have certain environment variables
# enabled to support them.
CFLAGS = -DALLOW_COVERING_INDEX_SCAN=1 $(CFLAGS)
CFLAGS = -DENABLE_FTS3_PARENTHESIS=1 $(CFLAGS)
CFLAGS = -DENABLE_LOAD_EXTENSION=1 $(CFLAGS)
CFLAGS = -DENABLE_SOUNDEX=1 $(CFLAGS)
CFLAGS = -DENABLE_STAT4=1 $(CFLAGS)
CFLAGS = -DENABLE_UPDATE_DELETE_LIMIT=1 $(CFLAGS)
CFLAGS = -DSQLITE_ENABLE_FTS3=1 $(CFLAGS)
CFLAGS = -DSQLITE_ENABLE_FTS4=1 $(CFLAGS)
CFLAGS = -DSQLITE_ENABLE_FTS5=1 $(CFLAGS)
CFLAGS = -DSQLITE_ENABLE_GEOPOLY=1 $(CFLAGS)
CFLAGS = -DSQLITE_ENABLE_MATH_FUNCTIONS=1 $(CFLAGS)
CFLAGS = -DSQLITE_ENABLE_RBU=1 $(CFLAGS)
CFLAGS = -DSQLITE_ENABLE_RTREE=1 $(CFLAGS)
CFLAGS = -DSQLITE_OMIT_DEPRECATED=1 $(CFLAGS)
CFLAGS = -DSQLITE_ENABLE_DBSTAT_VTAB=1 $(CFLAGS)
# TODO: We should allow the person building to be able to specify this
CFLAGS = -DNDEBUG=1 $(CFLAGS)
# Set Erlang-specific compile flags
!IFNDEF ERL_CFLAGS
ERL_CFLAGS = -I"$(ERL_EI_INCLUDE_DIR)"
!ENDIF
all: clean priv\sqlite3_nif.dll
clean:
del /f /q priv
Makefile.auto.win:
erl -noshell -eval "io:format(\"ERTS_INCLUDE_PATH=~ts/erts-~ts/include/\", [code:root_dir(), erlang:system_info(version)])." -s erlang halt > $@
!IFDEF ERTS_INCLUDE_PATH
priv\sqlite3_nif.dll:
if NOT EXIST "priv" mkdir "priv"
$(CC) $(ERL_CFLAGS) $(CFLAGS) -I"$(ERTS_INCLUDE_PATH)" -LD -MD -Fe$@ $(SRC)
!ELSE
priv\sqlite3_nif.dll: Makefile.auto.win
$(NMAKE) -F Makefile.win priv\sqlite3_nif.dll
!ENDIF

View File

@@ -0,0 +1,264 @@
# Exqlite
[![Build Status](https://github.com/elixir-sqlite/exqlite/workflows/CI/badge.svg)](https://github.com/elixir-sqlite/exqlite/actions)
[![Hex Package](https://img.shields.io/hexpm/v/exqlite.svg)](https://hex.pm/packages/exqlite)
[![Hex Docs](https://img.shields.io/badge/hex-docs-blue.svg)](https://hexdocs.pm/exqlite)
An Elixir SQLite3 library.
If you are looking for the Ecto adapter, take a look at the
[Ecto SQLite3 library][ecto_sqlite3].
Documentation: https://hexdocs.pm/exqlite
Package: https://hex.pm/packages/exqlite
## Caveats
* Prepared statements are not cached.
* Prepared statements are not immutable. You must be careful when manipulating
statements and binding values to statements. Do not try to manipulate the
statements concurrently. Keep it isolated to one process.
* Simultaneous writing is not supported by SQLite3 and will not be supported
here.
* All native calls are run through the Dirty NIF scheduler.
* Datetimes are stored without offsets. This is due to how SQLite3 handles date
and times. If you would like to store a timezone, you will need to create a
second column somewhere storing the timezone name and shifting it when you
get it from the database. This is more reliable than storing the offset as
`+03:00` as it does not respect daylight savings time.
* When storing `BLOB` values, you have to use `{:blob, the_binary}`, otherwise
it will be interpreted as a string.
## Installation
```elixir
defp deps do
[
{:exqlite, "~> 0.27"}
]
end
```
## Configuration
### Runtime Configuration
```elixir
config :exqlite,
default_chunk_size: 100,
type_extensions: [MyApp.TypeExtension]
```
* `default_chunk_size` - The chunk size that is used when multi-stepping when
not specifying the chunk size explicitly.
* `type_extensions`: An optional list of modules that implement the
Exqlite.TypeExtension behaviour, allowing types beyond the default set that
can be stored and retrieved from the database.
### Compile-time Configuration
In `config/config.exs`,
```elixir
config :exqlite, force_build: false
```
* `force_build` - Set `true` to opt out of using precompiled artefacts.
This option only affects the default configuration. For advanced configuation,
this library will always compile natively.
## Advanced Configuration
### Defining Extra Compile Flags
You can enable certain features by doing the following:
```bash
export EXQLITE_SYSTEM_CFLAGS=-DSQLITE_ENABLE_DBSTAT_VTAB=1
```
Or you can pass extra environment variables using the Elixir config:
```elixir
config :exqlite,
force_build: true,
make_env: %{
"EXQLITE_SYSTEM_CFLAGS" => "-DSQLITE_ENABLE_DBSTAT_VTAB=1",
"V" => "1"
}
```
### Listing Flags Used For Compilation
If you `export V=1` the flags used for compilation will be output to stdout.
### Using System Installed Libraries
This will vary depending on the operating system.
```bash
# tell exqlite that we wish to use some other sqlite installation. this will prevent sqlite3.c and friends from compiling
export EXQLITE_USE_SYSTEM=1
# Tell exqlite where to find the `sqlite3.h` file
export EXQLITE_SYSTEM_CFLAGS=-I/usr/include
# tell exqlite which sqlite implementation to use
export EXQLITE_SYSTEM_LDFLAGS=-L/lib -lsqlite3
```
After exporting those variables you can then invoke `mix deps.compile`. Note if you
re-export those values, you will need to recompile the `exqlite` dependency in order to
pickup those changes.
### Database Encryption
As of version 0.9, `exqlite` supports loading database engines at runtime rather than compiling `sqlite3.c` itself.
This can be used to support database level encryption via alternate engines such as [SQLCipher](https://www.zetetic.net/sqlcipher/design)
or the [Official SEE extension](https://www.sqlite.org/see/doc/trunk/www/readme.wiki). Once you have either of those projects installed
on your system, use the following environment variables during compilation:
```bash
# tell exqlite that we wish to use some other sqlite installation. this will prevent sqlite3.c and friends from compiling
export EXQLITE_USE_SYSTEM=1
# Tell exqlite where to find the `sqlite3.h` file
export EXQLITE_SYSTEM_CFLAGS=-I/usr/local/include/sqlcipher
# tell exqlite which sqlite implementation to use
export EXQLITE_SYSTEM_LDFLAGS=-L/usr/local/lib -lsqlcipher
```
Once you have `exqlite` configured, you can use the `:key` option in the database config to enable encryption:
```elixir
config :exqlite, key: "super-secret'
```
## Usage
The `Exqlite.Sqlite3` module usage is fairly straight forward.
```elixir
# We'll just keep it in memory right now
{:ok, conn} = Exqlite.Sqlite3.open(":memory:")
# Create the table
:ok = Exqlite.Sqlite3.execute(conn, "create table test (id integer primary key, stuff text)")
# Prepare a statement
{:ok, statement} = Exqlite.Sqlite3.prepare(conn, "insert into test (stuff) values (?1)")
:ok = Exqlite.Sqlite3.bind(statement, ["Hello world"])
# Step is used to run statements
:done = Exqlite.Sqlite3.step(conn, statement)
# Prepare a select statement
{:ok, statement} = Exqlite.Sqlite3.prepare(conn, "select id, stuff from test")
# Get the results
{:row, [1, "Hello world"]} = Exqlite.Sqlite3.step(conn, statement)
# No more results
:done = Exqlite.Sqlite3.step(conn, statement)
# Release the statement.
#
# It is recommended you release the statement after using it to reclaim the memory
# asap, instead of letting the garbage collector eventually releasing the statement.
#
# If you are operating at a high load issuing thousands of statements, it would be
# possible to run out of memory or cause a lot of pressure on memory.
:ok = Exqlite.Sqlite3.release(conn, statement)
```
### Using SQLite3 native extensions
Exqlite supports loading [run-time loadable SQLite3 extensions](https://www.sqlite.org/loadext.html).
A selection of precompiled extensions for popular CPU types / architectures is
available by installing the [ExSqlean](https://github.com/mindreframer/ex_sqlean)
package. This package wraps [SQLean: all the missing SQLite functions](https://github.com/nalgeon/sqlean).
```elixir
alias Exqlite.Basic
{:ok, conn} = Basic.open("db.sqlite3")
:ok = Basic.enable_load_extension(conn)
# load the regexp extension - https://github.com/nalgeon/sqlean/blob/main/docs/re.md
Basic.load_extension(conn, ExSqlean.path_for("re"))
# run some queries to test the new `regexp_like` function
{:ok, [[1]], ["value"]} = Basic.exec(conn, "select regexp_like('the year is 2021', ?) as value", ["2021"]) |> Basic.rows()
{:ok, [[0]], ["value"]} = Basic.exec(conn, "select regexp_like('the year is 2021', ?) as value", ["2020"]) |> Basic.rows()
# prevent loading further extensions
:ok = Basic.disable_load_extension(conn)
{:error, %Exqlite.Error{message: "not authorized"}, _} = Basic.load_extension(conn, ExSqlean.path_for("re"))
# close connection
Basic.close(conn)
```
It is also possible to load extensions using the `Connection` configuration. For example:
```elixir
arch_dir =
System.cmd("uname", ["-sm"])
|> elem(0)
|> String.trim()
|> String.replace(" ", "-")
|> String.downcase() # => "darwin-arm64"
config :myapp, arch_dir: arch_dir
# global
config :exqlite, load_extensions: [ "./priv/sqlite/\#{arch_dir}/rotate" ]
# per connection in a Phoenix app
config :myapp, Myapp.Repo,
database: "path/to/db",
load_extensions: [
"./priv/sqlite/\#{arch_dir}/vector0",
"./priv/sqlite/\#{arch_dir}/vss0"
]
```
See [Exqlite.Connection.connect/1](https://hexdocs.pm/exqlite/Exqlite.Connection.html#connect/1)
for more information. When using extensions for SQLite3, they must be compiled
for the environment you are targeting.
## Why SQLite3
I needed an Ecto3 adapter to store time series data for a personal project. I
didn't want to go through the hassle of trying to setup a postgres database or
mysql database when I was just wanting to explore data ingestion and some map
reduce problems.
I also noticed that other SQLite3 implementations didn't really fit my needs. At
some point I also wanted to use this with a nerves project on an embedded device
that would be resiliant to power outages and still maintain some state that
`ets` can not afford.
## Under The Hood
We are using the Dirty NIF scheduler to execute the sqlite calls. The rationale
behind this is that maintaining each sqlite's connection command pool is
complicated and error prone.
## Compiling NIF for Windows
When compiling on Windows, you will need the [Build Tools](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022) or equivalent toolchain. Please make sure you have the correct environment variables, including path to compiler and linker and architecture that matches `erl.exe` (likely x64).
You may also need to invoke `vcvarsall.bat amd64` _before_ running `mix`.
A guide is available at [guides/windows.md](./guides/windows.md)
## Contributing
Feel free to check the project out and submit pull requests.
[ecto_sqlite3]: <https://github.com/elixir-sqlite/ecto_sqlite3>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,730 @@
/*
** 2006 June 7
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This header file defines the SQLite interface for use by
** shared libraries that want to be imported as extensions into
** an SQLite instance. Shared libraries that intend to be loaded
** as extensions by SQLite should #include this file instead of
** sqlite3.h.
*/
#ifndef SQLITE3EXT_H
#define SQLITE3EXT_H
#include "sqlite3.h"
/*
** The following structure holds pointers to all of the SQLite API
** routines.
**
** WARNING: In order to maintain backwards compatibility, add new
** interfaces to the end of this structure only. If you insert new
** interfaces in the middle of this structure, then older different
** versions of SQLite will not be able to load each other's shared
** libraries!
*/
struct sqlite3_api_routines {
void * (*aggregate_context)(sqlite3_context*,int nBytes);
int (*aggregate_count)(sqlite3_context*);
int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
int (*bind_double)(sqlite3_stmt*,int,double);
int (*bind_int)(sqlite3_stmt*,int,int);
int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
int (*bind_null)(sqlite3_stmt*,int);
int (*bind_parameter_count)(sqlite3_stmt*);
int (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
const char * (*bind_parameter_name)(sqlite3_stmt*,int);
int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
int (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
int (*busy_timeout)(sqlite3*,int ms);
int (*changes)(sqlite3*);
int (*close)(sqlite3*);
int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,
int eTextRep,const char*));
int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,
int eTextRep,const void*));
const void * (*column_blob)(sqlite3_stmt*,int iCol);
int (*column_bytes)(sqlite3_stmt*,int iCol);
int (*column_bytes16)(sqlite3_stmt*,int iCol);
int (*column_count)(sqlite3_stmt*pStmt);
const char * (*column_database_name)(sqlite3_stmt*,int);
const void * (*column_database_name16)(sqlite3_stmt*,int);
const char * (*column_decltype)(sqlite3_stmt*,int i);
const void * (*column_decltype16)(sqlite3_stmt*,int);
double (*column_double)(sqlite3_stmt*,int iCol);
int (*column_int)(sqlite3_stmt*,int iCol);
sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol);
const char * (*column_name)(sqlite3_stmt*,int);
const void * (*column_name16)(sqlite3_stmt*,int);
const char * (*column_origin_name)(sqlite3_stmt*,int);
const void * (*column_origin_name16)(sqlite3_stmt*,int);
const char * (*column_table_name)(sqlite3_stmt*,int);
const void * (*column_table_name16)(sqlite3_stmt*,int);
const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
const void * (*column_text16)(sqlite3_stmt*,int iCol);
int (*column_type)(sqlite3_stmt*,int iCol);
sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
int (*complete)(const char*sql);
int (*complete16)(const void*sql);
int (*create_collation)(sqlite3*,const char*,int,void*,
int(*)(void*,int,const void*,int,const void*));
int (*create_collation16)(sqlite3*,const void*,int,void*,
int(*)(void*,int,const void*,int,const void*));
int (*create_function)(sqlite3*,const char*,int,int,void*,
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*));
int (*create_function16)(sqlite3*,const void*,int,int,void*,
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*));
int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
int (*data_count)(sqlite3_stmt*pStmt);
sqlite3 * (*db_handle)(sqlite3_stmt*);
int (*declare_vtab)(sqlite3*,const char*);
int (*enable_shared_cache)(int);
int (*errcode)(sqlite3*db);
const char * (*errmsg)(sqlite3*);
const void * (*errmsg16)(sqlite3*);
int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
int (*expired)(sqlite3_stmt*);
int (*finalize)(sqlite3_stmt*pStmt);
void (*free)(void*);
void (*free_table)(char**result);
int (*get_autocommit)(sqlite3*);
void * (*get_auxdata)(sqlite3_context*,int);
int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
int (*global_recover)(void);
void (*interruptx)(sqlite3*);
sqlite_int64 (*last_insert_rowid)(sqlite3*);
const char * (*libversion)(void);
int (*libversion_number)(void);
void *(*malloc)(int);
char * (*mprintf)(const char*,...);
int (*open)(const char*,sqlite3**);
int (*open16)(const void*,sqlite3**);
int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
void (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
void *(*realloc)(void*,int);
int (*reset)(sqlite3_stmt*pStmt);
void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_double)(sqlite3_context*,double);
void (*result_error)(sqlite3_context*,const char*,int);
void (*result_error16)(sqlite3_context*,const void*,int);
void (*result_int)(sqlite3_context*,int);
void (*result_int64)(sqlite3_context*,sqlite_int64);
void (*result_null)(sqlite3_context*);
void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_value)(sqlite3_context*,sqlite3_value*);
void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
const char*,const char*),void*);
void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
char * (*xsnprintf)(int,char*,const char*,...);
int (*step)(sqlite3_stmt*);
int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
char const**,char const**,int*,int*,int*);
void (*thread_cleanup)(void);
int (*total_changes)(sqlite3*);
void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,
sqlite_int64),void*);
void * (*user_data)(sqlite3_context*);
const void * (*value_blob)(sqlite3_value*);
int (*value_bytes)(sqlite3_value*);
int (*value_bytes16)(sqlite3_value*);
double (*value_double)(sqlite3_value*);
int (*value_int)(sqlite3_value*);
sqlite_int64 (*value_int64)(sqlite3_value*);
int (*value_numeric_type)(sqlite3_value*);
const unsigned char * (*value_text)(sqlite3_value*);
const void * (*value_text16)(sqlite3_value*);
const void * (*value_text16be)(sqlite3_value*);
const void * (*value_text16le)(sqlite3_value*);
int (*value_type)(sqlite3_value*);
char *(*vmprintf)(const char*,va_list);
/* Added ??? */
int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
/* Added by 3.3.13 */
int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
int (*clear_bindings)(sqlite3_stmt*);
/* Added by 3.4.1 */
int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,
void (*xDestroy)(void *));
/* Added by 3.5.0 */
int (*bind_zeroblob)(sqlite3_stmt*,int,int);
int (*blob_bytes)(sqlite3_blob*);
int (*blob_close)(sqlite3_blob*);
int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,
int,sqlite3_blob**);
int (*blob_read)(sqlite3_blob*,void*,int,int);
int (*blob_write)(sqlite3_blob*,const void*,int,int);
int (*create_collation_v2)(sqlite3*,const char*,int,void*,
int(*)(void*,int,const void*,int,const void*),
void(*)(void*));
int (*file_control)(sqlite3*,const char*,int,void*);
sqlite3_int64 (*memory_highwater)(int);
sqlite3_int64 (*memory_used)(void);
sqlite3_mutex *(*mutex_alloc)(int);
void (*mutex_enter)(sqlite3_mutex*);
void (*mutex_free)(sqlite3_mutex*);
void (*mutex_leave)(sqlite3_mutex*);
int (*mutex_try)(sqlite3_mutex*);
int (*open_v2)(const char*,sqlite3**,int,const char*);
int (*release_memory)(int);
void (*result_error_nomem)(sqlite3_context*);
void (*result_error_toobig)(sqlite3_context*);
int (*sleep)(int);
void (*soft_heap_limit)(int);
sqlite3_vfs *(*vfs_find)(const char*);
int (*vfs_register)(sqlite3_vfs*,int);
int (*vfs_unregister)(sqlite3_vfs*);
int (*xthreadsafe)(void);
void (*result_zeroblob)(sqlite3_context*,int);
void (*result_error_code)(sqlite3_context*,int);
int (*test_control)(int, ...);
void (*randomness)(int,void*);
sqlite3 *(*context_db_handle)(sqlite3_context*);
int (*extended_result_codes)(sqlite3*,int);
int (*limit)(sqlite3*,int,int);
sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
const char *(*sql)(sqlite3_stmt*);
int (*status)(int,int*,int*,int);
int (*backup_finish)(sqlite3_backup*);
sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
int (*backup_pagecount)(sqlite3_backup*);
int (*backup_remaining)(sqlite3_backup*);
int (*backup_step)(sqlite3_backup*,int);
const char *(*compileoption_get)(int);
int (*compileoption_used)(const char*);
int (*create_function_v2)(sqlite3*,const char*,int,int,void*,
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*),
void(*xDestroy)(void*));
int (*db_config)(sqlite3*,int,...);
sqlite3_mutex *(*db_mutex)(sqlite3*);
int (*db_status)(sqlite3*,int,int*,int*,int);
int (*extended_errcode)(sqlite3*);
void (*log)(int,const char*,...);
sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
const char *(*sourceid)(void);
int (*stmt_status)(sqlite3_stmt*,int,int);
int (*strnicmp)(const char*,const char*,int);
int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
int (*wal_autocheckpoint)(sqlite3*,int);
int (*wal_checkpoint)(sqlite3*,const char*);
void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
int (*blob_reopen)(sqlite3_blob*,sqlite3_int64);
int (*vtab_config)(sqlite3*,int op,...);
int (*vtab_on_conflict)(sqlite3*);
/* Version 3.7.16 and later */
int (*close_v2)(sqlite3*);
const char *(*db_filename)(sqlite3*,const char*);
int (*db_readonly)(sqlite3*,const char*);
int (*db_release_memory)(sqlite3*);
const char *(*errstr)(int);
int (*stmt_busy)(sqlite3_stmt*);
int (*stmt_readonly)(sqlite3_stmt*);
int (*stricmp)(const char*,const char*);
int (*uri_boolean)(const char*,const char*,int);
sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
const char *(*uri_parameter)(const char*,const char*);
char *(*xvsnprintf)(int,char*,const char*,va_list);
int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
/* Version 3.8.7 and later */
int (*auto_extension)(void(*)(void));
int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64,
void(*)(void*));
int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64,
void(*)(void*),unsigned char);
int (*cancel_auto_extension)(void(*)(void));
int (*load_extension)(sqlite3*,const char*,const char*,char**);
void *(*malloc64)(sqlite3_uint64);
sqlite3_uint64 (*msize)(void*);
void *(*realloc64)(void*,sqlite3_uint64);
void (*reset_auto_extension)(void);
void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64,
void(*)(void*));
void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64,
void(*)(void*), unsigned char);
int (*strglob)(const char*,const char*);
/* Version 3.8.11 and later */
sqlite3_value *(*value_dup)(const sqlite3_value*);
void (*value_free)(sqlite3_value*);
int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64);
int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64);
/* Version 3.9.0 and later */
unsigned int (*value_subtype)(sqlite3_value*);
void (*result_subtype)(sqlite3_context*,unsigned int);
/* Version 3.10.0 and later */
int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int);
int (*strlike)(const char*,const char*,unsigned int);
int (*db_cacheflush)(sqlite3*);
/* Version 3.12.0 and later */
int (*system_errno)(sqlite3*);
/* Version 3.14.0 and later */
int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
char *(*expanded_sql)(sqlite3_stmt*);
/* Version 3.18.0 and later */
void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64);
/* Version 3.20.0 and later */
int (*prepare_v3)(sqlite3*,const char*,int,unsigned int,
sqlite3_stmt**,const char**);
int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int,
sqlite3_stmt**,const void**);
int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
void *(*value_pointer)(sqlite3_value*,const char*);
int (*vtab_nochange)(sqlite3_context*);
int (*value_nochange)(sqlite3_value*);
const char *(*vtab_collation)(sqlite3_index_info*,int);
/* Version 3.24.0 and later */
int (*keyword_count)(void);
int (*keyword_name)(int,const char**,int*);
int (*keyword_check)(const char*,int);
sqlite3_str *(*str_new)(sqlite3*);
char *(*str_finish)(sqlite3_str*);
void (*str_appendf)(sqlite3_str*, const char *zFormat, ...);
void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list);
void (*str_append)(sqlite3_str*, const char *zIn, int N);
void (*str_appendall)(sqlite3_str*, const char *zIn);
void (*str_appendchar)(sqlite3_str*, int N, char C);
void (*str_reset)(sqlite3_str*);
int (*str_errcode)(sqlite3_str*);
int (*str_length)(sqlite3_str*);
char *(*str_value)(sqlite3_str*);
/* Version 3.25.0 and later */
int (*create_window_function)(sqlite3*,const char*,int,int,void*,
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*),
void (*xValue)(sqlite3_context*),
void (*xInv)(sqlite3_context*,int,sqlite3_value**),
void(*xDestroy)(void*));
/* Version 3.26.0 and later */
const char *(*normalized_sql)(sqlite3_stmt*);
/* Version 3.28.0 and later */
int (*stmt_isexplain)(sqlite3_stmt*);
int (*value_frombind)(sqlite3_value*);
/* Version 3.30.0 and later */
int (*drop_modules)(sqlite3*,const char**);
/* Version 3.31.0 and later */
sqlite3_int64 (*hard_heap_limit64)(sqlite3_int64);
const char *(*uri_key)(const char*,int);
const char *(*filename_database)(const char*);
const char *(*filename_journal)(const char*);
const char *(*filename_wal)(const char*);
/* Version 3.32.0 and later */
const char *(*create_filename)(const char*,const char*,const char*,
int,const char**);
void (*free_filename)(const char*);
sqlite3_file *(*database_file_object)(const char*);
/* Version 3.34.0 and later */
int (*txn_state)(sqlite3*,const char*);
/* Version 3.36.1 and later */
sqlite3_int64 (*changes64)(sqlite3*);
sqlite3_int64 (*total_changes64)(sqlite3*);
/* Version 3.37.0 and later */
int (*autovacuum_pages)(sqlite3*,
unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int),
void*, void(*)(void*));
/* Version 3.38.0 and later */
int (*error_offset)(sqlite3*);
int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**);
int (*vtab_distinct)(sqlite3_index_info*);
int (*vtab_in)(sqlite3_index_info*,int,int);
int (*vtab_in_first)(sqlite3_value*,sqlite3_value**);
int (*vtab_in_next)(sqlite3_value*,sqlite3_value**);
/* Version 3.39.0 and later */
int (*deserialize)(sqlite3*,const char*,unsigned char*,
sqlite3_int64,sqlite3_int64,unsigned);
unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*,
unsigned int);
const char *(*db_name)(sqlite3*,int);
/* Version 3.40.0 and later */
int (*value_encoding)(sqlite3_value*);
/* Version 3.41.0 and later */
int (*is_interrupted)(sqlite3*);
/* Version 3.43.0 and later */
int (*stmt_explain)(sqlite3_stmt*,int);
/* Version 3.44.0 and later */
void *(*get_clientdata)(sqlite3*,const char*);
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
/* Version 3.50.0 and later */
int (*setlk_timeout)(sqlite3*,int,int);
/* Version 3.51.0 and later */
int (*set_errmsg)(sqlite3*,int,const char*);
int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int);
};
/*
** This is the function signature used for all extension entry points. It
** is also defined in the file "loadext.c".
*/
typedef int (*sqlite3_loadext_entry)(
sqlite3 *db, /* Handle to the database. */
char **pzErrMsg, /* Used to set error string on failure. */
const sqlite3_api_routines *pThunk /* Extension API function pointers. */
);
/*
** The following macros redefine the API routines so that they are
** redirected through the global sqlite3_api structure.
**
** This header file is also used by the loadext.c source file
** (part of the main SQLite library - not an extension) so that
** it can get access to the sqlite3_api_routines structure
** definition. But the main library does not want to redefine
** the API. So the redefinition macros are only valid if the
** SQLITE_CORE macros is undefined.
*/
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
#define sqlite3_aggregate_context sqlite3_api->aggregate_context
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_aggregate_count sqlite3_api->aggregate_count
#endif
#define sqlite3_bind_blob sqlite3_api->bind_blob
#define sqlite3_bind_double sqlite3_api->bind_double
#define sqlite3_bind_int sqlite3_api->bind_int
#define sqlite3_bind_int64 sqlite3_api->bind_int64
#define sqlite3_bind_null sqlite3_api->bind_null
#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count
#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index
#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name
#define sqlite3_bind_text sqlite3_api->bind_text
#define sqlite3_bind_text16 sqlite3_api->bind_text16
#define sqlite3_bind_value sqlite3_api->bind_value
#define sqlite3_busy_handler sqlite3_api->busy_handler
#define sqlite3_busy_timeout sqlite3_api->busy_timeout
#define sqlite3_changes sqlite3_api->changes
#define sqlite3_close sqlite3_api->close
#define sqlite3_collation_needed sqlite3_api->collation_needed
#define sqlite3_collation_needed16 sqlite3_api->collation_needed16
#define sqlite3_column_blob sqlite3_api->column_blob
#define sqlite3_column_bytes sqlite3_api->column_bytes
#define sqlite3_column_bytes16 sqlite3_api->column_bytes16
#define sqlite3_column_count sqlite3_api->column_count
#define sqlite3_column_database_name sqlite3_api->column_database_name
#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
#define sqlite3_column_decltype sqlite3_api->column_decltype
#define sqlite3_column_decltype16 sqlite3_api->column_decltype16
#define sqlite3_column_double sqlite3_api->column_double
#define sqlite3_column_int sqlite3_api->column_int
#define sqlite3_column_int64 sqlite3_api->column_int64
#define sqlite3_column_name sqlite3_api->column_name
#define sqlite3_column_name16 sqlite3_api->column_name16
#define sqlite3_column_origin_name sqlite3_api->column_origin_name
#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16
#define sqlite3_column_table_name sqlite3_api->column_table_name
#define sqlite3_column_table_name16 sqlite3_api->column_table_name16
#define sqlite3_column_text sqlite3_api->column_text
#define sqlite3_column_text16 sqlite3_api->column_text16
#define sqlite3_column_type sqlite3_api->column_type
#define sqlite3_column_value sqlite3_api->column_value
#define sqlite3_commit_hook sqlite3_api->commit_hook
#define sqlite3_complete sqlite3_api->complete
#define sqlite3_complete16 sqlite3_api->complete16
#define sqlite3_create_collation sqlite3_api->create_collation
#define sqlite3_create_collation16 sqlite3_api->create_collation16
#define sqlite3_create_function sqlite3_api->create_function
#define sqlite3_create_function16 sqlite3_api->create_function16
#define sqlite3_create_module sqlite3_api->create_module
#define sqlite3_create_module_v2 sqlite3_api->create_module_v2
#define sqlite3_data_count sqlite3_api->data_count
#define sqlite3_db_handle sqlite3_api->db_handle
#define sqlite3_declare_vtab sqlite3_api->declare_vtab
#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache
#define sqlite3_errcode sqlite3_api->errcode
#define sqlite3_errmsg sqlite3_api->errmsg
#define sqlite3_errmsg16 sqlite3_api->errmsg16
#define sqlite3_exec sqlite3_api->exec
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_expired sqlite3_api->expired
#endif
#define sqlite3_finalize sqlite3_api->finalize
#define sqlite3_free sqlite3_api->free
#define sqlite3_free_table sqlite3_api->free_table
#define sqlite3_get_autocommit sqlite3_api->get_autocommit
#define sqlite3_get_auxdata sqlite3_api->get_auxdata
#define sqlite3_get_table sqlite3_api->get_table
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_global_recover sqlite3_api->global_recover
#endif
#define sqlite3_interrupt sqlite3_api->interruptx
#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid
#define sqlite3_libversion sqlite3_api->libversion
#define sqlite3_libversion_number sqlite3_api->libversion_number
#define sqlite3_malloc sqlite3_api->malloc
#define sqlite3_mprintf sqlite3_api->mprintf
#define sqlite3_open sqlite3_api->open
#define sqlite3_open16 sqlite3_api->open16
#define sqlite3_prepare sqlite3_api->prepare
#define sqlite3_prepare16 sqlite3_api->prepare16
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
#define sqlite3_profile sqlite3_api->profile
#define sqlite3_progress_handler sqlite3_api->progress_handler
#define sqlite3_realloc sqlite3_api->realloc
#define sqlite3_reset sqlite3_api->reset
#define sqlite3_result_blob sqlite3_api->result_blob
#define sqlite3_result_double sqlite3_api->result_double
#define sqlite3_result_error sqlite3_api->result_error
#define sqlite3_result_error16 sqlite3_api->result_error16
#define sqlite3_result_int sqlite3_api->result_int
#define sqlite3_result_int64 sqlite3_api->result_int64
#define sqlite3_result_null sqlite3_api->result_null
#define sqlite3_result_text sqlite3_api->result_text
#define sqlite3_result_text16 sqlite3_api->result_text16
#define sqlite3_result_text16be sqlite3_api->result_text16be
#define sqlite3_result_text16le sqlite3_api->result_text16le
#define sqlite3_result_value sqlite3_api->result_value
#define sqlite3_rollback_hook sqlite3_api->rollback_hook
#define sqlite3_set_authorizer sqlite3_api->set_authorizer
#define sqlite3_set_auxdata sqlite3_api->set_auxdata
#define sqlite3_snprintf sqlite3_api->xsnprintf
#define sqlite3_step sqlite3_api->step
#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
#define sqlite3_total_changes sqlite3_api->total_changes
#define sqlite3_trace sqlite3_api->trace
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings
#endif
#define sqlite3_update_hook sqlite3_api->update_hook
#define sqlite3_user_data sqlite3_api->user_data
#define sqlite3_value_blob sqlite3_api->value_blob
#define sqlite3_value_bytes sqlite3_api->value_bytes
#define sqlite3_value_bytes16 sqlite3_api->value_bytes16
#define sqlite3_value_double sqlite3_api->value_double
#define sqlite3_value_int sqlite3_api->value_int
#define sqlite3_value_int64 sqlite3_api->value_int64
#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type
#define sqlite3_value_text sqlite3_api->value_text
#define sqlite3_value_text16 sqlite3_api->value_text16
#define sqlite3_value_text16be sqlite3_api->value_text16be
#define sqlite3_value_text16le sqlite3_api->value_text16le
#define sqlite3_value_type sqlite3_api->value_type
#define sqlite3_vmprintf sqlite3_api->vmprintf
#define sqlite3_vsnprintf sqlite3_api->xvsnprintf
#define sqlite3_overload_function sqlite3_api->overload_function
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
#define sqlite3_clear_bindings sqlite3_api->clear_bindings
#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob
#define sqlite3_blob_bytes sqlite3_api->blob_bytes
#define sqlite3_blob_close sqlite3_api->blob_close
#define sqlite3_blob_open sqlite3_api->blob_open
#define sqlite3_blob_read sqlite3_api->blob_read
#define sqlite3_blob_write sqlite3_api->blob_write
#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2
#define sqlite3_file_control sqlite3_api->file_control
#define sqlite3_memory_highwater sqlite3_api->memory_highwater
#define sqlite3_memory_used sqlite3_api->memory_used
#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc
#define sqlite3_mutex_enter sqlite3_api->mutex_enter
#define sqlite3_mutex_free sqlite3_api->mutex_free
#define sqlite3_mutex_leave sqlite3_api->mutex_leave
#define sqlite3_mutex_try sqlite3_api->mutex_try
#define sqlite3_open_v2 sqlite3_api->open_v2
#define sqlite3_release_memory sqlite3_api->release_memory
#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem
#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig
#define sqlite3_sleep sqlite3_api->sleep
#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit
#define sqlite3_vfs_find sqlite3_api->vfs_find
#define sqlite3_vfs_register sqlite3_api->vfs_register
#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister
#define sqlite3_threadsafe sqlite3_api->xthreadsafe
#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob
#define sqlite3_result_error_code sqlite3_api->result_error_code
#define sqlite3_test_control sqlite3_api->test_control
#define sqlite3_randomness sqlite3_api->randomness
#define sqlite3_context_db_handle sqlite3_api->context_db_handle
#define sqlite3_extended_result_codes sqlite3_api->extended_result_codes
#define sqlite3_limit sqlite3_api->limit
#define sqlite3_next_stmt sqlite3_api->next_stmt
#define sqlite3_sql sqlite3_api->sql
#define sqlite3_status sqlite3_api->status
#define sqlite3_backup_finish sqlite3_api->backup_finish
#define sqlite3_backup_init sqlite3_api->backup_init
#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount
#define sqlite3_backup_remaining sqlite3_api->backup_remaining
#define sqlite3_backup_step sqlite3_api->backup_step
#define sqlite3_compileoption_get sqlite3_api->compileoption_get
#define sqlite3_compileoption_used sqlite3_api->compileoption_used
#define sqlite3_create_function_v2 sqlite3_api->create_function_v2
#define sqlite3_db_config sqlite3_api->db_config
#define sqlite3_db_mutex sqlite3_api->db_mutex
#define sqlite3_db_status sqlite3_api->db_status
#define sqlite3_extended_errcode sqlite3_api->extended_errcode
#define sqlite3_log sqlite3_api->log
#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64
#define sqlite3_sourceid sqlite3_api->sourceid
#define sqlite3_stmt_status sqlite3_api->stmt_status
#define sqlite3_strnicmp sqlite3_api->strnicmp
#define sqlite3_unlock_notify sqlite3_api->unlock_notify
#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint
#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint
#define sqlite3_wal_hook sqlite3_api->wal_hook
#define sqlite3_blob_reopen sqlite3_api->blob_reopen
#define sqlite3_vtab_config sqlite3_api->vtab_config
#define sqlite3_vtab_on_conflict sqlite3_api->vtab_on_conflict
/* Version 3.7.16 and later */
#define sqlite3_close_v2 sqlite3_api->close_v2
#define sqlite3_db_filename sqlite3_api->db_filename
#define sqlite3_db_readonly sqlite3_api->db_readonly
#define sqlite3_db_release_memory sqlite3_api->db_release_memory
#define sqlite3_errstr sqlite3_api->errstr
#define sqlite3_stmt_busy sqlite3_api->stmt_busy
#define sqlite3_stmt_readonly sqlite3_api->stmt_readonly
#define sqlite3_stricmp sqlite3_api->stricmp
#define sqlite3_uri_boolean sqlite3_api->uri_boolean
#define sqlite3_uri_int64 sqlite3_api->uri_int64
#define sqlite3_uri_parameter sqlite3_api->uri_parameter
#define sqlite3_uri_vsnprintf sqlite3_api->xvsnprintf
#define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2
/* Version 3.8.7 and later */
#define sqlite3_auto_extension sqlite3_api->auto_extension
#define sqlite3_bind_blob64 sqlite3_api->bind_blob64
#define sqlite3_bind_text64 sqlite3_api->bind_text64
#define sqlite3_cancel_auto_extension sqlite3_api->cancel_auto_extension
#define sqlite3_load_extension sqlite3_api->load_extension
#define sqlite3_malloc64 sqlite3_api->malloc64
#define sqlite3_msize sqlite3_api->msize
#define sqlite3_realloc64 sqlite3_api->realloc64
#define sqlite3_reset_auto_extension sqlite3_api->reset_auto_extension
#define sqlite3_result_blob64 sqlite3_api->result_blob64
#define sqlite3_result_text64 sqlite3_api->result_text64
#define sqlite3_strglob sqlite3_api->strglob
/* Version 3.8.11 and later */
#define sqlite3_value_dup sqlite3_api->value_dup
#define sqlite3_value_free sqlite3_api->value_free
#define sqlite3_result_zeroblob64 sqlite3_api->result_zeroblob64
#define sqlite3_bind_zeroblob64 sqlite3_api->bind_zeroblob64
/* Version 3.9.0 and later */
#define sqlite3_value_subtype sqlite3_api->value_subtype
#define sqlite3_result_subtype sqlite3_api->result_subtype
/* Version 3.10.0 and later */
#define sqlite3_status64 sqlite3_api->status64
#define sqlite3_strlike sqlite3_api->strlike
#define sqlite3_db_cacheflush sqlite3_api->db_cacheflush
/* Version 3.12.0 and later */
#define sqlite3_system_errno sqlite3_api->system_errno
/* Version 3.14.0 and later */
#define sqlite3_trace_v2 sqlite3_api->trace_v2
#define sqlite3_expanded_sql sqlite3_api->expanded_sql
/* Version 3.18.0 and later */
#define sqlite3_set_last_insert_rowid sqlite3_api->set_last_insert_rowid
/* Version 3.20.0 and later */
#define sqlite3_prepare_v3 sqlite3_api->prepare_v3
#define sqlite3_prepare16_v3 sqlite3_api->prepare16_v3
#define sqlite3_bind_pointer sqlite3_api->bind_pointer
#define sqlite3_result_pointer sqlite3_api->result_pointer
#define sqlite3_value_pointer sqlite3_api->value_pointer
/* Version 3.22.0 and later */
#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange
#define sqlite3_value_nochange sqlite3_api->value_nochange
#define sqlite3_vtab_collation sqlite3_api->vtab_collation
/* Version 3.24.0 and later */
#define sqlite3_keyword_count sqlite3_api->keyword_count
#define sqlite3_keyword_name sqlite3_api->keyword_name
#define sqlite3_keyword_check sqlite3_api->keyword_check
#define sqlite3_str_new sqlite3_api->str_new
#define sqlite3_str_finish sqlite3_api->str_finish
#define sqlite3_str_appendf sqlite3_api->str_appendf
#define sqlite3_str_vappendf sqlite3_api->str_vappendf
#define sqlite3_str_append sqlite3_api->str_append
#define sqlite3_str_appendall sqlite3_api->str_appendall
#define sqlite3_str_appendchar sqlite3_api->str_appendchar
#define sqlite3_str_reset sqlite3_api->str_reset
#define sqlite3_str_errcode sqlite3_api->str_errcode
#define sqlite3_str_length sqlite3_api->str_length
#define sqlite3_str_value sqlite3_api->str_value
/* Version 3.25.0 and later */
#define sqlite3_create_window_function sqlite3_api->create_window_function
/* Version 3.26.0 and later */
#define sqlite3_normalized_sql sqlite3_api->normalized_sql
/* Version 3.28.0 and later */
#define sqlite3_stmt_isexplain sqlite3_api->stmt_isexplain
#define sqlite3_value_frombind sqlite3_api->value_frombind
/* Version 3.30.0 and later */
#define sqlite3_drop_modules sqlite3_api->drop_modules
/* Version 3.31.0 and later */
#define sqlite3_hard_heap_limit64 sqlite3_api->hard_heap_limit64
#define sqlite3_uri_key sqlite3_api->uri_key
#define sqlite3_filename_database sqlite3_api->filename_database
#define sqlite3_filename_journal sqlite3_api->filename_journal
#define sqlite3_filename_wal sqlite3_api->filename_wal
/* Version 3.32.0 and later */
#define sqlite3_create_filename sqlite3_api->create_filename
#define sqlite3_free_filename sqlite3_api->free_filename
#define sqlite3_database_file_object sqlite3_api->database_file_object
/* Version 3.34.0 and later */
#define sqlite3_txn_state sqlite3_api->txn_state
/* Version 3.36.1 and later */
#define sqlite3_changes64 sqlite3_api->changes64
#define sqlite3_total_changes64 sqlite3_api->total_changes64
/* Version 3.37.0 and later */
#define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages
/* Version 3.38.0 and later */
#define sqlite3_error_offset sqlite3_api->error_offset
#define sqlite3_vtab_rhs_value sqlite3_api->vtab_rhs_value
#define sqlite3_vtab_distinct sqlite3_api->vtab_distinct
#define sqlite3_vtab_in sqlite3_api->vtab_in
#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first
#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next
/* Version 3.39.0 and later */
#ifndef SQLITE_OMIT_DESERIALIZE
#define sqlite3_deserialize sqlite3_api->deserialize
#define sqlite3_serialize sqlite3_api->serialize
#endif
#define sqlite3_db_name sqlite3_api->db_name
/* Version 3.40.0 and later */
#define sqlite3_value_encoding sqlite3_api->value_encoding
/* Version 3.41.0 and later */
#define sqlite3_is_interrupted sqlite3_api->is_interrupted
/* Version 3.43.0 and later */
#define sqlite3_stmt_explain sqlite3_api->stmt_explain
/* Version 3.44.0 and later */
#define sqlite3_get_clientdata sqlite3_api->get_clientdata
#define sqlite3_set_clientdata sqlite3_api->set_clientdata
/* Version 3.50.0 and later */
#define sqlite3_setlk_timeout sqlite3_api->setlk_timeout
/* Version 3.51.0 and later */
#define sqlite3_set_errmsg sqlite3_api->set_errmsg
#define sqlite3_db_status64 sqlite3_api->db_status64
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
/* This case when the file really is being compiled as a loadable
** extension */
# define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api=0;
# define SQLITE_EXTENSION_INIT2(v) sqlite3_api=v;
# define SQLITE_EXTENSION_INIT3 \
extern const sqlite3_api_routines *sqlite3_api;
#else
/* This case when the file is being statically linked into the
** application */
# define SQLITE_EXTENSION_INIT1 /*no-op*/
# define SQLITE_EXTENSION_INIT2(v) (void)v; /* unused parameter */
# define SQLITE_EXTENSION_INIT3 /*no-op*/
#endif
#endif /* SQLITE3EXT_H */

View File

@@ -0,0 +1,28 @@
%{
"exqlite-nif-2.16-aarch64-apple-darwin-0.36.0.tar.gz" => "sha256:c1032fc21142b399dff1d04dc982f5df28903ff0a2a95f29b70ef9191e048bb2",
"exqlite-nif-2.16-aarch64-linux-gnu-0.36.0.tar.gz" => "sha256:6552f9cc187a916a01ea631a8265cad2c0ad942a7493033f80874aae4a3bec26",
"exqlite-nif-2.16-aarch64-linux-musl-0.36.0.tar.gz" => "sha256:3b2868ae094306b8bed5a5b9de194358eb99bb1dd537498fae2f26fdc55c8bc4",
"exqlite-nif-2.16-armv7l-linux-gnueabihf-0.36.0.tar.gz" => "sha256:425bc8e473ac4916b791d4cb463c8c20822c74a7801e5eaac1b81ac869e546ee",
"exqlite-nif-2.16-i686-linux-gnu-0.36.0.tar.gz" => "sha256:b142008ab0bc70a52182ed1df55c1fd24bd7052367bd4fc155c1b39f32849b8f",
"exqlite-nif-2.16-powerpc64le-linux-gnu-0.36.0.tar.gz" => "sha256:055c2a7831c8fdafe675dd01a4ff4703c326cdef19a1d76a5e1afc1a54978868",
"exqlite-nif-2.16-riscv64-linux-gnu-0.36.0.tar.gz" => "sha256:10ed8ab8684c32930a9c9f526940182d969a58eb992f3a9e17edde8848c00fda",
"exqlite-nif-2.16-riscv64-linux-musl-0.36.0.tar.gz" => "sha256:7e7d2894792c32936a1eb4d6c5ea970cf3765e92d9f0648fae917c729dcda438",
"exqlite-nif-2.16-s390x-linux-gnu-0.36.0.tar.gz" => "sha256:e81d7a0a2d157148dc38451ffc98fa17442eca0af6d4f473221f246fa032f5d9",
"exqlite-nif-2.16-x86_64-apple-darwin-0.36.0.tar.gz" => "sha256:abd9aeeccfd2bdcd775e4590ef4ae375fd347f1c7d0ab0f24bcf85498a59d1ca",
"exqlite-nif-2.16-x86_64-linux-gnu-0.36.0.tar.gz" => "sha256:b609be28ddc62825441ba2e094f00d298a882a503b1a865fa74506fdf0dcb113",
"exqlite-nif-2.16-x86_64-linux-musl-0.36.0.tar.gz" => "sha256:0dface2bb37234f12767bef0d57975c3818beeba573c786fc8e819eafb851135",
"exqlite-nif-2.16-x86_64-windows-msvc-0.36.0.tar.gz" => "sha256:dec20a05617b808673b3266399eb44e0bf21006721c5de3f94d75d4247e7bd0b",
"exqlite-nif-2.17-aarch64-apple-darwin-0.36.0.tar.gz" => "sha256:1779b848424c0bba893406a232dfba01caaa661c6ab79b83f4ff6f875b551299",
"exqlite-nif-2.17-aarch64-linux-gnu-0.36.0.tar.gz" => "sha256:42063fc2f2e63ec1cc319e894b4425130dda48e8ddc39ef47877ce657afe9e56",
"exqlite-nif-2.17-aarch64-linux-musl-0.36.0.tar.gz" => "sha256:21bc2d753b3c85bb91a57bcab89b3ad18c88e24392ee6e6b681d3178b71e339c",
"exqlite-nif-2.17-armv7l-linux-gnueabihf-0.36.0.tar.gz" => "sha256:3c2941464f848c2e49eecd2cde7c30d03632117b7847902725fbe2dd2999eb45",
"exqlite-nif-2.17-i686-linux-gnu-0.36.0.tar.gz" => "sha256:33cb0e4c86d9e3c679ceed54141aa088bb4fe1e628155b282b2ad0201b66c0fa",
"exqlite-nif-2.17-powerpc64le-linux-gnu-0.36.0.tar.gz" => "sha256:67f48217a041d9f279f372ae16412543ea24f8c92c5749c40b66c16835934055",
"exqlite-nif-2.17-riscv64-linux-gnu-0.36.0.tar.gz" => "sha256:62cde955d57bc1fd87797537d56147658ed7e3a62a1875ef39a4a7a8bad4ab60",
"exqlite-nif-2.17-riscv64-linux-musl-0.36.0.tar.gz" => "sha256:7d6aa20ef6c872b7995d125fb426f0189940049253079fce52f4ff7dbbeb4f4a",
"exqlite-nif-2.17-s390x-linux-gnu-0.36.0.tar.gz" => "sha256:77a357b94fd876e0288daea817e3bbec2516d307b6c6cbaa2224ecdc670f5f5f",
"exqlite-nif-2.17-x86_64-apple-darwin-0.36.0.tar.gz" => "sha256:bb2df40b43313bf969c7bd2a903971fbe6d200916506e6bf13a2d8f5ef17b6f2",
"exqlite-nif-2.17-x86_64-linux-gnu-0.36.0.tar.gz" => "sha256:1c8402cbc1232f00fee08bb76a4dd0e75221a995accd7835a34bbda080c0d2df",
"exqlite-nif-2.17-x86_64-linux-musl-0.36.0.tar.gz" => "sha256:60ceb146f5023345e0aeadbc7aa59a2c5c601b421c4b041f6d05c7a436931cea",
"exqlite-nif-2.17-x86_64-windows-msvc-0.36.0.tar.gz" => "sha256:3b46bc0f25ee81aeae3932a7c2b569a16a0a501db7146ec5bef7bf04d313d47d",
}

View File

@@ -0,0 +1,43 @@
{<<"links">>,
[{<<"Changelog">>,
<<"https://github.com/elixir-sqlite/exqlite/blob/main/CHANGELOG.md">>},
{<<"GitHub">>,<<"https://github.com/elixir-sqlite/exqlite">>}]}.
{<<"name">>,<<"exqlite">>}.
{<<"version">>,<<"0.36.0">>}.
{<<"description">>,<<"An Elixir SQLite3 library">>}.
{<<"elixir">>,<<"~> 1.14">>}.
{<<"app">>,<<"exqlite">>}.
{<<"files">>,
[<<"lib">>,<<"lib/exqlite.ex">>,<<"lib/exqlite">>,<<"lib/exqlite/error.ex">>,
<<"lib/exqlite/flags.ex">>,<<"lib/exqlite/pragma.ex">>,
<<"lib/exqlite/query.ex">>,<<"lib/exqlite/result.ex">>,
<<"lib/exqlite/stream.ex">>,<<"lib/exqlite/basic.ex">>,
<<"lib/exqlite/type_extension.ex">>,<<"lib/exqlite/connection.ex">>,
<<"lib/exqlite/sqlite3.ex">>,<<"lib/exqlite/sqlite3_nif.ex">>,
<<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,
<<".clang-format">>,<<"c_src">>,<<"c_src/sqlite3ext.h">>,
<<"c_src/sqlite3.c">>,<<"c_src/sqlite3.h">>,<<"c_src/sqlite3_nif.c">>,
<<"Makefile">>,<<"Makefile.win">>,<<"checksum.exs">>]}.
{<<"licenses">>,[<<"MIT">>]}.
{<<"requirements">>,
[[{<<"name">>,<<"db_connection">>},
{<<"app">>,<<"db_connection">>},
{<<"optional">>,false},
{<<"requirement">>,<<"~> 2.1">>},
{<<"repository">>,<<"hexpm">>}],
[{<<"name">>,<<"elixir_make">>},
{<<"app">>,<<"elixir_make">>},
{<<"optional">>,false},
{<<"requirement">>,<<"~> 0.8">>},
{<<"repository">>,<<"hexpm">>}],
[{<<"name">>,<<"cc_precompiler">>},
{<<"app">>,<<"cc_precompiler">>},
{<<"optional">>,false},
{<<"requirement">>,<<"~> 0.1">>},
{<<"repository">>,<<"hexpm">>}],
[{<<"name">>,<<"table">>},
{<<"app">>,<<"table">>},
{<<"optional">>,true},
{<<"requirement">>,<<"~> 0.1.0">>},
{<<"repository">>,<<"hexpm">>}]]}.
{<<"build_tools">>,[<<"mix">>,<<"make">>]}.

View File

@@ -0,0 +1,104 @@
defmodule Exqlite do
@moduledoc """
SQLite3 driver for Elixir.
"""
alias Exqlite.Connection
alias Exqlite.Error
alias Exqlite.Query
alias Exqlite.Result
@doc "See `Exqlite.Connection.connect/1`"
@spec start_link([Connection.connection_opt()]) :: {:ok, pid()} | {:error, Error.t()}
def start_link(opts) do
DBConnection.start_link(Connection, opts)
end
@spec query(DBConnection.conn(), iodata(), list(), list()) ::
{:ok, Result.t()} | {:error, Exception.t()}
def query(conn, statement, params \\ [], opts \\ []) do
query = %Query{name: "", statement: IO.iodata_to_binary(statement)}
case DBConnection.prepare_execute(conn, query, params, opts) do
{:ok, _query, result} ->
{:ok, result}
otherwise ->
otherwise
end
end
@spec query!(DBConnection.conn(), iodata(), list(), list()) :: Result.t()
def query!(conn, statement, params \\ [], opts \\ []) do
case query(conn, statement, params, opts) do
{:ok, result} -> result
{:error, err} -> raise err
end
end
@spec prepare(DBConnection.conn(), iodata(), list()) ::
{:ok, Query.t()} | {:error, Exception.t()}
def prepare(conn, name, statement, opts \\ []) do
query = %Query{name: name, statement: statement}
DBConnection.prepare(conn, query, opts)
end
@spec prepare!(DBConnection.conn(), iodata(), iodata(), list()) :: Query.t()
def prepare!(conn, name, statement, opts \\ []) do
query = %Query{name: name, statement: statement}
DBConnection.prepare!(conn, query, opts)
end
@spec prepare_execute(DBConnection.conn(), iodata(), iodata(), list(), list()) ::
{:ok, Query.t(), Result.t()} | {:error, Error.t()}
def prepare_execute(conn, name, statement, params, opts \\ []) do
query = %Query{name: name, statement: statement}
DBConnection.prepare_execute(conn, query, params, opts)
end
@spec prepare_execute!(DBConnection.conn(), iodata(), iodata(), list(), list()) ::
{Query.t(), Result.t()}
def prepare_execute!(conn, name, statement, params, opts \\ []) do
query = %Query{name: name, statement: statement}
DBConnection.prepare_execute!(conn, query, params, opts)
end
@spec execute(DBConnection.conn(), Query.t(), list(), list()) ::
{:ok, Result.t()} | {:error, Error.t()}
def execute(conn, query, params, opts \\ []) do
DBConnection.execute(conn, query, params, opts)
end
@spec execute!(DBConnection.conn(), Query.t(), list(), list()) :: Result.t()
def execute!(conn, query, params, opts \\ []) do
DBConnection.execute!(conn, query, params, opts)
end
@spec close(DBConnection.conn(), Query.t(), list()) :: :ok | {:error, Exception.t()}
def close(conn, query, opts \\ []) do
with {:ok, _} <- DBConnection.close(conn, query, opts) do
:ok
end
end
@spec close!(DBConnection.conn(), Query.t(), list()) :: :ok
def close!(conn, query, opts \\ []) do
DBConnection.close!(conn, query, opts)
:ok
end
@spec transaction(DBConnection.conn(), (DBConnection.t() -> result), list()) ::
{:ok, result} | {:error, any}
when result: var
def transaction(conn, fun, opts \\ []) do
DBConnection.transaction(conn, fun, opts)
end
@spec rollback(DBConnection.t(), term()) :: no_return()
def rollback(conn, reason), do: DBConnection.rollback(conn, reason)
@spec child_spec([Connection.connection_opt()]) :: :supervisor.child_spec()
def child_spec(opts) do
DBConnection.child_spec(Connection, opts)
end
end

View File

@@ -0,0 +1,48 @@
defmodule Exqlite.Basic do
@moduledoc """
A very basic API for simple use cases.
"""
alias Exqlite.Connection
alias Exqlite.Query
alias Exqlite.Sqlite3
alias Exqlite.Error
alias Exqlite.Result
def open(path) do
Connection.connect(database: path)
end
def close(%Connection{} = conn) do
case Sqlite3.close(conn.db) do
:ok -> :ok
{:error, reason} -> {:error, %Error{message: to_string(reason)}}
end
end
def exec(%Connection{} = conn, stmt, args \\ []) do
%Query{statement: stmt} |> Connection.handle_execute(args, [], conn)
end
def rows(exec_result) do
case exec_result do
{:ok, %Query{}, %Result{rows: rows, columns: columns}, %Connection{}} ->
{:ok, rows, columns}
{:error, %Error{message: message}, %Connection{}} ->
{:error, to_string(message)}
end
end
def load_extension(conn, path) do
exec(conn, "select load_extension(?)", [path])
end
def enable_load_extension(conn) do
Sqlite3.enable_load_extension(conn.db, true)
end
def disable_load_extension(conn) do
Sqlite3.enable_load_extension(conn.db, false)
end
end

View File

@@ -0,0 +1,741 @@
defmodule Exqlite.Connection do
@moduledoc """
This module implements connection details as defined in DBProtocol.
## Attributes
- `db` - The sqlite3 database reference.
- `path` - The path that was used to open.
- `transaction_status` - The status of the connection. Can be `:idle` or `:transaction`.
## Unknowns
- How are pooled connections going to work? Since sqlite3 doesn't allow for
simultaneous access. We would need to check if the write ahead log is
enabled on the database. We can't assume and set the WAL pragma because the
database may be stored on a network volume which would cause potential
issues.
Notes:
- we try to closely follow structure and naming convention of myxql.
- sqlite thrives when there are many small conventions, so we may not implement
some strategies employed by other adapters. See https://sqlite.org/np1queryprob.html
"""
use DBConnection
alias Exqlite.Error
alias Exqlite.Pragma
alias Exqlite.Query
alias Exqlite.Result
alias Exqlite.Sqlite3
require Logger
defstruct [
:db,
:default_transaction_mode,
:directory,
:path,
:transaction_status,
:status,
:chunk_size,
:before_disconnect
]
@type t() :: %__MODULE__{
db: Sqlite3.db(),
directory: String.t() | nil,
path: String.t(),
transaction_status: :idle | :transaction,
status: :idle | :busy,
chunk_size: integer(),
before_disconnect: (t -> any) | {module, atom, [any]} | nil
}
@type journal_mode() :: :delete | :truncate | :persist | :memory | :wal | :off
@type temp_store() :: :default | :file | :memory
@type synchronous() :: :extra | :full | :normal | :off
@type auto_vacuum() :: :none | :full | :incremental
@type locking_mode() :: :normal | :exclusive
@type transaction_mode() :: :deferred | :immediate | :exclusive
@type connection_opt() ::
{:database, String.t()}
| {:default_transaction_mode, transaction_mode()}
| {:mode, Sqlite3.open_opt()}
| {:journal_mode, journal_mode()}
| {:temp_store, temp_store()}
| {:synchronous, synchronous()}
| {:foreign_keys, :on | :off}
| {:cache_size, integer()}
| {:cache_spill, :on | :off}
| {:case_sensitive_like, boolean()}
| {:auto_vacuum, auto_vacuum()}
| {:locking_mode, locking_mode()}
| {:secure_delete, :on | :off}
| {:wal_auto_check_point, integer()}
| {:busy_timeout, integer()}
| {:chunk_size, integer()}
| {:journal_size_limit, integer()}
| {:soft_heap_limit, integer()}
| {:hard_heap_limit, integer()}
| {:key, String.t()}
| {:custom_pragmas, [{keyword(), integer() | boolean() | String.t()}]}
| {:before_disconnect, (t -> any) | {module, atom, [any]} | nil}
@impl true
@doc """
Initializes the Ecto Exqlite adapter.
For connection configurations we use the defaults that come with SQLite3, but
we recommend which options to choose. We do not default to the recommended
because we don't know what your environment is like.
Allowed options:
* `:database` - The path to the database. In memory is allowed. You can use
`:memory` or `":memory:"` to designate that.
* `:default_transaction_mode` - one of `deferred` (default), `immediate`,
or `exclusive`. If a mode is not specified in a call to `Repo.transaction/2`,
this will be the default transaction mode.
* `:mode` - use `:readwrite` to open the database for reading and writing
, `:readonly` to open it in read-only mode or `[:readonly | :readwrite, :nomutex]`
to open it with no mutex mode. `:readwrite` will also create
the database if it doesn't already exist. Defaults to `:readwrite`.
Note: [:readwrite, :nomutex] is not recommended.
* `:journal_mode` - Sets the journal mode for the sqlite connection. Can be
one of the following `:delete`, `:truncate`, `:persist`, `:memory`,
`:wal`, or `:off`. Defaults to `:delete`. It is recommended that you use
`:wal` due to support for concurrent reads. Note: `:wal` does not mean
concurrent writes.
* `:temp_store` - Sets the storage used for temporary tables. Default is
`:default`. Allowed values are `:default`, `:file`, `:memory`. It is
recommended that you use `:memory` for storage.
* `:synchronous` - Can be `:extra`, `:full`, `:normal`, or `:off`. Defaults
to `:normal`.
* `:foreign_keys` - Sets if foreign key checks should be enforced or not.
Can be `:on` or `:off`. Default is `:on`.
* `:cache_size` - Sets the cache size to be used for the connection. This is
an odd setting as a positive value is the number of pages in memory to use
and a negative value is the size in kilobytes to use. Default is `-2000`.
It is recommended that you use `-64000`.
* `:cache_spill` - The cache_spill pragma enables or disables the ability of
the pager to spill dirty cache pages to the database file in the middle of
a transaction. By default it is `:on`, and for most applications, it
should remain so.
* `:case_sensitive_like`
* `:auto_vacuum` - Defaults to `:none`. Can be `:none`, `:full` or
`:incremental`. Depending on the database size, `:incremental` may be
beneficial.
* `:locking_mode` - Defaults to `:normal`. Allowed values are `:normal` or
`:exclusive`. See [sqlite documentation][1] for more information.
* `:secure_delete` - Defaults to `:off`. If enabled, it will cause SQLite3
to overwrite records that were deleted with zeros.
* `:wal_auto_check_point` - Sets the write-ahead log auto-checkpoint
interval. Default is `1000`. Setting the auto-checkpoint size to zero or a
negative value turns auto-checkpointing off.
* `:busy_timeout` - Sets the busy timeout in milliseconds for a connection.
Default is `2000`.
* `:chunk_size` - The chunk size for bulk fetching. Defaults to `50`.
* `:key` - Optional key to set during database initialization. This PRAGMA
is often used to set up database level encryption.
* `:journal_size_limit` - The size limit in bytes of the journal.
* `:soft_heap_limit` - The size limit in bytes for the heap limit.
* `:hard_heap_limit` - The size limit in bytes for the heap.
* `:custom_pragmas` - A list of custom pragmas to set on the connection, for example to configure extensions.
* `:serialized` - A SQLite database which was previously serialized, to load into the database after connection.
* `:load_extensions` - A list of paths identifying extensions to load. Defaults to `[]`.
The provided list will be merged with the global extensions list, set on `:exqlite, :load_extensions`.
Be aware that the path should handle pointing to a library compiled for the current architecture.
Example configuration:
```
arch_dir =
System.cmd("uname", ["-sm"])
|> elem(0)
|> String.trim()
|> String.replace(" ", "-")
|> String.downcase() # => "darwin-arm64"
config :myapp, arch_dir: arch_dir
# global
config :exqlite, load_extensions: [ "./priv/sqlite/\#{arch_dir}/rotate" ]
# per connection in a Phoenix app
config :myapp, Myapp.Repo,
database: "path/to/db",
load_extensions: [
"./priv/sqlite/\#{arch_dir}/vector0",
"./priv/sqlite/\#{arch_dir}/vss0"
]
```
* `:before_disconnect` - A function to run before disconnect, either a
2-arity fun or `{module, function, args}` with the close reason and
`t:Exqlite.Connection.t/0` prepended to `args` or `nil` (default: `nil`)
For more information about the options above, see [sqlite documentation][1]
[1]: https://www.sqlite.org/pragma.html
"""
@spec connect([connection_opt()]) :: {:ok, t()} | {:error, Exception.t()}
def connect(options) do
database = Keyword.get(options, :database)
options =
Keyword.put_new(
options,
:chunk_size,
Application.get_env(:exqlite, :default_chunk_size, 50)
)
case database do
nil ->
{:error,
%Error{
message: """
You must provide a :database to the database. \
Example: connect(database: "./") or connect(database: :memory)\
"""
}}
:memory ->
do_connect(":memory:", options)
_ ->
do_connect(database, options)
end
end
@impl true
def disconnect(err, %__MODULE__{db: db} = state) do
if state.before_disconnect != nil do
apply(state.before_disconnect, [err, state])
end
case Sqlite3.close(db) do
:ok -> :ok
{:error, reason} -> {:error, %Error{message: to_string(reason)}}
end
end
@impl true
def checkout(%__MODULE__{status: :idle} = state) do
{:ok, %{state | status: :busy}}
end
def checkout(%__MODULE__{status: :busy} = state) do
{:disconnect, %Error{message: "Database is busy"}, state}
end
@impl true
def ping(state), do: {:ok, state}
##
## Handlers
##
@impl true
def handle_prepare(%Query{} = query, options, state) do
with {:ok, query} <- prepare(query, options, state) do
{:ok, query, state}
end
end
@impl true
def handle_execute(%Query{} = query, params, options, state) do
with {:ok, query} <- prepare(query, options, state) do
execute(:execute, query, params, state)
end
end
@doc """
Begin a transaction.
For full info refer to sqlite docs: https://sqlite.org/lang_transaction.html
Note: default transaction mode is DEFERRED.
"""
@impl true
def handle_begin(options, %{transaction_status: transaction_status} = state) do
# This doesn't handle more than 2 levels of transactions.
#
# One possible solution would be to just track the number of open
# transactions and use that for driving the transaction status being idle or
# in a transaction.
#
# I do not know why the other official adapters do not track this and just
# append level on the savepoint. Instead the rollbacks would just completely
# revert the issues when it may be desirable to fix something while in the
# transaction and then commit.
mode = Keyword.get(options, :mode, state.default_transaction_mode)
case mode do
:deferred when transaction_status == :idle ->
handle_transaction(:begin, "BEGIN TRANSACTION", state)
:transaction when transaction_status == :idle ->
handle_transaction(:begin, "BEGIN TRANSACTION", state)
:immediate when transaction_status == :idle ->
handle_transaction(:begin, "BEGIN IMMEDIATE TRANSACTION", state)
:exclusive when transaction_status == :idle ->
handle_transaction(:begin, "BEGIN EXCLUSIVE TRANSACTION", state)
mode
when mode in [:deferred, :immediate, :exclusive, :savepoint] and
transaction_status == :transaction ->
handle_transaction(:begin, "SAVEPOINT exqlite_savepoint", state)
end
end
@impl true
def handle_commit(options, %{transaction_status: transaction_status} = state) do
case Keyword.get(options, :mode, :deferred) do
:savepoint when transaction_status == :transaction ->
handle_transaction(
:commit_savepoint,
"RELEASE SAVEPOINT exqlite_savepoint",
state
)
mode
when mode in [:deferred, :immediate, :exclusive, :transaction] and
transaction_status == :transaction ->
handle_transaction(:commit, "COMMIT", state)
end
end
@impl true
def handle_rollback(options, %{transaction_status: transaction_status} = state) do
case Keyword.get(options, :mode, :deferred) do
:savepoint when transaction_status == :transaction ->
with {:ok, _result, state} <-
handle_transaction(
:rollback_savepoint,
"ROLLBACK TO SAVEPOINT exqlite_savepoint",
state
) do
handle_transaction(
:rollback_savepoint,
"RELEASE SAVEPOINT exqlite_savepoint",
state
)
end
mode
when mode in [:deferred, :immediate, :exclusive, :transaction] ->
handle_transaction(:rollback, "ROLLBACK TRANSACTION", state)
end
end
@doc """
Close a query prepared by `handle_prepare/3` with the database. Return
`{:ok, result, state}` on success and to continue,
`{:error, exception, state}` to return an error and continue, or
`{:disconnect, exception, state}` to return an error and disconnect.
This callback is called in the client process.
"""
@impl true
def handle_close(query, _opts, state) do
Sqlite3.release(state.db, query.ref)
{:ok, nil, state}
end
@impl true
def handle_declare(%Query{} = query, params, opts, state) do
# We emulate cursor functionality by just using a prepared statement and
# step through it. Thus we just return the query ref as the cursor.
with {:ok, query} <- prepare_no_cache(query, opts, state),
{:ok, query} <- bind_params(query, params, state) do
{:ok, query, query.ref, state}
end
end
@impl true
def handle_deallocate(%Query{} = query, _cursor, _opts, state) do
Sqlite3.release(state.db, query.ref)
{:ok, nil, state}
end
@impl true
def handle_fetch(%Query{statement: statement}, cursor, opts, state) do
chunk_size = opts[:chunk_size] || opts[:max_rows] || state.chunk_size
case Sqlite3.multi_step(state.db, cursor, chunk_size) do
{:done, rows} ->
{:halt, %Result{rows: rows, command: :fetch, num_rows: length(rows)}, state}
{:rows, rows} ->
{:cont, %Result{rows: rows, command: :fetch, num_rows: chunk_size}, state}
{:error, reason} ->
{:error, %Error{message: to_string(reason), statement: statement}, state}
:busy ->
{:error, %Error{message: "Database is busy", statement: statement}, state}
end
end
@impl true
def handle_status(_opts, state) do
{state.transaction_status, state}
end
### ----------------------------------
# Internal functions and helpers
### ----------------------------------
defp set_pragma(db, pragma_name, value) do
Sqlite3.execute(db, "PRAGMA #{pragma_name} = #{value}")
end
defp get_pragma(db, pragma_name) do
{:ok, statement} = Sqlite3.prepare(db, "PRAGMA #{pragma_name}")
case Sqlite3.fetch_all(db, statement) do
{:ok, [[value]]} -> {:ok, value}
_ -> :error
end
end
defp maybe_set_pragma(db, pragma_name, value) do
case get_pragma(db, pragma_name) do
{:ok, current} ->
if current == value do
:ok
else
set_pragma(db, pragma_name, value)
end
_ ->
set_pragma(db, pragma_name, value)
end
end
defp set_key(db, options) do
# we can't use maybe_set_pragma here since
# the only thing that will work on an encrypted
# database without error is setting the key.
case Keyword.fetch(options, :key) do
{:ok, key} -> set_pragma(db, "key", key)
_ -> :ok
end
end
defp set_custom_pragmas(db, options) do
# we can't use maybe_set_pragma because some pragmas
# are required to be set before the database is e.g. decrypted.
case Keyword.fetch(options, :custom_pragmas) do
{:ok, list} -> do_set_custom_pragmas(db, list)
_ -> :ok
end
end
defp do_set_custom_pragmas(db, list) do
list
|> Enum.reduce_while(:ok, fn {key, value}, :ok ->
case set_pragma(db, key, value) do
:ok -> {:cont, :ok}
{:error, _reason} -> {:halt, :error}
end
end)
end
defp set_pragma_if_present(_db, _pragma, nil), do: :ok
defp set_pragma_if_present(db, pragma, value), do: set_pragma(db, pragma, value)
defp set_journal_size_limit(db, options) do
set_pragma_if_present(
db,
"journal_size_limit",
Keyword.get(options, :journal_size_limit)
)
end
defp set_soft_heap_limit(db, options) do
set_pragma_if_present(db, "soft_heap_limit", Keyword.get(options, :soft_heap_limit))
end
defp set_hard_heap_limit(db, options) do
set_pragma_if_present(db, "hard_heap_limit", Keyword.get(options, :hard_heap_limit))
end
defp set_journal_mode(db, options) do
set_pragma_if_present(db, "journal_mode", Keyword.get(options, :journal_mode))
end
defp set_temp_store(db, options) do
set_pragma(db, "temp_store", Pragma.temp_store(options))
end
defp set_synchronous(db, options) do
set_pragma(db, "synchronous", Pragma.synchronous(options))
end
defp set_foreign_keys(db, options) do
set_pragma(db, "foreign_keys", Pragma.foreign_keys(options))
end
defp set_cache_size(db, options) do
maybe_set_pragma(db, "cache_size", Pragma.cache_size(options))
end
defp set_cache_spill(db, options) do
set_pragma(db, "cache_spill", Pragma.cache_spill(options))
end
defp set_case_sensitive_like(db, options) do
set_pragma(db, "case_sensitive_like", Pragma.case_sensitive_like(options))
end
defp set_auto_vacuum(db, options) do
set_pragma(db, "auto_vacuum", Pragma.auto_vacuum(options))
end
defp set_locking_mode(db, options) do
set_pragma(db, "locking_mode", Pragma.locking_mode(options))
end
defp set_secure_delete(db, options) do
set_pragma(db, "secure_delete", Pragma.secure_delete(options))
end
defp set_wal_auto_check_point(db, options) do
set_pragma(db, "wal_autocheckpoint", Pragma.wal_auto_check_point(options))
end
defp set_busy_timeout(db, options) do
set_pragma(db, "busy_timeout", Pragma.busy_timeout(options))
end
defp deserialize(db, options) do
case Keyword.get(options, :serialized, nil) do
nil -> :ok
serialized -> Sqlite3.deserialize(db, serialized)
end
end
defp load_extensions(db, options) do
global_extensions = Application.get_env(:exqlite, :load_extensions, [])
extensions =
Keyword.get(options, :load_extensions, [])
|> Enum.concat(global_extensions)
|> Enum.uniq()
do_load_extensions(db, extensions)
end
defp do_load_extensions(_db, []), do: :ok
defp do_load_extensions(db, extensions) do
Sqlite3.enable_load_extension(db, true)
Enum.each(extensions, fn extension ->
Logger.debug(fn -> "Exqlite: loading extension `#{extension}`" end)
Sqlite3.execute(db, "SELECT load_extension('#{extension}')")
end)
Sqlite3.enable_load_extension(db, false)
end
defp do_connect(database, options) do
with {:ok, directory} <- resolve_directory(database),
:ok <- mkdir_p(directory),
{:ok, db} <- Sqlite3.open(database, options),
:ok <- set_key(db, options),
:ok <- set_custom_pragmas(db, options),
:ok <- set_journal_mode(db, options),
:ok <- set_temp_store(db, options),
:ok <- set_synchronous(db, options),
:ok <- set_foreign_keys(db, options),
:ok <- set_cache_size(db, options),
:ok <- set_cache_spill(db, options),
:ok <- set_auto_vacuum(db, options),
:ok <- set_locking_mode(db, options),
:ok <- set_secure_delete(db, options),
:ok <- set_wal_auto_check_point(db, options),
:ok <- set_case_sensitive_like(db, options),
:ok <- set_busy_timeout(db, options),
:ok <- set_journal_size_limit(db, options),
:ok <- set_soft_heap_limit(db, options),
:ok <- set_hard_heap_limit(db, options),
:ok <- load_extensions(db, options),
:ok <- deserialize(db, options) do
state = %__MODULE__{
db: db,
default_transaction_mode:
Keyword.get(options, :default_transaction_mode, :deferred),
directory: directory,
path: database,
transaction_status: :idle,
status: :idle,
chunk_size: Keyword.get(options, :chunk_size),
before_disconnect: Keyword.get(options, :before_disconnect, nil)
}
{:ok, state}
else
{:error, reason} ->
{:error, %Exqlite.Error{message: to_string(reason)}}
end
end
def maybe_put_command(query, options) do
case Keyword.get(options, :command) do
nil -> query
command -> %{query | command: command}
end
end
# Attempt to retrieve the cached query, if it doesn't exist, we'll prepare one
# and cache it for later.
defp prepare(%Query{statement: statement} = query, options, state) do
query = maybe_put_command(query, options)
with {:ok, ref} <- Sqlite3.prepare(state.db, IO.iodata_to_binary(statement)),
query <- %{query | ref: ref} do
{:ok, query}
else
{:error, reason} ->
{:error, %Error{message: to_string(reason), statement: statement}, state}
end
end
# Prepare a query and do not cache it.
defp prepare_no_cache(%Query{statement: statement} = query, options, state) do
query = maybe_put_command(query, options)
case Sqlite3.prepare(state.db, statement) do
{:ok, ref} ->
{:ok, %{query | ref: ref}}
{:error, reason} ->
{:error, %Error{message: to_string(reason), statement: statement}, state}
end
end
@spec maybe_changes(Sqlite3.db(), Query.t()) :: integer() | nil
defp maybe_changes(db, %Query{command: command})
when command in [:update, :insert, :delete] do
case Sqlite3.changes(db) do
{:ok, total} -> total
_ -> nil
end
end
defp maybe_changes(_, _), do: nil
# when we have an empty list of columns, that signifies that
# there was no possible return tuple (e.g., update statement without RETURNING)
# and in that case, we return nil to signify no possible result.
defp maybe_rows([], []), do: nil
defp maybe_rows(rows, _cols), do: rows
defp execute(call, %Query{} = query, params, state) do
with {:ok, query} <- bind_params(query, params, state),
{:ok, columns} <- get_columns(query, state),
{:ok, rows} <- get_rows(query, state),
{:ok, transaction_status} <- Sqlite3.transaction_status(state.db),
changes <- maybe_changes(state.db, query) do
case query.command do
command when command in [:delete, :insert, :update] ->
{
:ok,
query,
Result.new(
command: call,
num_rows: changes,
rows: maybe_rows(rows, columns)
),
%{state | transaction_status: transaction_status}
}
_ ->
{
:ok,
query,
Result.new(
command: call,
columns: columns,
rows: rows,
num_rows: Enum.count(rows)
),
%{state | transaction_status: transaction_status}
}
end
end
end
defp bind_params(%Query{ref: ref, statement: statement} = query, params, state)
when ref != nil do
try do
Sqlite3.bind(ref, params)
rescue
e -> {:error, %Error{message: Exception.message(e), statement: statement}, state}
else
:ok -> {:ok, query}
end
end
defp get_columns(%Query{ref: ref, statement: statement}, state) do
case Sqlite3.columns(state.db, ref) do
{:ok, columns} ->
{:ok, columns}
{:error, reason} ->
{:error, %Error{message: to_string(reason), statement: statement}, state}
end
end
defp get_rows(%Query{ref: ref, statement: statement}, state) do
case Sqlite3.fetch_all(state.db, ref, state.chunk_size) do
{:ok, rows} ->
{:ok, rows}
{:error, reason} ->
{:error, %Error{message: to_string(reason), statement: statement}, state}
end
end
defp handle_transaction(call, statement, state) do
with :ok <- Sqlite3.execute(state.db, statement),
{:ok, transaction_status} <- Sqlite3.transaction_status(state.db) do
result = %Result{
command: call,
rows: [],
columns: [],
num_rows: 0
}
{:ok, result, %{state | transaction_status: transaction_status}}
else
{:error, reason} ->
{:disconnect, %Error{message: to_string(reason), statement: statement}, state}
end
end
defp resolve_directory(":memory:"), do: {:ok, nil}
defp resolve_directory("file:" <> _ = uri) do
case URI.parse(uri) do
%{path: path} when is_binary(path) ->
{:ok, Path.dirname(path)}
_ ->
{:error, "No path in #{inspect(uri)}"}
end
end
defp resolve_directory(path), do: {:ok, Path.dirname(path)}
# SQLITE_OPEN_CREATE will create the DB file if not existing, but
# will not create intermediary directories if they are missing.
# So let's preemptively create the intermediate directories here
# before trying to open the DB file.
defp mkdir_p(nil), do: :ok
defp mkdir_p(directory), do: File.mkdir_p(directory)
end

View File

@@ -0,0 +1,18 @@
defmodule Exqlite.Error do
@moduledoc """
The error emitted from SQLite or a general error with the library.
"""
defexception [:message, :statement]
@type t :: %__MODULE__{
message: String.t(),
statement: String.t()
}
@impl true
def message(%__MODULE__{message: message, statement: nil}), do: message
def message(%__MODULE__{message: message, statement: statement}),
do: "#{message}\n#{statement}"
end

View File

@@ -0,0 +1,35 @@
defmodule Exqlite.Flags do
@moduledoc false
import Bitwise
# https://www.sqlite.org/c3ref/c_open_autoproxy.html
@file_open_flags [
sqlite_open_readonly: 0x00000001,
sqlite_open_readwrite: 0x00000002,
sqlite_open_create: 0x00000004,
sqlite_open_deleteonclos: 0x00000008,
sqlite_open_exclusive: 0x00000010,
sqlite_open_autoproxy: 0x00000020,
sqlite_open_uri: 0x00000040,
sqlite_open_memory: 0x00000080,
sqlite_open_main_db: 0x00000100,
sqlite_open_temp_db: 0x00000200,
sqlite_open_transient_db: 0x00000400,
sqlite_open_main_journal: 0x00000800,
sqlite_open_temp_journal: 0x00001000,
sqlite_open_subjournal: 0x00002000,
sqlite_open_super_journal: 0x00004000,
sqlite_open_nomutex: 0x00008000,
sqlite_open_fullmutex: 0x00010000,
sqlite_open_sharedcache: 0x00020000,
sqlite_open_privatecache: 0x00040000,
sqlite_open_wal: 0x00080000,
sqlite_open_nofollow: 0x01000000,
sqlite_open_exrescode: 0x02000000
]
def put_file_open_flags(current_flags \\ 0, flags) do
Enum.reduce(flags, current_flags, &(&2 ||| Keyword.fetch!(@file_open_flags, &1)))
end
end

View File

@@ -0,0 +1,121 @@
defmodule Exqlite.Pragma do
@moduledoc """
Handles parsing extra options for the SQLite connection
"""
def busy_timeout(nil), do: busy_timeout([])
def busy_timeout(options) do
Keyword.get(options, :busy_timeout, 2000)
end
def journal_mode(nil), do: journal_mode([])
def journal_mode(options) do
case Keyword.get(options, :journal_mode, :delete) do
:delete -> "delete"
:memory -> "memory"
:off -> "off"
:persist -> "persist"
:truncate -> "truncate"
:wal -> "wal"
_ -> raise ArgumentError, "invalid :journal_mode"
end
end
def temp_store(nil), do: temp_store([])
def temp_store(options) do
case Keyword.get(options, :temp_store, :default) do
:file -> 1
:memory -> 2
:default -> 0
_ -> raise ArgumentError, "invalid :temp_store"
end
end
def synchronous(nil), do: synchronous([])
def synchronous(options) do
case Keyword.get(options, :synchronous, :normal) do
:extra -> 3
:full -> 2
:normal -> 1
:off -> 0
_ -> raise ArgumentError, "invalid :synchronous"
end
end
def foreign_keys(nil), do: foreign_keys([])
def foreign_keys(options) do
case Keyword.get(options, :foreign_keys, :on) do
:off -> 0
:on -> 1
_ -> raise ArgumentError, "invalid :foreign_keys"
end
end
def cache_size(nil), do: cache_size([])
def cache_size(options) do
Keyword.get(options, :cache_size, -2000)
end
def cache_spill(nil), do: cache_spill([])
def cache_spill(options) do
case Keyword.get(options, :cache_spill, :on) do
:off -> 0
:on -> 1
_ -> raise ArgumentError, "invalid :cache_spill"
end
end
def case_sensitive_like(nil), do: case_sensitive_like([])
def case_sensitive_like(options) do
case Keyword.get(options, :case_sensitive_like, :off) do
:off -> 0
:on -> 1
_ -> raise ArgumentError, "invalid :case_sensitive_like"
end
end
def auto_vacuum(nil), do: auto_vacuum([])
def auto_vacuum(options) do
case Keyword.get(options, :auto_vacuum, :none) do
:none -> 0
:full -> 1
:incremental -> 2
_ -> raise ArgumentError, "invalid :auto_vacuum"
end
end
def locking_mode(nil), do: locking_mode([])
def locking_mode(options) do
case Keyword.get(options, :locking_mode, :normal) do
:normal -> "NORMAL"
:exclusive -> "EXCLUSIVE"
_ -> raise ArgumentError, "invalid :locking_mode"
end
end
def secure_delete(nil), do: secure_delete([])
def secure_delete(options) do
case Keyword.get(options, :secure_delete, :off) do
:off -> 0
:on -> 1
_ -> raise ArgumentError, "invalid :secure_delete"
end
end
def wal_auto_check_point(nil), do: wal_auto_check_point([])
def wal_auto_check_point(options) do
Keyword.get(options, :wal_auto_check_point, 1000)
end
end

View File

@@ -0,0 +1,70 @@
defmodule Exqlite.Query do
@moduledoc """
Query struct returned from a successfully prepared query.
"""
@type t :: %__MODULE__{
statement: iodata(),
name: atom() | String.t(),
ref: reference() | nil,
command: :insert | :delete | :update | nil
}
defstruct statement: nil,
name: nil,
ref: nil,
command: nil
def build(options) do
statement = Keyword.get(options, :statement)
name = Keyword.get(options, :name)
ref = Keyword.get(options, :ref)
command =
case Keyword.get(options, :command) do
nil -> extract_command(statement)
value -> value
end
%__MODULE__{
statement: statement,
name: name,
ref: ref,
command: command
}
end
defp extract_command(nil), do: nil
defp extract_command(statement) do
cond do
String.contains?(statement, "INSERT") -> :insert
String.contains?(statement, "DELETE") -> :delete
String.contains?(statement, "UPDATE") -> :update
true -> nil
end
end
defimpl DBConnection.Query do
def parse(query, _opts) do
query
end
def describe(query, _opts) do
query
end
def encode(_query, params, _opts) do
params
end
def decode(_query, result, _opts) do
result
end
end
defimpl String.Chars do
def to_string(%{statement: statement}) do
IO.iodata_to_binary(statement)
end
end
end

View File

@@ -0,0 +1,35 @@
defmodule Exqlite.Result do
@moduledoc """
The database results.
"""
@type t :: %__MODULE__{
command: atom,
columns: [String.t()] | nil,
rows: [[term] | term] | nil,
num_rows: integer()
}
defstruct command: nil, columns: [], rows: [], num_rows: 0
def new(options) do
%__MODULE__{
command: Keyword.get(options, :command),
columns: Keyword.get(options, :columns, []),
rows: Keyword.get(options, :rows, []),
num_rows: Keyword.get(options, :num_rows, 0)
}
end
end
if Code.ensure_loaded?(Table.Reader) do
defimpl Table.Reader, for: Exqlite.Result do
def init(%{columns: columns}) when columns in [nil, []] do
{:rows, %{columns: []}, []}
end
def init(result) do
{:rows, %{columns: result.columns}, result.rows}
end
end
end

View File

@@ -0,0 +1,587 @@
defmodule Exqlite.Sqlite3 do
@moduledoc """
The interface to the NIF implementation.
"""
# If the database reference is closed, any prepared statements should be
# dereferenced as well. It is entirely possible that an application does
# not properly remove a stale reference.
#
# Will need to add a test for this and think of possible solution.
# Need to figure out if we can just stream results where we use this
# module as a sink.
alias Exqlite.Flags
alias Exqlite.Sqlite3NIF
@type db() :: reference()
@type statement() :: reference()
@type reason() :: atom() | String.t()
@type row() :: list()
@type open_mode :: :readwrite | :readonly | :nomutex
@type open_opt :: {:mode, :readwrite | :readonly | [open_mode()]}
@doc """
Opens a new sqlite database at the Path provided.
`path` can be `":memory"` to keep the sqlite database in memory.
## Options
* `:mode` - use `:readwrite` to open the database for reading and writing
, `:readonly` to open it in read-only mode or `[:readonly | :readwrite, :nomutex]`
to open it with no mutex mode. `:readwrite` will also create
the database if it doesn't already exist. Defaults to `:readwrite`.
Note: [:readwrite, :nomutex] is not recommended.
"""
@spec open(String.t(), [open_opt()]) :: {:ok, db()} | {:error, reason()}
def open(path, opts \\ []) do
mode = Keyword.get(opts, :mode, :readwrite)
Sqlite3NIF.open(path, flags_from_mode(mode))
end
defp flags_from_mode(:nomutex) do
raise ArgumentError,
"expected mode to be `:readwrite` or `:readonly`, can't use a single :nomutex mode"
end
defp flags_from_mode(:readwrite),
do: do_flags_from_mode([:readwrite], [])
defp flags_from_mode(:readonly),
do: do_flags_from_mode([:readonly], [])
defp flags_from_mode([_ | _] = modes),
do: do_flags_from_mode(modes, [])
defp flags_from_mode(mode) do
raise ArgumentError,
"expected mode to be `:readwrite`, `:readonly` or list of modes, but received #{inspect(mode)}"
end
defp do_flags_from_mode([:readwrite | tail], acc),
do: do_flags_from_mode(tail, [:sqlite_open_readwrite, :sqlite_open_create | acc])
defp do_flags_from_mode([:readonly | tail], acc),
do: do_flags_from_mode(tail, [:sqlite_open_readonly | acc])
defp do_flags_from_mode([:nomutex | tail], acc),
do: do_flags_from_mode(tail, [:sqlite_open_nomutex | acc])
defp do_flags_from_mode([mode | _tail], _acc) do
raise ArgumentError,
"expected mode to be `:readwrite`, `:readonly` or `:nomutex`, but received #{inspect(mode)}"
end
defp do_flags_from_mode([], acc),
do: Flags.put_file_open_flags(acc)
@doc """
Closes the database and releases any underlying resources.
"""
@spec close(db() | nil) :: :ok | {:error, reason()}
def close(nil), do: :ok
def close(conn), do: Sqlite3NIF.close(conn)
@doc """
Interrupt a long-running query.
> #### Warning {: .warning}
> If you are going to interrupt a long running process, it is unsafe to call
> `close/1` immediately after. You run the risk of undefined behavior. This
> is a limitation of the sqlite library itself. Please see the documentation
> https://www.sqlite.org/c3ref/interrupt.html for more information.
>
> If close must be called after, it is best to put a short sleep in order to
> let sqlite finish doing its book keeping.
"""
@spec interrupt(db() | nil) :: :ok | {:error, reason()}
def interrupt(nil), do: :ok
def interrupt(conn), do: Sqlite3NIF.interrupt(conn)
@doc """
Executes an sql script. Multiple stanzas can be passed at once.
"""
@spec execute(db(), String.t()) :: :ok | {:error, reason()}
def execute(conn, sql), do: Sqlite3NIF.execute(conn, sql)
@doc """
Get the number of changes recently.
**Note**: If triggers are used, the count may be larger than expected.
See: https://sqlite.org/c3ref/changes.html
"""
@spec changes(db()) :: {:ok, integer()} | {:error, reason()}
def changes(conn), do: Sqlite3NIF.changes(conn)
@spec prepare(db(), String.t()) :: {:ok, statement()} | {:error, reason()}
def prepare(conn, sql), do: Sqlite3NIF.prepare(conn, sql)
@doc """
Resets a prepared statement.
See: https://sqlite.org/c3ref/reset.html
"""
@spec reset(statement) :: :ok | {:error, atom()}
def reset(stmt), do: Sqlite3NIF.reset(stmt)
@doc """
Returns number of SQL parameters in a prepared statement.
iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?, ?")
iex> Sqlite3.bind_parameter_count(stmt)
2
"""
@spec bind_parameter_count(statement) :: non_neg_integer | {:error, atom()}
def bind_parameter_count(stmt), do: Sqlite3NIF.bind_parameter_count(stmt)
@type bind_value ::
NaiveDateTime.t()
| DateTime.t()
| Date.t()
| Time.t()
| number
| iodata
| {:blob, iodata}
| atom
@doc """
Resets a prepared statement and binds values to it.
iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?, ?, ?, ?, ?")
iex> Sqlite3.bind(stmt, [42, 3.14, "Alice", {:blob, <<0, 0, 0>>}, nil])
iex> Sqlite3.step(conn, stmt)
{:row, [42, 3.14, "Alice", <<0, 0, 0>>, nil]}
iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT :42, @pi, $name, @blob, :null")
iex> Sqlite3.bind(stmt, %{":42" => 42, "@pi" => 3.14, "$name" => "Alice", :"@blob" => {:blob, <<0, 0, 0>>}, ~c":null" => nil})
iex> Sqlite3.step(conn, stmt)
{:row, [42, 3.14, "Alice", <<0, 0, 0>>, nil]}
iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind(stmt, [42, 3.14, "Alice"])
** (ArgumentError) expected 1 arguments, got 3
iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?, ?")
iex> Sqlite3.bind(stmt, [42])
** (ArgumentError) expected 2 arguments, got 1
iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind(stmt, [:erlang.list_to_pid(~c"<0.0.0>")])
** (ArgumentError) unsupported type: #PID<0.0.0>
"""
@spec bind(
statement,
[bind_value] | %{optional(String.t()) => bind_value} | nil
) :: :ok
def bind(stmt, nil), do: bind(stmt, [])
def bind(stmt, args) when is_list(args) do
case bind_parameter_count(stmt) do
{:error, reason} ->
{:error, reason}
params_count ->
args_count = length(args)
if args_count == params_count do
bind_all(args, stmt, 1)
else
raise ArgumentError, "expected #{params_count} arguments, got #{args_count}"
end
end
end
def bind(stmt, args) when is_map(args) do
case bind_parameter_count(stmt) do
{:error, reason} ->
{:error, reason}
params_count ->
args_count = map_size(args)
if args_count == params_count do
bind_all_named(Map.to_list(args), stmt)
else
raise ArgumentError,
"expected #{params_count} named arguments, got #{args_count}: #{inspect(Map.keys(args))}"
end
end
end
defp bind_all([param | params], stmt, idx) do
do_bind(stmt, idx, param)
bind_all(params, stmt, idx + 1)
end
defp bind_all([], _stmt, _idx), do: :ok
defp bind_all_named([{name, param} | named_params], stmt) do
idx = Sqlite3NIF.bind_parameter_index(stmt, to_string(name))
if idx == 0 do
raise ArgumentError, "unknown named parameter: #{inspect(name)}"
end
do_bind(stmt, idx, param)
bind_all_named(named_params, stmt)
end
defp bind_all_named([], _stmt), do: :ok
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
defp do_bind(stmt, idx, param) do
case convert(param) do
i when is_integer(i) -> bind_integer(stmt, idx, i)
f when is_float(f) -> bind_float(stmt, idx, f)
b when is_binary(b) -> bind_text(stmt, idx, b)
b when is_list(b) -> bind_text(stmt, idx, IO.iodata_to_binary(b))
nil -> bind_null(stmt, idx)
:undefined -> bind_null(stmt, idx)
a when is_atom(a) -> bind_text(stmt, idx, Atom.to_string(a))
{:blob, b} when is_binary(b) -> bind_blob(stmt, idx, b)
{:blob, b} when is_list(b) -> bind_blob(stmt, idx, IO.iodata_to_binary(b))
_other -> raise ArgumentError, "unsupported type: #{inspect(param)}"
end
end
@spec columns(db(), statement()) :: {:ok, [binary()]} | {:error, reason()}
def columns(conn, statement), do: Sqlite3NIF.columns(conn, statement)
@spec step(db(), statement()) :: :done | :busy | {:row, row()} | {:error, reason()}
def step(conn, statement), do: Sqlite3NIF.step(conn, statement)
@spec multi_step(db(), statement()) ::
:busy | {:rows, [row()]} | {:done, [row()]} | {:error, reason()}
def multi_step(conn, statement) do
chunk_size = Application.get_env(:exqlite, :default_chunk_size, 50)
multi_step(conn, statement, chunk_size)
end
@spec multi_step(db(), statement(), integer()) ::
:busy | {:rows, [row()]} | {:done, [row()]} | {:error, reason()}
def multi_step(conn, statement, chunk_size) do
case Sqlite3NIF.multi_step(conn, statement, chunk_size) do
:busy ->
:busy
{:error, reason} ->
{:error, reason}
{:rows, rows} ->
{:rows, Enum.reverse(rows)}
{:done, rows} ->
{:done, Enum.reverse(rows)}
end
end
@spec last_insert_rowid(db()) :: {:ok, integer()}
def last_insert_rowid(conn), do: Sqlite3NIF.last_insert_rowid(conn)
@spec transaction_status(db()) :: {:ok, :idle | :transaction}
def transaction_status(conn), do: Sqlite3NIF.transaction_status(conn)
@doc """
Causes the database connection to free as much memory as it can. This is
useful if you are on a memory restricted system.
"""
@spec shrink_memory(db()) :: :ok | {:error, reason()}
def shrink_memory(conn) do
Sqlite3NIF.execute(conn, "PRAGMA shrink_memory")
end
@spec fetch_all(db(), statement(), integer()) :: {:ok, [row()]} | {:error, reason()}
def fetch_all(conn, statement, chunk_size) do
{:ok, try_fetch_all(conn, statement, chunk_size)}
catch
:throw, {:error, _reason} = error -> error
end
defp try_fetch_all(conn, statement, chunk_size) do
case multi_step(conn, statement, chunk_size) do
{:done, rows} -> rows
{:rows, rows} -> rows ++ try_fetch_all(conn, statement, chunk_size)
{:error, _reason} = error -> throw(error)
:busy -> throw({:error, "Database busy"})
end
end
@spec fetch_all(db(), statement()) :: {:ok, [row()]} | {:error, reason()}
def fetch_all(conn, statement) do
# Should this be done in the NIF? It can be _much_ faster to build a list
# there, but at the expense that it could block other dirty nifs from
# getting work done.
#
# For now this just works
chunk_size = Application.get_env(:exqlite, :default_chunk_size, 50)
fetch_all(conn, statement, chunk_size)
end
@doc """
Serialize the contents of the database to a binary.
"""
@spec serialize(db(), String.t()) :: {:ok, binary()} | {:error, reason()}
def serialize(conn, database \\ "main") do
Sqlite3NIF.serialize(conn, database)
end
@doc """
Disconnect from database and then reopen as an in-memory database based on
the serialized binary.
"""
@spec deserialize(db(), String.t(), binary()) :: :ok | {:error, reason()}
def deserialize(conn, database \\ "main", serialized) do
Sqlite3NIF.deserialize(conn, database, serialized)
end
def release(_conn, nil), do: :ok
@doc """
Once finished with the prepared statement, call this to release the underlying
resources.
This should be called whenever you are done operating with the prepared statement. If
the system has a high load the garbage collector may not clean up the prepared
statements in a timely manner and causing higher than normal levels of memory
pressure.
If you are operating on limited memory capacity systems, definitely call this.
"""
@spec release(db(), statement()) :: :ok | {:error, reason()}
def release(conn, statement) do
Sqlite3NIF.release(conn, statement)
end
@doc """
Allow loading native extensions.
"""
@spec enable_load_extension(db(), boolean()) :: :ok | {:error, reason()}
def enable_load_extension(conn, flag) do
if flag do
Sqlite3NIF.enable_load_extension(conn, 1)
else
Sqlite3NIF.enable_load_extension(conn, 0)
end
end
@doc """
Send data change notifications to a process.
Each time an insert, update, or delete is performed on the connection provided
as the first argument, a message will be sent to the pid provided as the second argument.
The message is of the form: `{action, db_name, table, row_id}`, where:
* `action` is one of `:insert`, `:update` or `:delete`
* `db_name` is a string representing the database name where the change took place
* `table` is a string representing the table name where the change took place
* `row_id` is an integer representing the unique row id assigned by SQLite
## Restrictions
* There are some conditions where the update hook will not be invoked by SQLite.
See the documentation for [more details](https://www.sqlite.org/c3ref/update_hook.html)
* Only one pid can listen to the changes on a given database connection at a time.
If this function is called multiple times for the same connection, only the last pid will
receive the notifications
* Updates only happen for the connection that is opened. For example, there
are two connections A and B. When an update happens on connection B, the
hook set for connection A will not receive the update, but the hook for
connection B will receive the update.
"""
@spec set_update_hook(db(), pid()) :: :ok | {:error, reason()}
def set_update_hook(conn, pid) do
Sqlite3NIF.set_update_hook(conn, pid)
end
@doc """
Set an authorizer that denies specific SQL operations.
Accepts a list of action atoms to deny. Any SQL statement that triggers a
denied action will fail with a "not authorized" error during preparation.
Pass an empty list to clear the authorizer.
## Action atoms
`:attach`, `:detach`, `:pragma`, `:insert`, `:update`, `:delete`,
`:create_table`, `:drop_table`, `:create_index`, `:drop_index`,
`:create_trigger`, `:drop_trigger`, `:create_view`, `:drop_view`,
`:alter_table`, `:reindex`, `:analyze`, `:function`, `:savepoint`,
`:transaction`, `:read`, `:select`, `:recursive`,
`:create_temp_table`, `:create_temp_index`, `:create_temp_trigger`,
`:create_temp_view`, `:drop_temp_table`, `:drop_temp_index`,
`:drop_temp_trigger`, `:drop_temp_view`, `:create_vtable`, `:drop_vtable`
## Examples
# Block ATTACH and DETACH (prevent cross-database reads)
:ok = Sqlite3.set_authorizer(conn, [:attach, :detach])
# Clear the authorizer
:ok = Sqlite3.set_authorizer(conn, [])
"""
@spec set_authorizer(db(), [atom()]) :: :ok | {:error, reason()}
def set_authorizer(conn, deny_list) when is_list(deny_list) do
Sqlite3NIF.set_authorizer(conn, deny_list)
end
@doc """
Send log messages to a process.
Each time a message is logged in SQLite a message will be sent to the pid provided as the argument.
The message is of the form: `{:log, rc, message}`, where:
* `rc` is an integer [result code](https://www.sqlite.org/rescode.html) or an [extended result code](https://www.sqlite.org/rescode.html#extrc)
* `message` is a string representing the log message
See [`SQLITE_CONFIG_LOG`](https://www.sqlite.org/c3ref/c_config_covering_index_scan.html) and
["The Error And Warning Log"](https://www.sqlite.org/errlog.html) for more details.
## Restrictions
* Only one pid can listen to the log messages at a time.
If this function is called multiple times, only the last pid will
receive the notifications
"""
@spec set_log_hook(pid()) :: :ok | {:error, reason()}
def set_log_hook(pid) do
Sqlite3NIF.set_log_hook(pid)
end
@sqlite_ok 0
@doc """
Binds a text value to a prepared statement.
iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind_text(stmt, 1, "Alice")
:ok
"""
@spec bind_text(statement, non_neg_integer, String.t()) :: :ok
def bind_text(stmt, index, text) do
case Sqlite3NIF.bind_text(stmt, index, text) do
@sqlite_ok -> :ok
rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc)
end
end
@doc """
Binds a blob value to a prepared statement.
iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind_blob(stmt, 1, <<0, 0, 0>>)
:ok
"""
@spec bind_blob(statement, non_neg_integer, binary) :: :ok
def bind_blob(stmt, index, blob) do
case Sqlite3NIF.bind_blob(stmt, index, blob) do
@sqlite_ok -> :ok
rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc)
end
end
@doc """
Binds an integer value to a prepared statement.
iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind_integer(stmt, 1, 42)
:ok
"""
@spec bind_integer(statement, non_neg_integer, integer) :: :ok
def bind_integer(stmt, index, integer) do
case Sqlite3NIF.bind_integer(stmt, index, integer) do
@sqlite_ok -> :ok
rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc)
end
end
@doc """
Binds a float value to a prepared statement.
iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind_float(stmt, 1, 3.14)
:ok
"""
@spec bind_float(statement, non_neg_integer, float) :: :ok
def bind_float(stmt, index, float) do
case Sqlite3NIF.bind_float(stmt, index, float) do
@sqlite_ok -> :ok
rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc)
end
end
@doc """
Binds a null value to a prepared statement.
iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind_null(stmt, 1)
:ok
"""
@spec bind_null(statement, non_neg_integer) :: :ok
def bind_null(stmt, index) do
case Sqlite3NIF.bind_null(stmt, index) do
@sqlite_ok -> :ok
rc -> raise Exqlite.Error, message: errmsg(stmt) || errstr(rc)
end
end
defp errmsg(stmt), do: Sqlite3NIF.errmsg(stmt)
defp errstr(rc), do: Sqlite3NIF.errstr(rc)
defp convert(%Date{} = val), do: Date.to_iso8601(val)
defp convert(%Time{} = val), do: Time.to_iso8601(val)
defp convert(%NaiveDateTime{} = val), do: NaiveDateTime.to_iso8601(val)
defp convert(%DateTime{time_zone: "Etc/UTC"} = val), do: NaiveDateTime.to_iso8601(val)
defp convert(%DateTime{} = datetime) do
raise ArgumentError, "#{inspect(datetime)} is not in UTC"
end
defp convert(val) do
convert_with_type_extensions(type_extensions(), val)
end
defp convert_with_type_extensions(nil, val), do: val
defp convert_with_type_extensions([], val), do: val
defp convert_with_type_extensions([extension | other_extensions], val) do
case extension.convert(val) do
nil ->
convert_with_type_extensions(other_extensions, val)
{:ok, converted} ->
converted
{:error, reason} ->
raise ArgumentError,
"Failed conversion by TypeExtension #{extension}: #{inspect(val)}. Reason: #{inspect(reason)}."
end
end
defp type_extensions do
Application.get_env(:exqlite, :type_extensions)
end
end

View File

@@ -0,0 +1,106 @@
defmodule Exqlite.Sqlite3NIF do
@moduledoc """
This is the module where all of the NIF entry points reside. Calling this directly
should be avoided unless you are aware of what you are doing.
"""
@compile {:autoload, false}
@on_load {:load_nif, 0}
@type db() :: reference()
@type statement() :: reference()
@type reason() :: :atom | String.Chars.t()
@type row() :: list()
def load_nif() do
path = :filename.join(:code.priv_dir(:exqlite), ~c"sqlite3_nif")
:erlang.load_nif(path, 0)
end
@spec open(String.t(), integer()) :: {:ok, db()} | {:error, reason()}
def open(_path, _flags), do: :erlang.nif_error(:not_loaded)
@spec close(db()) :: :ok | {:error, reason()}
def close(_conn), do: :erlang.nif_error(:not_loaded)
@spec interrupt(db()) :: :ok | {:error, reason()}
def interrupt(_conn), do: :erlang.nif_error(:not_loaded)
@spec execute(db(), String.t()) :: :ok | {:error, reason()}
def execute(_conn, _sql), do: :erlang.nif_error(:not_loaded)
@spec changes(db()) :: {:ok, integer()} | {:error, reason()}
def changes(_conn), do: :erlang.nif_error(:not_loaded)
@spec prepare(db(), String.t()) :: {:ok, statement()} | {:error, reason()}
def prepare(_conn, _sql), do: :erlang.nif_error(:not_loaded)
@spec step(db(), statement()) :: :done | :busy | {:row, row()} | {:error, reason()}
def step(_conn, _statement), do: :erlang.nif_error(:not_loaded)
@spec multi_step(db(), statement(), integer()) ::
:busy | {:rows, [row()]} | {:done, [row()]} | {:error, reason()}
def multi_step(_conn, _statement, _chunk_size), do: :erlang.nif_error(:not_loaded)
@spec columns(db(), statement()) :: {:ok, list(binary())} | {:error, reason()}
def columns(_conn, _statement), do: :erlang.nif_error(:not_loaded)
@spec last_insert_rowid(db()) :: {:ok, integer()}
def last_insert_rowid(_conn), do: :erlang.nif_error(:not_loaded)
@spec transaction_status(db()) :: {:ok, :idle | :transaction}
def transaction_status(_conn), do: :erlang.nif_error(:not_loaded)
@spec serialize(db(), String.t()) :: {:ok, binary()} | {:error, reason()}
def serialize(_conn, _database), do: :erlang.nif_error(:not_loaded)
@spec deserialize(db(), String.t(), binary()) :: :ok | {:error, reason()}
def deserialize(_conn, _database, _serialized), do: :erlang.nif_error(:not_loaded)
@spec release(db(), statement()) :: :ok | {:error, reason()}
def release(_conn, _statement), do: :erlang.nif_error(:not_loaded)
@spec enable_load_extension(db(), integer()) :: :ok | {:error, reason()}
def enable_load_extension(_conn, _flag), do: :erlang.nif_error(:not_loaded)
@spec set_update_hook(db(), pid()) :: :ok | {:error, reason()}
def set_update_hook(_conn, _pid), do: :erlang.nif_error(:not_loaded)
@spec set_authorizer(db(), [atom()]) :: :ok | {:error, reason()}
def set_authorizer(_conn, _deny_list), do: :erlang.nif_error(:not_loaded)
@spec set_log_hook(pid()) :: :ok | {:error, reason()}
def set_log_hook(_pid), do: :erlang.nif_error(:not_loaded)
@spec bind_parameter_count(statement) :: integer
def bind_parameter_count(_stmt), do: :erlang.nif_error(:not_loaded)
@spec bind_parameter_index(statement, String.t()) :: integer
def bind_parameter_index(_stmt, _name), do: :erlang.nif_error(:not_loaded)
@spec bind_text(statement, non_neg_integer, String.t()) :: integer()
def bind_text(_stmt, _index, _text), do: :erlang.nif_error(:not_loaded)
@spec bind_blob(statement, non_neg_integer, binary) :: integer()
def bind_blob(_stmt, _index, _blob), do: :erlang.nif_error(:not_loaded)
@spec bind_integer(statement, non_neg_integer, integer) :: integer()
def bind_integer(_stmt, _index, _integer), do: :erlang.nif_error(:not_loaded)
@spec bind_float(statement, non_neg_integer, float) :: integer()
def bind_float(_stmt, _index, _float), do: :erlang.nif_error(:not_loaded)
@spec bind_null(statement, non_neg_integer) :: integer()
def bind_null(_stmt, _index), do: :erlang.nif_error(:not_loaded)
@spec reset(statement) :: :ok
def reset(_stmt), do: :erlang.nif_error(:not_loaded)
@spec errmsg(db | statement) :: String.t() | nil
def errmsg(_db_or_stmt), do: :erlang.nif_error(:not_loaded)
@spec errstr(integer) :: String.t()
def errstr(_rc), do: :erlang.nif_error(:not_loaded)
# add statement inspection tooling https://sqlite.org/c3ref/expanded_sql.html
end

View File

@@ -0,0 +1,42 @@
defmodule Exqlite.Stream do
@moduledoc false
defstruct [:conn, :query, :params, :options]
@type t :: %Exqlite.Stream{}
defimpl Enumerable do
def reduce(%Exqlite.Stream{query: %Exqlite.Query{} = query} = stream, acc, fun) do
# Possibly need to pass a chunk size option along so that we can let
# the NIF chunk it.
%Exqlite.Stream{conn: conn, params: params, options: opts} = stream
stream = %DBConnection.Stream{
conn: conn,
query: query,
params: params,
opts: opts
}
DBConnection.reduce(stream, acc, fun)
end
def reduce(%Exqlite.Stream{query: statement} = stream, acc, fun) do
%Exqlite.Stream{conn: conn, params: params, options: opts} = stream
query = %Exqlite.Query{name: "", statement: statement}
stream = %DBConnection.PrepareStream{
conn: conn,
query: query,
params: params,
opts: opts
}
DBConnection.reduce(stream, acc, fun)
end
def member?(_, _), do: {:error, __MODULE__}
def count(_), do: {:error, __MODULE__}
def slice(_), do: {:error, __MODULE__}
end
end

View File

@@ -0,0 +1,14 @@
defmodule Exqlite.TypeExtension do
@moduledoc """
A behaviour that defines the API for extensions providing custom data loaders and dumpers
for Ecto schemas.
"""
@doc """
Takes a value and convers it to data suitable for storage in the database.
Returns a tagged :ok/:error tuple. If the value is not convertable by this
extension, returns nil.
"""
@callback convert(value :: term) :: {:ok, term} | {:error, reason :: term} | nil
end

View File

@@ -0,0 +1,156 @@
defmodule Exqlite.MixProject do
use Mix.Project
@version "0.36.0"
def project do
[
app: :exqlite,
version: @version,
elixir: "~> 1.14",
compilers: [:elixir_make] ++ Mix.compilers(),
make_targets: ["all"],
make_clean: ["clean"],
make_force_build: Application.get_env(:exqlite, :force_build, false),
make_precompiler: make_precompiler(),
make_precompiler_url:
"https://github.com/elixir-sqlite/exqlite/releases/download/v#{@version}/@{artefact_filename}",
make_precompiler_filename: "sqlite3_nif",
make_precompiler_nif_versions: make_precompiler_nif_versions(),
make_env: Application.get_env(:exqlite, :make_env, %{}),
cc_precompiler: cc_precompiler(),
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps(),
package: package(),
description: description(),
test_paths: test_paths(System.get_env("EXQLITE_INTEGRATION")),
elixirc_paths: elixirc_paths(Mix.env()),
dialyzer: dialyzer(),
# Docs
name: "Exqlite",
source_url: "https://github.com/elixir-sqlite/exqlite",
homepage_url: "https://github.com/elixir-sqlite/exqlite",
docs: docs()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:db_connection, "~> 2.1"},
{:ex_sqlean, "~> 0.8.5", only: [:dev, :test]},
{:elixir_make, "~> 0.8", runtime: false},
{:cc_precompiler, "~> 0.1", runtime: false},
{:ex_doc, "~> 0.27", only: :dev, runtime: false},
{:temp, "~> 0.4", only: [:dev, :test]},
{:credo, "~> 1.6", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
{:table, "~> 0.1.0", optional: true}
]
end
defp aliases do
[
lint: ["format --check-formatted", "credo --all", "dialyzer"]
]
end
defp description do
"An Elixir SQLite3 library"
end
defp make_precompiler do
if System.get_env("EXQLITE_USE_SYSTEM") != nil do
nil
else
{:nif, CCPrecompiler}
end
end
defp package do
[
files: ~w(
lib
.formatter.exs
mix.exs
README.md
LICENSE
.clang-format
c_src
Makefile*
checksum.exs
),
name: "exqlite",
licenses: ["MIT"],
links: %{
"GitHub" => "https://github.com/elixir-sqlite/exqlite",
"Changelog" => "https://github.com/elixir-sqlite/exqlite/blob/main/CHANGELOG.md"
}
]
end
defp docs do
[
main: "readme",
extras: docs_extras(),
source_ref: "v#{@version}",
source_url: "https://github.com/elixir-sqlite/exqlite"
]
end
defp docs_extras do
[
"README.md": [title: "Readme"],
"guides/windows.md": [],
"CHANGELOG.md": []
]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp test_paths(nil), do: ["test"]
defp test_paths(_any), do: ["integration_test/exqlite"]
defp dialyzer do
[
plt_add_deps: :apps_direct,
plt_add_apps: ~w(table)a
]
end
def make_precompiler_nif_versions do
[
versions: ["2.16", "2.17"]
]
end
defp cc_precompiler do
[
cleanup: "clean",
compilers: %{
{:unix, :linux} => %{
:include_default_ones => true,
"x86_64-linux-musl" => "x86_64-linux-musl-",
"aarch64-linux-musl" => "aarch64-linux-musl-",
"riscv64-linux-musl" => "riscv64-linux-musl-"
},
{:unix, :darwin} => %{
:include_default_ones => true
},
{:win32, :nt} => %{
:include_default_ones => true
}
}
]
end
end