;;; SPDX-License-Identifier: LGPL-3.0-or-later ;;; SPDX-FileCopyrightText: Copyright © 2010-2021 Tony Garnock-Jones #lang syndicate ;; SQLite driver (provide (struct-out sqlite-db) (struct-out sqlite-db-ready) (struct-out sqlite-row) (struct-out sqlite-exec) (struct-out sqlite-create-table) (struct-out sqlite-insert) (struct-out sqlite-delete) sqlite-exec! sqlite-create-table! sqlite-insert! sqlite-delete! (struct-out discard) ;; from syndicate/pattern ) (require db) (require racket/set) (require racket/string) (require syndicate/pattern) (define-logger syndicate/sqlite) (struct sqlite-db (path) #:prefab) (assertion-struct sqlite-db-ready (db)) (assertion-struct sqlite-row (db table columns)) (message-struct sqlite-exec (db template arguments id)) (message-struct sqlite-status (id value)) (message-struct sqlite-create-table (db table column-names id)) (message-struct sqlite-insert (db table columns id)) (message-struct sqlite-delete (db table columns id)) (define (sqlite-call db msg-proc) (define id (gensym 'exec)) (react/suspend (k) (on (message (sqlite-status id $v)) (if (exn? v) (raise v) (k v))) (on (asserted (sqlite-db-ready db)) (send! (msg-proc id))))) (define (sqlite-exec! db template . arguments) (sqlite-call db (lambda (id) (sqlite-exec db template arguments id)))) (define (sqlite-create-table! db table . column-names) (sqlite-call db (lambda (id) (sqlite-create-table db table column-names id)))) (define (sqlite-insert! db table . columns) (sqlite-call db (lambda (id) (sqlite-insert db table columns id)))) (define (sqlite-delete! db table . columns) (sqlite-call db (lambda (id) (sqlite-delete db table columns id)))) (define (strip-capture p) (if (capture? p) (strip-capture (capture-detail p)) p)) (spawn #:name 'drivers/sqlite (during/spawn ($ db (sqlite-db $path)) #:name (list 'drivers/sqlite path) (define handle (sqlite3-connect #:database path #:mode 'create)) ;; TODO: #:use-place ? (on-stop (disconnect handle)) (assert (sqlite-db-ready db)) (on (message (sqlite-exec db $template $arguments $id)) (with-handlers [(exn:fail? (lambda (e) (send! (sqlite-status id e))))] (log-syndicate/sqlite-debug "~s ~s" template arguments) (send! (sqlite-status id (apply query-exec handle template arguments))))) (field [known-tables (set)]) (on (message (sqlite-create-table db $table $column-names $id)) (with-handlers [(exn:fail? (lambda (e) (send! (sqlite-status id e))))] (define template (format "create table ~a (~a)" table (string-join column-names ", "))) (define arguments '()) (table-facet handle known-tables db table column-names) (log-syndicate/sqlite-debug "~s ~s" template arguments) (send! (sqlite-status id (apply query-exec handle template arguments))))) (on-start (for [(table (query-list handle "select distinct name from sqlite_master where type='table'"))] (define column-names (map (lambda (r) (vector-ref r 1)) (query-rows handle (string-append "pragma table_info(" table ")")))) (table-facet handle known-tables db table column-names))))) (define (table-facet handle known-tables db table column-names) (when (not (set-member? (known-tables) table)) (known-tables (set-add (known-tables) table)) (react (on (message ($ m (sqlite-insert db table $columns $id))) (with-handlers [(exn:fail? (lambda (e) (send! (sqlite-status id e))))] (define template (format "insert into ~a values (~a)" table (string-join (for/list [(i (in-naturals 1)) (c columns)] (format "$~a" i)) ", "))) (define arguments columns) (log-syndicate/sqlite-debug "~s ~s" template arguments) (send! (sqlite-status id (apply query-exec handle template arguments))))) (on (message ($ m (sqlite-delete db table $columns $id))) (with-handlers [(exn:fail? (lambda (e) (send! (sqlite-status id e))))] (define filters (for/list [(n column-names) (c columns) #:when (not (discard? c))] (list n c))) (define template (format "delete from ~a~a" table (if (null? filters) "" (format " where ~a" (string-join (for/list [(i (in-naturals 1)) (f filters)] (format "~a = $~a" (car f) i)) " and "))))) (define arguments (map cadr filters)) (log-syndicate/sqlite-debug "~s ~s" template arguments) (send! (sqlite-status id (apply query-exec handle template arguments))))) (define (row-facet columns) (react (assert (sqlite-row db table columns)) (on (message (sqlite-delete db table $cs _)) (when (for/and [(c1 columns) (c2 cs)] (or (discard? c2) (equal? c1 c2))) (stop-current-facet))))) (during/spawn (observe (sqlite-row db table $column-patterns0)) (define column-patterns (let ((ps (strip-capture column-patterns0))) (if (discard? ps) (for/list [(n column-names)] (discard)) ps))) (define filters (for/list [(n column-names) (p (map strip-capture column-patterns)) #:when (not (discard? p))] (list n p))) (define initial-rows (let () (define template (format "select distinct * from ~a~a" table (if (null? filters) "" (format " where ~a" (string-join (for/list [(i (in-naturals 1)) (f filters)] (format "~a = $~a" (car f) i)) " and "))))) (define arguments (map cadr filters)) (log-syndicate/sqlite-debug "~s ~s" template arguments) (map vector->list (apply query-rows handle template (map cadr filters))))) (on-start (for-each row-facet initial-rows)) (on (message (sqlite-insert db table $columns _)) (when (for/and [(n column-names) (c columns)] (match (assoc n filters) [(list _ v) (equal? c v)] [#f #t])) (row-facet columns)))))))