diametric 0.0.4 → 0.1.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.
Files changed (67) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +21 -18
  3. data/Jarfile +15 -1
  4. data/README.md +22 -14
  5. data/Rakefile +17 -1
  6. data/bin/datomic-rest +33 -0
  7. data/bin/download-datomic +13 -0
  8. data/datomic_version.yml +4 -0
  9. data/diametric.gemspec +9 -6
  10. data/ext/diametric/DiametricCollection.java +147 -0
  11. data/ext/diametric/DiametricConnection.java +113 -0
  12. data/ext/diametric/DiametricDatabase.java +107 -0
  13. data/ext/diametric/DiametricEntity.java +90 -0
  14. data/ext/diametric/DiametricListenableFuture.java +47 -0
  15. data/ext/diametric/DiametricObject.java +66 -0
  16. data/ext/diametric/DiametricPeer.java +414 -0
  17. data/ext/diametric/DiametricService.java +102 -0
  18. data/ext/diametric/DiametricUUID.java +61 -0
  19. data/ext/diametric/DiametricUtils.java +183 -0
  20. data/lib/boolean_type.rb +3 -0
  21. data/lib/diametric.rb +24 -0
  22. data/lib/diametric/entity.rb +219 -14
  23. data/lib/diametric/generators/active_model.rb +2 -2
  24. data/lib/diametric/persistence.rb +0 -1
  25. data/lib/diametric/persistence/common.rb +28 -9
  26. data/lib/diametric/persistence/peer.rb +122 -87
  27. data/lib/diametric/persistence/rest.rb +4 -3
  28. data/lib/diametric/query.rb +94 -23
  29. data/lib/diametric/rest_service.rb +74 -0
  30. data/lib/diametric/service_base.rb +77 -0
  31. data/lib/diametric/transactor.rb +86 -0
  32. data/lib/diametric/version.rb +1 -1
  33. data/lib/diametric_service.jar +0 -0
  34. data/lib/value_enums.rb +8 -0
  35. data/spec/conf_helper.rb +55 -0
  36. data/spec/config/free-transactor-template.properties +73 -0
  37. data/spec/config/logback.xml +59 -0
  38. data/spec/data/seattle-data0.dtm +452 -0
  39. data/spec/data/seattle-data1.dtm +326 -0
  40. data/spec/developer_create_sample.rb +39 -0
  41. data/spec/developer_query_spec.rb +120 -0
  42. data/spec/diametric/config_spec.rb +1 -1
  43. data/spec/diametric/entity_spec.rb +263 -0
  44. data/spec/diametric/peer_api_spec.rb +147 -0
  45. data/spec/diametric/persistence/peer_many2many_spec.rb +76 -0
  46. data/spec/diametric/persistence/peer_spec.rb +13 -22
  47. data/spec/diametric/persistence/rest_spec.rb +12 -19
  48. data/spec/diametric/query_spec.rb +4 -5
  49. data/spec/diametric/rest_service_spec.rb +56 -0
  50. data/spec/diametric/transactor_spec.rb +68 -0
  51. data/spec/integration_spec.rb +5 -3
  52. data/spec/parent_child_sample.rb +42 -0
  53. data/spec/peer_integration_spec.rb +106 -22
  54. data/spec/peer_seattle_spec.rb +200 -0
  55. data/spec/rc2013_seattle_big.rb +82 -0
  56. data/spec/rc2013_seattle_small.rb +60 -0
  57. data/spec/rc2013_simple_sample.rb +72 -0
  58. data/spec/seattle_integration_spec.rb +106 -0
  59. data/spec/simple_validation_sample.rb +31 -0
  60. data/spec/spec_helper.rb +31 -45
  61. data/spec/support/entities.rb +157 -0
  62. data/spec/support/gen_entity_class.rb +2 -0
  63. data/spec/support/persistence_examples.rb +9 -5
  64. data/spec/test_version_file.yml +4 -0
  65. metadata +131 -75
  66. data/Jarfile.lock +0 -134
  67. data/lib/jrclj.rb +0 -63
