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.
@@ -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