dm-hibernate-adapter 0.1pre-java

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 (34) hide show
  1. data/lib/dm-hibernate-adapter.rb +471 -0
  2. data/lib/dm-hibernate-adapter/dialects.rb +37 -0
  3. data/lib/dm-hibernate-adapter/hibernate.rb +403 -0
  4. data/lib/dm-hibernate-adapter/spec/setup.rb +27 -0
  5. data/lib/dm-hibernate-adapter/transaction.rb +27 -0
  6. data/lib/dm-hibernate-adapter_ext.jar +0 -0
  7. data/lib/jibernate.rb +2 -0
  8. data/spec/abstract_adapter/adapter_shared_spec.rb +514 -0
  9. data/spec/abstract_adapter/dm-hibernate-adapter_spec.rb +25 -0
  10. data/spec/abstract_adapter/rcov.opts +6 -0
  11. data/spec/abstract_adapter/spec.opts +4 -0
  12. data/spec/abstract_adapter/spec_helper.rb +8 -0
  13. data/spec/dm_core/adapter_spec.rb +12 -0
  14. data/spec/dm_core/rcov.opts +6 -0
  15. data/spec/dm_core/spec.opts +5 -0
  16. data/spec/dm_core/spec_helper.rb +42 -0
  17. data/spec/log4j.properties +11 -0
  18. data/spec/transient/dm-hibernate-adapter_spec.rb +57 -0
  19. data/spec/transient/lib/adapter_helpers.rb +107 -0
  20. data/spec/transient/lib/collection_helpers.rb +18 -0
  21. data/spec/transient/lib/counter_adapter.rb +38 -0
  22. data/spec/transient/lib/pending_helpers.rb +46 -0
  23. data/spec/transient/lib/rspec_immediate_feedback_formatter.rb +54 -0
  24. data/spec/transient/rcov.opts +6 -0
  25. data/spec/transient/shared/adapter_shared_spec.rb +408 -0
  26. data/spec/transient/shared/finder_shared_spec.rb +1513 -0
  27. data/spec/transient/shared/model_spec.rb +165 -0
  28. data/spec/transient/shared/property_spec.rb +412 -0
  29. data/spec/transient/shared/resource_shared_spec.rb +1226 -0
  30. data/spec/transient/shared/resource_spec.rb +133 -0
  31. data/spec/transient/shared/sel_shared_spec.rb +112 -0
  32. data/spec/transient/spec.opts +4 -0
  33. data/spec/transient/spec_helper.rb +14 -0
  34. metadata +210 -0
