Skip to content

Commit

Permalink
feat: extensible table
Browse files Browse the repository at this point in the history
  • Loading branch information
d12frosted committed Nov 9, 2022
1 parent d2620c8 commit b18ed96
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 102 deletions.
73 changes: 38 additions & 35 deletions test/vulpea-db-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -898,47 +898,50 @@
(org-roam-db-sync 'force)

;; initially there are no vulpea specific tables
(pcase-dolist (`(,table ,_) vulpea-db--schemata)
(expect (org-roam-db-query
[:select name
:from sqlite_master
:where (and (= type 'table)
(= name $r1))]
(emacsql-escape-identifier table))
:to-equal nil))
(pcase-dolist (`(,index-name ,_ ,_) vulpea-db--indices)
(expect (org-roam-db-query
[:select name
:from sqlite_master
:where (and (= type 'index)
(= name $r1))]
(emacsql-escape-identifier index-name))
:to-equal nil))
(-each vulpea-db--tables
(-lambda ((table _ _ indices))
(expect (org-roam-db-query
[:select name
:from sqlite_master
:where (and (= type 'table)
(= name $r1))]
(emacsql-escape-identifier table))
:to-equal nil)
(-each indices
(-lambda ((index-name))
(expect (org-roam-db-query
[:select name
:from sqlite_master
:where (and (= type 'index)
(= name $r1))]
(emacsql-escape-identifier index-name))
:to-equal nil)))))

;; then we setup vulpea-db
(message "vulpea-db-setup")
(vulpea-db-autosync-enable)

;; and vulpea specific tables should exist
(pcase-dolist (`(,table ,_) vulpea-db--schemata)
(expect (org-roam-db-query
[:select name
:from sqlite_master
:where (and (= type 'table)
(= name $r1))]
(emacsql-escape-identifier table))
:to-equal (list (list (intern (emacsql-escape-identifier table))))))
(pcase-dolist (`(,index-name ,_ ,_) vulpea-db--indices)
(expect (org-roam-db-query
[:select name
:from sqlite_master
:where (and (= type 'index)
(= name $r1))]
(emacsql-escape-identifier index-name))
:to-equal (list (list (intern (emacsql-escape-identifier index-name))))))
(expect (caar (org-roam-db-query [:select version :from cache :where (= id "vulpea")]))
:to-equal
vulpea-db-version)
(-each vulpea-db--tables
(-lambda ((table version _ indices))
(expect (org-roam-db-query
[:select name
:from sqlite_master
:where (and (= type 'table)
(= name $r1))]
(emacsql-escape-identifier table))
:to-equal (list (list (intern (emacsql-escape-identifier table)))))
(-each indices
(-lambda ((index-name))
(expect (org-roam-db-query
[:select name
:from sqlite_master
:where (and (= type 'index)
(= name $r1))]
(emacsql-escape-identifier index-name))
:to-equal (list (list (intern (emacsql-escape-identifier index-name)))))))
(expect (caar (org-roam-db-query [:select version :from versions :where (= id $s1)] table))
:to-equal version)))

;; sync a file
(message "update file")
Expand Down
2 changes: 1 addition & 1 deletion test/vulpea-perf-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
(require 'org-roam)
(require 'vulpea)

(defconst vulpea-perf-zip-branch "attach-dir")
(defconst vulpea-perf-zip-branch "extensible-table")

(defconst vulpea-perf-zip-url
(format
Expand Down
171 changes: 105 additions & 66 deletions vulpea-db.el
Original file line number Diff line number Diff line change
Expand Up @@ -344,10 +344,16 @@ If the FILE is relative, it is considered to be relative to



(defconst vulpea-db-version 3)
(defconst vulpea-db-reserved-names
'(notes meta versions
files nodes aliases citations refs tags links)
"List of reserved table names.
(defconst vulpea-db--schemata
Includes Vulpea tables as well as Org roam tables.")

(defvar vulpea-db--tables
'((notes
1
([(id :not-null :primary-key)
(path :not-null)
(level :not-null)
Expand All @@ -360,6 +366,7 @@ If the FILE is relative, it is considered to be relative to
attach]
(:foreign-key [path] :references files [file] :on-delete :cascade)))
(meta
1
([(node-id :not-null)
(prop :not-null)
(value :not-null)]
Expand All @@ -368,15 +375,42 @@ If the FILE is relative, it is considered to be relative to
:references
nodes [id]
:on-delete
:cascade)))
(cache
:cascade))
((meta-node-id [node-id])))
(versions
1
([(id :not-null :primary-key)
(version :not-null)])))
"Vulpea db schemata.")

(defconst vulpea-db--indices
'((meta-node-id meta [node-id]))
"Vulpea db indices.")
(version :not-null)]))))

(defun vulpea-db-define-table (name version schema &optional indices)
"Define a table with NAME in `org-roam-db'.
Keep in mind that the names defined in `vulpea-db-reserved-names'
are not allowed.
VERSION is used to automatically upgrade whenever SCHEMA or
INDICES change.
A table SCHEMA is a list whose first element is a vector of
column specifications. The rest of the list specifies table
constraints. A column identifier is a symbol and a column's
specification can either be just this symbol or it can include
constraints as a list. Because EmacSQL stores entire Lisp objects
as values, the only relevant (and allowed) types are integer,
float, and object (default).
Optionally you may define INDICES for this table - an association
list, where car is a unique name of the index and cdr is vector
of columns to be indexed.
Consult with `emacsql' documentation to learn more about SCHEMA
and INDICES."
(when (seq-contains-p vulpea-db-reserved-names name)
(user-error "Name %s is reserved and can't be used" name))
(add-to-list
'vulpea-db--tables
`(name ,version ,schema ,indices)
'append))

(defvar vulpea-db--initalized nil
"Non-nil when database was initialized.")
Expand All @@ -388,24 +422,25 @@ GET-DB is a function that returns connection to database."
(when-let ((db (funcall get-db)))
(unless vulpea-db--initalized
(emacsql-with-transaction db
(pcase-dolist (`(,table ,schema) vulpea-db--schemata)
(unless (emacsql db
[:select name
:from sqlite_master
:where (and (= type 'table)
(= name $r1))]
(emacsql-escape-identifier table))
(emacsql db [:create-table $i1 $S2] table schema)))
(pcase-dolist (`(,index-name ,table ,columns)
vulpea-db--indices)
(unless (emacsql db
[:select name
:from sqlite_master
:where (and (= type 'index)
(= name $r1))]
(emacsql-escape-identifier index-name))
(emacsql db [:create-index $i1 :on $i2 $S3]
index-name table columns)))))
(-each vulpea-db--tables
(-lambda ((table-name _ schema indices))
(unless (emacsql db
[:select name
:from sqlite_master
:where (and (= type 'table)
(= name $r1))]
(emacsql-escape-identifier table-name))
(emacsql db [:create-table $i1 $S2] table-name schema))
(-each indices
(-lambda ((index-name columns))
(unless (emacsql db
[:select name
:from sqlite_master
:where (and (= type 'index)
(= name $r1))]
(emacsql-escape-identifier index-name))
(emacsql db [:create-index $i1 :on $i2 $S3]
index-name table-name columns))))))))
(setq vulpea-db--initalized t)
db))

Expand All @@ -421,17 +456,13 @@ GET-DB is a function that returns connection to database."
(cond
(enabled
(setq vulpea-db--initalized nil)
;; attach custom schemata
(seq-each
(lambda (schema)
(add-to-list 'org-roam-db--table-schemata schema 'append))
vulpea-db--schemata)

;; attach custom indices
(seq-each
(lambda (index)
(add-to-list 'org-roam-db--table-indices index 'append))
vulpea-db--indices)
;; attach custom schemata and indices
(-each vulpea-db--tables
(-lambda ((table-name _ schema indices))
(add-to-list 'org-roam-db--table-schemata `(,table-name ,schema) 'append)
(-each indices
(-lambda ((index-name columns))
(add-to-list 'org-roam-db--table-indices `(,index-name ,table-name ,columns) 'append)))))

;; make sure that extra tables exist table exists
(advice-add 'org-roam-db :around #'vulpea-db--init)
Expand All @@ -449,23 +480,33 @@ GET-DB is a function that returns connection to database."
'org-roam-db-map-links :after #'vulpea-db-insert-links)

(when (file-exists-p org-roam-db-location)
(let ((version (or (caar (emacsql (org-roam-db)
[:select version
:from cache
:where (= id "vulpea")]))
0)))
(when (< version vulpea-db-version)
(org-roam-message (format "Upgrading the vulpea database from version %d to version %d"
version vulpea-db-version))
(org-roam-db-sync t)
(let ((db (org-roam-db)))
(emacsql db [:update cache
:set (= version $s1)
:where (= id "vulpea")]
vulpea-db-version)
(emacsql db [:insert :or :ignore :into cache [id version]
:values ["vulpea" $s1]]
vulpea-db-version))))))
(when-let* ((db (org-roam-db))
(changed (-find
(-lambda ((table-name version1))
(let ((version0 (or (caar (emacsql
(org-roam-db)
[:select version
:from versions
:where (= id $s1)]
table-name))
0)))
(when (< version0 version1)
(org-roam-message
(format
"Doing vulpea database sync to upgrade '%s' table from version %d to version %d"
table-name version0 version1)))))
vulpea-db--tables)))
(org-roam-db-sync t)
(let ((db (org-roam-db)))
(-each vulpea-db--tables
(-lambda ((table-name version))
(emacsql db [:update versions
:set (= version $s2)
:where (= id $s1)]
table-name version)
(emacsql db [:insert :or :ignore :into versions [id version]
:values [$s1 $s2]]
table-name version)))))))
(t
(setq vulpea-db--initalized nil)
(advice-remove 'org-roam-db-map-links #'vulpea-db-insert-links)
Expand All @@ -474,16 +515,14 @@ GET-DB is a function that returns connection to database."
(advice-remove
'org-roam-db-insert-file-node #'vulpea-db-insert-file-note)
(advice-remove 'org-roam-db #'vulpea-db--init)
(seq-each
(lambda (schema)
(setq org-roam-db--table-schemata
(delete schema org-roam-db--table-schemata)))
vulpea-db--schemata)
(seq-each
(lambda (index)
(setq org-roam-db--table-indices
(delete index org-roam-db--table-indices)))
vulpea-db--indices)))))
(-each vulpea-db--tables
(-lambda ((table-name _ schema indices))
(setq org-roam-db--table-schemata
(delete `(,table-name ,schema) org-roam-db--table-schemata))
(-each indices
(-lambda ((index-name columns))
(setq org-roam-db--table-indices
(delete `(,index-name ,table-name ,columns) org-roam-db--table-indices))))))))))

;;;###autoload
(defun vulpea-db-autosync-enable ()
Expand Down

0 comments on commit b18ed96

Please sign in to comment.