210 lines
6.3 KiB
Plaintext
210 lines
6.3 KiB
Plaintext
(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 \"git@veitheller.de:git/carpentry/sqlite3.git@0.0.6\")
|
||
```
|
||
|
||
## Usage
|
||
|
||
The module `SQLite3` provides facilities for opening, closing, and querying
|
||
databases.
|
||
|
||
```clojure
|
||
(load \"git@veitheller.de:git/carpentry/sqlite3.git@0.0.6\")
|
||
|
||
; opening DBs can fail, for the purposes of this example we
|
||
; ignore that
|
||
(defn main []
|
||
(let-do [db (Result.unsafe-from-success (SQLite3.open \"db\"))]
|
||
; Let's make sure our table is there
|
||
(ignore
|
||
(SQLite3.query &db
|
||
\"CREATE TABLE IF NOT EXISTS mytable (name TEXT, age INT)\"
|
||
&[]))
|
||
|
||
; we can prepare statements
|
||
(ignore
|
||
(SQLite3.query &db
|
||
\"INSERT INTO mytable VALUES (?1, ?2);\"
|
||
&[(to-sqlite3 @\"Carp\") (to-sqlite3 4)]))
|
||
|
||
; 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 prn [s]
|
||
(SQLite3.Type.str s))
|
||
(implements prn SQLite3.Type.prn)
|
||
|
||
(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 CChar)) "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 CChar)] Int) "SQLite3_open_c")
|
||
(private exec-)
|
||
(hidden exec-)
|
||
(register exec- (Fn [&SQLite (Ptr CChar) &(Array SQLiteColumn)] SQLiteRes) "SQLite3_exec_c")
|
||
(private error-)
|
||
(hidden error-)
|
||
(register error- (Fn [SQLite] (Ptr CChar)) "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)))
|
||
(implements to-sqlite3 Bool.to-sqlite3))
|
||
|
||
(defmodule Int
|
||
(defn to-sqlite3 [i] (SQLite3.Type.Integer i))
|
||
(implements to-sqlite3 Int.to-sqlite3))
|
||
|
||
(defmodule Long
|
||
(defn to-sqlite3 [l] (SQLite3.Type.Integer (to-int (the Long l))))
|
||
(implements to-sqlite3 Long.to-sqlite3))
|
||
|
||
(defmodule Float
|
||
(defn to-sqlite3 [f] (SQLite3.Type.Floating (Double.from-float f)))
|
||
(implements to-sqlite3 Float.to-sqlite3))
|
||
|
||
(defmodule Double
|
||
(defn to-sqlite3 [d] (SQLite3.Type.Floating d))
|
||
(implements to-sqlite3 Double.to-sqlite3))
|
||
|
||
(defmodule String
|
||
(defn to-sqlite3 [s] (SQLite3.Type.Text s))
|
||
(implements to-sqlite3 String.to-sqlite3))
|
||
|