1/* Copyright (C) 2025 Boris Vassilev <boris.vassilev@gmail.com>
    2
    3Permission is hereby granted, free of charge, to any person
    4obtaining a copy of this software and associated documentation
    5files (the "Software"), to deal in the Software without
    6restriction, including without limitation the rights to use,
    7copy, modify, merge, publish, distribute, sublicense, and/or sell
    8copies of the Software, and to permit persons to whom the
    9Software is furnished to do so, subject to the following
   10conditions:
   11
   12The above copyright notice and this permission notice shall be
   13included in all copies or substantial portions of the Software.
   14
   15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   16EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
   17OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   18NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
   19HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
   20WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   21FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
   22OTHER DEALINGS IN THE SOFTWARE.
   23*/
   24:- module(sqlite, [
   25            sql_command/2,
   26            sql_query/3,
   27            sql_query_all/3,
   28            sqlite_initialize/0,
   29            sqlite_shutdown/0,
   30            sqlite_version/1,
   31            sqlite_schema/2,
   32            sqlite_open/3,
   33            sqlite_close/1,
   34            sqlite_prepare/3,
   35            sqlite_prepare/4,
   36            sqlite_bind/2,
   37            sqlite_reset/1,
   38            sqlite_sql/2,
   39            sqlite_expanded_sql/2,
   40            sqlite_column_names/2,
   41            sqlite_finalize/1,
   42            sqlite_do/1,
   43            sqlite_one/2,
   44            sqlite_many/4,
   45            sqlite_row/2,
   46            sqlite_status/4,
   47            sqlite_db_status/5,
   48            sqlite_stmt_status/4 ] ).

Prolog bindings for SQLite

This module provides partial access to the C-language interface of SQLite.

It exposes the database connection object sqlite3 and the prepared statement object sqlite3_stmt, along with some of the essential functions using these objects. Please refer to the SQLite documentation and the implementation in c/swiplite.c when using this library. To make it easier to find the relevant docs, I have tried to consistently provide links.

Most of the predicates in this module are as close as possible in naming and semantics to the corresponding functions in the C interface. One exception is sqlite_bind/2, which converts values from Prolog terms to corresponding SQLite column datatype. Similarly, sqlite_do/1, sqlite_one/2, and sqlite_many/4 wrap the necessary calls to sqlite3_step() and convert the results of SELECT queries to Prolog terms.

The database connection and prepared statement objects are represented in SWI-Prolog as blobs. They are garbage collected, but finalizing a statement or closing a database connection (and, alternatively, not doing it) have reprecussions, especially for long-running programs. The code in this library uses exclusively the *_v2 versions of the SQLite C interface. In particular:

The sqlite3_close_v2() interface is intended for use with host languages that are garbage collected, and where the order in which destructors are called is arbitrary.
   88:- use_foreign_library(foreign(swiplite)).   89:- use_module(library(dcg/basics)).   90
   91:- multifile prolog:error_message//1.   92
   93prolog:error_message(sqlite_error(Caller, Code, Str, Message)) -->
   94    [ '[~s] (~d) ~s - ~s'-[Caller, Code, Str, Message] ].
   95prolog:error_message(swiplite_error(Caller, Message)) -->
   96    [ '[~s] ~s'-[Caller, Message] ].
 sqlite_version(-Version:atom) is det
Unify Version with the version of SQLite currently in use
  102sqlite_version(V) :-
  103    setup_call_cleanup(sqlite_open('', DB, [memory(true)]),
  104        sql_query_all(DB, "select sqlite_version()", [row(V0)]),
  105        sqlite_close(DB)),
  106    atom_string(V, V0).
 sql_command(++Connection:blob, ++SQL:text) is det
Execute the command in SQL.

This assumes that the command is not a SELECT query and it does not return any rows.

Arguments:
Connection- A database connection obtained with sqlite_open/3
SQL- The text of the command to be executed
To be done
- Allow the user to supply a generator for bind values
  120sql_command(DB, SQL) :-
  121    setup_call_cleanup(sqlite_prepare(DB, SQL, S, [bind_parameter_count(0)]),
  122        sqlite_do(S),
  123        sqlite_finalize(S)).
  124
  125/*
  126sql_command(DB, SQL, Bind_vars, Generator) :-
  127    setup_call_cleanup(sqlite_prepare(DB, SQL, S),
  128        sql_transaction(DB,
  129            forall(call(Generator, Bind_vars),
  130                (   sqlite_bind(S, Bind_vars),
  131                    sqlite_do(S)
  132                ))),
  133        sqlite_finalize(S)).
  134*/
 sql_query(++Connection:blob, ++SQL:text, -Row:row) is semidet
