dm-hibernate-adapter 0.1pre-java

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