caruby-core 2.1.1 → 2.1.2
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/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
|