Fix :db.unique/value, which should be per (a, v) pair, not per v-value.
This commit is contained in:
parent
1b1cc6f18e
commit
a1cc372d43
3 changed files with 43 additions and 17 deletions
|
@ -264,13 +264,12 @@
|
|||
(<? (<insert-fulltext-value db v))
|
||||
v)]
|
||||
(<? (exec
|
||||
["INSERT INTO datoms VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" e a v tx
|
||||
["INSERT INTO datoms VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" e a v tx
|
||||
tag ;; value_type_tag
|
||||
(ds/indexing? schema a) ;; index_avet
|
||||
(ds/ref? schema a) ;; index_vaet
|
||||
fulltext? ;; index_fulltext
|
||||
(ds/unique-value? schema a) ;; unique_value
|
||||
(ds/unique-identity? schema a) ;; unique_identity
|
||||
(ds/unique? schema a) ;; unique_value
|
||||
])))
|
||||
(if fulltext?
|
||||
(<? (exec
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
value_type_tag SMALLINT NOT NULL,
|
||||
index_avet TINYINT NOT NULL DEFAULT 0, index_vaet TINYINT NOT NULL DEFAULT 0,
|
||||
index_fulltext TINYINT NOT NULL DEFAULT 0,
|
||||
unique_value TINYINT NOT NULL DEFAULT 0, unique_identity TINYINT NOT NULL DEFAULT 0)"
|
||||
unique_value TINYINT NOT NULL DEFAULT 0)"
|
||||
"CREATE INDEX idx_datoms_eavt ON datoms (e, a, value_type_tag, v)"
|
||||
"CREATE INDEX idx_datoms_aevt ON datoms (a, e, value_type_tag, v)"
|
||||
|
||||
|
@ -38,16 +38,11 @@
|
|||
;; exclusive.
|
||||
"CREATE INDEX idx_datoms_fulltext ON datoms (value_type_tag, v, a, e) WHERE index_fulltext IS NOT 0"
|
||||
|
||||
;; TODO: possibly remove this index. :db.unique/value should be asserted by the transactor in
|
||||
;; all cases, but the index may speed up some of SQLite's query planning. For now, it services
|
||||
;; to validate the transactor implementation. Note that tag is needed here, since we could have
|
||||
;; a keyword (stored as ":foo") that overlaps a string value ":foo".
|
||||
"CREATE UNIQUE INDEX idx_datoms_unique_value ON datoms (value_type_tag, v) WHERE unique_value IS NOT 0"
|
||||
;; TODO: possibly remove this index. :db.unique/identity should be asserted by the transactor in
|
||||
;; all cases, but the index may speed up some of SQLite's query planning. For now, it serves to
|
||||
;; validate the transactor implementation. Note that tag is needed here to differentiate, e.g.,
|
||||
;; keywords and strings.
|
||||
"CREATE UNIQUE INDEX idx_datoms_unique_identity ON datoms (a, value_type_tag, v) WHERE unique_identity IS NOT 0"
|
||||
;; TODO: possibly remove this index. :db.unique/{value,identity} should be asserted by the
|
||||
;; transactor in all cases, but the index may speed up some of SQLite's query planning. For now,
|
||||
;; it serves to validate the transactor implementation. Note that tag is needed here to
|
||||
;; differentiate, e.g., keywords and strings.
|
||||
"CREATE UNIQUE INDEX idx_datoms_unique_value ON datoms (a, value_type_tag, v) WHERE unique_value IS NOT 0"
|
||||
|
||||
"CREATE TABLE transactions (e INTEGER NOT NULL, a SMALLINT NOT NULL, v BLOB NOT NULL, tx INTEGER NOT NULL, added TINYINT NOT NULL DEFAULT 1, value_type_tag SMALLINT NOT NULL)"
|
||||
"CREATE INDEX idx_transactions_tx ON transactions (tx)"
|
||||
|
@ -65,17 +60,17 @@
|
|||
|
||||
;; A view transparently interpolating fulltext indexed values into the datom structure.
|
||||
"CREATE VIEW fulltext_datoms AS
|
||||
SELECT e, a, fulltext_values.text AS v, tx, value_type_tag, index_avet, index_vaet, index_fulltext, unique_value, unique_identity
|
||||
SELECT e, a, fulltext_values.text AS v, tx, value_type_tag, index_avet, index_vaet, index_fulltext, unique_value
|
||||
FROM datoms, fulltext_values
|
||||
WHERE datoms.index_fulltext IS NOT 0 AND datoms.v = fulltext_values.rowid"
|
||||
|
||||
;; A view transparently interpolating all entities (fulltext and non-fulltext) into the datom structure.
|
||||
"CREATE VIEW all_datoms AS
|
||||
SELECT e, a, v, tx, value_type_tag, index_avet, index_vaet, index_fulltext, unique_value, unique_identity
|
||||
SELECT e, a, v, tx, value_type_tag, index_avet, index_vaet, index_fulltext, unique_value
|
||||
FROM datoms
|
||||
WHERE index_fulltext IS 0
|
||||
UNION ALL
|
||||
SELECT e, a, v, tx, value_type_tag, index_avet, index_vaet, index_fulltext, unique_value, unique_identity
|
||||
SELECT e, a, v, tx, value_type_tag, index_avet, index_vaet, index_fulltext, unique_value
|
||||
FROM fulltext_datoms"
|
||||
|
||||
;; Materialized views of the schema.
|
||||
|
|
|
@ -657,3 +657,35 @@
|
|||
|
||||
(finally
|
||||
(<? (d/<close conn))))))))))
|
||||
|
||||
(deftest-async test-unique-value
|
||||
(with-tempfile [t (tempfile)]
|
||||
(let [conn (<? (d/<connect t))]
|
||||
(try
|
||||
(let [tx0 (:tx (<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1)
|
||||
:db/ident :test/x
|
||||
:db/unique :db.unique/value
|
||||
:db/valueType :db.type/long
|
||||
:db.install/_attribute :db.part/db}
|
||||
{:db/id (d/id-literal :db.part/user -2)
|
||||
:db/ident :test/y
|
||||
:db/unique :db.unique/value
|
||||
:db/valueType :db.type/long
|
||||
:db.install/_attribute :db.part/db}])))]
|
||||
|
||||
(testing "can insert different :db.unique/value attributes with the same value"
|
||||
(let [report1 (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -1) :test/x 12345]]))
|
||||
eid1 (get-in report1 [:tempids (d/id-literal :db.part/user -1)])
|
||||
report2 (<? (d/<transact! conn [[:db/add (d/id-literal :db.part/user -2) :test/y 12345]]))
|
||||
eid2 (get-in report2 [:tempids (d/id-literal :db.part/user -2)])]
|
||||
(is (= (<? (<datoms-after (d/db conn) tx0))
|
||||
#{[eid1 :test/x 12345]
|
||||
[eid2 :test/y 12345]}))))
|
||||
|
||||
(testing "can't upsert a :db.unique/value field"
|
||||
(is (thrown-with-msg?
|
||||
ExceptionInfo #"because of unique constraint"
|
||||
(<? (d/<transact! conn [{:db/id (d/id-literal :db.part/user -1) :test/x 12345 :test/y 99999}]))))))
|
||||
|
||||
(finally
|
||||
(<? (d/<close conn)))))))
|
||||
|
|
Loading…
Reference in a new issue