branch: externals/triples
commit 5e17182a5374a1656761bb26832fd21c6f168685
Author: Andrew Hyatt <ahy...@gmail.com>
Commit: GitHub <nore...@github.com>

    Add a float type and document types (#18)
---
 README.org      |  2 ++
 triples-test.el | 72 ++++++++++++++++++++++++++++++++++++++++++---------------
 triples.el      | 11 +++++----
 3 files changed, 61 insertions(+), 24 deletions(-)

diff --git a/README.org b/README.org
index 1e4112e24b..34b6151183 100644
--- a/README.org
+++ b/README.org
@@ -29,6 +29,8 @@ This adds a type called =person=, which can be set on any 
entity.  There's anoth
 
 The =person= has 2 properties, =name=, and =age=.  They are both marked as 
unique, so they take a single value, not a list.  If =:base/unique= was not 
true, the value would be a list.  We also specify what type it is, which can be 
any elisp type.  =employee= is similarly constructed, but has an interesting 
property, =reportees=, which is a =base/virtual-reversed= property, meaning 
that it is supplied with values, but rather can get them from the reversed 
relation of =employee/manager=.
 
+A valid =base/type= maps to elisp types, so can be values such as =integer=, 
=float=, =vector=, =cons=, =symbol=, or =string=.
+
 We'll explore how these types are used can be used in the section after next.
 ** The triples concept
 A triple is a unit of data consisting of a /subject/, a /predicate/, an 
/object/, and, optionally, internal metadata about the unit.  The triple can be 
thought of as a link between the subject and object via the predicate.
diff --git a/triples-test.el b/triples-test.el
index 39c9ef6021..590f11aa27 100644
--- a/triples-test.el
+++ b/triples-test.el
@@ -155,21 +155,45 @@
     ;; this will fail.
     (should (= 0 (length (triples-db-select-pred-op db :person/age '> 
1000))))))
 