@@ -8,8 +8,8 @@ module Diametric
8
8
  "#{klass}.all"
9
9
  end
10
10
 
11
- def self.find(klass, params=nil)
12
- "#{klass}.get(#{params})"
11
+ def self.find(klass, params=nil, connection=nil, resolve=false)
12
+ "#{klass}.get(#{params}, #{connection}, #{resolve})"
13
13
  end
14
14
 
15
15
  def self.build(klass, params=nil)
@@ -37,7 +37,6 @@ module Diametric
37
37
 
38
38
  def self.persistence_class(uri)
39
39
  if uri =~ /^datomic:/
40
- require 'diametric/persistence/peer'
41
40
  @conn_type = :peer
42
41
  Peer
43
42
  else
@@ -36,25 +36,44 @@ module Diametric
36
36
  end
37
37
 
38
38
  module ClassMethods
39
- def create_schema
40
- transact(schema)
39
+ def create_schema(connection=nil)
40
+ if self.instance_variable_get("@peer")
41
+ connection ||= Diametric::Persistence::Peer.connect
42
+ connection.transact(schema)
43
+ else
44
+ transact(schema)
45
+ end
41
46
  end
42
47
 
43
- def all
44
- Diametric::Query.new(self).all
48
+ def all(connection=nil, resolve=false)
49
+ if self.instance_variable_get("@peer")
50
+ connection ||= Diametric::Persistence::Peer.connect
51
+ end
52
+ Diametric::Query.new(self, connection, resolve).all
45
53
  end
46
54
 
47
- def first(conditions = {})
48
- where(conditions).first
55
+ def first(conditions = {}, connection=nil, resolve=false)
56
+ if self.instance_variable_get("@peer")
57
+ connection ||= Diametric::Persistence::Peer.connect
58
+ end
59
+ where(conditions, connection, resolve).first
49
60
  end
50
61
 
51
- def where(conditions = {})
52
- query = Diametric::Query.new(self)
62
+ def where(conditions = {}, connection=nil, resolve=false)
63
+ if self.instance_variable_get("@peer")
64
+ connection ||= Diametric::Persistence::Peer.connect
65
+ end
66
+ query = Diametric::Query.new(self, connection, resolve)
53
67
  query.where(conditions)
54
68
  end
55
69
 
56
70
  def filter(*filter)
57
- query = Diametric::Query.new(self)
71
+ if self.instance_variable_get("@peer")
72
+ connection = Diametric::Persistence::Peer.connect
73
+ else
74
+ connection = nil
75
+ end
76
+ query = Diametric::Query.new(self, connection, true)
58
77
  query.filter(*filter)
59
78
  end
60
79
  end
@@ -1,118 +1,153 @@
1
- unless defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
2
- raise "This module requires the use of JRuby."
3
- end
4
-
5
- require 'diametric'
6
1
  require 'diametric/persistence/common'
7
2
 
8
- require 'java'
9
- require 'lock_jar'
10
- lockfile = File.expand_path( "../../../../Jarfile.lock", __FILE__ )
11
- # Loads the classpath with Jars from the lockfile
12
- LockJar.load(lockfile)
13
-
14
- require 'jrclj'
15
- java_import "clojure.lang.Keyword"
16
-
17
3
  module Diametric
18
4
  module Persistence
19
5
  module Peer
20
- include_package "datomic"
21
6
 
22
- @connection = nil
23
- @persisted_classes = Set.new
7
+ def save(connection=nil)
8
+ return false unless valid?
9
+ return true unless changed?
10
+ connection ||= Diametric::Persistence::Peer.connect
24
11
 
25
- def self.included(base)
26
- base.send(:include, Diametric::Persistence::Common)
27
- base.send(:extend, ClassMethods)
28
- @persisted_classes.add(base)
29
- end
12
+ parsed_data = []
13
+ parse_tx_data(tx_data, parsed_data)
30
14
 
