diametric 0.0.4 → 0.1.1

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