caruby-core 2.1.1 → 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/History.md +5 -1
- data/lib/caruby/database/cache.rb +20 -9
- data/lib/caruby/database/lazy_loader.rb +1 -1
- data/lib/caruby/database/operation.rb +2 -0
- data/lib/caruby/database/persistable.rb +19 -13
- data/lib/caruby/database/persistence_service.rb +14 -38
- data/lib/caruby/database/persistifier.rb +86 -37
- data/lib/caruby/database/reader.rb +87 -87
- data/lib/caruby/database/reader_template_builder.rb +7 -4
- data/lib/caruby/database/sql_executor.rb +32 -15
- data/lib/caruby/database/writer.rb +20 -12
- data/lib/caruby/database/writer_template_builder.rb +10 -6
- data/lib/caruby/database.rb +97 -55
- data/lib/caruby/helpers/coordinate.rb +4 -4
- data/lib/caruby/helpers/person.rb +2 -2
- data/lib/caruby/helpers/properties.rb +4 -4
- data/lib/caruby/helpers/roman.rb +2 -2
- data/lib/caruby/helpers/version.rb +1 -1
- data/lib/caruby/json/serializable.rb +17 -0
- data/lib/caruby/metadata/propertied.rb +8 -1
- data/lib/caruby/metadata/property_characteristics.rb +41 -46
- data/lib/caruby/metadata.rb +1 -2
- data/lib/caruby/migration/migrator.rb +108 -0
- data/lib/caruby/resource.rb +2 -2
- data/lib/caruby/version.rb +1 -1
- data/lib/caruby.rb +0 -2
- data/test/lib/caruby/database/cache_test.rb +1 -3
- metadata +6 -8
- data/lib/caruby/caruby-src.tar.gz +0 -0
- data/lib/caruby/json/deserializer.rb +0 -15
- data/lib/caruby/json/serializer.rb +0 -69
@@ -13,7 +13,7 @@ module CaRuby
|
|
13
13
|
def initialize
|
14
14
|
super
|
15
15
|
# the query template builder
|
16
|
-
@
|
16
|
+
@rdr_tmpl_bldr = TemplateBuilder.new
|
17
17
|
# the fetch result matcher
|
18
18
|
@matcher = FetchedMatcher.new
|
19
19
|
# the fetched copier
|
@@ -25,7 +25,7 @@ module CaRuby
|
|
25
25
|
# visitor that merges the fetched object graph
|
26
26
|
@ftchd_mrg_vstr = Jinx::MergeVisitor.new(:matcher => @matcher, :copier => copier) { |ref| ref.class.fetched_domain_attributes }
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
# Returns an array of objects matching the specified query template and attribute path.
|
30
30
|
# The obj_or_hql argument is either a domain object template or a
|
31
31
|
# Hibernate[http://www.hibernate.org/docs.html] HQL statement. If obj_or_hql
|
@@ -57,36 +57,46 @@ module CaRuby
|
|
57
57
|
# enable change tracking and lazy-loading
|
58
58
|
persistify(result)
|
59
59
|
end
|
60
|
-
|
61
|
-
# Fetches the given domain object from the database.
|
62
|
-
#
|
63
|
-
# If
|
64
|
-
#
|
60
|
+
|
61
|
+
# Fetches the given target domain object from the database. The target
|
62
|
+
# domain object class {Propertied#searchable_attributes} are used in the match.
|
63
|
+
# If this domain object does not have a set of searchable attributes with
|
64
|
+
# non-nil values, then this method returns nil.
|
65
65
|
#
|
66
66
|
# If the :create option is set, then this method creates an object if the
|
67
67
|
# find is unsuccessful.
|
68
68
|
#
|
69
|
-
#
|
69
|
+
# If the fetched domain object is the same class as the target domain object,
|
70
|
+
# then the fetched content is merged into the target and this method returns
|
71
|
+
# the target. If the fetched domain object is a subclass of the target domain
|
72
|
+
# object, then the fetched domain object is returned.
|
73
|
+
#
|
74
|
+
# @param [Jinx::Resource] obj the target domain object to find
|
70
75
|
# @param [Hash, Symbol] opts the find options
|
71
76
|
# @option opts [Boolean] :create whether to create the object if it is not found
|
72
77
|
# @return [Jinx::Resource, nil] the domain object if found, nil otherwise
|
73
78
|
# @raise [DatabaseError] if obj is not a domain object or more than object
|
74
79
|
# matches the obj attribute values
|
80
|
+
# @see #query
|
75
81
|
def find(obj, opts=nil)
|
76
82
|
return if obj.nil?
|
77
83
|
perform(:find, obj) do
|
78
|
-
|
84
|
+
fetched = find_object(obj)
|
85
|
+
if fetched.nil? then
|
86
|
+
logger.info { "#{obj.qp} not found." }
|
87
|
+
create(obj) if Options.get(:create, opts)
|
88
|
+
elsif fetched.equal?(obj) then
|
79
89
|
logger.info { "Found #{obj}." }
|
80
90
|
obj
|
81
91
|
else
|
82
|
-
logger.info { "#{
|
83
|
-
|
92
|
+
logger.info { "Found #{fetched} matching the find template #{obj}." }
|
93
|
+
fetched
|
84
94
|
end
|
85
95
|
end
|
86
96
|
end
|
87
|
-
|
88
|
-
# Returns whether the given domain object has a database identifier or exists
|
89
|
-
# This method fetches the object from the database if necessary.
|
97
|
+
|
98
|
+
# Returns whether the given domain object has a database identifier or exists
|
99
|
+
# in the database. This method fetches the object from the database if necessary.
|
90
100
|
#
|
91
101
|
# @param [Jinx::Resource, <Jinx::Resource>] obj the domain object(s) to find
|
92
102
|
# @return [Boolean] whether the domain object(s) exist in the database
|
@@ -96,14 +106,14 @@ module CaRuby
|
|
96
106
|
elsif obj.collection? then
|
97
107
|
obj.all? { |item| exists?(item) }
|
98
108
|
else
|
99
|
-
obj.identifier or find(obj)
|
109
|
+
!!obj.identifier or find(obj).equal?(obj)
|
100
110
|
end
|
101
111
|
end
|
102
112
|
|
103
113
|
private
|
104
|
-
|
114
|
+
|
105
115
|
RESULT_PRINTER = PrintWrapper.new { |obj| obj.pp_s }
|
106
|
-
|
116
|
+
|
107
117
|
# Queries the given obj_or_hql as described in {#query} and makes a detoxified copy of the
|
108
118
|
# toxic caCORE search result.
|
109
119
|
#
|
@@ -121,7 +131,7 @@ module CaRuby
|
|
121
131
|
# detoxify the toxic caCORE result
|
122
132
|
detoxify(toxic)
|
123
133
|
end
|
124
|
-
|
134
|
+
|
125
135
|
# Queries the given obj_or_hql as described in {#query} and returns the toxic caCORE search result.
|
126
136
|
#
|
127
137
|
# @param (see #query)
|
@@ -131,7 +141,7 @@ module CaRuby
|
|
131
141
|
path_s = path.join('.') unless path.empty?
|
132
142
|
# guard against recursive call back into the same operation
|
133
143
|
if query_redundant?(obj_or_hql, path_s) then
|
134
|
-
|
144
|
+
raise DatabaseError.new("Query #{obj_or_hql.qp} #{path_s} recursively called in context #{print_operations}")
|
135
145
|
end
|
136
146
|
# perform the query
|
137
147
|
perform(:query, obj_or_hql, :attribute => path_s) { query_with_path(obj_or_hql, path) }
|
@@ -140,11 +150,11 @@ module CaRuby
|
|
140
150
|
def query_redundant?(obj_or_hql, path)
|
141
151
|
@operations.detect { |op| op.type == :query and query_subject_redundant?(op.subject, obj_or_hql) and op.attribute == path }
|
142
152
|
end
|
143
|
-
|
153
|
+
|
144
154
|
def query_subject_redundant?(s1, s2)
|
145
155
|
s1 == s2 or (Jinx::Resource === s1 and Jinx::Resource === s2 and s1.identifier and s1.identifier == s2.identifier)
|
146
156
|
end
|
147
|
-
|
157
|
+
|
148
158
|
# @return an array of objects matching the given query template and path
|
149
159
|
# @see #query
|
150
160
|
def query_with_path(obj_or_hql, path)
|
@@ -154,14 +164,14 @@ module CaRuby
|
|
154
164
|
# gather the results of querying on those penultimate result objects with the last
|
155
165
|
# attribute as the path
|
156
166
|
unless path.empty? then
|
157
|
-
if attribute.nil? then
|
167
|
+
if attribute.nil? then raise DatabaseError.new("Query path includes empty attribute: #{path.join('.')}.nil") end
|
158
168
|
logger.debug { "Decomposing query on #{obj_or_hql} with path #{path.join('.')}.#{attribute} into query on #{path.join('.')} followed by #{attribute}..." }
|
159
169
|
return query_safe(obj_or_hql, *path).map { |parent| query_toxic(parent, attribute) }.flatten
|
160
170
|
end
|
161
171
|
# perform the attribute query
|
162
172
|
query_with_attribute(obj_or_hql, attribute)
|
163
173
|
end
|
164
|
-
|
174
|
+
|
165
175
|
# Returns an array of objects matching the given query template and optional attribute.
|
166
176
|
# @see #query
|
167
177
|
def query_with_attribute(obj_or_hql, attribute=nil)
|
@@ -195,20 +205,20 @@ module CaRuby
|
|
195
205
|
def merge_fetched(source, target)
|
196
206
|
@ftchd_mrg_vstr.visit(source, target) { |src, tgt| tgt.copy_volatile_attributes(src) }
|
197
207
|
end
|
198
|
-
|
208
|
+
|
199
209
|
def print_query_result(result)
|
200
210
|
count_s = 'result object'.quantify(result.size)
|
201
211
|
result_printer = result.wrap { |item| RESULT_PRINTER.wrap(item) }
|
202
212
|
"Persistence service query returned #{count_s}: #{result_printer.pp_s(:single_line)}"
|
203
213
|
end
|
204
|
-
|
214
|
+
|
205
215
|
def query_hql(hql)
|
206
216
|
java_name = hql[/from\s+(\S+)/i, 1]
|
207
|
-
|
217
|
+
raise DatabaseError.new("Could not determine target type from HQL: #{hql}") if java_name.nil?
|
208
218
|
tgt = Class.to_ruby(java_name)
|
209
219
|
persistence_service(tgt).query(hql)
|
210
220
|
end
|
211
|
-
|
221
|
+
|
212
222
|
# Returns an array of objects fetched from the database which matches
|
213
223
|
# a template and follows the given optional domain attribute, if present.
|
214
224
|
#
|
@@ -228,12 +238,12 @@ module CaRuby
|
|
228
238
|
elsif invertible_query?(obj, attribute) then
|
229
239
|
query_with_inverted_reference(obj, attribute)
|
230
240
|
else
|
231
|
-
tmpl = @
|
241
|
+
tmpl = @rdr_tmpl_bldr.build_template(obj)
|
232
242
|
return Array::EMPTY_ARRAY if tmpl.nil?
|
233
243
|
query_on_template(tmpl, attribute)
|
234
244
|
end
|
235
245
|
end
|
236
|
-
|
246
|
+
|
237
247
|
# Returns an array of objects fetched from the database which matches
|
238
248
|
# the given template and follows the given optional domain attribute.
|
239
249
|
def query_on_template(template, attribute=nil)
|
@@ -241,7 +251,7 @@ module CaRuby
|
|
241
251
|
svc = persistence_service(tgt)
|
242
252
|
attribute ? svc.query(template, attribute) : svc.query(template)
|
243
253
|
end
|
244
|
-
|
254
|
+
|
245
255
|
# Queries on the given template and attribute by issuing a HQL query with an identifier condition.
|
246
256
|
#
|
247
257
|
# @param (see #query_object)
|
@@ -252,17 +262,17 @@ module CaRuby
|
|
252
262
|
sa = source[/([[:alnum:]])[[:alnum:]]*$/, 1].downcase
|
253
263
|
# the HQL condition
|
254
264
|
hql = "from #{source} #{sa} where #{sa}.id = #{obj.identifier}"
|
255
|
-
|
265
|
+
|
256
266
|
# the join attribute property
|
257
267
|
if attribute then
|
258
268
|
pd = obj.class.property(attribute).property_descriptor
|
259
269
|
hql.insert(0, "select #{sa}.#{pd.name} ")
|
260
270
|
end
|
261
271
|
logger.debug { "Querying on #{obj} #{attribute} using HQL identifier criterion..." }
|
262
|
-
|
272
|
+
|
263
273
|
query_hql(hql)
|
264
274
|
end
|
265
|
-
|
275
|
+
|
266
276
|
# Returns whether the query specified by the given search object and attribute can be
|
267
277
|
# inverted as a query on a template of type attribute which references the object.
|
268
278
|
# This condition holds if the search object has a key and attribute is a non-abstract
|
@@ -277,10 +287,11 @@ module CaRuby
|
|
277
287
|
inv_prop = pa.inverse_property
|
278
288
|
inv_prop and inv_prop.searchable? and finder_parameters(obj)
|
279
289
|
end
|
280
|
-
|
281
|
-
# Queries the given query object attribute by querying an attribute type template which references
|
290
|
+
|
291
|
+
# Queries the given query object attribute by querying an attribute type template which references
|
292
|
+
# the target object.
|
282
293
|
#
|
283
|
-
# @quirk caCORE caCORE
|
294
|
+
# @quirk caCORE caCORE search enters an infinite loop when the search argument has an object
|
284
295
|
# reference graph cycle. Work-around is to ensure that reference integrity is broken in the search
|
285
296
|
# argument by not setting inverse attributes.
|
286
297
|
#
|
@@ -297,14 +308,14 @@ module CaRuby
|
|
297
308
|
# The Java property writer to set the tmpl inverse to ref.
|
298
309
|
# Use the property writer rather than the attribute writer in order to curtail automatically
|
299
310
|
# adding tmpl to the ref attribute value when the inv_prop attribute is set to ref.
|
300
|
-
wtr = inv_prop.
|
311
|
+
wtr = inv_prop.java_writer
|
301
312
|
# parameterize tmpl with inverse ref
|
302
313
|
tmpl.send(wtr, ref)
|
303
314
|
# submit the query
|
304
315
|
logger.debug { "Submitting #{obj.qp} #{attribute} inverted query template #{tmpl.qp} ..." }
|
305
316
|
persistence_service(tmpl.class).query(tmpl)
|
306
317
|
end
|
307
|
-
|
318
|
+
|
308
319
|
# Finds the database content matching the given search object and merges the matching
|
309
320
|
# database values into the object. The find uses the search object secondary or alternate
|
310
321
|
# key for the search.
|
@@ -315,14 +326,16 @@ module CaRuby
|
|
315
326
|
# If a match is found, then each missing search object non-domain-valued attribute is set to
|
316
327
|
# the fetched attribute value and this method returns the search object.
|
317
328
|
#
|
318
|
-
# @quirk caCORE there is no caCORE find utility method to update a search target with persistent
|
319
|
-
# so it is done manually here.
|
329
|
+
# @quirk caCORE there is no caCORE find utility method to update a search target with persistent
|
330
|
+
# content, so it is done manually here.
|
320
331
|
#
|
321
|
-
# @param
|
322
|
-
# @return
|
323
|
-
# @raise [DatabaseError] if more than object matches the
|
332
|
+
# @param (see #find)
|
333
|
+
# @return (see #find)
|
334
|
+
# @raise [DatabaseError] if more than object matches the target object attribute values or if
|
324
335
|
# the search object is a dependent entity that does not reference an owner
|
325
336
|
def find_object(obj)
|
337
|
+
# The transient set includes every object for which a find failed. This set is used to preclude
|
338
|
+
# a recursive unsuccessful search.
|
326
339
|
if @transients.include?(obj) then
|
327
340
|
logger.debug { "Find #{obj.qp} obviated since the search was previously unsuccessful in the current database operation context." }
|
328
341
|
return
|
@@ -334,23 +347,30 @@ module CaRuby
|
|
334
347
|
return obj if obj.equal?(fetched)
|
335
348
|
|
336
349
|
logger.debug { "Fetch #{obj.qp} matched database object #{fetched}." }
|
350
|
+
# The target is no longer a transient since the find succeeded (see above).
|
337
351
|
@transients.delete(obj)
|
338
|
-
#
|
352
|
+
# If the fetched object is a subclass of the target, then return the fetched object.
|
353
|
+
if fetched.class < obj.class then
|
354
|
+
# Clean up the fetched object before it is returned.
|
355
|
+
detoxify(fetched)
|
356
|
+
return persistify(fetched)
|
357
|
+
end
|
358
|
+
# Recursively copy the nondomain attributes of the fetched domain object references.
|
359
|
+
# This merge sets the target identifier and fills in missing values of other nondomain
|
360
|
+
# attributes.
|
339
361
|
merge_fetched(fetched, obj)
|
340
362
|
# Inject the lazy loader for loadable domain reference attributes.
|
341
363
|
persistify(obj, fetched)
|
342
364
|
obj
|
343
365
|
end
|
344
|
-
|
345
|
-
# Fetches the object matching the specified object
|
366
|
+
|
367
|
+
# Fetches the object matching the specified domain object from the database.
|
346
368
|
#
|
347
369
|
# @see #find_object
|
348
370
|
def fetch_object(obj)
|
349
371
|
# If there is an identifier, then work around the caCORE identifier query bug by delegating
|
350
372
|
# to the HQL identifier query.
|
351
|
-
if obj.identifier
|
352
|
-
return query_on_identifier(obj).first
|
353
|
-
end
|
373
|
+
return query_on_identifier(obj).first if obj.identifier
|
354
374
|
# Make the finder template with key attributes.
|
355
375
|
tmpl = finder_template(obj)
|
356
376
|
# If a template could be made, then fetch on the template.
|
@@ -367,18 +387,14 @@ module CaRuby
|
|
367
387
|
# submit the query on the template
|
368
388
|
logger.debug { "Query template for finding #{obj.qp}: #{template}." }
|
369
389
|
result = query_on_template(template)
|
370
|
-
#
|
371
|
-
# possible cause is an
|
390
|
+
# It is an error to have an ambiguous result.
|
391
|
+
# A possible cause is an unenforced secondary or alternate key.
|
372
392
|
if result.size > 1 then
|
373
|
-
|
374
|
-
# it is an error to have an ambiguous result
|
375
|
-
logger.error("Fetch error - #{msg}:\n#{obj}")
|
376
|
-
Jinx.fail(DatabaseError, msg)
|
393
|
+
raise DatabaseError.new("More than one match for #{obj.class.qp} find with template #{template.qp}:\n#{template.dump}")
|
377
394
|
end
|
378
|
-
|
379
395
|
result.first
|
380
396
|
end
|
381
|
-
|
397
|
+
|
382
398
|
# If the given domain object is a dependent with an unfetched owner, then this method fetches
|
383
399
|
# the owner and attempts to match the owner dependent to this object.
|
384
400
|
#
|
@@ -388,11 +404,11 @@ module CaRuby
|
|
388
404
|
owner = nil
|
389
405
|
oattr = obj.class.owner_attributes.detect { |pa| owner = obj.send(pa) }
|
390
406
|
return if owner.nil? or owner.fetched?
|
391
|
-
|
407
|
+
|
392
408
|
logger.debug { "Querying #{obj.qp} by matching on the #{oattr} owner #{owner.qp} dependents..." }
|
393
409
|
inv_prop = obj.class.property(oattr)
|
394
410
|
if inv_prop.nil? then
|
395
|
-
|
411
|
+
raise DatabaseError.new("#{dep.class.qp} owner attribute #{oattr} does not have a #{owner.class.qp} inverse dependent attribute.")
|
396
412
|
end
|
397
413
|
inv = inv_prop.inverse
|
398
414
|
# fetch the owner if necessary
|
@@ -402,19 +418,20 @@ module CaRuby
|
|
402
418
|
logger.debug { "Found #{obj.qp} by fetching the owner #{owner}." }
|
403
419
|
return obj
|
404
420
|
end
|
405
|
-
|
421
|
+
|
406
422
|
# try to match a fetched owner dependent
|
407
423
|
deps = lazy_loader.enable { owner.send(inv) }
|
408
424
|
if obj.identifier then
|
409
425
|
logger.debug { "Found #{obj.qp} by fetching the owner #{owner} #{inv} dependents." }
|
410
426
|
return obj
|
411
427
|
else
|
412
|
-
logger.debug { "#{obj.qp} does not match one of the fetched owner #{owner} #{inv} dependents #{deps}." }
|
428
|
+
logger.debug { "#{obj.qp} does not match one of the fetched owner #{owner} #{inv} dependents #{deps.qp}." }
|
413
429
|
nil
|
414
430
|
end
|
415
431
|
end
|
416
|
-
|
417
|
-
# Returns a copy of
|
432
|
+
|
433
|
+
# Returns a copy of the given domain object containing only those key attributes used
|
434
|
+
# in a find operation.
|
418
435
|
#
|
419
436
|
# @quirk caCORE Bug #79: caCORE search fetches on all non-nil attributes, except
|
420
437
|
# occasionally the identifier. There is no indication of how to identify uniquely
|
@@ -422,9 +439,9 @@ module CaRuby
|
|
422
439
|
# application configuration.
|
423
440
|
def finder_template(obj)
|
424
441
|
hash = finder_parameters(obj) || return
|
425
|
-
@
|
442
|
+
@rdr_tmpl_bldr.build_template(obj, hash)
|
426
443
|
end
|
427
|
-
|
444
|
+
|
428
445
|
# Fetches the given object attribute value from the database.
|
429
446
|
#
|
430
447
|
# @quirk caCORE there is no association fetch for caCORE 3.1 and earlier;
|
@@ -460,7 +477,7 @@ module CaRuby
|
|
460
477
|
logger.debug { "Fetching association #{attribute} for #{obj}..." }
|
461
478
|
# load the object if necessary
|
462
479
|
unless exists?(obj) then
|
463
|
-
|
480
|
+
raise DatabaseError.new("Can't fetch an association since the referencing object is not found in the database: #{obj}")
|
464
481
|
end
|
465
482
|
# fetch the reference
|
466
483
|
result = query_safe(obj, attribute)
|
@@ -476,7 +493,7 @@ module CaRuby
|
|
476
493
|
# Unbracket the result if the search propery is not a collection.
|
477
494
|
prop.collection? ? result : result.first
|
478
495
|
end
|
479
|
-
|
496
|
+
|
480
497
|
# Fetches the given object attribute reference from the database and sets the property value.
|
481
498
|
#
|
482
499
|
# @param (see #fetch_association)
|
@@ -484,7 +501,7 @@ module CaRuby
|
|
484
501
|
def load_association(obj, attribute)
|
485
502
|
obj.set_property_value(attribute, fetch_association(obj, attribute))
|
486
503
|
end
|
487
|
-
|
504
|
+
|
488
505
|
# @return [{Symbol => Object}, nil] the find operation key attributes, or nil if there is no complete key
|
489
506
|
#
|
490
507
|
# @quirk caCORE caCORE search fetches on all non-nil attributes, except occasionally the identifier
|
@@ -496,7 +513,7 @@ module CaRuby
|
|
496
513
|
key_value_hash(obj, obj.class.secondary_key_attributes) or
|
497
514
|
key_value_hash(obj, obj.class.alternate_key_attributes)
|
498
515
|
end
|
499
|
-
|
516
|
+
|
500
517
|
# @return [{Symbol => Object}, nil] the attribute => value hash suitable for a finder template
|
501
518
|
# if obj has searchable values for all of the given key attributes, nil otherwise
|
502
519
|
def key_value_hash(obj, attributes)
|
@@ -525,7 +542,7 @@ module CaRuby
|
|
525
542
|
value
|
526
543
|
end
|
527
544
|
end
|
528
|
-
|
545
|
+
|
529
546
|
# Returns whether the obj attribute value is either not a domain object reference or exists
|
530
547
|
# in the database.
|
531
548
|
#
|
@@ -535,23 +552,6 @@ module CaRuby
|
|
535
552
|
return false if value.nil?
|
536
553
|
obj.class.nondomain_attribute?(pa) or value.identifier
|
537
554
|
end
|
538
|
-
|
539
|
-
# Sets the template attribute to a new search reference object created from source.
|
540
|
-
# The reference contains only the source identifier.
|
541
|
-
#
|
542
|
-
# @quirk caCORE The search template must break inverse integrity by clearing an owner inverse reference,
|
543
|
-
# since a dependent => onwer => dependent cycle causes a caCORE search infinite loop.
|
544
|
-
#
|
545
|
-
# @return [Jinx::Resource, nil] the search reference, or nil if source does not exist in the database
|
546
|
-
def add_search_template_reference(template, source, attribute)
|
547
|
-
return if not exists?(source)
|
548
|
-
ref = source.copy(:identifier)
|
549
|
-
template.set_property_value(attribute, ref)
|
550
|
-
inverse = template.class.property(attribute).derived_inverse
|
551
|
-
ref.clear_attribute(inverse) if inverse
|
552
|
-
logger.debug { "Search reference parameter #{attribute} for #{template.qp} set to #{ref} copied from #{source.qp}" }
|
553
|
-
ref
|
554
|
-
end
|
555
555
|
end
|
556
556
|
end
|
557
557
|
end
|
@@ -43,13 +43,16 @@ module CaRuby
|
|
43
43
|
# source domain object. The reference contains only the source identifier, if it exists,
|
44
44
|
# or the source non-domain attributes otherwise.
|
45
45
|
#
|
46
|
+
# @quirk caCORE The search template must break inverse integrity by clearing an owner inverse reference,
|
47
|
+
# since a dependent => owner => dependent cycle causes a caCORE search infinite loop.
|
48
|
+
#
|
46
49
|
# @return [Jinx::Resource] the search reference
|
47
50
|
def add_search_template_reference(template, source, attribute)
|
48
51
|
ref = source.identifier ? source.copy(:identifier) : source.copy
|
49
|
-
# Disable inverse integrity
|
50
|
-
#
|
51
|
-
#
|
52
|
-
wtr = template.class.property(attribute).
|
52
|
+
# Disable inverse integrity by using the Java property writer instead of the attribute writer.
|
53
|
+
# The attribute writer will add a reference from ref to template, which introduces a
|
54
|
+
# template => ref => template cycle that causes a caCORE search infinite loop.
|
55
|
+
wtr = template.class.property(attribute).java_writer
|
53
56
|
template.send(wtr, ref)
|
54
57
|
logger.debug { "Search reference parameter #{attribute} for #{template.qp} set to #{ref} copied from #{source.qp}" }
|
55
58
|
ref
|
@@ -24,26 +24,34 @@ module CaRuby
|
|
24
24
|
# @option opts [String] :database the mandatory database name
|
25
25
|
# @option opts [String] :database_user the mandatory database username (not the application login name)
|
26
26
|
# @option opts [String] :database_password the optional database password (not the application login password)
|
27
|
+
# @option opts [String] :database_type the optional database type (default +mysql+)
|
27
28
|
# @option opts [String] :database_host the optional database host
|
28
29
|
# @option opts [Integer] :database_port the optional database port number
|
29
|
-
# @option opts [Integer] :database_port the optional database port number
|
30
30
|
# @option opts [String] :database_driver the optional DBI connect driver string, e.g. +jdbc:mysql+
|
31
31
|
# @option opts [String] :database_url the optional database connection URL
|
32
32
|
# @option opts [String] :database_driver_class the optional DBI connect driver class name
|
33
33
|
# @raise [CaRuby::ConfigurationError] if an option is invalid
|
34
34
|
def initialize(opts)
|
35
35
|
if opts.empty? then
|
36
|
-
|
36
|
+
raise CaRuby::ConfigurationError.new("The caRuby database connection properties were not found.")
|
37
37
|
end
|
38
38
|
app_host = Options.get(:host, opts, 'localhost')
|
39
39
|
db_host = Options.get(:database_host, opts, app_host)
|
40
40
|
db_type = Options.get(:database_type, opts, 'mysql')
|
41
41
|
db_driver = Options.get(:database_driver, opts) { default_driver_string(db_type) }
|
42
42
|
db_port = Options.get(:database_port, opts) { default_port(db_type) }
|
43
|
-
db_name = Options.get(:database, opts)
|
44
|
-
@db_url = Options.get(:database_url, opts)
|
43
|
+
db_name = Options.get(:database, opts)
|
44
|
+
@db_url = Options.get(:database_url, opts) do
|
45
|
+
# If there is a db name, then make the default db url.
|
46
|
+
# Otherwise, raise an error.
|
47
|
+
if db_name then
|
48
|
+
"#{db_driver}://#{db_host}:#{db_port}/#{db_name}"
|
49
|
+
else
|
50
|
+
raise_missing_option_error(:database)
|
51
|
+
end
|
52
|
+
end
|
45
53
|
@dbi_url = 'dbi:' + @db_url
|
46
|
-
@username = Options.get(:database_user, opts) {
|
54
|
+
@username = Options.get(:database_user, opts) { raise_missing_option_error(:database_user) }
|
47
55
|
@password = Options.get(:database_password, opts)
|
48
56
|
@driver_class = Options.get(:database_driver_class, opts, default_driver_class(db_type))
|
49
57
|
# The effective connection options.
|
@@ -65,7 +73,7 @@ module CaRuby
|
|
65
73
|
# @yield [dbh] the transaction statements
|
66
74
|
# @yieldparam [RDBI::Database] dbh the database handle
|
67
75
|
def execute
|
68
|
-
RDBI.connect(:JDBC, :database => @db_url, :user => @username, :password => @password, :driver_class=> @driver_class) do |dbh|
|
76
|
+
RDBI.connect(:JDBC, :database => @db_url, :user => @username, :password => @password, :driver_class => @driver_class) do |dbh|
|
69
77
|
yield dbh
|
70
78
|
end
|
71
79
|
end
|
@@ -74,13 +82,19 @@ module CaRuby
|
|
74
82
|
#
|
75
83
|
# @param [String] sql the SQL to execute
|
76
84
|
# @param [Array] args the SQL bindings
|
85
|
+
# @yield [row] operate on the result
|
86
|
+
# @yield [Array] the result row
|
77
87
|
# @return [Array] the query result
|
78
|
-
def query(sql, *args)
|
88
|
+
def query(sql, *args, &block)
|
79
89
|
fetched = nil
|
80
90
|
execute do |dbh|
|
81
|
-
|
82
|
-
|
83
|
-
|
91
|
+
result = dbh.execute(sql, *args)
|
92
|
+
if block_given? then
|
93
|
+
result.each(&block)
|
94
|
+
else
|
95
|
+
fetched = result.fetch(:all)
|
96
|
+
end
|
97
|
+
result.finish
|
84
98
|
end
|
85
99
|
fetched
|
86
100
|
end
|
@@ -149,7 +163,8 @@ module CaRuby
|
|
149
163
|
case db_type.downcase
|
150
164
|
when 'mysql' then 'Jdbc:mysql'
|
151
165
|
when 'oracle' then 'Oracle'
|
152
|
-
|
166
|
+
when 'jdbc' then 'Jdbc'
|
167
|
+
else raise CaRuby::ConfigurationError.new("Default database connection driver string could not be determined for database type #{db_type}")
|
153
168
|
end
|
154
169
|
end
|
155
170
|
|
@@ -157,7 +172,8 @@ module CaRuby
|
|
157
172
|
case db_type.downcase
|
158
173
|
when 'mysql' then MYSQL_DRIVER_CLASS_NAME
|
159
174
|
when 'oracle' then ORACLE_DRIVER_CLASS_NAME
|
160
|
-
|
175
|
+
when 'jdbc' then ''
|
176
|
+
else raise CaRuby::ConfigurationError.new("Default database connection driver class could not be determined for database type #{db_type}")
|
161
177
|
end
|
162
178
|
end
|
163
179
|
|
@@ -165,12 +181,13 @@ module CaRuby
|
|
165
181
|
case db_type.downcase
|
166
182
|
when 'mysql' then 3306
|
167
183
|
when 'oracle' then 1521
|
168
|
-
|
184
|
+
when 'jdbc' then -1
|
185
|
+
else raise CaRuby::ConfigurationError.new("Default database connection port could not be determined for database type #{db_type}")
|
169
186
|
end
|
170
187
|
end
|
171
188
|
|
172
|
-
def
|
173
|
-
|
189
|
+
def raise_missing_option_error(option)
|
190
|
+
raise CaRuby::ConfigurationError.new("Database connection property not found: #{option}")
|
174
191
|
end
|
175
192
|
end
|
176
193
|
end
|