31
- def self.connection
32
- @connection
33
- end
15
+ map = connection.transact(parsed_data).get
34
16
 
35
- def self.create_schemas
36
- @persisted_classes.each do |klass|
37
- klass.create_schema
38
- end
39
- end
17
+ resolve_changes([self], map)
40
18
 
41
- module ClassMethods
42
- include_package "datomic"
19
+ map
20
+ end
43
21
 
44
- def connect(options = {})
45
- uri = options[:uri]
46
- Java::Datomic::Peer.create_database(uri)
47
- @connection = Java::Datomic::Peer.connect(uri)
22
+ def resolve_tempid(map, id)
23
+ if id.to_s =~ /-\d+/
24
+ return Diametric::Persistence::Peer.resolve_tempid(map, id)
48
25
  end
26
+ return id
27
+ end
49
28
 
50
- def disconnect
51
- @connection = nil
29
+ def parse_tx_data(data, result)
30
+ queue = []
31
+ data.each do |c_data|
32
+ if c_data.is_a? Hash
33
+ parse_hash_data(c_data, result, queue)
34
+ elsif c_data.is_a? Array
35
+ parse_array_data(c_data, result)
36
+ end
52
37
  end
38
+ parse_tx_data(queue, result) unless queue.empty?
39
+ end
53
40
 
54
- def connection
55
- @connection || Diametric::Persistence::Peer.connection
56
- end
41
+ def parse_hash_data(c_hash, result, queue)
42
+ hash = {}
43
+ c_hash.each do |c_key, c_value|
44
+ if c_value.respond_to?(:tx_data)
45
+ if c_value.tx_data.empty?
46
+ hash[c_key] = c_value.dbid
47
+ else
48
+ c_value.dbid = c_value.tempid
49
+ hash[c_key] = c_value.dbid
50
+ queue << c_value.tx_data.first
51
+ end
52
+ elsif c_value.is_a? Set
53
+ set_value = Set.new
54
+ c_value.each do |s|
55
+ if s.respond_to?(:tx_data) && s.tx_data.empty?
56
+ set_value << s.dbid
57
+ elsif s.respond_to?(:tx_data)
58
+ set_value << s.tx_data[":db/id"]
59
+ parsed_tx_data(s, result)
60
+ else
61
+ set_value << s
62
+ end
63
+ end
64
+ hash[c_key] = set_value
65
+ elsif c_value.respond_to?(:eid)
66
+ hash[c_key] = c_value.eid
67
+ else
68
+ hash[c_key] = c_value
69
+ end
70
+ end
71
+ result << hash
72
+ end
57
73
 
58
- def transact(data)
59
- data = clj.edn_convert(data)
60
- res = connection.transact(data)
61
- res.get
74
+ def parse_array_data(c_array, result)
75
+ array = []
76
+ c_array.each do |c_value|
77
+ if c_value.respond_to?(:to_a)
78
+ a_values = []
79
+ c_value.to_a.each do |a_value|
80
+ if a_value.respond_to?(:tx_data) && a_value.tx_data.empty?
81
+ a_values << a_value.dbid
82
+ else
83
+ a_values << a_value
84
+ end
85
+ end
86
+ array << a_values
87
+ else
88
+ array << c_value
89
+ end
62
90
  end
91
+ result << array
92
+ end
63
93
 
64
- def get(dbid)
65
- entity = self.new
66
- self.all.each do |e|
67
- next unless e.dbid == dbid.to_i
68
- e.attribute_names.each {|n| entity.send((n.to_s+"=").to_sym, e.send(n))}
94
+ def resolve_changes(parents, map)
95
+ queue = []
96
+ parents.each do |child|
97
+ child.attributes.each do |a_key, a_val|
98
+ if a_val.respond_to?(:tx_data)
99
+ queue << a_val
100
+ end
69
101
  end