Get rows from the result set of the statement in SQL on backtracking.

If the result set is empty, fail.

Arguments:
Connection- A database connection obtained with sqlite_open/3
SQL- The text of the command to be executed
Row- Unified with row(Col1, Col2, ...) on backtracking
  146sql_query(DB, SQL, Row) :-
  147    setup_call_cleanup(sqlite_prepare(DB, SQL, S, [bind_parameter_count(0)]),
  148        sqlite_row(S, Row),
  149        sqlite_finalize(S)).
 sql_query_all(++Connection:blob, ++SQL:text, -Rows:list(row)) is semidet
Get all rows from the result set of the statement in SQL as a list in Rows.
Arguments:
Connection- A database connection obtained with sqlite_open/3
SQL- The text of the command to be executed
Rows- The list of rows with all rows in the result set
  159sql_query_all(DB, SQL, Rs) :-
  160    setup_call_cleanup(sqlite_prepare(DB, SQL, S, [bind_parameter_count(0)]),
  161        sqlite_many(S, _, Rs, []),
  162        sqlite_finalize(S)).
 sqlite_schema(++Connection:blob, -Schema:text) is det
Query the DB schema for that connection.

This is a convenience predicate returning the sql column of the table sqlite_schema.

Arguments:
Connection- A database connection obtained with sqlite_open/3
Schema- A list of objects in the current schema
See also
- The Schema Table
- PRAGMA table_list
- PRAGMA table_info
- PRAGMA table_xinfo
  179sqlite_schema(Connection, Schema) :-
  180    setup_call_cleanup(sqlite_prepare(Connection,
  181            "Select sql from sqlite_schema where sql is not null", S),
  182        sqlite_many(S, _, Schema, []),
  183        sqlite_finalize(S)).
  184
  185
  186:- predicate_options(sqlite_open/3, 3,
  187        [ mode(oneof([read,write,create])),
  188          memory(boolean),
  189          threaded(oneof([single,multi,serialized]))
  190        ]).
 sqlite_open(++File:text, -Connection:blob, ++Options:list) is det
Open Connection to the database in File using Options

The options are used to set the flags argument in the call to sqlite3_open_v2(). The following options are recognized:

mode Mode
Determines how the database is opened:
ValueCorresponding flags
read (default)SQLITE_OPEN_READONLY
writeSQLITE_OPEN_READWRITE
createSQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
memory(Bool)
Open as an in-memory database:
ValueCorresponding flags
false (default)(empty)
trueSQLITE_OPEN_MEMORY
threaded(Threaded)
Threading mode for this database connection:
ValueCorresponding flags
single (default)(empty)
multiSQLITE_OPEN_NOMUTEX
serialized=SQLITE_OPEN_FULLMUTEX
foreign_keys(Bool)
Enable foreign keys for this connection. Defaults to true and will enable foreign keys for this connection and throw an error if foreign keys are not supported. Setting this option to false is required if the used version of SQLite does not support foreign keys.
ValueBehaviour
true (default)Enable foreign keys
falseDo not check for foreign keys
Arguments:
File- Relative path to the database file. Interpreted as UTF-8 string.
Connection- A blob with the database connection.
Options- A list of options
See also
- sqlite3_open_v2()
- Using SQLite in multi-threaded applications
To be done
- Support all available SQLITE_OPEN_* flags.
 sqlite_close(++Connection:blob) is det
Close a Connection opened with sqlite_open/3
Arguments:
Connection- A database connection obtained with sqlite_open/3
See also
- sqlite3_close_v2()
 sqlite_prepare(++Connection:blob, ++SQL:text, -Statement:blob, ++Options) is det
Compile Statement from the text in SQL using the database in Connection, using the provided options

The UTF-8 encoded text in SQL is parsed up to the first nul, or up to the end of the first SQL statement. SQL parameters are initially all set to NULL. Anonymous variables are not allowed. If ?NNN parameters are used, they must be numbered starting from 1, without any gaps.

