Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 52 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Currently this library is not on maven so you have to add it via git deps (note:
```clojure
andersmurphy/sqlite4clj
{:git/url "https://github.com/andersmurphy/sqlite4clj"
:git/sha "7cc1fc180c683a27aa3f91d434729eb07f9702f6"}
:git/sha "ecf9876626985d61bd27605ea6748aed25eaad87"}
```

Initialise a db:
Expand Down Expand Up @@ -220,8 +220,9 @@ sqlite4clj automatically encodes any EDN object you pass it:
```

This effectively lets you use SQLite as an EDN document store.
When decoding BLOB values, sqlite4clj reads the exact SQLite BLOB size before parsing EDN.

Encoding is done with [fast-edn](https://github.com/tonsky/fast-edn) as text and then converted into bytes. From my testing this was faster than both [deed](https://github.com/igrishaev/deed) and [nippy](https://github.com/taoensso/nippy) despite being a text format. Being a text format it is stable and can be swapped out for faster EDN text serialises without breaking changes. Of course, this also means only EDN data is support and not arbitrary Java classes.
Encoding is done with [fast-edn](https://github.com/tonsky/fast-edn) as text and then converted into bytes. From my testing this was faster than both [deed](https://github.com/igrishaev/deed) and [nippy](https://github.com/taoensso/nippy) despite being a text format. Being a text format it is stable and can be swapped out for faster EDN text serialises without breaking changes. Of course, this also means only EDN data is supported and not arbitrary Java classes.

## Application functions

Expand All @@ -240,7 +241,55 @@ Declaring and using an application function:
;; [["46536a4a-0b1e-4749-9c01-f44f73de3b91" {:type "foo", :a 3, :b 3}]]
```

When dealing with columns that are encoded EDN blobs they will automatically decoded.
When dealing with columns that are encoded EDN blobs they will automatically be decoded.

You can unregister scalar functions with:

```clojure
(d/remove-function db "entity_type")
;; optional arity-specific removal:
(d/remove-function db "entity_type" 1)
```

## Application aggregates

SQLite also supports [Application-Defined Aggregate Functions](https://www.sqlite.org/appfunc.html). sqlite4clj exposes these via `d/create-aggregate`.

The aggregate step callback receives `[state & sql-args]`, and arity is inferred as `step-arity - 1` by default:

```clojure
(d/create-aggregate db "sum_n"
(fn [state n]
(+ (or state 0) n))
(fn [state] state)
{:deterministic? true})

(d/q (:reader db) ["SELECT sum_n(checks) FROM session"])
;; =>
;; [42]
```

You can also:

- pass vars (for repl-driven redefinition), e.g. `#'sum-step` and `#'sum-final`
- set `:arity` explicitly (`-1` for variadic aggregates)
- set `:initial-state` to control empty-input results
- remove aggregates with `d/remove-aggregate` (optionally by arity)

```clojure
(d/create-aggregate db "sum_empty_init"
(fn [state n] (+ (or state 0) n))
(fn [state] state)
{:initial-state 10})

(d/q (:writer db) ["SELECT sum_empty_init(n) FROM (SELECT 1 AS n WHERE 0)"])
;; =>
;; [10]

(d/remove-aggregate db "sum_n")
;; optional arity-specific removal:
(d/remove-aggregate db "sum_n" 1)
```

## Indexing on encoded edn blobs

Expand Down
43 changes: 42 additions & 1 deletion src/sqlite4clj/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
(:require
[sqlite4clj.impl.api :as api]
[sqlite4clj.impl.functions :as funcs]
[sqlite4clj.impl.functions-aggregates :as aggs]
[clojure.core.cache.wrapped :as cache])
(:import
(java.util.concurrent BlockingQueue LinkedBlockingQueue)))
Expand Down Expand Up @@ -240,7 +241,8 @@
:reader reader
;; Prevents application function callback pointers from getting
;; garbage collected.
:internal {:app-functions (atom {})}}))
:internal {:app-functions (atom {})
:app-aggregates (atom {})}}))

(defmacro with-conn
"Use the same connection for a series of queries (not a transaction) without
Expand Down Expand Up @@ -355,3 +357,42 @@
(funcs/remove-function db name))
([db name arity]
(funcs/remove-function db name arity)))

(defn create-aggregate
"register a user-defined aggregate function with sqlite on all connections.

parameters:
- db: database from init-db!
- name: string function name
- step-f-or-var: step callback function or var
- final-f-or-var: final callback function or var
- opts: a map of options that can include:

the sqlite function flags (see https://www.sqlite.org/c3ref/c_deterministic.html)
- :deterministic? (boolean)
- :direct-only? (boolean)
- :innocuous? (boolean)
- :sub-type? (boolean)
- :result-sub-type? (boolean)
- :self-order1? (boolean)

aggregate options:
- :arity (int): SQL arity. -1 means variadic.
- :initial-state: initial step state used for empty inputs when provided.

by default SQL arity is inferred from step-f-or-var as:
sql arity = (step arity - 1) where the first argument is aggregate state.
step callbacks must have signature:
(fn [state & sql-args] new-state)
final callbacks must have signature:
(fn [state] result)"
[db name step-f-or-var final-f-or-var & {:as opts}]
(aggs/create-aggregate db name step-f-or-var final-f-or-var opts))

(defn remove-aggregate
"unregister a user-defined aggregate from sqlite on all connections.
if an arity is not provided, it will unregister all arities for the aggregate."
([db name]
(aggs/remove-aggregate db name))
([db name arity]
(aggs/remove-aggregate db name arity)))
6 changes: 6 additions & 0 deletions src/sqlite4clj/impl/api.clj
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,12 @@
::mem/pointer] ;; xDestroy (destructor)
::mem/int)

(defcfn aggregate-context
sqlite3_aggregate_context
[::mem/pointer ;; sqlite3_context*
::mem/int] ;; nBytes
::mem/pointer)

(defcfn value-text
sqlite3_value_text
[::mem/pointer] ::mem/c-string)
Expand Down
Loading