70
- entity.dbid = dbid.to_i
71
- entity
102
+ child.instance_variable_set("@previously_changed", child.changes)
103
+ child.changed_attributes.clear
104
+ child.dbid = resolve_tempid(map, child.dbid)
105
+ child.instance_variable_set("@tx_map", map)
72
106
  end
107
+ resolve_changes(queue, map) unless queue.empty?
108
+ end
73
109
 
74
- def q(query, args)
75
- Java::Datomic::Peer.q(clj.edn_convert(query), connection.db, *args)
76
- end
110
+ def retract_entity(dbid)
111
+ Diametric::Persistence::Peer.retract_entity(dbid)
112
+ end
77
113
 
78
- def clj
79
- @clj ||= JRClj.new
114
+ def method_missing(method_name, *args, &block)
115
+ result = /(.+)_from_this_(.+)/.match(method_name)
116
+ if result
117
+ query_string = ":#{result[1]}/_#{result[2]}"
118
+ entities = Diametric::Persistence::Peer.reverse_q(args[0].db, self.dbid, query_string)
119
+ entities.collect {|e| self.class.from_dbid_or_entity(e, args[0])}
120
+ else
121
+ super
80
122
  end
81
123
  end
82
124
 
83
- extend ClassMethods
84
-
85
- # Save the entity
86
- #
87
- # @example Save the entity.
88
- # entity.save
89
- #
90
- # @return [ true, false ] True is success, false if not.
91
- def save
92
- return false unless valid?
93
- return true unless changed?
94
-
95
- res = self.class.transact(tx_data)
96
- if dbid.nil?
97
- self.dbid = Java::Datomic::Peer.resolve_tempid(
98
- res[:"db-after".to_clj],
99
- res[:tempids.to_clj],
100
- self.class.clj.edn_convert(tempid))
125
+ module ClassMethods
126
+ def get(dbid, connection=nil, resolve=false)
127
+ entity = self.new
128
+ dbid = dbid.to_i if dbid.is_a?(String)
129
+ connection ||= Diametric::Persistence::Peer.connect
130
+ entity = self.from_dbid_or_entity(dbid, connection, resolve)
131
+ entity
101
132
  end
102
133
 
103
- @previously_changed = changes
104
- @changed_attributes.clear
105
-
106
- res
107
- end
108
-
109
- def to_edn
110
- self.dbid
111
- end
134
+ def q(query, args, conn_or_db=nil)
135
+ conn_or_db ||= Diametric::Persistence::Peer.connect
136
+ if conn_or_db.is_a?(Diametric::Persistence::Connection)
137
+ db = conn_or_db.db
138
+ else
139
+ db = conn_or_db
140
+ end
112
141
 
113
- def retract_entity(dbid)
114
- query = [Java::Datomic::Util.list(":db.fn/retractEntity", dbid)]
115
- self.class.connection.transact(query).get
142
+ results = Diametric::Persistence::Peer.q(query, db, args)
143
+ # Diametric query expects the first element of each array in
144
+ # results is dbid. Wraps dbid here by
145
+ # Diametric::Persistence::Object to make it consistent
146
+ results.each do |r|
147
+ r[0] = Diametric::Persistence::Object.new(r[0])
148
+ end
149
+ results
150
+ end
116
151
  end
117
152
  end
118
153
  end
@@ -47,8 +47,9 @@ module Diametric
47
47
  connection.transact(database, data)
48
48
  end
49
49
 
50
- def get(dbid)
51
- res = connection.entity(database, dbid)
50
+ def get(dbid, conn=nil, resolve=false)
51
+ conn ||= connection
52
+ res = conn.entity(database, dbid.to_i)
52
53
 
53
54
  # TODO tighten regex to only allow fields with the model name