Two options are supported. If the option bind_parameter_count(Value) is provided, Value is unified with the number of bind variables in the prepared statement.

If the option rest(SQL_rest) is provided, the trailing content of SQL is unified with SQL_rest as a string.

Arguments:
Connection- A database connection obtained with sqlite_open/3
SQL- The UTF8-encoded text of the SQL as text
See also
- sqlite_bind/2
- SQL statement parameters in SQLite
- sqlite3_bind_parameter_count()
  278sqlite_prepare(Connection, SQL, Statement) :-
  279    sqlite_prepare(Connection, SQL, Statement, []).
 sqlite_prepare(++Connection:blob, ++SQL:text, -Statement:blob) is det
Compile Statement from the text in SQL using the database in Connection; same as sqlite_prepare/4 with empty options list
Arguments:
Connection- A database connection obtained with sqlite_open/3
SQL- The UTF8-encoded text of the SQL as text
See also
- sqlite_prepare/4
  293/* sqlite_prepare(
  294            ++Connection:blob, ++SQL:text,
  295            -Statement:blob, -N_bind:integer, -Rest:string) is det
  296
  297Compile Statement from the text in SQL using the database in Connection
  298
  299The UTF-8 encoded text in SQL is parsed up to the first nul, or
  300up to the end of the first SQL statement. SQL parameters are
  301initially all set to =|NULL|=. Anonymous variables are not allowed.
  302If *|?|*_|NNN|_ parameters are used, they must be numbered
  303starting from 1, without any gaps.
  304
  305@arg Connection A database connection obtained with sqlite_open/3
  306@arg SQL The UTF8-encoded text of the SQL as an atom, string,
  307         or list of codes
  308@arg N_bind The number of bind parameters in the statement
  309@arg Rest A UTF8-encoded string containing the trailing content of SQL
  310
  311@see sqlite_bind/2
  312@see [SQL statement parameters in SQLite](https://www.sqlite.org/lang_expr.html#varparam)
  313@see [`sqlite3_bind_parameter_count()`](https://www.sqlite.org/c3ref/bind_parameter_count.html)
  314*/
 sqlite_finalize(++Statement:blob) is det
Delete a prepared statement
Arguments:
Connection- A database connection obtained with sqlite_open/3
See also
- sqlite3_finalize()
 sqlite_bind(++Statement:blob, ++Bind_values:bv) is det
Use Bind_values to set the variables in Statement

The term in Bind_values must be named "bv" (bind values). Use an empty list [] to set a variable to NULL.

?- sqlite_prepare(DB, "Select ?1, ?2", S),
   sqlite_bind(S, bv('a', [])),
   sqlite_expanded_sql(S, E).
E = "Select 'a', NULL".

Each term in the Bind_values argument is used to set the variable with the same index in the SQL statement; both start counting at 1.

In addition to using the empty list to represent SQL NULL:

Arguments:
Statement- A statement compiled with sqlite_prepare/3
Bind_values- A flat term with functor bv/<number of parameters>
Statement- A blob with the compiled statement
See also
- sqlite_prepare/3
- sqlite_sql/2
- sqlite_expanded_sql/2
- SQL statement parameters in SQLite
To be done
- Support more types
 sqlite_reset(++Statement:blob) is det
Reset Statement
Arguments:
Statement- A statement compiled with sqlite_prepare/3
See also
- sqlite3_reset()
 sqlite_sql(++Statement:blob, -SQL:atom) is det
Unify SQL with the UTF-8 text used to create the prepared statement
Arguments:
Statement- A statement compiled with sqlite_prepare/3
SQL- An atom with the original text of the statement
See also
- sqlite_prepare/3
- sqlite_bind/2
- sqlite3_sql()
 sqlite_expanded_sql(++Statement:blob, -Expanded_SQL:string) is det
Retrieve the SQL statement with bind parameters expanded
Arguments:
Statement- A statement compiled with sqlite_prepare/3
Expanded_SQL- A string with the expanded statement
See also
- sqlite_prepare/3
- sqlite_bind/2
- sqlite3_expanded_sql()
 sqlite_column_names(++Statement:blob, -Column_names:cols) is det
Retrieve the column names of a SELECT statement

