dm-hibernate-adapter 0.1pre-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|