dalton 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|