[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/triples 6afcb290ca 15/19: Support both emacs 29 sqlite
From: |
ELPA Syncer |
Subject: |
[elpa] externals/triples 6afcb290ca 15/19: Support both emacs 29 sqlite and emacsql. |
Date: |
Sat, 5 Nov 2022 11:58:17 -0400 (EDT) |
branch: externals/triples
commit 6afcb290ca87934fb230789e3c99c64a4002ecd5
Author: Andrew Hyatt <ahyatt@gmail.com>
Commit: Andrew Hyatt <ahyatt@gmail.com>
Support both emacs 29 sqlite and emacsql.
---
README.org | 4 +-
triples-test.el | 204 +++++++++++++++++++++++++++++-------------------
triples.el | 235 ++++++++++++++++++++++++++++++++++++++++----------------
3 files changed, 295 insertions(+), 148 deletions(-)
diff --git a/README.org b/README.org
index 214e4f663f..66ab22eb64 100644
--- a/README.org
+++ b/README.org
@@ -1,6 +1,6 @@
#+TITLE: Triples
-The =triples= module is a standard database module designed for use in other
emacs modules. It works with the =sqlite= module, and provides a simple way of
storing entities and their associated schema. The triples module is well
suited to graph-like applications, where links between entities are important.
The module has wrappers for most common operations, but it is anticipated that
occasionally client modules would need to make their own =sqlite= calls. Many
database can be handled [...]
+The =triples= module is a standard database module designed for use in other
emacs modules. It works with either the builtin sqlite in Emacs 29 or the
[[https://github.com/magit/emacsql][emacsql]] module, and provides a simple way
of storing entities and their associated schema. The triples module is well
suited to graph-like applications, where links between entities are important.
The module has wrappers for most common operations, but it is anticipated that
occasionally client modu [...]
* Maturity
This module is very new should be considered alpha quality.
@@ -73,7 +73,7 @@ There are other useful functions, including:
- =triples-with-predicate=, gets all triples that is about a specific property,
- =triples-with-predicate-object=, get all subjects whose predicate is equal
to /object/,
- =triples-subjects-of-type=, get all subjects which have a particular type.
-* Preciate, with type and without
+* Predicates, with type and without
Sometimes the triples library will require predicates that are without type,
and sometimes with type, or "combined predicates". The rule is that if the
type is already specified in the function, it does not need to be respecified.
If the type is not specified, it is included in the combined predicate.
When returning data, if data is from just one type, the type is not returned
in the returned predicates. If the data is from multiple types, the type is
returned as combined predicates.
diff --git a/triples-test.el b/triples-test.el
index 2c9afe098d..784557fc67 100644
--- a/triples-test.el
+++ b/triples-test.el
@@ -1,7 +1,10 @@
;;; triples-test.el --- Tests for triples module. -*- lexical-binding: t; -*-
+;; Note: It's important to test this on emacs 29, with emacsql installed, so we
+;; can make both types of sqlite backend work.
(require 'triples)
(require 'seq)
(require 'kv)
+(require 'emacsql)
;;; Code:
@@ -26,86 +29,127 @@ easily debug into it.")
(let ((sql-database triples-test-db-file))
(sql-sqlite (format "*schema test db SQL %s*" triples-test-db-file))))
-(ert-deftest triples-test-insert ()
- (triples-test-with-temp-db
- (triples--insert db "sub" 'pred "obj")
- ;; Test for emacsql compability
- (should (equal (sqlite-select db "SELECT * FROM triples")
- '(("\"sub\"" "pred" "\"obj\"" "()"))))
- ;; Test that it replaces - this shouldn't result in two rows.
- (triples--insert db "sub" 'pred "obj")
- (should (equal (sqlite-select db "SELECT count(*) FROM triples")
- '((1))))
- ;; Test that colons in the predicate are stripped away when stored.
- (triples--insert db "sub" :test/pred "obj")
- (should (equal (sqlite-select db "SELECT count(*) FROM triples WHERE
predicate = ?"
- '("test/pred"))
- '((1))))
- ;; Test we correctly test for bad inputs.
- (should-error (triples--insert db "sub" "pred" "obj"))
- (should-error (triples--insert db "sub" 'pred "obj" '(ordinary-list)))
- (should-error (triples--insert db "sub" 'pred "obj" "string"))
- ;; Test that we can have symbol subject and objects
- (triples--insert db 'sub 'pred 'obj)
- (should (equal (sqlite-select db "SELECT * FROM triples WHERE subject = ?"
'("sub"))
- '(("sub" "pred" "obj" "()"))))))
-
-(ert-deftest triples-test-delete ()
- (triples-test-with-temp-db
- (triples--insert db 1 'pred 2)
- (triples--insert db 2 'pred 1)
- (triples--delete db 1)
- (should (equal (sqlite-select db "SELECT count(*) FROM triples")
- '((1))))
- (should (equal (sqlite-select db "SELECT count(*) FROM triples WHERE
subject = ?" '(1))
- '((0))))
- (triples--insert db 1 'pred 2)
- (triples--delete db nil nil 2)
- (should (equal (sqlite-select db "SELECT count(*) FROM triples WHERE
object = ?" '(2))
- '((0))))
- (triples--insert db 1 'pred 2)
- (triples--delete db nil 'pred nil)
- (should (equal (sqlite-select db "SELECT count(*) FROM triples")
- '((0))))))
-
-(ert-deftest triples-test-delete-subject-predicate-prefix ()
- (triples-test-with-temp-db
- (triples--insert db 1 'test/foo 2)
- (triples--insert db 1 'bar/bar 1)
- (triples--delete-subject-predicate-prefix db 1 'test)
- (should (equal (sqlite-select db "SELECT count(*) FROM triples")
- '((1))))
- ;; Make sure colons are stripped.
- (triples--delete-subject-predicate-prefix db 1 :bar)
- (should (equal (sqlite-select db "SELECT count(*) FROM triples")
- '((0))))))
-
-(ert-deftest triples-test-select ()
- (triples-test-with-temp-db
- (triples--insert db 1 'pred 2 '(:a 1))
- (let ((expected '((1 pred 2 (:a 1)))))
- (should (equal (triples--select db 1) expected))
- (should (equal (triples--select db nil 'pred) expected))
- (should (equal (triples--select db nil nil 2) expected))
- (should (equal (triples--select db 1 nil 2) expected))
- (should (equal (triples--select db 1 'pred 2) expected))
- (should (equal '((1)) (triples--select db 1 nil nil nil '(subject))))
- (should (equal '((1 pred)) (triples--select db 1 nil nil nil '(subject
predicate)))))))
-
-(ert-deftest triples-test-select-with-pred-prefix ()
- (triples-test-with-temp-db
- (triples--insert db 'sub1 'pred/foo 'obj)
- (triples--insert db 'sub1 'pred/bar 'obj)
- (triples--insert db 'sub2 'pred/foo 'obj)
- (should (equal (triples-test-list-sort (triples--select-pred-prefix db
'sub1 'pred))
- (triples-test-list-sort '((sub1 pred/foo obj nil)
- (sub1 pred/bar obj nil)))))))
-
-(ert-deftest triples-test-select-predicate-object-fragment ()
- (triples-test-with-temp-db
- (triples--insert db 'sub1 'pred/foo "a whole phrase")
- (should (equal (triples--select-predicate-object-fragment db 'pred/foo
"whole")
- '((sub1 pred/foo "a whole phrase" nil))))))
+(defun triples-test-insert (mode)
+ (let ((triples--sqlite-interface mode))
+ (triples-test-with-temp-db
+ (triples--insert db "sub" 'pred "obj")
+ (should (equal (triples--select db)
+ '(("sub" pred "obj" nil))))
+ ;; Test that we actually are storing with builtin something compatible
+ ;; with emacsql.
+ (when (eq mode 'builtin)
+ (should (equal (sqlite-select db "SELECT * FROM triples")
+ '(("\"sub\"" "pred" "\"obj\"" "()")))))
+ ;; Test that it replaces - this shouldn't result in two rows.
+ (triples--insert db "sub" 'pred "obj")
+ (should (= (length (triples--select db)) 1))
+ ;; Test that colons in the predicate are stripped away when stored.
+ (triples--insert db "sub" :test/pred "obj")
+ (should (= (length (triples--select db nil 'test/pred)) 1))
+ ;; Test we correctly test for bad inputs.
+ (should-error (triples--insert db "sub" "pred" "obj"))
+ (should-error (triples--insert db "sub" 'pred "obj" '(ordinary-list)))
+ (should-error (triples--insert db "sub" 'pred "obj" "string"))
+ ;; Test that we can have symbol subject and objects
+ (triples--insert db 'sub 'pred 'obj)
+ (should (equal
+ (triples--select db 'sub)
+ '((sub pred obj nil)))))))
+
+(ert-deftest triples-test-insert-builtin ()
+ (triples-test-insert 'builtin))
+
+(ert-deftest triples-test-insert-emacsql ()
+ (triples-test-insert 'emacsql))
+
+(defun triples-test-delete (mode)
+ (let ((triples--sqlite-interface mode))
+ (triples-test-with-temp-db
+ (triples--insert db 1 'pred 2)
+ (triples--insert db 2 'pred 1)
+ (triples--delete db 1)
+ (should (= 1 (length (triples--select db))))
+ (should (= 0 (length (triples--select db 1))))
+ (triples--insert db 1 'pred 2)
+ (triples--delete db nil nil 2)
+ (should (= 0 (length (triples--select db nil nil 2))))
+ (triples--insert db 1 'pred 2)
+ (triples--delete db nil 'pred nil)
+ (should (= 0 (length (triples--select db)))))))
+
+(ert-deftest triples-test-delete-builtin ()
+ (triples-test-delete 'builtin))
+
+(ert-deftest triples-test-delete-emacsql ()
+ (triples-test-delete 'emacsql))
+
+(defun triples-test-delete-subject-predicate-prefix (mode)
+ (let ((triples--sqlite-interface mode))
+ (triples-test-with-temp-db
+ (triples--insert db 1 'test/foo 2)
+ (triples--insert db 1 'bar/bar 1)
+ (triples--delete-subject-predicate-prefix db 1 'test)
+ (should (= 1 (length (triples--select db))))
+ ;; Make sure colons are stripped.
+ (triples--delete-subject-predicate-prefix db 1 :bar)
+ (should (= 0 (length (triples--select db)))))))
+
+(ert-deftest triples-test-delete-subject-predicate-prefix-builtin ()
+ (triples-test-delete-subject-predicate-prefix 'builtin))
+
+(ert-deftest triples-test-delete-subject-predicate-prefix-emacsql ()
+ (triples-test-delete-subject-predicate-prefix 'emacsql))
+
+(defun triples-test-select (mode)
+ (let ((triples--sqlite-interface mode))
+ (triples-test-with-temp-db
+ (triples--insert db 1 'pred 2 '(:a 1))
+ (let ((expected '((1 pred 2 (:a 1)))))
+ (should (equal (triples--select db 1) expected))
+ (should (equal (triples--select db nil 'pred) expected))
+ (should (equal (triples--select db nil nil 2) expected))
+ (should (equal (triples--select db 1 nil 2) expected))
+ (should (equal (triples--select db 1 'pred 2) expected))
+ (should (equal '((1)) (triples--select db 1 nil nil nil '(subject))))
+ (should (equal '((1 pred)) (triples--select db 1 nil nil nil '(subject
predicate))))))))
+
+(ert-deftest triples-test-select-builtin ()
+ (triples-test-select 'builtin))
+
+(ert-deftest triples-test-select-emacsql ()
+ (triples-test-select 'emacsql))
+
+(defun triples-test-select-with-pred-prefix (mode)
+ (let ((triples--sqlite-interface mode))
+ (triples-test-with-temp-db
+ (triples--insert db 'sub1 'pred/foo 'obj)
+ (triples--insert db 'sub1 'pred/bar 'obj)
+ (triples--insert db 'sub2 'pred/foo 'obj)
+ (should (equal (triples-test-list-sort (triples--select-pred-prefix db
'sub1 'pred))
+ (triples-test-list-sort '((sub1 pred/foo obj nil)
+ (sub1 pred/bar obj nil))))))))
+
+(ert-deftest triples-test-select-with-pred-prefix-builtin ()
+ (triples-test-select 'builtin))
+
+(ert-deftest triples-test-select-with-pred-prefix-emacsql ()
+ (triples-test-select 'emacsql))
+
+(defun triples-test-select-predicate-object-fragment (mode)
+ (let ((triples--sqlite-interface mode))
+ (triples-test-with-temp-db
+ (triples--insert db 'sub1 'pred/foo "a whole phrase")
+ (should (equal (triples--select-predicate-object-fragment db 'pred/foo
"whole")
+ '((sub1 pred/foo "a whole phrase" nil)))))))
+
+(ert-deftest triples-test-select-predicate-object-fragment-builtin ()
+ (triples-test-select-predicate-object-fragment 'builtin))
+
+(ert-deftest triples-test-select-predicate-object-fragment-emacsql ()
+ (triples-test-select-predicate-object-fragment 'emacsql))
+
+;; After this we don't bother testing both with emacsql and the builtin sqlite,
+;; since if the functions tested above work, it should also work for both.
(defun triples-test-op-equals (result target)
(and (equal (car result) (car target))
diff --git a/triples.el b/triples.el
index 37c51e7e3c..ca42b023db 100644
--- a/triples.el
+++ b/triples.el
@@ -4,7 +4,7 @@
;; Author: Andrew Hyatt <ahyatt@gmail.com>
;; Homepage: https://github.com/ahyatt/triples
-;; Package-Requires: ((seq "2.0") (emacs29))
+;; Package-Requires: ((seq "2.0"))
;; Keywords: triples, kg, data, sqlite
;; Version: 0.0
;; This program is free software; you can redistribute it and/or
@@ -25,6 +25,9 @@
;; triples: subject, predicate, objects, plus some extra metadata. This data
;; structure provides a way to store data according to an extensible schema,
and
;; provide an API offering two-way links between all information stored.
+;;
+;; This package requires either emacs 29 or the emacsql package to be
installed.
+
(require 'cl-macs)
(require 'seq)
@@ -32,33 +35,58 @@
;;; Code:
+(defvar triples-sqlite-interface 'builtin
+ "The interface to sqlite to use.
+Either `builtin' or `emacsql'. This only is used when the version
+is emacs 29 or greater (when builtin is available) and emacsql
+package is installed, otherwise triples will just use what is
+available.")
+
+(defun triples--sqlite-interface ()
+ "Return the sqlite interface to use.
+See the `triples-sqlite-interface' variable for more information.
+This will return either `builtin' or `emacsql'."
+ (if (and (>= emacs-major-version 29)
+ (featurep 'emacsql))
+ triples-sqlite-interface
+ (if (>= emacs-major-version 29) 'builtin 'emacsql)))
+
(defun triples-connect (file)
"Connect to the database FILE and make sure it is populated."
- (let* ((db (sqlite-open file)))
- (sqlite-execute db "CREATE TABLE IF NOT EXISTS triples(subject TEXT NOT
NULL, predicate TEXT NOT NULL, object TEXT, properties TEXT NOT NULL)")
- (sqlite-execute db "CREATE INDEX IF NOT EXISTS subject_idx ON triples
(subject)")
- (sqlite-execute db "CREATE INDEX IF NOT EXISTS subject_predicate_idx ON
triples (subject, predicate)")
- (sqlite-execute db "CREATE INDEX IF NOT EXISTS predicate_object_idx ON
triples (predicate, object)")
- (sqlite-execute db "CREATE UNIQUE INDEX IF NOT EXISTS
subject_predicate_object_properties_idx ON triples (subject, predicate, object,
properties)")
- db))
+ (unless (or (>= emacs-major-version 29)
+ (featurep 'emacsql))
+ (error "The triples package requires either emacs 29 or the emacsql
package to be installed."))
+ (pcase (triples--sqlite-interface)
+ ('builtin (let* ((db (sqlite-open file)))
+ (sqlite-execute db "CREATE TABLE IF NOT EXISTS triples(subject
TEXT NOT NULL, predicate TEXT NOT NULL, object TEXT, properties TEXT NOT NULL)")
+ (sqlite-execute db "CREATE INDEX IF NOT EXISTS subject_idx ON
triples (subject)")
+ (sqlite-execute db "CREATE INDEX IF NOT EXISTS
subject_predicate_idx ON triples (subject, predicate)")
+ (sqlite-execute db "CREATE INDEX IF NOT EXISTS
predicate_object_idx ON triples (predicate, object)")
+ (sqlite-execute db "CREATE UNIQUE INDEX IF NOT EXISTS
subject_predicate_object_properties_idx ON triples (subject, predicate, object,
properties)")
+ db))
+ ('emacsql
+ (require 'emacsql)
+ (let* ((db (emacsql-sqlite file))
+ (triple-table-exists
+ (emacsql db [:select name
+ :from sqlite_master
+ :where (= type table) :and (= name
'triples)])))
+ (unless triple-table-exists
+ (emacsql db [:create-table triples ([(subject text :not-null)
+ (predicate text :not-null)
+ (object :not-null)
+ (properties)])])
+ (emacsql db [:create-index subject_idx :on triples [subject]])
+ (emacsql db [:create-index subject_predicate_idx :on triples [subject
predicate]])
+ (emacsql db [:create-index predicate_object_idx :on triples
[predicate object]])
+ (emacsql db [:create-unique-index
subject_predicate_object_properties_idx :on triples [subject predicate object
properties]]))
+ db))))
(defun triples-close (db)
"Close sqlite database DB."
- (sqlite-close db))
-
-(defun triples--subjects (triples)
- "Return all unique subjects in TRIPLES."
- (seq-uniq (mapcar #'car triples)))
-
-(defun triples--group-by-subjects (triples)
- "Return an alist of subject to TRIPLES with that subject."
- (let ((subj-to-triples (make-hash-table :test #'equal)))
- (dolist (triple triples)
- (puthash (car triple)
- (cons triple (gethash (car triple) subj-to-triples))
- subj-to-triples))
- (cl-loop for k being the hash-keys of subj-to-triples using (hash-values v)
- collect (cons k v))))
+ (pcase (triples--sqlite-interface)
+ ('builtin (sqlite-close db))
+ ('emacsql (emacsql-close db))))
(defun triples--decolon (sym)
"Remove colon from SYM."
@@ -103,76 +131,151 @@ normal schema checks, so should not be called from
client programs."
(error "Predicates in triples must always be symbols"))
(unless (plistp properties)
(error "Properties stored must always be plists"))
- (sqlite-execute db "REPLACE INTO TRIPLES VALUES (?, ?, ?, ?)"
- (list (triples-standardize-val subject)
- (triples-standardize-val (triples--decolon predicate))
- (triples-standardize-val object)
- ;; Properties cannot be null, since in sqlite each
null value
- ;; is distinct from each other, so replace would not
replace
- ;; duplicate triples each with null properties.
- (triples-standardize-val properties))))
+ (pcase (triples--sqlite-interface)
+ ('builtin
+ (sqlite-execute db "REPLACE INTO triples VALUES (?, ?, ?, ?)"
+ (list (triples-standardize-val subject)
+ (triples-standardize-val (triples--decolon
predicate))
+ (triples-standardize-val object)
+ ;; Properties cannot be null, since in sqlite each
null value
+ ;; is distinct from each other, so replace would
not replace
+ ;; duplicate triples each with null properties.
+ (triples-standardize-val properties))))
+ ('emacsql
+ (emacsql db [:replace :into triples :values $v1]
+ [subject predicate object (triples-standardize-val
properties)]))))
+
+(defun triples--emacsql-andify (wc)
+ "In emacsql where clause WC, insert `:and' between query elements.
+Returns the new list with the added `:and.'s. The first element
+MUST be there `:where' clause. This does reverse the clause
+elements, but it shouldn't matter."
+ (cons (car wc) ;; the :where clause
+ (let ((clauses (cdr wc))
+ (result))
+ (while clauses
+ (push (car clauses) result)
+ (if (cdr clauses) (push :and result))
+ (setq clauses (cdr clauses)))
+ result)))
(defun triples--delete (db &optional subject predicate object properties)
"Delete triples matching SUBJECT, PREDICATE, OBJECT, PROPERTIES.
If any of these are nil, they will not selected for. If you set
all to nil, everything will be deleted, so be careful!"
- (sqlite-execute
- db
- (concat "DELETE FROM TRIPLES"
- (when (or subject predicate object properties)
- (concat " WHERE "
- (string-join
- (seq-filter #'identity
- (list (when subject "SUBJECT = ?")
- (when predicate "PREDICATE = ?")
- (when object "OBJECT = ?")
- (when properties "PROPERTIES = ?")))
- " AND "))))
- (mapcar #'triples-standardize-val (seq-filter #'identity (list subject
predicate object properties)))))
+ (pcase (triples--sqlite-interface)
+ ('builtin (sqlite-execute
+ db
+ (concat "DELETE FROM triples"
+ (when (or subject predicate object properties)
+ (concat " WHERE "
+ (string-join
+ (seq-filter #'identity
+ (list (when subject "SUBJECT =
?")
+ (when predicate "PREDICATE
= ?")
+ (when object "OBJECT = ?")
+ (when properties
"PROPERTIES = ?")))
+ " AND "))))
+ (mapcar #'triples-standardize-val (seq-filter #'identity (list
subject predicate object properties)))))
+ ('emacsql (emacsql db
+ (apply #'vector
+ (append '(:delete :from triples)
+ (when (or subject predicate object
properties)
+ (triples--emacsql-andify
+ (append
+ '(:where)
+ (when subject '((= subject $s1)))
+ (when predicate '((= predicate $r2)))
+ (when object '((= object $s3)))
+ (when properties '((= properties
$r4))))))))
+ (seq-filter #'identity (list subject predicate object
properties))))))
(defun triples--delete-subject-predicate-prefix (db subject pred-prefix)
"Delete triples matching SUBJECT and predicates with PRED-PREFIX."
(unless (symbolp pred-prefix)
(error "Predicates in triples must always be symbols"))
- (sqlite-execute db "DELETE FROM TRIPLES WHERE subject = ? AND predicate LIKE
?"
+ (pcase (triples--sqlite-interface)
+ ('builtin (sqlite-execute db "DELETE FROM triples WHERE subject = ? AND
predicate LIKE ?"
(list (triples-standardize-val subject)
(format "%s/%%" (triples--decolon pred-prefix)))))
+ ('emacsql (emacsql db [:delete :from triples :where (= subject $s1) :and
(like predicate $r2)]
+ subject (format "%s/%%" (triples--decolon
pred-prefix))))))
(defun triples--select-pred-prefix (db subject pred-prefix)
"Return rows matching SUBJECT and PRED-PREFIX."
- (mapcar (lambda (row) (mapcar #'triples-standardize-result row))
+ (pcase (triples--sqlite-interface)
+ ('builtin (mapcar (lambda (row) (mapcar #'triples-standardize-result row))
(sqlite-select db "SELECT * FROM triples WHERE subject = ? AND
predicate LIKE ?"
(list (triples-standardize-val subject)
(format "%s/%%" pred-prefix)))))
+ ('emacsql (emacsql db [:select * :from triples :where (= subject $s1) :and
(like predicate $r2)]
+ subject (format "%s/%%" pred-prefix)))))
(defun triples--select-predicate-object-fragment (db predicate object-fragment)
"Return rows with PREDICATE and with OBJECT-FRAGMENT in object."
- (mapcar (lambda (row) (mapcar #'triples-standardize-result row))
- (sqlite-select db "SELECT * from triples WHERE predicate = ? AND
object LIKE ?"
- (list (triples-standardize-val predicate)
- (format "%%%s%%" object-fragment)))))
+ (pcase (triples--sqlite-interface)
+ ('builtin (mapcar (lambda (row) (mapcar #'triples-standardize-result row))
+ (sqlite-select db "SELECT * from triples WHERE predicate
= ? AND object LIKE ?"
+ (list (triples-standardize-val predicate)
+ (format "%%%s%%"
object-fragment)))))
+ ('emacsql (emacsql db [:select * :from triples :where (= predicate $r1)
:and (like object $s2)]
+ predicate (format "%%%s%%" object-fragment)))))
(defun triples--select (db &optional subject predicate object properties
selector)
"Return rows matching SUBJECT, PREDICATE, OBJECT, PROPERTIES.
If any of these are nil, they are not included in the select
statement. The SELECTOR is list of symbols subject, precicate,
object, properties to retrieve or nil for *."
- (mapcar (lambda (row) (mapcar #'triples-standardize-result row))
- (sqlite-select db
- (concat "SELECT "
- (if selector
- (mapconcat (lambda (e) (format "%s" e))
selector ", ")
- "*") " FROM triples"
- (when (or subject predicate object
properties)
- (concat " WHERE "
- (string-join
- (seq-filter #'identity
- (list (when subject
"SUBJECT = ?")
- (when
predicate "PREDICATE = ?")
- (when object
"OBJECT = ?")
- (when
properties "PROPERTIES = ?")))
- " AND "))))
- (mapcar #'triples-standardize-val (seq-filter
#'identity (list subject predicate object properties))))))
+ (pcase (triples--sqlite-interface)
+ ('builtin (mapcar (lambda (row) (mapcar #'triples-standardize-result row))
+ (sqlite-select db
+ (concat "SELECT "
+ (if selector
+ (mapconcat (lambda (e)
(format "%s" e)) selector ", ")
+ "*") " FROM triples"
+ (when (or subject predicate
object properties)
+ (concat " WHERE "
+ (string-join
+ (seq-filter #'identity
+ (list
(when subject "SUBJECT = ?")
+
(when predicate "PREDICATE = ?")
+
(when object "OBJECT = ?")
+
(when properties "PROPERTIES = ?")))
+ " AND "))))
+ (mapcar #'triples-standardize-val
(seq-filter #'identity (list subject predicate object properties))))))
+ ('emacsql (emacsql db (apply #'vector
+ (append `(:select
+ ,(if selector
+ (mapconcat (lambda (e) (format
"%s" e)) selector ", ")
+ '*)
+ :from triples)
+ (when (or subject predicate object
properties)
+ (triples--emacsql-andify
+ (append
+ '(:where)
+ (when subject '((= subject $s1)))
+ (when predicate '((= predicate
$r2)))
+ (when object '((= object $s3)))
+ (when properties '((= properties
$r4))))))))
+ (seq-filter #'identity (list subject predicate object
properties))))))
+
+;; Code after this point should not call sqlite or emacsql directly. If any
more
+;; calls are needed, put them in a defun, make it work for sqlite and emacsql,
+;; and put them above.
+
+(defun triples--subjects (triples)
+ "Return all unique subjects in TRIPLES."
+ (seq-uniq (mapcar #'car triples)))
+
+(defun triples--group-by-subjects (triples)
+ "Return an alist of subject to TRIPLES with that subject."
+ (let ((subj-to-triples (make-hash-table :test #'equal)))
+ (dolist (triple triples)
+ (puthash (car triple)
+ (cons triple (gethash (car triple) subj-to-triples))
+ subj-to-triples))
+ (cl-loop for k being the hash-keys of subj-to-triples using (hash-values v)
+ collect (cons k v))))
(defun triples--add (db op)
"Perform OP on DB."
- [elpa] externals/triples 7d5aca3bb8 02/19: Fix issue where single element lists were not being treated as lists., (continued)
- [elpa] externals/triples 7d5aca3bb8 02/19: Fix issue where single element lists were not being treated as lists., ELPA Syncer, 2022/11/05
- [elpa] externals/triples 6f8f3376f1 06/19: Add `triples-set-types'., ELPA Syncer, 2022/11/05
- [elpa] externals/triples 5e8abd2989 01/19: Initial commit of triples module., ELPA Syncer, 2022/11/05
- [elpa] externals/triples cc5629fe5c 07/19: Wrap all database access in `triples-set-types' in a transaction., ELPA Syncer, 2022/11/05
- [elpa] externals/triples 257de87fdc 08/19: Minor code cleanup., ELPA Syncer, 2022/11/05
- [elpa] externals/triples 4627d6ed6d 10/19: Fix minor mistakes in ert tests., ELPA Syncer, 2022/11/05
- [elpa] externals/triples fb63dfe44a 12/19: Convert to sqlite., ELPA Syncer, 2022/11/05
- [elpa] externals/triples 0252dad7d1 17/19: Fixes from code review from Stefan Monnier., ELPA Syncer, 2022/11/05
- [elpa] externals/triples d17b3d6e17 19/19: Merge branch 'combined'., ELPA Syncer, 2022/11/05
- [elpa] externals/triples 2dae3d49b9 18/19: Various fixes for emacsql code, which wasn't being tested correctly., ELPA Syncer, 2022/11/05
- [elpa] externals/triples 6afcb290ca 15/19: Support both emacs 29 sqlite and emacsql.,
ELPA Syncer <=
- [elpa] externals/triples aca95ba7f3 03/19: Ensure that we don't duplicate triples., ELPA Syncer, 2022/11/05
- [elpa] externals/triples d82cc1d6b8 14/19: Finish basic sqlite layer, and fix everything so tests work., ELPA Syncer, 2022/11/05
- [elpa] externals/triples 8d7d3c13f4 05/19: Make the combined to and from functions public., ELPA Syncer, 2022/11/05
- [elpa] externals/triples ad6e329540 04/19: Fix for ert tests broken by the last commit., ELPA Syncer, 2022/11/05
- [elpa] externals/triples fdbbd5f61d 09/19: Added package-requires., ELPA Syncer, 2022/11/05
- [elpa] externals/triples cef7ad3a81 11/19: Remove emacs requirement for now., ELPA Syncer, 2022/11/05
- [elpa] externals/triples cca16121d9 13/19: Fix bugs in `triples-remove-type'., ELPA Syncer, 2022/11/05
- [elpa] externals/triples 3593f55dfb 16/19: Support numbers stored via emacsql., ELPA Syncer, 2022/11/05