initial
This commit is contained in:
39
README.md
Normal file
39
README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# sqlite3
|
||||
|
||||
is a simple high-level wrapper around SQLite3. It doesn’t intend to wrap
|
||||
everything, but it tries to be useful.
|
||||
|
||||
## Installation
|
||||
|
||||
```clojure
|
||||
(load "https://veitheller.de/git/carpentry/sqlite3@0.0.1")
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The module `SQLite3` provides facilities for opening, closing, and querying
|
||||
databases.
|
||||
|
||||
```clojure
|
||||
(load "https://veitheller.de/git/carpentry/sqlite3@0.0.1")
|
||||
|
||||
; opening DBs can fail, for the purposes of this example we
|
||||
; ignore that
|
||||
(let-do [db (Result.unsafe-from-success (SQLite3.open "db"))]
|
||||
; we can prepare statements
|
||||
(println* &(SQLite3.query &db "INSERT INTO mytable VALUES (?1, ?2);"
|
||||
&[(to-sqlite3 @"hello") (to-sqlite3 100)]))
|
||||
; and query things
|
||||
(println* &(SQLite3.query &db "SELECT * from mytable;" &[]))
|
||||
(SQLite3.close db)
|
||||
```
|
||||
|
||||
Because `open` and `query` return `Result` types, we could also use
|
||||
combinators!
|
||||
|
||||
For more information, check out [the
|
||||
documentation](https://veitheller.de/sqlite3)!
|
||||
|
||||
<hr/>
|
||||
|
||||
Have fun!
|
166
docs/SQLite3.html
Normal file
166
docs/SQLite3.html
Normal file
@@ -0,0 +1,166 @@
|
||||
<!DOCTYPE HTML>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="logo">
|
||||
<a href="http://github.com/carp-lang/Carp">
|
||||
<img src="logo.png">
|
||||
</a>
|
||||
<div class="title">
|
||||
sqlite3
|
||||
</div>
|
||||
<div class="index">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="SQLite3.html">
|
||||
SQLite3
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<h1>
|
||||
SQLite3
|
||||
</h1>
|
||||
<div class="module-description">
|
||||
<p>is a simple high-level wrapper around SQLite3. It doesn’t intend
|
||||
to wrap everything, but it tries to be useful.</p>
|
||||
<h2>Installation</h2>
|
||||
<pre><code class="language-clojure">(load "https://veitheller.de/git/carpentry/sqlite3@0.0.1")
|
||||
</code></pre>
|
||||
<h2>Usage</h2>
|
||||
<p>The module <code>SQLite3</code> provides facilities for opening, closing, and querying
|
||||
databases.</p>
|
||||
<pre><code class="language-clojure">(load "https://veitheller.de/git/carpentry/sqlite3@0.0.1")
|
||||
|
||||
; opening DBs can fail, for the purposes of this example we
|
||||
; ignore that
|
||||
(let-do [db (Result.unsafe-from-success (SQLite3.open "db"))]
|
||||
; we can prepare statements
|
||||
(println* &(SQLite3.query &db "INSERT INTO mytable VALUES (?1, ?2);"
|
||||
&[(to-sqlite3 @"hello") (to-sqlite3 100)]))
|
||||
; and query things
|
||||
(println* &(SQLite3.query &db "SELECT * from mytable;" &[]))
|
||||
(SQLite3.close db)
|
||||
</code></pre>
|
||||
<p>Because <code>open</code> and <code>query</code> return <code>Result</code> types, we could also use
|
||||
combinators!</p>
|
||||
|
||||
</div>
|
||||
<div class="binder">
|
||||
<a class="anchor" href="#SQLite">
|
||||
<h3 id="SQLite">
|
||||
SQLite
|
||||
</h3>
|
||||
</a>
|
||||
<div class="description">
|
||||
doc-stub
|
||||
</div>
|
||||
<p class="sig">
|
||||
a
|
||||
</p>
|
||||
<span>
|
||||
|
||||
</span>
|
||||
<p class="doc">
|
||||
<p>is the opaque database type. You’ll need one of those to query
|
||||
anything.</p>
|
||||
<p>It can be obtained by using <a href="#open">open</a>.</p>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
<div class="binder">
|
||||
<a class="anchor" href="#Type">
|
||||
<h3 id="Type">
|
||||
Type
|
||||
</h3>
|
||||
</a>
|
||||
<div class="description">
|
||||
module
|
||||
</div>
|
||||
<p class="sig">
|
||||
Module
|
||||
</p>
|
||||
<span>
|
||||
|
||||
</span>
|
||||
<p class="doc">
|
||||
<p>represent all the SQLite types we can represent.</p>
|
||||
<p>The constructors are <code>Null</code>, <code>Integer</code>, <code>Floating</code>, <code>Text</code>, and <code>Blob</code>. Most
|
||||
primitive Carp types can be casted to appropriate SQLite types by using the
|
||||
<code>to-sqlite3</code> interface.</p>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
<div class="binder">
|
||||
<a class="anchor" href="#close">
|
||||
<h3 id="close">
|
||||
close
|
||||
</h3>
|
||||
</a>
|
||||
<div class="description">
|
||||
external
|
||||
</div>
|
||||
<p class="sig">
|
||||
(λ [SQLite] ())
|
||||
</p>
|
||||
<span>
|
||||
|
||||
</span>
|
||||
<p class="doc">
|
||||
<p>closes a database.</p>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
<div class="binder">
|
||||
<a class="anchor" href="#open">
|
||||
<h3 id="open">
|
||||
open
|
||||
</h3>
|
||||
</a>
|
||||
<div class="description">
|
||||
defn
|
||||
</div>
|
||||
<p class="sig">
|
||||
(λ [&String] (Result SQLite String))
|
||||
</p>
|
||||
<pre class="args">
|
||||
(open s)
|
||||
</pre>
|
||||
<p class="doc">
|
||||
<p>opens a database with the filename <code>s</code>.</p>
|
||||
<p>If it fails, we return an error message using <code>Result.Error</code>.</p>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
<div class="binder">
|
||||
<a class="anchor" href="#query">
|
||||
<h3 id="query">
|
||||
query
|
||||
</h3>
|
||||
</a>
|
||||
<div class="description">
|
||||
defn
|
||||
</div>
|
||||
<p class="sig">
|
||||
(λ [(Ref SQLite), &String, (Ref (Array Type))] (Result (Array (Array Type)) String))
|
||||
</p>
|
||||
<pre class="args">
|
||||
(query db s p)
|
||||
</pre>
|
||||
<p class="doc">
|
||||
<p>queries the database <code>db</code> using the query <code>s</code> and the parameters
|
||||
<code>p</code>.</p>
|
||||
<p>If it fails, we return an error message using <code>Result.Error</code>.</p>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
110
docs/style.css
Normal file
110
docs/style.css
Normal file
@@ -0,0 +1,110 @@
|
||||
html {
|
||||
font-family: "Helvetica", sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
font-family: "Hasklig", "Lucida Console", monospace;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
.module-description {
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 3em auto auto auto;
|
||||
width: 80%;
|
||||
max-width: 610px;
|
||||
min-width: 400px
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 1em;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 400;
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0em;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.binder {
|
||||
margin: 0em 0em 3.5em 0em;
|
||||
}
|
||||
|
||||
.sig {
|
||||
font-family: "Hasklig", "Lucida Console", monospace;
|
||||
margin: 0.5em 0em 0.5em 0em;
|
||||
}
|
||||
|
||||
.args {
|
||||
background-color: #eee;
|
||||
display: inline-block;
|
||||
white-space: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #eee;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: 0.3em;
|
||||
font-size: 0.8em;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.huge {
|
||||
font-size: 15em;
|
||||
margin: 0em;
|
||||
}
|
||||
|
||||
/* Smaller screens */
|
||||
@media only screen and (max-width: 600px) {
|
||||
.logo {
|
||||
margin: 1em;
|
||||
text-align: left;
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
.logo img {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 50%;
|
||||
}
|
||||
.content {
|
||||
margin: 0.5em;
|
||||
}
|
||||
.binder {
|
||||
margin: 0em 0em 1.5em 0em;
|
||||
}
|
||||
.sig {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
ul {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
.title, .index { display: none; }
|
19
examples/simple.carp
Normal file
19
examples/simple.carp
Normal file
@@ -0,0 +1,19 @@
|
||||
(load "sqlite3.carp")
|
||||
(use SQLite3)
|
||||
|
||||
(defn main []
|
||||
(let-do [db (Result.unsafe-from-success (SQLite3.open "test.db"))]
|
||||
; we can create a table
|
||||
(ignore
|
||||
(query &db "CREATE TABLE people (id INTEGER PRIMARY KEY AUTOINCREMENT, firstname TEXT, lastname TEXT, age INT);" &[])
|
||||
)
|
||||
; insert into it with a prepared statement
|
||||
(ignore
|
||||
(query &db "INSERT INTO people VALUES(?1, ?2, ?3, ?4);"
|
||||
&[(to-sqlite3 1) (to-sqlite3 @"Veit") (to-sqlite3 @"Heller") (to-sqlite3 26)]))
|
||||
; using the index, we can even insert out of order
|
||||
(ignore
|
||||
(query &db "INSERT INTO people(firstname, lastname, age) VALUES(?3, ?2, ?1);"
|
||||
&[(SQLite3.Type.Null) (to-sqlite3 @"Svedäng") (to-sqlite3 @"Erik")]))
|
||||
(println* &(query &db "SELECT * FROM people;" &[]))
|
||||
(close db)))
|
13
gendocs.carp
Normal file
13
gendocs.carp
Normal file
@@ -0,0 +1,13 @@
|
||||
(load "sqlite3.carp")
|
||||
|
||||
(defndynamic gendocs []
|
||||
(do
|
||||
(Project.config "title" "sqlite3")
|
||||
(Project.config "docs-directory" "./docs/")
|
||||
(Project.config "docs-logo" "")
|
||||
(Project.config "docs-styling" "style.css")
|
||||
(Project.config "docs-generate-index" false)
|
||||
(save-docs SQLite3)))
|
||||
|
||||
(gendocs)
|
||||
(quit)
|
194
sqlite3.carp
Normal file
194
sqlite3.carp
Normal file
@@ -0,0 +1,194 @@
|
||||
(relative-include "sqlite3_helper.h")
|
||||
(add-cflag "-lsqlite3")
|
||||
|
||||
(doc SQLite3 "is a simple high-level wrapper around SQLite3. It doesn’t intend
|
||||
to wrap everything, but it tries to be useful.
|
||||
|
||||
## Installation
|
||||
|
||||
```clojure
|
||||
(load \"https://veitheller.de/git/carpentry/sqlite3@0.0.1\")
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The module `SQLite3` provides facilities for opening, closing, and querying
|
||||
databases.
|
||||
|
||||
```clojure
|
||||
(load \"https://veitheller.de/git/carpentry/sqlite3@0.0.1\")
|
||||
|
||||
; opening DBs can fail, for the purposes of this example we
|
||||
; ignore that
|
||||
(let-do [db (Result.unsafe-from-success (SQLite3.open \"db\"))]
|
||||
; we can prepare statements
|
||||
(println* &(SQLite3.query &db \"INSERT INTO mytable VALUES (?1, ?2);\"
|
||||
&[(to-sqlite3 @\"hello\") (to-sqlite3 100)]))
|
||||
; and query things
|
||||
(println* &(SQLite3.query &db \"SELECT * from mytable;\" &[]))
|
||||
(SQLite3.close db)
|
||||
```
|
||||
|
||||
Because `open` and `query` return `Result` types, we could also use
|
||||
combinators!")
|
||||
(defmodule SQLite3
|
||||
(private sql_ok)
|
||||
(hidden sql_ok)
|
||||
(register sql_ok Int "SQLITE_OK")
|
||||
(private sql_int)
|
||||
(hidden sql_int)
|
||||
(register sql_int Int "SQLITE_INTEGER")
|
||||
(private sql_double)
|
||||
(hidden sql_double)
|
||||
(register sql_double Int "SQLITE_FLOAT")
|
||||
(private sql_text)
|
||||
(hidden sql_text)
|
||||
(register sql_text Int "SQLITE_TEXT")
|
||||
(private sql_blob)
|
||||
(hidden sql_blob)
|
||||
(register sql_blob Int "SQLITE_BLOB")
|
||||
|
||||
(doc SQLite "is the opaque database type. You’ll need one of those to query
|
||||
anything.
|
||||
|
||||
It can be obtained by using [open](#open).")
|
||||
(register-type SQLite)
|
||||
|
||||
(doc Type "represent all the SQLite types we can represent.
|
||||
|
||||
The constructors are `Null`, `Integer`, `Floating`, `Text`, and `Blob`. Most
|
||||
primitive Carp types can be casted to appropriate SQLite types by using the
|
||||
`to-sqlite3` interface.")
|
||||
(deftype Type
|
||||
(Null [])
|
||||
(Integer [Int])
|
||||
(Floating [Double])
|
||||
(Text [String])
|
||||
(Blob [String])
|
||||
)
|
||||
|
||||
(private SQLiteColumn)
|
||||
(hidden SQLiteColumn)
|
||||
(register-type SQLiteColumn)
|
||||
|
||||
(defmodule Type
|
||||
(defmodule SQLiteColumn
|
||||
(register nil (Fn [] SQLiteColumn) "SQLiteColumn_nil")
|
||||
(register int (Fn [Int] SQLiteColumn) "SQLiteColumn_int")
|
||||
(register float (Fn [Double] SQLiteColumn) "SQLiteColumn_float")
|
||||
(register text (Fn [String] SQLiteColumn) "SQLiteColumn_text")
|
||||
(register blob (Fn [String] SQLiteColumn) "SQLiteColumn_blob")
|
||||
)
|
||||
|
||||
(defn to-sqlite3-internal [x]
|
||||
(match x
|
||||
(Null) (SQLiteColumn.nil)
|
||||
(Integer i) (SQLiteColumn.int i)
|
||||
(Floating f) (SQLiteColumn.float f)
|
||||
(Text s) (SQLiteColumn.text s)
|
||||
(Blob s) (SQLiteColumn.blob s)))
|
||||
)
|
||||
|
||||
(defmodule SQLiteColumn
|
||||
(register tag (Fn [&SQLiteColumn] Int) "SQLiteColumn_tag")
|
||||
|
||||
(register from-integer (Fn [SQLiteColumn] Int) "SQLiteColumn_from_int")
|
||||
(register from-floating (Fn [SQLiteColumn] Double) "SQLiteColumn_from_float")
|
||||
(register from-text (Fn [SQLiteColumn] String) "SQLiteColumn_from_str")
|
||||
|
||||
(defn to-carp [c]
|
||||
(case (tag &c)
|
||||
sql_int (Type.Integer (from-integer c))
|
||||
sql_double (Type.Floating (from-floating c))
|
||||
sql_text (Type.Text (from-text c))
|
||||
sql_blob (Type.Blob (from-text c))
|
||||
(Type.Null))))
|
||||
|
||||
(private SQLiteRow)
|
||||
(hidden SQLiteRow)
|
||||
(register-type SQLiteRow)
|
||||
(defmodule SQLiteRow
|
||||
(register length (Fn [&SQLiteRow] Int) "SQLiteRow_length")
|
||||
(register nth (Fn [&SQLiteRow Int] SQLiteColumn) "SQLiteRow_nth")
|
||||
|
||||
(defn to-carp [r]
|
||||
(let-do [l (length &r)
|
||||
a (Array.allocate l)]
|
||||
(for [i 0 l]
|
||||
(Array.aset-uninitialized! &a i (SQLiteColumn.to-carp (nth &r i))))
|
||||
a))
|
||||
)
|
||||
|
||||
(private SQLiteRes)
|
||||
(hidden SQLiteRes)
|
||||
(register-type SQLiteRes)
|
||||
(defmodule SQLiteRes
|
||||
(register ok? (Fn [&SQLiteRes] Bool) "SQLiteRes_is_ok")
|
||||
(register length (Fn [&SQLiteRes] Int) "SQLiteRes_length")
|
||||
(register nth (Fn [&SQLiteRes Int] SQLiteRow) "SQLiteRes_nth")
|
||||
(register error (Fn [SQLiteRes] (Ptr Char)) "SQLiteRes_error")
|
||||
|
||||
(defn to-array [r]
|
||||
(let-do [l (length &r)
|
||||
a (Array.allocate l)]
|
||||
(for [i 0 l]
|
||||
(Array.aset-uninitialized! &a i (SQLiteRow.to-carp (nth &r i))))
|
||||
a))
|
||||
)
|
||||
|
||||
(private init)
|
||||
(hidden init)
|
||||
(register init (Fn [] SQLite))
|
||||
(private open-)
|
||||
(hidden open-)
|
||||
(register open- (Fn [&SQLite (Ptr Char)] Int) "SQLite3_open_c")
|
||||
(private exec-)
|
||||
(hidden exec-)
|
||||
(register exec- (Fn [&SQLite (Ptr Char) (Array SQLiteColumn)] SQLiteRes) "SQLite3_exec_c")
|
||||
(private error-)
|
||||
(hidden error-)
|
||||
(register error- (Fn [SQLite] (Ptr Char)) "SQLite3_error")
|
||||
|
||||
(doc open "opens a database with the filename `s`.
|
||||
|
||||
If it fails, we return an error message using `Result.Error`.")
|
||||
(defn open [s]
|
||||
(let [db (SQLite3.init)
|
||||
res (open- &db (cstr s))]
|
||||
(if (= res sql_ok)
|
||||
(Result.Success db)
|
||||
(Result.Error (from-cstr (error- db))))))
|
||||
|
||||
(doc query "queries the database `db` using the query `s` and the parameters
|
||||
`p`.
|
||||
|
||||
If it fails, we return an error message using `Result.Error`.")
|
||||
(defn query [db s p]
|
||||
(let [r (exec- db (cstr s) (Array.copy-map &(fn [x] (Type.to-sqlite3-internal @x)) p))]
|
||||
(if (SQLiteRes.ok? &r)
|
||||
(Result.Success (SQLiteRes.to-array r))
|
||||
(Result.Error (from-cstr (SQLiteRes.error r))))))
|
||||
|
||||
(doc close "closes a database.")
|
||||
(register close (Fn [SQLite] ()) "SQLite3_close_c")
|
||||
)
|
||||
|
||||
(definterface to-sqlite3 (Fn [a] SQLIte3.Type))
|
||||
|
||||
(defmodule Bool
|
||||
(defn to-sqlite3 [b] (SQLite3.Type.Integer (if b 1 0))))
|
||||
|
||||
(defmodule Int
|
||||
(defn to-sqlite3 [i] (SQLite3.Type.Integer i)))
|
||||
|
||||
(defmodule Long
|
||||
(defn to-sqlite3 [l] (SQLite3.Type.Integer (to-int (the Long l)))))
|
||||
|
||||
(defmodule Float
|
||||
(defn to-sqlite3 [f] (SQLite3.Type.Floating (Double.from-float f))))
|
||||
|
||||
(defmodule Double
|
||||
(defn to-sqlite3 [d] (SQLite3.Type.Floating d)))
|
||||
|
||||
(defmodule String
|
||||
(defn to-sqlite3 [s] (SQLite3.Type.Text s)))
|
298
sqlite3_helper.h
Normal file
298
sqlite3_helper.h
Normal file
@@ -0,0 +1,298 @@
|
||||
#include "sqlite3.h"
|
||||
|
||||
// --- BEGIN HELPERS ---
|
||||
|
||||
typedef struct {
|
||||
sqlite3* handle;
|
||||
} SQLite;
|
||||
|
||||
typedef struct {
|
||||
int tag;
|
||||
union {
|
||||
int i;
|
||||
double f;
|
||||
char* s;
|
||||
};
|
||||
} SQLiteColumn;
|
||||
|
||||
int SQLiteColumn_tag(SQLiteColumn* col) {
|
||||
return col->tag;
|
||||
}
|
||||
|
||||
int SQLiteColumn_from_int(SQLiteColumn col) {
|
||||
return col.i;
|
||||
}
|
||||
|
||||
double SQLiteColumn_from_float(SQLiteColumn col) {
|
||||
return col.f;
|
||||
}
|
||||
|
||||
char* SQLiteColumn_from_str(SQLiteColumn col) {
|
||||
return col.s;
|
||||
}
|
||||
|
||||
SQLiteColumn SQLiteColumn_nil() {
|
||||
SQLiteColumn res;
|
||||
res.tag = SQLITE_NULL;
|
||||
return res;
|
||||
}
|
||||
|
||||
SQLiteColumn SQLiteColumn_int(int i) {
|
||||
SQLiteColumn res;
|
||||
res.tag = SQLITE_INTEGER;
|
||||
res.i = i;
|
||||
return res;
|
||||
}
|
||||
|
||||
SQLiteColumn SQLiteColumn_float(double f) {
|
||||
SQLiteColumn res;
|
||||
res.tag = SQLITE_FLOAT;
|
||||
res.f = f;
|
||||
return res;
|
||||
}
|
||||
|
||||
SQLiteColumn SQLiteColumn_text(char* s) {
|
||||
SQLiteColumn res;
|
||||
res.tag = SQLITE_TEXT;
|
||||
res.s = s;
|
||||
return res;
|
||||
}
|
||||
|
||||
SQLiteColumn SQLiteColumn_blob(char* s) {
|
||||
SQLiteColumn res;
|
||||
res.tag = SQLITE_BLOB;
|
||||
res.s = s;
|
||||
return res;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int columns;
|
||||
SQLiteColumn* data;
|
||||
} SQLiteRow;
|
||||
|
||||
int SQLiteRow_length(SQLiteRow* row) {
|
||||
return row->columns;
|
||||
}
|
||||
|
||||
SQLiteColumn SQLiteRow_nth(SQLiteRow* row, int i) {
|
||||
return row->data[i];
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int capacity;
|
||||
int len;
|
||||
SQLiteRow* rows;
|
||||
} SQLiteRows;
|
||||
|
||||
SQLiteRow* SQLiteRows_next_row(SQLiteRows* rows) {
|
||||
SQLiteRow* res;
|
||||
if (rows->capacity <= rows->len) {
|
||||
if (!(rows->capacity)) rows->capacity = 10;
|
||||
else rows->capacity *= 2;
|
||||
rows->rows = realloc(rows->rows, (rows->capacity)*sizeof(SQLiteRow));
|
||||
}
|
||||
res = (rows->rows)+(rows->len);
|
||||
rows->len++;
|
||||
return res;
|
||||
}
|
||||
|
||||
void SQLiteRows_finalize(SQLiteRows* rows) {
|
||||
rows->capacity = rows->len;
|
||||
rows->rows = realloc(rows->rows, (rows->capacity)*sizeof(SQLiteRow));
|
||||
}
|
||||
|
||||
SQLiteRows SQLiteRows_new_rows() {
|
||||
SQLiteRows res;
|
||||
res.len = 0;
|
||||
res.capacity = 0;
|
||||
res.rows = NULL;
|
||||
return res;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int is;
|
||||
union {
|
||||
const char* err;
|
||||
SQLiteRows rows;
|
||||
};
|
||||
} SQLiteRes;
|
||||
#define OK 0
|
||||
#define ERR 1
|
||||
|
||||
int SQLiteRes_length(SQLiteRes* r) {
|
||||
return r->rows.len;
|
||||
}
|
||||
|
||||
SQLiteRow SQLiteRes_nth(SQLiteRes* r, int i) {
|
||||
return r->rows.rows[i];
|
||||
}
|
||||
|
||||
bool SQLiteRes_is_ok(SQLiteRes* r) {
|
||||
return r->is == OK;
|
||||
}
|
||||
|
||||
char* SQLiteRes_error(SQLiteRes r) {
|
||||
return (char*)r.err;
|
||||
}
|
||||
|
||||
// --- END HELPERS ---
|
||||
|
||||
SQLite SQLite3_init() {
|
||||
SQLite res;
|
||||
res.handle = NULL;
|
||||
return res;
|
||||
}
|
||||
|
||||
int SQLite3_open_c(SQLite* db, const char* filename) {
|
||||
sqlite3* c;
|
||||
int res = sqlite3_open(filename, &c);
|
||||
db->handle = c;
|
||||
return res;
|
||||
}
|
||||
|
||||
const char* SQLite3_exec_internal(sqlite3_stmt* s, SQLiteRows* rows) {
|
||||
int status;
|
||||
int len;
|
||||
const char* err = NULL;
|
||||
int count = sqlite3_column_count(s);
|
||||
|
||||
do {
|
||||
status = sqlite3_step(s);
|
||||
|
||||
if (status == SQLITE_ROW) {
|
||||
SQLiteRow* row = SQLiteRows_next_row(rows);
|
||||
row->columns = count;
|
||||
row->data = CARP_MALLOC(count*sizeof(SQLiteColumn));
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
SQLiteColumn* c = row->data+i;
|
||||
c->tag = sqlite3_column_type(s, i);
|
||||
switch(c->tag) {
|
||||
case SQLITE_INTEGER:
|
||||
c->i = sqlite3_column_int(s, i);
|
||||
break;
|
||||
case SQLITE_FLOAT:
|
||||
c->f = sqlite3_column_double(s, i);
|
||||
break;
|
||||
case SQLITE_TEXT: {
|
||||
len = sqlite3_column_bytes(s, i);
|
||||
c->s = CARP_MALLOC(len);
|
||||
memcpy(c->s, sqlite3_column_text(s, i), len);
|
||||
c->s[len] = '\0';
|
||||
break;
|
||||
}
|
||||
case SQLITE_BLOB: {
|
||||
len = sqlite3_column_bytes(s, i);
|
||||
c->s = CARP_MALLOC(len);
|
||||
memcpy(c->s, sqlite3_column_blob(s, i), len);
|
||||
c->s[len] = '\0';
|
||||
break;
|
||||
}
|
||||
case SQLITE_NULL:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (status == SQLITE_ROW);
|
||||
|
||||
if (status != SQLITE_DONE) {
|
||||
sqlite3* db = sqlite3_db_handle(s);
|
||||
err = sqlite3_errmsg(db);
|
||||
}
|
||||
|
||||
SQLiteRows_finalize(rows);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static const char* SQLite3_exec_ignore(sqlite3_stmt* s) {
|
||||
int status;
|
||||
const char* ret = NULL;
|
||||
do { status = sqlite3_step(s); } while (status == SQLITE_ROW);
|
||||
|
||||
/* Check for errors */
|
||||
if (status != SQLITE_DONE) {
|
||||
sqlite3* db = sqlite3_db_handle(s);
|
||||
ret = sqlite3_errmsg(db);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char* SQLite3_bind(sqlite3_stmt* s, Array p) {
|
||||
int res;
|
||||
const char* err = NULL;
|
||||
|
||||
for (int i = 0; i < p.len; i++) {
|
||||
SQLiteColumn val = ((SQLiteColumn*)p.data)[i];
|
||||
|
||||
switch (val.tag) {
|
||||
case SQLITE_NULL:
|
||||
res = sqlite3_bind_null(s, i+1);
|
||||
break;
|
||||
case SQLITE_INTEGER:
|
||||
res = sqlite3_bind_int(s, i+1, val.i);
|
||||
break;
|
||||
case SQLITE_FLOAT:
|
||||
res = sqlite3_bind_double(s, i+1, val.f);
|
||||
break;
|
||||
case SQLITE_TEXT:
|
||||
res = sqlite3_bind_text(s, i+1, val.s, strlen(val.s), SQLITE_STATIC);
|
||||
break;
|
||||
case SQLITE_BLOB:
|
||||
res = sqlite3_bind_blob(s, i+1, val.s, strlen(val.s), SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
if (res != SQLITE_OK) {
|
||||
sqlite3* db = sqlite3_db_handle(s);
|
||||
err = sqlite3_errmsg(db);
|
||||
}
|
||||
if (err) break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
SQLiteRes SQLite3_exec_c(SQLite* db, const char* stmt, Array p) {
|
||||
sqlite3_stmt* s = NULL;
|
||||
sqlite3_stmt* n = NULL;
|
||||
const char* err;
|
||||
SQLiteRes res;
|
||||
res.is = OK;
|
||||
res.rows = SQLiteRows_new_rows();
|
||||
|
||||
do {
|
||||
if (sqlite3_prepare_v2(db->handle, stmt, -1, &n, &stmt) != SQLITE_OK) {
|
||||
err = sqlite3_errmsg(db->handle);
|
||||
goto err;
|
||||
} else {
|
||||
if (n) {
|
||||
err = SQLite3_bind(n, p);
|
||||
if (err) goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (s) {
|
||||
err = n ? SQLite3_exec_ignore(s) : SQLite3_exec_internal(s, &(res.rows));
|
||||
if (err) goto err;
|
||||
}
|
||||
if (s) sqlite3_finalize(s);
|
||||
s = n;
|
||||
n = NULL;
|
||||
} while (s);
|
||||
|
||||
return res;
|
||||
err:
|
||||
if (s) sqlite3_finalize(s);
|
||||
if (n) sqlite3_finalize(n);
|
||||
res.is = ERR;
|
||||
res.err = err;
|
||||
return res;
|
||||
}
|
||||
|
||||
void SQLite3_close_c(SQLite db) {
|
||||
sqlite3_close_v2(db.handle);
|
||||
}
|
||||
|
||||
char* SQLite3_error(SQLite db) {
|
||||
return (char*)sqlite3_errmsg(db.handle);
|
||||
}
|
Reference in New Issue
Block a user