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.
- data/lib/dm-hibernate-adapter.rb +471 -0
- data/lib/dm-hibernate-adapter/dialects.rb +37 -0
- data/lib/dm-hibernate-adapter/hibernate.rb +403 -0
- data/lib/dm-hibernate-adapter/spec/setup.rb +27 -0
- data/lib/dm-hibernate-adapter/transaction.rb +27 -0
- data/lib/dm-hibernate-adapter_ext.jar +0 -0
- data/lib/jibernate.rb +2 -0
- data/spec/abstract_adapter/adapter_shared_spec.rb +514 -0
- data/spec/abstract_adapter/dm-hibernate-adapter_spec.rb +25 -0
- data/spec/abstract_adapter/rcov.opts +6 -0
- data/spec/abstract_adapter/spec.opts +4 -0
- data/spec/abstract_adapter/spec_helper.rb +8 -0
- data/spec/dm_core/adapter_spec.rb +12 -0
- data/spec/dm_core/rcov.opts +6 -0
- data/spec/dm_core/spec.opts +5 -0
- data/spec/dm_core/spec_helper.rb +42 -0
- data/spec/log4j.properties +11 -0
- data/spec/transient/dm-hibernate-adapter_spec.rb +57 -0
- data/spec/transient/lib/adapter_helpers.rb +107 -0
- data/spec/transient/lib/collection_helpers.rb +18 -0
- data/spec/transient/lib/counter_adapter.rb +38 -0
- data/spec/transient/lib/pending_helpers.rb +46 -0
- data/spec/transient/lib/rspec_immediate_feedback_formatter.rb +54 -0
- data/spec/transient/rcov.opts +6 -0
- data/spec/transient/shared/adapter_shared_spec.rb +408 -0
- data/spec/transient/shared/finder_shared_spec.rb +1513 -0
- data/spec/transient/shared/model_spec.rb +165 -0
- data/spec/transient/shared/property_spec.rb +412 -0
- data/spec/transient/shared/resource_shared_spec.rb +1226 -0
- data/spec/transient/shared/resource_spec.rb +133 -0
- data/spec/transient/shared/sel_shared_spec.rb +112 -0
- data/spec/transient/spec.opts +4 -0
- data/spec/transient/spec_helper.rb +14 -0
- 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
|