54
55
  attrs = res.data.map { |attr_symbol, value|
@@ -61,7 +62,7 @@ module Diametric
61
62
  entity
62
63
  end
63
64
 
64
- def q(query, args)
65
+ def q(query, args, unused=nil)
65
66
  args.unshift(connection.db_alias(database))
66
67
  res = connection.query(query, args)
67
68
  res.data
@@ -15,16 +15,19 @@ module Diametric
15
15
  class Query
16
16
  include Enumerable
17
17
 
18
- attr_reader :conditions, :filters, :model
18
+ attr_reader :conditions, :filters, :filter_attrs, :model, :connection, :resolve
19
19
 
20
20
  # Create a new Datomic query.
21
21
  #
22
22
  # @param model [Entity] This model must include +Datomic::Entity+. Including
23
23
  # a persistence module is optional.
24
- def initialize(model)
24
+ def initialize(model, connection_or_database=nil, resolve=false)
25
25
  @model = model
26
26
  @conditions = {}
27
27
  @filters = []
28
+ @filter_attrs = []
29
+ @conn_or_db = connection_or_database
30
+ @resolve = resolve
28
31
  end
29
32
 
30
33
  # Add conditions to your Datomic query. Conditions check for equality
@@ -69,6 +72,7 @@ module Diametric
69
72
  # that will be converted into a Datalog query.
70
73
  # @return [Query]
71
74
  def filter(*filter)
75
+ return peer_filter(filter) if self.model.instance_variable_get("@peer")
72
76
  query = self.dup
73
77
 
74
78
  if filter.first.is_a?(EDN::Type::List)
@@ -82,6 +86,22 @@ module Diametric
82
86
  query
83
87
  end
84
88
 
89
+ def peer_filter(*filter)
90
+ query = self.dup
91
+
92
+ if filter.first.is_a?(Array)
93
+ filter = filter.first
94
+ end
95
+ filter_attrs << filter[1].to_s
96
+ filter[1] = "?#{filter[1].to_s}"
97
+ filter = filter.tap {|e| e.to_s }.join(" ")
98
+ filter = "[(#{filter})]"
99
+
100
+ query.filters << filter
101
+ query
102
+ end
103
+
104
+
85
105
  # Loop through the query results. In order to use +each+, your model *must*
86
106
  # include a persistence API. At a minimum, it must have a +.q+ method that
87
107
  # returns an +Enumerable+ object.
@@ -90,12 +110,17 @@ module Diametric
90
110
  def each
91
111
  # TODO check to see if the model has a `.q` method and give
92
112
  # an appropriate error if not.
93
- res = model.q(*data)
94
-
113
+ res = model.q(*data, @conn_or_db)
95
114
  collapse_results(res).each do |entity|
115
+ if self.model.instance_variable_get("@peer") && @resolve
116
+ yield model.from_dbid_or_entity(entity.first.to_java, @conn_or_db, @resolve)
117
+ elsif self.model.instance_variable_get("@peer")
118
+ yield entity.first.to_java
96
119
  # The map is for compatibility with Java peer persistence.
97
120
  # TODO remove if possible
98
- yield model.from_query(entity.map { |x| x })
121
+ else
122
+ yield model.from_query(entity.map { |x| x }, @conn_or_db, @resolve)
123
+ end
99
124
  end
100
125
  end
101
126
 
@@ -113,6 +138,8 @@ module Diametric
113
138
  # is the Datomic query composed of Ruby data. The second element is
114
139
  # the arguments that used with the query.
115
140
  def data
141
+ return peer_data if self.model.instance_variable_get("@peer")
142
+
116
143
  vars = model.attribute_names.map { |attribute| ~"?#{attribute}" }
117
144
 
118
145
  from = conditions.map { |k, _| ~"?#{k}" }
@@ -133,6 +160,39 @@ module Diametric
133
160
  [query, args]
134
161
  end
135
162
 
163
+ def peer_data
164
+ if conditions.empty? && filters.empty?
165
+ args = [model.prefix]
166
+ query = <<-EOQ
167
+ [:find ?e
168
+ :in $ [?include-ns ...]
169
+ :where
170
+ [?e ?aid ?v]
171
+ [?aid :db/ident ?a]
172
+ [(namespace ?a) ?ns]
173
+ [(= ?ns ?include-ns)]]
174
+ EOQ
175
+ else
176
+ unless conditions.empty?
177
+ clauses = conditions.keys.inject("") do |memo, attribute|
178
+ memo + "[?e " + model.namespace(model.prefix, attribute) + " ?#{attribute} ] "
179
+ end
180
+ from = conditions.inject("[") {|memo, kv| memo + "?#{kv.shift} "} +"]"
181
+ args = conditions.map { |_, v| v }
182
+ end
183
+
184
+ unless filter_attrs.empty?
185
+ clauses ||= ""
186
+ clauses = filter_attrs.inject(clauses) do |memo, attribute|
187
+ memo + "[?e " + model.namespace(model.prefix, attribute) + " ?#{attribute} ] "
188
+ end
189
+ end
190
+ query = "[:find ?e :in $ #{from} :where #{clauses} #{filters.join}]"
191
+ end
192
+
193
+ [query, args]
194
+ end
195
+
136
196
  protected
137
197
 
138
198
  def conditions=(conditions)
@@ -156,32 +216,43 @@ module Diametric
156
216
  end
157
217
 
158
218
  def collapse_results(res)
219
+ return res unless @resolve
220
+
159
221
  res.group_by(&:first).map do |dbid, results|
160
222
  # extract dbid from results
161
223
  # NOTE: we prefer to_a.drop(1) over block arg decomposition because
162
224
  # result may be a Java::ClojureLang::PersistentVector
163
225
  results = results.map {|result| result.to_a.drop(1) }
164
226
 
165
- # Group values from all results into one result set
166
- # [["b", 123], ["c", 123]] #=> [["b", "c"], [123, 123]]
167
- grouped_values = results.transpose
168
-
169
- # Attach attribute names to each collection of values
170
- # => [[:letters, ["b", "c"]], [:number, [123, 123]]]
171
- attr_to_values = model.attributes.keys.zip(grouped_values)
172
-
173
- # Retain cardinality/many attributes as a collection,
174
- # but pick only one value for cardinality/one attributes
175
- collapsed_values = attr_to_values.map do |attr, values|
176
- if model.attributes[attr][:cardinality] == :many
177
- values
178
- else
179
- values.first
227
+ unless results.flatten.empty?
228
+ # Group values from all results into one result set
229
+ # [["b", 123], ["c", 123]] #=> [["b", "c"], [123, 123]]
230
+ grouped_values = results.transpose
231
+ attr_grouped_values = grouped_values[0...model.attributes.size]
232
+ enum_grouped_values = grouped_values[model.attributes.size..-1]
233
+
234
+ # Attach attribute names to each collection of values
235
+ # => [[:letters, ["b", "c"]], [:number, [123, 123]]]
236
+ attr_to_values = model.attributes.keys.zip(attr_grouped_values)
237
+
238
+ # Retain cardinality/many attributes as a collection,
239
+ # but pick only one value for cardinality/one attributes
240
+ collapsed_values = attr_to_values.map do |attr, values|
241
+ if model.attributes[attr][:cardinality] == :many
242
+ values
243
+ elsif model.attributes[attr][:value_type] == "ref" &&
244
+ model.enum_names.include?(attr)
245
+ enum_grouped_values.shift.first
246
+ else
247
+ values.first
248
+ end
180
249
  end
181
- end
182
250
 
183
- # Returning a singular result for each dbid
184
- [dbid, *collapsed_values]
251
+ # Returning a singular result for each dbid
252
+ [dbid, *collapsed_values]
253
+ else
254
+ [dbid]
255
+ end
185
256
  end
186
257
  end
187
258
  end