dalton 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.jrubyrc +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +8 -0
- data/Jarfile +7 -0
- data/README.md +35 -0
- data/Rakefile +7 -0
- data/dalton.gemspec +23 -0
- data/epl-v10.html +261 -0
- data/examples/posts_controller.rb +48 -0
- data/lib/dalton.rb +37 -0
- data/lib/dalton/attribute.rb +16 -0
- data/lib/dalton/connection.rb +104 -0
- data/lib/dalton/database.rb +48 -0
- data/lib/dalton/datomization.rb +18 -0
- data/lib/dalton/entity.rb +75 -0
- data/lib/dalton/exception.rb +68 -0
- data/lib/dalton/transaction_result.rb +33 -0
- data/lib/dalton/translation.rb +65 -0
- data/lib/dalton/undatomization.rb +11 -0
- data/lib/dalton/utility.rb +91 -0
- data/lib/dalton/version.rb +3 -0
- data/spec/dalton/connection_spec.rb +133 -0
- data/spec/dalton/database_spec.rb +85 -0
- data/spec/dalton/datomization_spec.rb +94 -0
- data/spec/dalton/entity_spec.rb +37 -0
- data/spec/dalton/translation_spec.rb +126 -0
- data/spec/dalton/utility_spec.rb +37 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/clojure_equal_matcher.rb +52 -0
- data/spec/support/datomic_context.rb +19 -0
- metadata +101 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
java_import "clojure.lang.Keyword"
|
2
|
+
java_import "clojure.lang.PersistentArrayMap"
|
3
|
+
java_import "clojure.lang.RT"
|
4
|
+
|
5
|
+
module Dalton
|
6
|
+
module Utility
|
7
|
+
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def run_clojure_function(namespaced_function, *arguments)
|
11
|
+
namespace, function_name = namespaced_function.to_s.split('/', 2)
|
12
|
+
namespace && function_name or
|
13
|
+
raise ArgumentError, "Namespaced function required. Got: #{namespaced_function.inspect}"
|
14
|
+
RT.var(namespace, function_name).fn.invoke(*arguments)
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_database_function(db, function_ident, *arguments)
|
18
|
+
function_entity = db.entity(Translation.from_ruby(function_ident))
|
19
|
+
function_entity.fn.invoke(*arguments)
|
20
|
+
end
|
21
|
+
|
22
|
+
def require_clojure(namespace)
|
23
|
+
require_function = RT.var("clojure.core", "require").fn
|
24
|
+
require_function.invoke(Java::ClojureLang::Symbol.intern(namespace))
|
25
|
+
end
|
26
|
+
|
27
|
+
require_clojure('datomic.function')
|
28
|
+
require_clojure('datomic.db')
|
29
|
+
|
30
|
+
def read_edn(edn)
|
31
|
+
readers = PersistentArrayMap.create({Keyword.intern('readers') => PersistentArrayMap.create({
|
32
|
+
Java::ClojureLang::Symbol.intern('db/fn') => RT.var('datomic.function', 'construct'),
|
33
|
+
Java::ClojureLang::Symbol.intern('db/id') => RT.var('datomic.db', 'id-literal')
|
34
|
+
})})
|
35
|
+
|
36
|
+
run_clojure_function("clojure.edn/read-string", readers, edn)
|
37
|
+
end
|
38
|
+
|
39
|
+
def rubify_edn(edn)
|
40
|
+
Translation.from_clj(read_edn(edn))
|
41
|
+
end
|
42
|
+
|
43
|
+
def clojure_equal?(one, other)
|
44
|
+
run_clojure_function('clojure.core/=', one, other)
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_edn(clojure_data)
|
48
|
+
run_clojure_function('clojure.core/pr-str', clojure_data)
|
49
|
+
end
|
50
|
+
|
51
|
+
def sym(s)
|
52
|
+
s = s.to_s if s.is_a? Symbol
|
53
|
+
Java::ClojureLang::Symbol.intern(s)
|
54
|
+
end
|
55
|
+
|
56
|
+
def gensym(s)
|
57
|
+
run_clojure_function('clojure.core/gensym', sym(s))
|
58
|
+
end
|
59
|
+
|
60
|
+
def tempid(partition)
|
61
|
+
Peer.tempid(kw(partition))
|
62
|
+
end
|
63
|
+
|
64
|
+
def gensym(s)
|
65
|
+
run_clojure_function('clojure.core/gensym', sym(s))
|
66
|
+
end
|
67
|
+
|
68
|
+
def kw(k)
|
69
|
+
k = k.to_s if k.is_a? Symbol
|
70
|
+
k = k[1..-1] if k.start_with? ':'
|
71
|
+
Java::ClojureLang::Keyword.intern(k)
|
72
|
+
end
|
73
|
+
|
74
|
+
def list(*items)
|
75
|
+
Dalton::Utility.run_clojure_function("clojure.core/list*", items)
|
76
|
+
end
|
77
|
+
|
78
|
+
def with_meta(value, meta)
|
79
|
+
Dalton::Utility.run_clojure_function("clojure.core/with-meta", value, meta)
|
80
|
+
end
|
81
|
+
|
82
|
+
def meta(value)
|
83
|
+
Dalton::Utility.run_clojure_function("clojure.core/meta", value)
|
84
|
+
end
|
85
|
+
|
86
|
+
def tag(value, tag)
|
87
|
+
with_meta(value, Dalton::Translation.from_ruby(tag: tag))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dalton::Connection do
|
4
|
+
include DatomicContext
|
5
|
+
|
6
|
+
let(:attribute) { :'db/doc' }
|
7
|
+
let(:value) { 'This is a test entity.' }
|
8
|
+
|
9
|
+
let!(:transaction_result) { conn.transact([{:'db/id' => Dalton::Connection.tempid, attribute => value}]) }
|
10
|
+
|
11
|
+
let(:entity_id) { transaction_result.tempids.values.first }
|
12
|
+
|
13
|
+
describe 'data storage, query, and retrieval' do
|
14
|
+
|
15
|
+
let(:query) { [:find, :'?e', :where, [:'?e', attribute, value]] }
|
16
|
+
let(:edn_query) { '[:find ?e :where [?e :db/doc "This is a test entity."]]' }
|
17
|
+
|
18
|
+
describe '#transact(datoms)' do
|
19
|
+
|
20
|
+
it 'stores data' do
|
21
|
+
expect(db.q(query).size).to eq(1)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns a transaction result' do
|
25
|
+
expect(transaction_result.db_before).to be_a(Dalton::Database)
|
26
|
+
expect(transaction_result.db_after).to be_a(Dalton::Database)
|
27
|
+
expect(transaction_result.tx_data).to be_a(Array)
|
28
|
+
expect(transaction_result.tempids).to be_a(Hash)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'refreshes the database' do
|
32
|
+
expect(conn.db).to eq(transaction_result.db_after)
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'errors' do
|
36
|
+
before do
|
37
|
+
# create an attribute with :db.unique/value
|
38
|
+
conn.transact([{:'db/id' => Dalton::Connection.tempid(:'db.part/db'),
|
39
|
+
:'db/ident' => :'user.test/unique-attr',
|
40
|
+
:'db/cardinality' => :'db.cardinality/one',
|
41
|
+
:'db/unique' => :'db.unique/value',
|
42
|
+
:'db/valueType' => :'db.type/string',
|
43
|
+
:'db.install/_attribute' => :'db.part/db'}])
|
44
|
+
|
45
|
+
tempid = Dalton::Connection.tempid(:'db.part/user')
|
46
|
+
conn.transact([[:'db/add', tempid, :'user.test/unique-attr', 'duplicate-value']])
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'in uniqueness' do
|
50
|
+
let(:error) {
|
51
|
+
err = nil
|
52
|
+
tempid = Dalton::Connection.tempid(:'db.part/user')
|
53
|
+
begin
|
54
|
+
conn.transact([[:'db/add', tempid, :'user.test/unique-attr', 'duplicate-value']])
|
55
|
+
rescue Dalton::UniqueConflict => e
|
56
|
+
err = e
|
57
|
+
end
|
58
|
+
err
|
59
|
+
}
|
60
|
+
|
61
|
+
it 'contains useful information' do
|
62
|
+
expect(error).to be_a(Dalton::UniqueConflict)
|
63
|
+
expect(error.attribute).to be(:'user.test/unique-attr')
|
64
|
+
expect(error.value).to eql('duplicate-value')
|
65
|
+
expect(error.existing_id).to be > 0
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'in type' do
|
70
|
+
let(:error) do
|
71
|
+
err = nil
|
72
|
+
tempid = Dalton::Connection.tempid(:'db.part/user')
|
73
|
+
begin
|
74
|
+
conn.transact([[:'db/add', tempid, :'user.test/unique-attr', 5]])
|
75
|
+
rescue Dalton::TypeError => e
|
76
|
+
err = e
|
77
|
+
end
|
78
|
+
err
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'contains useful information' do
|
82
|
+
expect(error).to be_a(Dalton::TypeError)
|
83
|
+
expect(error.attribute).to be(:'user.test/unique-attr')
|
84
|
+
expect(error.value).to eql('5') # TODO: this sucks, but they're indistinguishable!
|
85
|
+
expect(error.type).to be(:string)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#retract(entity)' do
|
92
|
+
shared_examples_for "a retraction" do
|
93
|
+
it 'retracts the entity' do
|
94
|
+
expect(conn.db.q(query).size).to eq(0)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when supplied an id" do
|
99
|
+
before do
|
100
|
+
conn.retract(entity_id)
|
101
|
+
end
|
102
|
+
|
103
|
+
it_behaves_like 'a retraction'
|
104
|
+
end
|
105
|
+
|
106
|
+
context "when supplied an entity" do
|
107
|
+
before do
|
108
|
+
conn.retract(conn.db.entity(entity_id))
|
109
|
+
end
|
110
|
+
|
111
|
+
it_behaves_like 'a retraction'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '#tempid?' do
|
117
|
+
subject {Dalton::Connection.tempid?(id)}
|
118
|
+
|
119
|
+
context 'with a temp id' do
|
120
|
+
let (:id) {Dalton::Connection.tempid}
|
121
|
+
|
122
|
+
it { should be true }
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'with a real id' do
|
126
|
+
let (:id) {entity_id}
|
127
|
+
|
128
|
+
it { should be false }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dalton::Database do
|
4
|
+
include DatomicContext
|
5
|
+
|
6
|
+
describe 'data storage, query, and retrieval' do
|
7
|
+
|
8
|
+
let(:attribute) { :'db/doc' }
|
9
|
+
let(:value) { 'This is a test entity.' }
|
10
|
+
|
11
|
+
let!(:transaction_result) { conn.transact([{:'db/id' => Dalton::Connection.tempid, attribute => value}]) }
|
12
|
+
|
13
|
+
let(:entity_id) { transaction_result.tempids.values.first }
|
14
|
+
|
15
|
+
let(:query) { [:find, :'?e', :where, [:'?e', attribute, value]] }
|
16
|
+
let(:edn_query) { '[:find ?e :where [?e :db/doc "This is a test entity."]]' }
|
17
|
+
|
18
|
+
describe '#q(query)' do
|
19
|
+
|
20
|
+
shared_examples_for 'a query' do
|
21
|
+
it 'runs the query' do
|
22
|
+
expect(query_result).to be_a(Set)
|
23
|
+
expect(query_result.size).to eq(1)
|
24
|
+
|
25
|
+
entity_id = query_result.first.first
|
26
|
+
entity = db.entity(entity_id)
|
27
|
+
|
28
|
+
expect(entity[attribute]).to eq(value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when the query is a ruby data structure" do
|
33
|
+
let(:query_result) { db.q(query) }
|
34
|
+
|
35
|
+
it_behaves_like 'a query'
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when the query is an EDN string" do
|
39
|
+
let(:query_result) { db.q(edn_query) }
|
40
|
+
|
41
|
+
it_behaves_like 'a query'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#entity(entity_id)' do
|
46
|
+
let(:entity) { db.entity(entity_id) }
|
47
|
+
|
48
|
+
it 'fetches an entity from the database' do
|
49
|
+
expect(entity[attribute]).to eq(value)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#retrieve(query)' do
|
54
|
+
let(:results) { db.retrieve(query) }
|
55
|
+
let(:entity) { results.first }
|
56
|
+
|
57
|
+
it 'runs a query and retrieves entities' do
|
58
|
+
expect(results.to_a.size).to eq(1)
|
59
|
+
expect(entity[attribute]).to eq(value)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns a lazy enumerator' do
|
63
|
+
expect(results).to be_a(Enumerator::Lazy)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#attribute" do
|
70
|
+
let(:attribute) { db.attribute(:'db/doc') }
|
71
|
+
it "retrieves an attribute definition" do
|
72
|
+
expect(attribute.hasAVET).to eq(false)
|
73
|
+
expect(attribute.hasFulltext).to eq(true)
|
74
|
+
expect(attribute.hasNoHistory).to eq(false)
|
75
|
+
expect(attribute.id).to be_a(Fixnum)
|
76
|
+
expect(attribute.ident).to eq(:'db/doc')
|
77
|
+
expect(attribute.isComponent).to eq(false)
|
78
|
+
expect(attribute.isIndexed).to eq(false)
|
79
|
+
expect(attribute.unique).to eq(nil)
|
80
|
+
expect(attribute.valueType).to eq(:'db.type/string')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dalton::Datomization do
|
4
|
+
include DatomicContext
|
5
|
+
|
6
|
+
before do
|
7
|
+
conn.set_up_datomizer
|
8
|
+
|
9
|
+
conn.transact([{:'db/id' => Dalton::Connection.tempid(':db.part/db'),
|
10
|
+
:'db/ident' => :'test/map',
|
11
|
+
:'db/valueType' => :'db.type/ref',
|
12
|
+
:'db/cardinality' => :'db.cardinality/many',
|
13
|
+
:'db/doc' => "A reference attribute for testing datomization",
|
14
|
+
:'db/isComponent' => true,
|
15
|
+
:'dmzr.ref/type' => :'dmzr.type/map',
|
16
|
+
:'db.install/_attribute' => :'db.part/db',
|
17
|
+
},
|
18
|
+
{:'db/id' => Dalton::Connection.tempid(':db.part/db'),
|
19
|
+
:'db/ident' => :'test/vector',
|
20
|
+
:'db/valueType' => :'db.type/ref',
|
21
|
+
:'db/cardinality' => :'db.cardinality/many',
|
22
|
+
:'db/doc' => "A reference attribute for testing datomization",
|
23
|
+
:'db/isComponent' => true,
|
24
|
+
:'dmzr.ref/type' => :'dmzr.type/vector',
|
25
|
+
:'db.install/_attribute' => :'db.part/db',
|
26
|
+
},
|
27
|
+
{:'db/id' => Dalton::Connection.tempid(':db.part/db'),
|
28
|
+
:'db/ident' => :'test/edn',
|
29
|
+
:'db/valueType' => :'db.type/string',
|
30
|
+
:'db/cardinality' => :'db.cardinality/one',
|
31
|
+
:'db/doc' => "An EDN string field for edenization testing.",
|
32
|
+
:'dmzr.ref/type' => :'dmzr.type/edn',
|
33
|
+
:'db.install/_attribute' => :'db.part/db'}])
|
34
|
+
end
|
35
|
+
|
36
|
+
shared_examples_for "it round trips via datomization" do |attribute|
|
37
|
+
it "should store and retrieve the value" do
|
38
|
+
id = Dalton::Connection.tempid
|
39
|
+
original_data = {:'db/id' => id, attribute => value}
|
40
|
+
real_id = conn.datomize(original_data)
|
41
|
+
round_tripped_data = conn.db.undatomize(real_id)
|
42
|
+
expect(round_tripped_data[attribute]).to eq(value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "with an empty map" do
|
47
|
+
let(:value) { {} }
|
48
|
+
|
49
|
+
it_should_behave_like "it round trips via datomization", :'test/map'
|
50
|
+
it_should_behave_like "it round trips via datomization", :'test/edn'
|
51
|
+
end
|
52
|
+
|
53
|
+
context "with a single map element" do
|
54
|
+
let(:value) { {:a => 'grue'} }
|
55
|
+
|
56
|
+
it_should_behave_like "it round trips via datomization", :'test/map'
|
57
|
+
it_should_behave_like "it round trips via datomization", :'test/edn'
|
58
|
+
end
|
59
|
+
|
60
|
+
context "with multiple map elements" do
|
61
|
+
let(:value) { {:a => 'grue', :b => 'wumpus'} }
|
62
|
+
|
63
|
+
it_should_behave_like "it round trips via datomization", :'test/map'
|
64
|
+
it_should_behave_like "it round trips via datomization", :'test/edn'
|
65
|
+
end
|
66
|
+
|
67
|
+
context "with a nested map" do
|
68
|
+
let(:value) { {:a => {:b => 'fnord'}} }
|
69
|
+
|
70
|
+
it_should_behave_like "it round trips via datomization", :'test/map'
|
71
|
+
it_should_behave_like "it round trips via datomization", :'test/edn'
|
72
|
+
end
|
73
|
+
|
74
|
+
context "with an empty array" do
|
75
|
+
let(:value) { [] }
|
76
|
+
|
77
|
+
it_should_behave_like "it round trips via datomization", :'test/vector'
|
78
|
+
it_should_behave_like "it round trips via datomization", :'test/edn'
|
79
|
+
end
|
80
|
+
|
81
|
+
context "with array values" do
|
82
|
+
let(:value) { ['a', 'b', 'c'] }
|
83
|
+
|
84
|
+
it_should_behave_like "it round trips via datomization", :'test/vector'
|
85
|
+
it_should_behave_like "it round trips via datomization", :'test/edn'
|
86
|
+
end
|
87
|
+
|
88
|
+
context "with nested data structure values" do
|
89
|
+
let(:value) { [0, 1, 'a', 'b', 'c', [{:a => {:b => 'fnord'}}, 'x', 'y', 'z']] }
|
90
|
+
|
91
|
+
it_should_behave_like "it round trips via datomization", :'test/vector'
|
92
|
+
it_should_behave_like "it round trips via datomization", :'test/edn'
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dalton::Entity do
|
4
|
+
include DatomicContext
|
5
|
+
|
6
|
+
before do
|
7
|
+
conn.transact([{:'db/id' => Dalton::Connection.tempid(':db.part/db'),
|
8
|
+
:'db/ident' => :'test/stuff',
|
9
|
+
:'db/valueType' => :'db.type/ref',
|
10
|
+
:'db/cardinality' => :'db.cardinality/one',
|
11
|
+
:'db/doc' => 'A reference attribute for testing datomization',
|
12
|
+
:'db/isComponent' => true,
|
13
|
+
:'db.install/_attribute' => :'db.part/db',
|
14
|
+
}])
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#to_h' do
|
18
|
+
let!(:transaction_result) {
|
19
|
+
conn.transact([{:'db/id' => Dalton::Connection.tempid,
|
20
|
+
:'db/doc' => 'foo',
|
21
|
+
:'test/stuff' => {:'db/id' => Dalton::Connection.tempid,
|
22
|
+
:'db/doc' => 'bar'}}
|
23
|
+
])
|
24
|
+
|
25
|
+
}
|
26
|
+
let(:tempids) { transaction_result.tempids.values.sort }
|
27
|
+
|
28
|
+
let(:entity) { conn.db.retrieve([:find, :'?e', :where, [:'?e', :'db/doc', 'foo']]).first }
|
29
|
+
subject { entity.to_h }
|
30
|
+
|
31
|
+
it 'should translate the entity to a hash' do
|
32
|
+
expect(subject).to eq({:'db/doc' => 'foo',
|
33
|
+
:'test/stuff' =>
|
34
|
+
{:'db/doc' => 'bar'}})
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|