dalton 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ module Dalton
2
+ class Attribute
3
+ def initialize(datomic_attribute)
4
+ @datomic_attribute = datomic_attribute
5
+ end
6
+
7
+ def method_missing(name, *args, &block)
8
+ if @datomic_attribute.respond_to?(name)
9
+ Translation.from_clj(@datomic_attribute.send(name, *args, &block))
10
+ else
11
+ super
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,104 @@
1
+ java_import "clojure.lang.Keyword"
2
+ java_import "datomic.Peer"
3
+
4
+ module Dalton
5
+ class Connection
6
+
7
+ include Dalton::Datomization
8
+
9
+ def initialize(uri)
10
+ @uri = uri
11
+ end
12
+
13
+ def self.connect(uri)
14
+ Peer.createDatabase(uri)
15
+ database = new(uri)
16
+ database.connect
17
+ return database
18
+ end
19
+
20
+ attr_reader :uri, :datomic_connection, :db
21
+
22
+ def create
23
+ Peer.createDatabase(uri) or
24
+ raise DatomicError, "Unable to create database at \"#{uri}\"."
25
+ end
26
+
27
+ def destroy
28
+ Peer.deleteDatabase(uri) or
29
+ raise DatomicError, "Unable to destroy database at \"#{uri}\"."
30
+ end
31
+
32
+ def connect
33
+ @datomic_connection = Peer.connect(uri) or
34
+ raise DatomicError, "Unable to connect to database at \"#{uri}\"."
35
+ refresh
36
+ true
37
+ end
38
+
39
+ def db=(new_db)
40
+ @db = new_db.is_a?(Database) ? new_db : Database.new(new_db)
41
+ end
42
+
43
+ def refresh
44
+ self.db = @datomic_connection.db
45
+ db
46
+ end
47
+
48
+ def transact(datoms)
49
+ data = self.class.convert_datoms(datoms)
50
+ # STDERR.puts "data=#{data.to_edn}"
51
+ result = TransactionResult.new(@datomic_connection.transact(data).get)
52
+ self.db = result.db_after
53
+ Translation.from_clj(result)
54
+ rescue Java::JavaUtilConcurrent::ExecutionException => e
55
+ cause = e.getCause
56
+ if cause.respond_to?(:data)
57
+ err_data = Translation.from_clj(cause.data)
58
+ case err_data[:'db/error']
59
+ when :'db.error/unique-conflict'
60
+ raise UniqueConflict.parse(cause.getMessage)
61
+ when :'db.error/wrong-type-for-attribute'
62
+ raise TypeError.parse(cause.getMessage)
63
+ end
64
+ end
65
+
66
+ raise DatomicError, "Transaction failed: #{e.getMessage}"
67
+ end
68
+
69
+
70
+ def retract(entity)
71
+ entity_id = entity.is_a?(Entity) ? entity.id : entity
72
+ transact([[:'db.fn/retractEntity', entity_id]])
73
+ end
74
+
75
+ def self.convert_datoms(datoms)
76
+ case datoms
77
+ when Array
78
+ Translation.from_ruby(datoms)
79
+ when String
80
+ Utility.read_edn(datoms)
81
+ else
82
+ raise ArgumentError, 'datoms must be an Array or a String containing EDN.'
83
+ end
84
+ end
85
+
86
+ def self.tempid(partition=:'db.part/user', id=nil)
87
+ partition = Keyword.intern(partition.to_s.sub(/^:/, ''))
88
+ if id
89
+ Peer.tempid(partition, id)
90
+ else
91
+ Peer.tempid(partition)
92
+ end
93
+ end
94
+
95
+ def self.tempid?(id)
96
+ 0 > case id
97
+ when Numeric
98
+ id
99
+ when Java::DatomicDb::DbId
100
+ id.get(Utility::kw('idx'))
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,48 @@
1
+ module Dalton
2
+
3
+ class Database
4
+
5
+ include Dalton::Undatomization
6
+
7
+ def initialize(datomic_database_value)
8
+ @datomic_db = datomic_database_value
9
+ end
10
+
11
+ attr_reader :datomic_db
12
+
13
+ def ==(other)
14
+ datomic_db == other.datomic_db
15
+ end
16
+
17
+ def q(query, *args)
18
+ translated_query = Translation.from_ruby(query)
19
+ # STDERR.puts "translated_query=#{translated_query.to_edn}"
20
+ result = Peer.q(translated_query, datomic_db, *args)
21
+ Translation.from_clj(result)
22
+ rescue Java::JavaUtilConcurrent::ExecutionException => e
23
+ raise DatomicError, "Query failed: #{e.getMessage}"
24
+ end
25
+
26
+ def entity(entity_id)
27
+ Entity.new(datomic_db.entity(Translation.from_ruby(entity_id)))
28
+ rescue Java::JavaUtilConcurrent::ExecutionException => e
29
+ raise DatomicError, "Entity retrieval failed: #{e.getMessage}"
30
+ end
31
+
32
+ def retrieve(query, *inputs)
33
+ q(query, *inputs).lazy.map { |result| entity(result.first) }
34
+ end
35
+
36
+ def attribute(id)
37
+ Attribute.new(datomic_db.attribute(Translation.from_ruby(id)))
38
+ end
39
+
40
+ def basis_t
41
+ datomic_db.basisT
42
+ end
43
+
44
+ def ==(other)
45
+ self.datomic_db == other.datomic_db
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,18 @@
1
+ module Dalton
2
+ module Datomization
3
+
4
+ Utility.require_clojure('goodguide.datomizer.datomize.setup')
5
+ Utility.require_clojure('goodguide.datomizer.datomize.decode')
6
+
7
+ def set_up_datomizer
8
+ Utility.run_clojure_function('goodguide.datomizer.datomize.setup/load-datomizer-schema', datomic_connection)
9
+ Utility.run_clojure_function('goodguide.datomizer.datomize.setup/load-datomizer-functions', datomic_connection)
10
+ end
11
+
12
+ def datomize(data)
13
+ result = transact([[:'dmzr/datomize', data]])
14
+ result.resolve_tempid(data[:'db/id'])
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,75 @@
1
+ module Dalton
2
+ class Entity
3
+
4
+ include Enumerable
5
+
6
+ def initialize(datomic_entity)
7
+ @datomic_entity = datomic_entity
8
+ end
9
+
10
+ attr_reader :datomic_entity
11
+
12
+ def db
13
+ Database.new(datomic_entity.db)
14
+ end
15
+
16
+ def get(key)
17
+ Translation.from_clj(datomic_entity.get(Translation.from_ruby(key)))
18
+ end
19
+
20
+ alias_method :[], :get
21
+
22
+ def touch
23
+ @datomic_entity.touch
24
+ self
25
+ end
26
+
27
+ def keys
28
+ datomic_entity.keySet.map{|x| x.sub(/^:/, '').to_sym}.to_a
29
+ end
30
+
31
+ def id
32
+ get(:'db/id')
33
+ end
34
+
35
+ def each
36
+ if block_given?
37
+ keys.each do |key|
38
+ yield [key, get(key)]
39
+ end
40
+ self
41
+ else
42
+ Enumerator.new(self)
43
+ end
44
+ end
45
+ alias_method :each_pair, :each
46
+
47
+ def to_h
48
+ Hash[map {|key, value|
49
+ [key, decode(value)]
50
+ }]
51
+ end
52
+
53
+ def ==(other)
54
+ other.instance_of?(self.class) && Utility.clojure_equal?(datomic_entity, other.datomic_entity)
55
+ end
56
+
57
+ def decode(value)
58
+ case value
59
+ when Dalton::Entity
60
+ value.to_h
61
+ when Set
62
+ Set.new(value.map{|x| decode(x)})
63
+ else
64
+ Translation.from_clj(value)
65
+ end
66
+ end
67
+
68
+ DB_FUNCTION_KEY = ::Keyword.intern('db/fn')
69
+
70
+ def fn
71
+ @datomic_entity.get(DB_FUNCTION_KEY)
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,68 @@
1
+ module Dalton
2
+ class UniqueConflict < DatomicError
3
+ # TODO: [jneen] this is terrible, but error handling is not implemented at the moment.
4
+ # eventually all this data should be accessible via (ex-data e).
5
+ MESSAGE_RE =
6
+ %r(^:db[.]error/unique-conflict Unique conflict: :([a-z./-]+), value: (.*?) already held by: (\d+) asserted for: (\d+)$)o
7
+
8
+ def self.parse(message)
9
+ message =~ MESSAGE_RE
10
+ raise ArgumentError, "invalid format: #{message.inspect}" unless $~
11
+ new(
12
+ attribute: $1.to_sym,
13
+ value: $2,
14
+ existing_id: Integer($3),
15
+ new_id: Integer($4),
16
+ )
17
+ end
18
+
19
+ attr_reader :message, :attribute, :value, :existing_id, :new_id
20
+
21
+ def initialize(opts={})
22
+ @attribute = opts.fetch(:attribute)
23
+ @value = opts.fetch(:value)
24
+ @existing_id = opts.fetch(:existing_id)
25
+ @new_id = opts.fetch(:new_id)
26
+ @message = "Unique conflict: tried to assign duplicate #@attribute to #@new_id, already held by #@existing_id. value: #@value"
27
+ end
28
+
29
+ def to_s
30
+ "#{self.class.name}: #@message"
31
+ end
32
+
33
+ def inspect
34
+ "#<#{self.class.name}: @attribute=#@attribute @value=#@value @existing_id=#@existing_id @new_id=#@new_id>"
35
+ end
36
+ end
37
+
38
+ class TypeError < DatomicError
39
+ MESSAGE_RE = %r(^:db[.]error/wrong-type-for-attribute Value (.*?) is not a valid :(\w+) for attribute :([a-z./-]+)$)
40
+
41
+ def self.parse(message)
42
+ message =~ MESSAGE_RE
43
+ raise ArgumentError, "invalid format: #{message.inspect}" unless $~
44
+ new(
45
+ value: $1,
46
+ type: $2.to_sym,
47
+ attribute: $3.to_sym
48
+ )
49
+ end
50
+
51
+ attr_reader :message, :value, :type, :attribute
52
+
53
+ def initialize(opts={})
54
+ @value = opts.fetch(:value)
55
+ @type = opts.fetch(:type)
56
+ @attribute = opts.fetch(:attribute)
57
+ @message = "Type error: tried to set #@attribute as #@value, expected type #@type"
58
+ end
59
+
60
+ def to_s
61
+ "#{self.class.name}: #@message"
62
+ end
63
+
64
+ def inspect
65
+ "#<#{self.class.name}: @attribute=#@attribute @value=#@value @type=#@type>"
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,33 @@
1
+ java_import "datomic.Peer"
2
+
3
+ module Dalton
4
+ class TransactionResult
5
+ def initialize(result_map)
6
+ @result_map = result_map
7
+ end
8
+
9
+ def db_before
10
+ Dalton::Database.new(@result_map.get(Java::Datomic::Connection.DB_BEFORE))
11
+ end
12
+
13
+ def db_after
14
+ Dalton::Database.new(@result_map.get(Java::Datomic::Connection.DB_AFTER))
15
+ end
16
+
17
+ def tx_data
18
+ Translation.from_clj(@result_map.get(Java::Datomic::Connection.TX_DATA))
19
+ end
20
+
21
+ def raw_tempids
22
+ @result_map.get(Java::Datomic::Connection.TEMPIDS)
23
+ end
24
+
25
+ def tempids
26
+ Translation.from_clj(raw_tempids)
27
+ end
28
+
29
+ def resolve_tempid(tempid)
30
+ Peer.resolve_tempid(db_after.datomic_db, raw_tempids, tempid)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,65 @@
1
+ java_import "clojure.lang.PersistentHashSet"
2
+ java_import "clojure.lang.Keyword"
3
+
4
+ module Zweikopf
5
+ module Primitive
6
+ def self.is_primitive_type?(obj) # monkey patch to remove DateTime from list of primitives and allow them to be converted. :-/
7
+ [String, Fixnum, Integer, Float, TrueClass, FalseClass].include?(obj.class)
8
+ end
9
+ end
10
+
11
+ module Keyword
12
+ # Monkey patch special handling for datalog variables
13
+ def self.from_ruby(keyword)
14
+ if keyword.to_s =~ /^[?$]/
15
+ Java::ClojureLang::Symbol.intern(keyword.to_s)
16
+ else
17
+ ::Keyword.intern(keyword.to_s)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ module Dalton
24
+ module Translation
25
+
26
+ module_function
27
+
28
+ #TODO: Fork Zweikopf, add Set handling, submit pull request
29
+
30
+ def from_clj(object)
31
+ Zweikopf::Transformer.from_clj(object) do |value|
32
+ case value
33
+ when Java::ClojureLang::Symbol
34
+ value.to_s.to_sym
35
+ when Java::JavaUtil::Set
36
+ Set.new(value.map{|x| from_clj(x)})
37
+ when Java::JavaUtil::ArrayList
38
+ value.map { |x| from_clj(x) }
39
+ when Java::Datomic::Entity, Java::DatomicQuery::EntityMap
40
+ Dalton::Entity.new(value)
41
+ when Java::JavaUtil::Date
42
+ Time.at(value.getTime / 1000).to_datetime
43
+ else
44
+ value
45
+ end
46
+ end
47
+ end
48
+
49
+ def from_ruby(object)
50
+ Zweikopf::Transformer.from_ruby(object) do |value|
51
+ case value
52
+ when ::Set
53
+ PersistentHashSet.create(value.map{|x| from_ruby(x)})
54
+ when Dalton::Entity
55
+ value.datomic_entity
56
+ when DateTime
57
+ Java::JavaUtil::Date.new(value.to_time.to_i * 1000)
58
+ else
59
+ value
60
+ end
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,11 @@
1
+ module Dalton
2
+
3
+ module Undatomization
4
+ def undatomize(id)
5
+ e = entity(id)
6
+ clojure_data = Utility.run_clojure_function("goodguide.datomizer.datomize.decode/undatomize", e.datomic_entity)
7
+ Translation.from_clj(clojure_data)
8
+ end
9
+ end
10
+
11
+ end