For a SELECT statement, the result is a flat term cols(column_1, column_2, ...).

If the prepared statement does not have a result set with columns in it, Column_names is unified with cols().

Arguments:
Statement- A statement compiled with sqlite_prepare/3
Column_names- A flat term with functor cols/<number of columns>
See also
- sqlite3_column_name()
- sqlite3_column_count()
 sqlite_do(++Statement:blob) is det
Evaluate a statement that has no results

For example, CREATE or INSERT statements must be evaluated using sqlite_do/1, while a SELECT needs either sqlite_one/2 or sqlite_many/4.

Statement is reset automatically upon success.

Arguments:
Statement- A statement compiled with sqlite_prepare/3
Errors
- swiplite_error When the statement has results
 sqlite_one(++Statement:blob, Result:row) is det
Evaluate a SELECT statement with exactly one row in the result set

Statement is reset automatically upon success.

Arguments:
Statement- A SELECT statement compiled with sqlite_prepare/3
Result- A flat term with functor row/<number of columns>
Errors
- swiplite_error When the statement does not have exactly one result row
 sqlite_many(++Statement:blob, ?N:nonneg, -R:list(row), ?T) is det
Evaluate a statement to collect results in the difference list R-T.

When N is a free variable, fetch all rows of the result set and unify N with the number of rows.

Otherwise, fetch up to N result rows in R.

R and T form a difference list. When there are no more results in the result set, T is unified with the empty list [].

A statement evaluated with sqlite_many/4 must be explictly reset using sqlite_reset/1 after all rows in the result set have been fetched. Until it is reset, consecutive calls will unify N with 0 and both R and T with the empty list [].

Arguments:
Statement- A SELECT statement compiled with sqlite_prepare/3
N- Number of rows
R- Rows of the result set
T- Tail of R
See also
- sqlite3_column_count()
 sqlite_row(++Statement:blob, -Row:row) is semidet
Get rows from the result set of Statement on backtracking.

If the result set is empty, fail.

Arguments:
Statement- A SELECT statement compiled with sqlite_prepare/3
Row- A row in the result set
 sqlite_status(++Op:sqlite_status_code, -Current:integer, -Highwater:integer, +Reset:boolean) is det
Query SQLite runtime status.

The first argument is an atom that corresponds to one of the SQLITE_STATUS_ codes, with the SQLITE_STATUS_ prefix dropped and in lowercase. For example, memory_used or parser_stack.

Arguments:
Op- SQLite run-time status parameter
Current- is the current value of the parameter
Highwater- is the highest recorder value
Reset- When true, the Highwater value is reset after it is returned
See also
- sqlite_db_status/5
- sqlite_stmt_status/4
- SQLite Runtime Status
- Status Parameters
 sqlite_db_status(++DB:sqlite_connection, ++Op:sqlite_db_status_code, -Current:integer, -Highwater:integer, +Reset:boolean) is det
Query SQLite database connection status.

The first argument is a database connection blob. The second argument is an atom that corresponds to one of the SQLITE_DBSTATUS_ codes, with the SQLITE_DBSTATUS_ prefix dropped and in lowercase. For example, cache_used or deferred_fks.

Arguments:
DB- A database connection obtained with sqlite_open/3
Op- SQLite status parameter for database connections
Current- is the current value of the parameter
Highwater- is the highest recorder value
Reset- When true, the Highwater value is reset after it is returned
See also
- sqlite_status/4
- sqlite_stmt_status/4
- Database Connection Status
- Status Parameters for database connections
 sqlite_stmt_status(++DB:sqlite_statement, ++Op:sqlite_stmt_status_code, -Counter:integer, +Reset:boolean) is det
Query SQLite prepared statement status.

The first argument is a prepared statement blob. The second argument is an atom that corresponds to one of the SQLITE_STMTSTATUS_ codes, with the SQLITE_STMTSTATUS_ prefix dropped and in lowercase. For example, sort or reprepare.

Arguments:
DB- A prepared statement obtained with sqlite_prepare/3
Op- SQLite status parameter for prepared statement
Counter- is the current value of the corresponding counter
Reset- When true, the counter is reset after it is returned
See also
- sqlite_status/4
- sqlite_db_status/5
- Prepared Statement Status
- Status Parameters for prepared statements