+(triples-deftest triples-test-db-select-pred-op-float ()
+  (triples-test-with-temp-db
+    (triples-add-schema db 'measurement '(value :base/unique t :base/type 
float))
+    (triples-set-subject db 'm1 '(measurement :value 20.5))
+    (triples-set-subject db 'm2 '(measurement :value 40.25))
+    (triples-set-subject db 'm3 '(measurement :value 60.0))
+    (should (= 2 (length (triples-db-select-pred-op db :measurement/value '> 
20.5))))
+    (should (= 3 (length (triples-db-select-pred-op db :measurement/value '>= 
20.5))))
+    (should (= 2 (length (triples-db-select-pred-op db :measurement/value '!= 
20.5))))
+    (should (= 3 (length (triples-db-select-pred-op db :measurement/value '!= 
30.0))))
+    (should (= 1 (length (triples-db-select-pred-op db :measurement/value '= 
20.5))))
+    (should (= 2 (length (triples-db-select-pred-op db :measurement/value '< 
60.0))))
+    (should (= 3 (length (triples-db-select-pred-op db :measurement/value '<= 
60.0))))
+    (should (= 0 (length (triples-db-select-pred-op db :measurement/value '> 
60.0))))
+    ;; Test with a value that might cause issues if treated as string
+    (should (= 0 (length (triples-db-select-pred-op db :measurement/value '> 
100.75))))))
+
+(ert-deftest triples-test-symbols ()
+  (triples-test-with-temp-db
+    (triples-add-schema db 'enum '(value :base/unique t :base/type symbol))
+    (triples-set-type db 'foo 'enum :value 'bar)
+    (should (equal '(:value bar) (triples-get-type db 'foo 'enum)))))
+
 (ert-deftest triples-test-builtin-emacsql-compat ()
   (cl-loop for subject in '(1 a "a") do
            (let ((triples-sqlite-interface 'builtin))
              (triples-test-with-temp-db
                (triples-add-schema db 'person
                                    '(name :base/unique t :base/type string)
-                                   '(age :base/unique t :base/type integer))
-               (triples-set-type db subject 'person :name "Alice Aardvark" 
:age 41)
-               (should (equal (triples-get-type db subject 'person)
-                              '(:age 41 :name "Alice Aardvark")))
+                                   '(age :base/unique t :base/type integer)
+                                   '(temperature :base/unique t :base/type 
float))
+               (triples-set-type db subject 'person :name "Alice Aardvark" 
:age 41 :temperature 36.6)
+               (should (equal (triples-test-plist-sort (triples-get-type db 
subject 'person))
+                              (triples-test-plist-sort '(:age 41 :name "Alice 
Aardvark" :temperature 36.6))))
                (triples-close db)
                (let* ((triples-sqlite-interface 'emacsql)
                       (db (triples-connect db-file)))
-                 (should (equal (triples-get-type db subject 'person)
-                                '(:age 41 :name "Alice Aardvark")))
+                 (should (equal (triples-test-plist-sort (triples-get-type db 
subject 'person))
+                                (triples-test-plist-sort '(:age 41 :name 
"Alice Aardvark" :temperature 36.6))))
                  (triples-close db))
                ;; Just so the last close will work.
                (setq db (triples-connect db-file))))))
@@ -180,15 +204,16 @@
              (triples-test-with-temp-db
                (triples-add-schema db 'person
                                    '(name :base/unique t :base/type string)
-                                   '(age :base/unique t :base/type integer))
-               (triples-set-type db subject 'person :name "Alice Aardvark" 
:age 41)
-               (should (equal (triples-get-type db subject 'person)
-                              '(:age 41 :name "Alice Aardvark")))
+                                   '(age :base/unique t :base/type integer)
+                                   '(temperature :base/unique t :base/type 
float))
+               (triples-set-type db subject 'person :name "Alice Aardvark" 
:age 41 :temperature 36.6)
+               (should (equal (triples-test-plist-sort (triples-get-type db 
subject 'person))
+                              (triples-test-plist-sort '(:age 41 :name "Alice 
Aardvark" :temperature 36.6))))
                (triples-close db)
                (let* ((triples-sqlite-interface 'builtin)
                       (db (triples-connect db-file)))
-                 (should (equal (triples-get-type db subject 'person)
-                                '(:age 41 :name "Alice Aardvark")))
+                 (should (equal (triples-test-plist-sort (triples-get-type db 
subject 'person))
+                                (triples-test-plist-sort '(:age 41 :name 
"Alice Aardvark" :temperature 36.6))))
                  (triples-close db))
                ;; Just so the last close will work.
                (setq db (triples-connect db-file))))))
@@ -273,9 +298,17 @@
               ("Bert" named/alias "Bert" (:index 0))
               ("Bert" named/alias "Berty" (:index 1)))))))
 
+(defun triples-test-plist-sort (plist)
+  "Sort PLIST in a standard way, for comparison."
+  (kvalist->plist
+   (kvalist-sort (kvplist->alist plist)
+                 (lambda (a b) (string< (format "%s" a) (format "%s" b))))))
+
 (ert-deftest triples-schema-compliant ()
   (let ((pal '((named/name :base/type string :base/unique t)
                (named/alternate-names :base/type string :base/unique nil)
+               (measurement/value :base/type float :base/unique t)
+               (enum/value :base/type symbol :base/unique t)
                ;; Alias doesn't specify base/unique or base/type, so anything 
is fine.
                (named/alias))))
     (should (triples-verify-schema-compliant '(("foo" named/name "bar")) pal))
@@ -285,13 +318,14 @@
     (should-error (triples-verify-schema-compliant '(("foo" 
named/alternate-names "bar" nil)) pal))
     (should (triples-verify-schema-compliant '(("foo" named/alias "bar" nil)) 
pal))
     (should (triples-verify-schema-compliant '(("foo" named/alias 5 nil)) pal))
-    (should (triples-verify-schema-compliant '(("foo" named/alias 5 (:index 
0))) pal))))
-
-(defun triples-test-plist-sort (plist)
-  "Sort PLIST in a standard way, for comparison."
-  (kvalist->plist
-   (kvalist-sort (kvplist->alist plist)
-                 (lambda (a b) (string< (format "%s" a) (format "%s" b))))))
+    (should (triples-verify-schema-compliant '(("foo" named/alias 5 (:index 
0))) pal))
+    ;; Integers are not floats, so cannot be used for float values.
+    (should-error (triples-verify-schema-compliant '(("m1" measurement/value 
36)) pal))
+    (should (triples-verify-schema-compliant '(("m1" measurement/value 36.6)) 
pal))
+    (should-error (triples-verify-schema-compliant '(("m1" measurement/value 
"not-a-float")) pal))
+    (should-error (triples-verify-schema-compliant '(("m1" measurement/value 
36.6 (:index 0))) pal))
+    (should-error (triples-verify-schema-compliant '(("foo" enum/value 
"mysymbol")) pal))
+    (should (triples-verify-schema-compliant '(("foo" enum/value mysymbol)) 
pal))))
 
 (ert-deftest triples-crud ()
   (triples-test-with-temp-db
diff --git a/triples.el b/triples.el
index 4eb1412295..63d20b447a 100644
--- a/triples.el
+++ b/triples.el
@@ -1,12 +1,12 @@
 ;;; triples.el --- A flexible triple-based database for use in apps  -*- 
lexical-binding: t; -*-
 
-;; Copyright (c) 2022, 2023  Free Software Foundation, Inc.
+;; Copyright (c) 2022-2025  Free Software Foundation, Inc.
 
 ;; Author: Andrew Hyatt <ahy...@gmail.com>
 ;; Homepage: https://github.com/ahyatt/triples
 ;; Package-Requires: ((seq "2.0") (emacs "28.1"))
 ;; Keywords: triples, kg, data, sqlite
-;; Version: 0.5.1
+;; Version: 0.6.0
 ;; This program is free software; you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
 ;; published by the Free Software Foundation; either version 2 of the
@@ -205,6 +205,7 @@ to be stringified."
       ;; what it would be turned into by the pcase above.
       ((pred null) "()")
       ((pred integerp) val)
+      ((pred floatp) val)
       (_ (format "%S" val)))))
 
 (defun triples-standardize-result (result)
@@ -328,9 +329,9 @@ If LIMIT is a positive integer, limit the results to that 
number."
                (sqlite-select
                 db
                 (concat "SELECT * FROM triples WHERE predicate = ? AND  "
-                        (if (numberp val)
-                            "CAST(object AS INTEGER) "
-                          "object COLLATE NOCASE ")
+                        (cond ((integerp val) "CAST(object AS INTEGER) ")
+                              ((floatp val) "CAST(object AS REAL) ")
+                              (t "object COLLATE NOCASE "))
                         (symbol-name op) " ?"
                         (when properties " AND properties = ?")
                         (when (and limit (> limit 0)) (format " LIMIT %d" 
limit)))

Reply via email to