@@ -0,0 +1,471 @@
1
+ require 'java'
2
+ begin
3
+ require 'dm-hibernate-adapter_ext.jar'
4
+ rescue LoadError
5
+ warn "missing extension jar, may be it is already in the parent classloader"
6
+ end
7
+ java_import 'de.saumya.jibernate.UpdateWork'
8
+ require 'slf4r'
9
+ require 'slf4r/java_logger'
10
+
11
+ if require 'dm-core'
12
+ DataMapper.logger = Slf4r::LoggerFacade.new(DataMapper)
13
+ end
14
+
15
+ require 'dm-core/adapters/abstract_adapter'
16
+
17
+ require 'jruby/core_ext'
18
+ require 'stringio'
19
+
20
+ require 'dm-hibernate-adapter/dialects'
21
+ require 'dm-hibernate-adapter/hibernate'
22
+ require 'dm-hibernate-adapter/transaction'
23
+
24
+
25
+ module DataMapper
26
+ module Adapters
27
+
28
+ java_import org.hibernate.criterion.Restrictions # ie. Restriction.eq
29
+ java_import org.hibernate.criterion.Order # ie. Order.asc
30
+
31
+ class HibernateAdapter < AbstractAdapter
32
+
33
+ @@logger = Slf4r::LoggerFacade.new(HibernateAdapter)
34
+
35
+ DRIVERS = {
36
+ :H2 => "org.h2.Driver",
37
+ :HSQL => "org.hsqldb.jdbcDriver",
38
+ :Derby => "org.apache.derby.jdbc.EmbeddedDriver",
39
+ :MySQL5 => "com.mysql.jdbc.Driver",
40
+ :MySQL5InnoDB => "com.mysql.jdbc.Driver",
41
+ :MySQL => "com.mysql.jdbc.Driver",
42
+ :MySQLInnoDB => "com.mysql.jdbc.Driver",
43
+ :MySQLMyISAM => "com.mysql.jdbc.Driver",
44
+ :PostgreSQL => "org.postgresql.Driver",
45
+ }
46
+
47
+ DataMapper::Model.append_inclusions( Hibernate::Model )
48
+
49
+ extend( Chainable )
50
+
51
+ def initialize(name, options = {})
52
+ dialect = options.delete(:dialect)
53
+ username = options.delete(:username)
54
+ password = options.delete(:password)
55
+ driver = options.delete(:driver) || DRIVERS[dialect.to_sym]
56
+ pool_size = options.delete(:pool_size) || "1"
57
+ url = options.delete(:url)
58
+ url += "jdbc:" unless url =~ /^jdbc:/
59
+
60
+ super( name, options )
61
+
62
+ Hibernate.dialect = Hibernate::Dialects.const_get(dialect.to_s)
63
+ Hibernate.current_session_context_class = "thread"
64
+
65
+ Hibernate.connection_driver_class = driver.to_s
66
+ Hibernate.connection_url = url.to_s # ie. "jdbc:h2:jibernate"
67
+ Hibernate.connection_username = username.to_s # ie. "sa"
68
+ Hibernate.connection_password = password.to_s # ie. ""
69
+ Hibernate.connection_pool_size = pool_size.to_s
70
+
71
+ Hibernate.properties["cache.provider_class"] = "org.hibernate.cache.NoCacheProvider"
72
+ Hibernate.properties["hbm2ddl.auto"] = "update"
73
+ Hibernate.properties["format_sql"] = "false"
74
+ Hibernate.properties["show_sql"] = "true"
75
+
76
+ end
77
+
78
+ # @param [Enumerable<Resource>] resources
79
+ # The list of resources (model instances) to create
80
+ #
81
+ # @return [Integer]
82
+ # The number of records that were actually saved into the data-store
83
+ #
84
+ # @api semipublic
85
+ def create(resources)
86
+ @@logger.debug("create #{resources.inspect}")
87
+ count = 0
88
+ unit_of_work do |session|
89
+
90
+ resources.each do |resource|
91
+ begin
92
+ session.persist(resource)
93
+ count += 1
94
+ rescue NativeException => e
95
+ @@logger.debug("error creating #{resource.inspect()}", e.cause())
96
+ session.clear()
97
+ raise e
98
+ end
99
+ end
100
+ end
101
+ count
102
+ end
103
+
104
+ # @param [Hash(Property => Object)] attributes
105
+ # hash of attribute values to set, keyed by Property
106
+ # @param [Collection] collection
107
+ # collection of records to be updated
108
+ #
109
+ # @return [Integer]
110
+ # the number of records updated
111
+ #
112
+ # @api semipublic
113
+ def update(attributes, collection)
114
+
115
+ log_update(attributes, collection)
116
+ count = 0
117
+ unit_of_work do |session|
118
+ collection.each do |resource|
119
+ session.update(resource)
120
+ count += 1
121
+ end
122
+ end
123
+ count
124
+ end
125
+
126
+ # @param [Query] query
127
+ # the query to match resources in the datastore
128
+ #
129
+ # @return [Enumerable<Hash>]
130
+ # an array of hashes to become resources
131
+ #
132
+ # @api semipublic
133
+ def read(query)
134
+
135
+ log_read(query)
136
+ conditions = query.conditions
137
+ model = query.model
138
+ limit = query.limit
139
+ offset = query.offset
140
+ order = query.order
141
+
142
+ result = []
143
+
144
+ unit_of_work do |session|
145
+
146
+ criteria = session.create_criteria(model.to_java_class_name)
147
+ # where ...
148
+ criteria.add(parse_conditions_tree(conditions,model)) unless conditions.nil?
149
+ # limit ...
150
+ criteria.set_max_results(limit) unless limit.nil?
151
+ # offset
152
+ criteria.set_first_result(offset) unless offset.nil?
153
+ # order by
154
+ unless order.nil?
155
+ order.each do |direction|
156
+ operator = direction.operator
157
+ # TODO column name may differ from property name
158
+ column = direction.target.name
159
+ if operator == :desc
160
+ order = Order.desc(column.to_s.to_java_string)
161
+ else
162
+ order = Order.asc(column.to_s.to_java_string)
163
+ end
164
+
165
+ criteria.add_order(order)
166
+ end
167
+ end
168
+
169
+ @@logger.debug(criteria.to_s)
170
+
171
+ # TODO handle exceptions
172
+ result = criteria.list
173
+
174
+ end
175
+ result.to_a
176
+ end
177
+
178
+ # @param [Collection] collection
179
+ # collection of records to be deleted
180
+ #
181
+ # @return [Integer]
182
+ # the number of records deleted
183
+ #
184
+ # @api semipublic
185
+ def delete(resources)
186
+
187
+ unit_of_work do |session|
188
+ resources.each do |resource|
189
+ @@logger.debug("deleting #{resource.inspect}")
190
+ session.delete(resource)
191
+ end
192
+ end
193
+ resources.size
194
+ end
195
+
196
+ # extension to the adapter API
197
+
198
+ def execute_update(sql)
199
+
200
+ unit_of_work do |session|
201
+ session.do_work(UpdateWork.new(sql))
202
+ end
203
+ end
204
+
205
+ # <dm-transactions>
206
+
207
+ # Produces a fresh transaction primitive for this Adapter
208
+ #
209
+ # Used by Transaction to perform its various tasks.
210
+ #
211
+ # @return [Object]
212
+ # a new Object that responds to :close, :begin, :commit,
213
+ # and :rollback,
214
+ #
215
+ # @api private
216
+ def transaction_primitive()
217
+ # DataObjects::Transaction.create_for_uri(normalized_uri)
218
+ Hibernate::Transaction.new()
219
+ end
220
+
221
+ # Pushes the given Transaction onto the per thread Transaction stack so
222
+ # that everything done by this Adapter is done within the context of said
223
+ # Transaction.
224
+ #
225
+ # @param [Transaction] transaction
226
+ # a Transaction to be the 'current' transaction until popped.
227
+ #
228
+ # @return [Array(Transaction)]
229
+ # the stack of active transactions for the current thread
230
+ #
231
+ # @api private
232
+ #
233
+ def push_transaction(transaction)
234
+ transactions() << transaction
235
+ end
236
+
237
+ # Pop the 'current' Transaction from the per thread Transaction stack so
238
+ # that everything done by this Adapter is no longer necessarily within the
239
+ # context of said Transaction.
240
+ #
241
+ # @return [Transaction]
242
+ # the former 'current' transaction.
243
+ #
244
+ # @api private
245
+ def pop_transaction()
246
+ transactions().pop()
247
+ end
248
+
249
+
250
+ # Retrieve the current transaction for this Adapter.
251
+ #
252
+ # Everything done by this Adapter is done within the context of this
253
+ # Transaction.
254
+ #
255
+ # @return [Transaction]
256
+ # the 'current' transaction for this Adapter.
257
+ #
258
+ # @api private
259
+ def current_transaction()
260
+ transactions().last()
261
+ end
262
+
263
+ # </dm-transactions>
264
+
265
+
266
+
267
+ private
268
+
269
+ # @api private
270
+ def transactions()
271
+ Thread.current[:dm_transactions] ||= {}
272
+ Thread.current[:dm_transactions][object_id] ||= []
273
+ end
274
+
275
+ def unit_of_work( &block )
276
+ # TODO state of the session should be also checked!
277
+ current_tx = current_transaction()
278
+
279
+ if current_tx
280
+ block.call( current_tx.primitive_for( self ).session() )
281
+ else
282
+ Hibernate.tx( &block )
283
+ end
284
+ end
285
+
286
+ def cast_to_hibernate (value, model_type)
287
+ #TODO ADD MORE TYPES!!!
288
+ case value
289
+ when Fixnum
290
+ # XXX Warning. ie Integer.value_of(value) returns cached objects already converted to Ruby objects!
291
+ if model_type == Java::JavaLang::Integer then java.lang.Integer.new(value)
292
+ elsif model_type == Java::JavaLang::Long then java.lang.Long.new(value)
293
+ else puts "---other Hibernate type, object: #{value} type: #{value.class} Hibernate type: #{model_type} ---"
294
+ end
295
+ when Float then java.lang.Float.new(value)
296
+ when String then value.to_java_string
297
+ when Array
298
+ # if there is WHERE x IN ( ) -> WHERE x IN ( null ) should be used
299
+ value = [nil] if value.empty?
300
+ (value.map{|object| cast_to_hibernate(object, model_type)}).to_java
301
+ when Range then (value.to_a.map{|object| cast_to_hibernate(object, model_type)}).to_java
302
+ when NilClass then nil
303
+ when Regexp then value.source.to_java_string
304
+ else
305
+ puts "---other Ruby type, object: #{value} type: #{value.class} ---"
306
+ value.to_s.to_java_string
307
+ end
308
+ end
309
+
310
+ def handle_comparison(con, model)
311
+ subject = nil
312
+ value = nil
313
+
314
+ case con.subject
315
+ when DataMapper::Property
316
+ subject = con.subject.name.to_s # property/column name
317
+ value = con.value # value used in comparison
318
+ when DataMapper::Associations::ManyToOne::Relationship
319
+ # TODO allow multicolumn keys !!!
320
+ subject = con.subject.parent_key.first.name.to_s
321
+ value = con.subject.parent_key.get(con.value).first # value used in comparison
322
+ when DataMapper::Associations::OneToMany::Relationship
323
+ # TODO allow multicolumn keys !!!
324
+ # TODO why the break in symetry ?
325
+ subject = con.subject.parent_key.first.name.to_s
326
+ # why does is not work: con.subject.child_key.get(con.value).first ???
327
+ value = con.subject.child_key.first.get(con.value.first) # value used in comparison
328
+ end
329
+ model_type = model.to_java_type(model.properties[subject.to_sym].class) # Java type of property (used in typecasting)
330
+ dialect = Hibernate.dialect # SQL dialect for current configuration
331
+
332
+ case con
333
+ when DataMapper::Query::Conditions::EqualToComparison
334
+ # special case handling IS NULL/ NOT (x IS NULL)
335
+ value.class == NilClass ? Restrictions.isNull(subject) :
336
+ Restrictions.eq(subject, cast_to_hibernate(value, model_type))
337
+
338
+ when DataMapper::Query::Conditions::GreaterThanComparison
339
+ Restrictions.gt(subject, cast_to_hibernate(value, model_type))
340
+
341
+ when DataMapper::Query::Conditions::LessThanComparison
342
+ Restrictions.lt(subject, cast_to_hibernate(value, model_type))
343
+
344
+ when DataMapper::Query::Conditions::LikeComparison
345
+ Restrictions.like(subject, cast_to_hibernate(value, model_type))
346
+
347
+ when DataMapper::Query::Conditions::GreaterThanOrEqualToComparison
348
+ Restrictions.ge(subject, cast_to_hibernate(value, model_type))
349
+
350
+ when DataMapper::Query::Conditions::LessThanOrEqualToComparison
351
+ Restrictions.le(subject, cast_to_hibernate(value, model_type))
352
+
353
+ when DataMapper::Query::Conditions::InclusionComparison
354
+ if value.class == Array
355
+ # special case handling :x => 1..110 / :x => [1,2,3]
356
+ Restrictions.in(subject, cast_to_hibernate(value, model_type))
357
+ else
358
+ # XXX proper ordering?
359
+ arr = value.is_a?(Fixnum) ? [value] : value.to_a
360
+ lo = arr.first
361
+ hi = arr.last
362
+ if lo.nil? || hi.nil?
363
+ Restrictions.in(subject, cast_to_hibernate(value, model_type))
364
+ else
365
+ Restrictions.between(subject, cast_to_hibernate(lo, model_type), cast_to_hibernate(hi, model_type))
366
+ end
367
+ end
368
+
369
+ when DataMapper::Query::Conditions::RegexpComparison
370
+
371
+ if dialect == "org.hibernate.dialect.HSQLDialect"
372
+ Restrictions.sqlRestriction("(regexp_matches (" +subject + ", ?))",
373
+ cast_to_hibernate(value, model_type), org::hibernate::Hibernate::STRING)
374
+ elsif dialect == "org.hibernate.dialect.PostgreSQLDialect"
375
+ Restrictions.sqlRestriction("(" + subject +" ~ ?)",
376
+ cast_to_hibernate(value, model_type), org::hibernate::Hibernate::STRING)
377
+ # elsif dialect == "org.hibernate.dialect.DerbyDialect"
378
+ # TODO implement custom matching function for some dbs (see README on Wiki)
379
+ else
380
+ Restrictions.sqlRestriction("(" + subject +" regexp ?)",
381
+ cast_to_hibernate(value, model_type), org::hibernate::Hibernate::STRING)
382
+ end
383
+
384
+ end
385
+ end
386
+
387
+ def parse_all_children(children, model, operand)
388
+ operand = children.inject(operand){ |op,child| op.add(parse_conditions_tree(child, model))}
389
+ end
390
+
391
+ def parse_the_only_child(child,model)
392
+ parse_conditions_tree(child, model)
393
+ end
394
+
395
+ def handle_operation(con, model)
396
+ children = con.children
397
+
398
+ case con
399
+ when DataMapper::Query::Conditions::AndOperation
400
+ parse_all_children(children, model, Restrictions.conjunction())
401
+
402
+ when DataMapper::Query::Conditions::OrOperation
403
+ parse_all_children(children, model, Restrictions.disjunction())
404
+
405
+ when DataMapper::Query::Conditions::NotOperation
406
+ #XXX only one child may be negated in DM?
407
+ child = children.first
408
+
409
+ if !(child.respond_to? :children) &&
410
+ (child.class == DataMapper::Query::Conditions::InclusionComparison) &&
411
+ (child.value.class == Array) && (child.value.empty?)
412
+
413
+ subject = child.subject.name.to_s
414
+ # XXX ugly workaround for Model.all(:x.not => [])
415
+ Restrictions.sqlRestriction(" ( "+ subject +" is null or " + subject +" is not null ) ")
416
+ else
417
+ Restrictions.not(parse_the_only_child(child,model))
418
+ end
419
+
420
+ when DataMapper::Query::Conditions::NullOperation
421
+ # XXX NullOperation is not used in dm_core at the moment
422
+ raise NotImplementedError, "#{con.class} is not not used in dm_core"
423
+ end
424
+ end
425
+
426
+ def parse_conditions_tree(conditions, model)
427
+ #conditions has children ? (in fact -> "is it comparison or operand?")
428
+ unless conditions.respond_to?(:children)
429
+ handle_comparison(conditions, model)
430
+ else
431
+ handle_operation(conditions, model)
432
+ end
433
+ end
434
+
435
+ # ----- helper methods - printers -----
436
+
437
+ # @param [Query] query
438
+ # the query to print it out formatted
439
+ #
440
+ # @api private
441
+ def log_read(query)
442
+ @@logger.debug <<-EOT
443
+ read()
444
+ query:
445
+ #{query.inspect}
446
+ model:
447
+ #{query.model}
448
+ conditions:
449
+ #{query.conditions}
450
+ EOT
451
+ end
452
+
453
+ # @param [Hash(Property => Object)] attributes
454
+ # hash of attribute values to print it out formatted, keyed by Property
455
+ # @param [Collection] collection
456
+ # collection of records to print it out formatted
457
+ #
458
+ # @api private
459
+ def log_update(attributes,collection)
460
+ @@logger.debug <<-EOT
461
+ update()
462
+ attributes:
463
+ #{attributes.inspect}
464
+ collection:
465
+ #{collection.inspect}
466
+ EOT
467
+ end
468
+
469
+ end
470
+ end
471
+ end