lafcadio 0.9.0 → 0.9.1
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/lafcadio.rb +1 -1
- data/lib/lafcadio.rb~ +1 -1
- data/lib/lafcadio/domain.rb +363 -255
- data/lib/lafcadio/domain.rb~ +201 -230
- data/lib/lafcadio/mock.rb +64 -70
- data/lib/lafcadio/mock.rb~ +48 -49
- data/lib/lafcadio/objectField.rb +137 -127
- data/lib/lafcadio/objectField.rb~ +7 -7
- data/lib/lafcadio/objectStore.rb +560 -586
- data/lib/lafcadio/objectStore.rb~ +495 -536
- data/lib/lafcadio/query.rb +320 -213
- data/lib/lafcadio/query.rb~ +129 -131
- data/lib/lafcadio/schema.rb +10 -14
- data/lib/lafcadio/schema.rb~ +1 -1
- data/lib/lafcadio/test.rb +174 -75
- data/lib/lafcadio/test.rb~ +191 -0
- data/lib/lafcadio/util.rb +1 -32
- data/lib/lafcadio/util.rb~ +1 -6
- metadata +2 -2
@@ -12,10 +12,10 @@ module Lafcadio
|
|
12
12
|
|
13
13
|
def self.instantiate_from_xml( domain_class, fieldElt ) #:nodoc:
|
14
14
|
parameters = instantiation_parameters( fieldElt )
|
15
|
-
|
15
|
+
create_with_args( domain_class, parameters )
|
16
16
|
end
|
17
17
|
|
18
|
-
def self.
|
18
|
+
def self.create_with_args( domain_class, parameters ) #:nodoc:
|
19
19
|
instance = self.new( domain_class, parameters['name'] )
|
20
20
|
if ( db_field_name = parameters['db_field_name'] )
|
21
21
|
instance.db_field_name = db_field_name
|
@@ -277,7 +277,7 @@ module Lafcadio
|
|
277
277
|
( $' || linked_type.name ).camel_case_to_underscore
|
278
278
|
end
|
279
279
|
|
280
|
-
def self.
|
280
|
+
def self.create_with_args( domain_class, parameters ) #:nodoc:
|
281
281
|
linked_type = parameters['linked_type']
|
282
282
|
instance = self.new(
|
283
283
|
domain_class, linked_type,
|
@@ -293,7 +293,7 @@ module Lafcadio
|
|
293
293
|
def self.instantiation_parameters( fieldElt ) #:nodoc:
|
294
294
|
parameters = super( fieldElt )
|
295
295
|
linked_typeStr = fieldElt.attributes['linked_type']
|
296
|
-
linked_type =
|
296
|
+
linked_type = Class.by_name linked_typeStr
|
297
297
|
parameters['linked_type'] = linked_type
|
298
298
|
parameters['delete_cascade'] = fieldElt.attributes['delete_cascade'] == 'y'
|
299
299
|
parameters
|
@@ -395,7 +395,7 @@ module Lafcadio
|
|
395
395
|
# enums as the +enums+ argument.
|
396
396
|
#
|
397
397
|
class EnumField < StringField
|
398
|
-
def self.
|
398
|
+
def self.create_with_args( domain_class, parameters ) #:nodoc:
|
399
399
|
self.new( domain_class, parameters['name'], parameters['enums'] )
|
400
400
|
end
|
401
401
|
|
@@ -457,7 +457,7 @@ module Lafcadio
|
|
457
457
|
|
458
458
|
# FloatField represents a decimal value.
|
459
459
|
class FloatField < ObjectField
|
460
|
-
def self.
|
460
|
+
def self.create_with_args( domain_class, parameters ) #:nodoc:
|
461
461
|
self.new( domain_class, parameters['name'] )
|
462
462
|
end
|
463
463
|
|
@@ -504,7 +504,7 @@ module Lafcadio
|
|
504
504
|
end
|
505
505
|
|
506
506
|
class SubsetDomainObjectField < DomainObjectField #:nodoc:
|
507
|
-
def self.
|
507
|
+
def self.create_with_args( domain_class, parameters )
|
508
508
|
self.new( domain_class, parameters['linked_type'],
|
509
509
|
parameters['subset_field'], parameters['name'] )
|
510
510
|
end
|
data/lib/lafcadio/objectStore.rb
CHANGED
@@ -5,238 +5,18 @@ require 'lafcadio/query'
|
|
5
5
|
require 'lafcadio/util'
|
6
6
|
|
7
7
|
module Lafcadio
|
8
|
-
class Committer #:nodoc:
|
9
|
-
INSERT = 1
|
10
|
-
UPDATE = 2
|
11
|
-
DELETE = 3
|
12
|
-
|
13
|
-
attr_reader :commit_type, :db_object
|
14
|
-
|
15
|
-
def initialize(db_object, dbBridge, cache)
|
16
|
-
@db_object = db_object
|
17
|
-
@dbBridge = dbBridge
|
18
|
-
@cache = cache
|
19
|
-
@objectStore = ObjectStore.get_object_store
|
20
|
-
@commit_type = nil
|
21
|
-
end
|
22
|
-
|
23
|
-
def execute
|
24
|
-
@db_object.verify if LafcadioConfig.new()['checkFields'] == 'onCommit'
|
25
|
-
set_commit_type
|
26
|
-
@db_object.last_commit = get_last_commit
|
27
|
-
@db_object.pre_commit_trigger
|
28
|
-
update_dependent_domain_objects if @db_object.delete
|
29
|
-
@dbBridge.commit @db_object
|
30
|
-
unless @db_object.pk_id
|
31
|
-
@db_object.pk_id = @dbBridge.last_pk_id_inserted
|
32
|
-
end
|
33
|
-
@cache.update_after_commit self
|
34
|
-
@db_object.post_commit_trigger
|
35
|
-
end
|
36
|
-
|
37
|
-
def get_last_commit
|
38
|
-
if @db_object.delete
|
39
|
-
DomainObject::COMMIT_DELETE
|
40
|
-
elsif @db_object.pk_id
|
41
|
-
DomainObject::COMMIT_EDIT
|
42
|
-
else
|
43
|
-
DomainObject::COMMIT_ADD
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def set_commit_type
|
48
|
-
if @db_object.delete
|
49
|
-
@commit_type = DELETE
|
50
|
-
elsif @db_object.pk_id
|
51
|
-
@commit_type = UPDATE
|
52
|
-
else
|
53
|
-
@commit_type = INSERT
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def update_dependent_domain_objects
|
58
|
-
dependent_classes = @db_object.domain_class.dependent_classes
|
59
|
-
dependent_classes.keys.each { |aClass|
|
60
|
-
field = dependent_classes[aClass]
|
61
|
-
collection = @objectStore.get_filtered( aClass.name, @db_object,
|
62
|
-
field.name )
|
63
|
-
collection.each { |dependentObject|
|
64
|
-
if field.delete_cascade
|
65
|
-
dependentObject.delete = true
|
66
|
-
else
|
67
|
-
dependentObject.send( field.name + '=', nil )
|
68
|
-
end
|
69
|
-
@objectStore.commit(dependentObject)
|
70
|
-
}
|
71
|
-
}
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
class CouldntMatchDomainClassError < RuntimeError #:nodoc:
|
76
|
-
end
|
77
|
-
|
78
|
-
class DbBridge #:nodoc:
|
79
|
-
@@last_pk_id_inserted = nil
|
80
|
-
|
81
|
-
def self._load(aString)
|
82
|
-
aString =~ /db_conn:/
|
83
|
-
db_conn_str = $'
|
84
|
-
begin
|
85
|
-
db_conn = Marshal.load db_conn_str
|
86
|
-
rescue TypeError
|
87
|
-
db_conn = nil
|
88
|
-
end
|
89
|
-
DbConnection.set_db_connection db_conn
|
90
|
-
new
|
91
|
-
end
|
92
|
-
|
93
|
-
def initialize
|
94
|
-
@db_conn = DbConnection.get_db_connection
|
95
|
-
ObjectSpace.define_finalizer( self, proc { |id|
|
96
|
-
DbConnection.get_db_connection.disconnect
|
97
|
-
} )
|
98
|
-
end
|
99
|
-
|
100
|
-
def _dump(aDepth)
|
101
|
-
dbDump = @dbh.respond_to?( '_dump' ) ? @dbh._dump : @dbh.class.to_s
|
102
|
-
"dbh:#{dbDump}"
|
103
|
-
end
|
104
|
-
|
105
|
-
def commit(db_object)
|
106
|
-
sqlMaker = DomainObjectSqlMaker.new(db_object)
|
107
|
-
sqlMaker.sql_statements.each { |sql, binds| execute_commit( sql, binds ) }
|
108
|
-
if sqlMaker.sql_statements[0].first =~ /insert/
|
109
|
-
sql = 'select last_insert_id()'
|
110
|
-
result = execute_select( sql )
|
111
|
-
@@last_pk_id_inserted = result[0]['last_insert_id()'].to_i
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def execute_commit( sql, binds ); @db_conn.do( sql, *binds ); end
|
116
|
-
|
117
|
-
def execute_select(sql)
|
118
|
-
maybe_log sql
|
119
|
-
begin
|
120
|
-
@db_conn.select_all( sql )
|
121
|
-
rescue DBI::DatabaseError => e
|
122
|
-
raise $!.to_s + ": #{ e.errstr }"
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def get_collection_by_query(query)
|
127
|
-
domain_class = query.domain_class
|
128
|
-
execute_select( query.to_sql ).collect { |row_hash|
|
129
|
-
domain_class.new( SqlValueConverter.new( domain_class, row_hash ) )
|
130
|
-
}
|
131
|
-
end
|
132
|
-
|
133
|
-
def group_query( query )
|
134
|
-
execute_select( query.to_sql ).map { |row| query.result_row( row ) }
|
135
|
-
end
|
136
|
-
|
137
|
-
def last_pk_id_inserted; @@last_pk_id_inserted; end
|
138
|
-
|
139
|
-
def maybe_log(sql)
|
140
|
-
config = LafcadioConfig.new
|
141
|
-
if config['logSql'] == 'y'
|
142
|
-
sqllog = Log4r::Logger['sql'] || Log4r::Logger.new( 'sql' )
|
143
|
-
filename = File.join( config['logdir'], config['sqlLogFile'] || 'sql' )
|
144
|
-
outputter = Log4r::FileOutputter.new( 'outputter',
|
145
|
-
{ :filename => filename } )
|
146
|
-
sqllog.outputters = outputter
|
147
|
-
sqllog.info sql
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
def transaction( action )
|
152
|
-
tr = Transaction.new @db_conn
|
153
|
-
tr.commit
|
154
|
-
begin
|
155
|
-
action.call tr
|
156
|
-
tr.commit
|
157
|
-
rescue RollbackError
|
158
|
-
# rollback handled by Transaction
|
159
|
-
rescue
|
160
|
-
err_to_raise = $!
|
161
|
-
tr.rollback false
|
162
|
-
raise err_to_raise
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
class Transaction
|
167
|
-
def initialize( db_conn ); @db_conn = db_conn; end
|
168
|
-
|
169
|
-
def commit; @db_conn.commit; end
|
170
|
-
|
171
|
-
def rollback( raise_error = true )
|
172
|
-
@db_conn.rollback
|
173
|
-
raise RollbackError if raise_error
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
class RollbackError < StandardError; end
|
178
|
-
end
|
179
|
-
|
180
|
-
class DbConnection < ContextualService::Service
|
181
|
-
@@connectionClass = DBI
|
182
|
-
@@db_name = nil
|
183
|
-
@@dbh = nil
|
184
|
-
|
185
|
-
def self.flush
|
186
|
-
DbConnection.set_db_connection( nil )
|
187
|
-
@@dbh = nil
|
188
|
-
end
|
189
|
-
|
190
|
-
def self.set_connection_class( aClass ); @@connectionClass = aClass; end
|
191
|
-
|
192
|
-
def self.set_db_name( db_name ); @@db_name = db_name; end
|
193
|
-
|
194
|
-
def self.set_dbh( dbh ); @@dbh = dbh; end
|
195
|
-
|
196
|
-
def initialize
|
197
|
-
@@dbh = load_new_dbh if @@dbh.nil?
|
198
|
-
@dbh = @@dbh
|
199
|
-
end
|
200
|
-
|
201
|
-
def disconnect; @dbh.disconnect if @dbh; end
|
202
|
-
|
203
|
-
def load_new_dbh
|
204
|
-
config = LafcadioConfig.new
|
205
|
-
dbName = @@db_name || config['dbname']
|
206
|
-
dbAndHost = nil
|
207
|
-
if dbName && config['dbhost']
|
208
|
-
dbAndHost = "dbi:Mysql:#{ dbName }:#{ config['dbhost'] }"
|
209
|
-
else
|
210
|
-
dbAndHost = "dbi:#{config['dbconn']}"
|
211
|
-
end
|
212
|
-
@@dbh = @@connectionClass.connect( dbAndHost, config['dbuser'],
|
213
|
-
config['dbpassword'] )
|
214
|
-
@@dbh['AutoCommit'] = false
|
215
|
-
@@dbh
|
216
|
-
end
|
217
|
-
|
218
|
-
def method_missing( symbol, *args )
|
219
|
-
@dbh.send( symbol, *args )
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
8
|
class DomainObjectInitError < RuntimeError #:nodoc:
|
224
|
-
attr_reader :messages
|
225
|
-
|
226
|
-
def initialize(messages)
|
227
|
-
@messages = messages
|
228
|
-
end
|
229
9
|
end
|
230
10
|
|
231
11
|
class DomainObjectNotFoundError < RuntimeError #:nodoc:
|
232
12
|
end
|
233
13
|
|
234
14
|
# The DomainObjectProxy is used when retrieving domain objects that are
|
235
|
-
# linked to other domain objects with DomainObjectFields. In terms of
|
236
|
-
# and
|
237
|
-
#
|
238
|
-
# domain object
|
239
|
-
#
|
15
|
+
# linked to other domain objects with DomainObjectFields. In terms of
|
16
|
+
# +domain_class+ and +pk_id+, a DomainObjectProxy instance looks to the
|
17
|
+
# outside world like the domain object it's supposed to represent. It only
|
18
|
+
# retrieves its domain object from the database when member data is
|
19
|
+
# requested.
|
240
20
|
#
|
241
21
|
# In normal usage you will probably never manipulate a DomainObjectProxy
|
242
22
|
# directly, but you may discover it by accident by calling
|
@@ -247,122 +27,43 @@ module Lafcadio
|
|
247
27
|
|
248
28
|
attr_accessor :domain_class, :pk_id
|
249
29
|
|
250
|
-
def initialize(
|
251
|
-
if
|
252
|
-
@domain_class =
|
253
|
-
@pk_id =
|
254
|
-
elsif
|
255
|
-
@db_object = domain_classOrDbObject
|
30
|
+
def initialize( *args ) #:nodoc:
|
31
|
+
if args.size == 2
|
32
|
+
@domain_class = args.first
|
33
|
+
@pk_id = args.last
|
34
|
+
elsif args.first.is_a?( DomainObject )
|
256
35
|
@d_obj_retrieve_time = Time.now
|
257
|
-
@domain_class =
|
258
|
-
@pk_id =
|
36
|
+
@domain_class = args.first.class
|
37
|
+
@pk_id = args.first.pk_id
|
259
38
|
else
|
260
39
|
raise ArgumentError
|
261
40
|
end
|
262
|
-
@db_object = nil
|
263
41
|
end
|
264
42
|
|
265
|
-
def
|
266
|
-
object_store = ObjectStore.get_object_store
|
43
|
+
def db_object #:nodoc:
|
267
44
|
if @db_object.nil? || needs_refresh?
|
268
|
-
@db_object =
|
45
|
+
@db_object = ObjectStore.get_object_store.get( @domain_class, @pk_id )
|
269
46
|
@d_obj_retrieve_time = Time.now
|
270
47
|
end
|
271
48
|
@db_object
|
272
49
|
end
|
273
50
|
|
274
|
-
def hash
|
275
|
-
|
51
|
+
def hash #:nodoc:
|
52
|
+
db_object.hash
|
276
53
|
end
|
277
54
|
|
278
|
-
def method_missing(methodId, *args)
|
279
|
-
|
55
|
+
def method_missing( methodId, *args ) #:nodoc:
|
56
|
+
db_object.send( methodId, *args )
|
280
57
|
end
|
281
58
|
|
282
|
-
def needs_refresh?
|
59
|
+
def needs_refresh? #:nodoc:
|
283
60
|
object_store = ObjectStore.get_object_store
|
284
61
|
last_commit_time = object_store.last_commit_time( @domain_class, @pk_id )
|
285
|
-
|
62
|
+
last_commit_time && last_commit_time > @d_obj_retrieve_time
|
286
63
|
end
|
287
64
|
|
288
|
-
def to_s
|
289
|
-
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
class DomainObjectSqlMaker #:nodoc:
|
294
|
-
attr_reader :bind_values
|
295
|
-
|
296
|
-
def initialize(obj); @obj = obj; end
|
297
|
-
|
298
|
-
def delete_sql( domain_class )
|
299
|
-
"delete from #{ domain_class.table_name} " +
|
300
|
-
"where #{ domain_class.sql_primary_key_name }=#{ @obj.pk_id }"
|
301
|
-
end
|
302
|
-
|
303
|
-
def get_name_value_pairs( domain_class )
|
304
|
-
nameValues = []
|
305
|
-
domain_class.class_fields.each { |field|
|
306
|
-
unless field.instance_of?( PrimaryKeyField )
|
307
|
-
value = @obj.send(field.name)
|
308
|
-
unless field.db_will_automatically_write
|
309
|
-
nameValues << field.name_for_sql
|
310
|
-
nameValues <<(field.value_for_sql(value))
|
311
|
-
end
|
312
|
-
if field.bind_write?
|
313
|
-
@bind_values << value
|
314
|
-
end
|
315
|
-
end
|
316
|
-
}
|
317
|
-
QueueHash.new( *nameValues )
|
318
|
-
end
|
319
|
-
|
320
|
-
def insert_sql( domain_class )
|
321
|
-
fields = domain_class.class_fields
|
322
|
-
nameValuePairs = get_name_value_pairs( domain_class )
|
323
|
-
if domain_class.is_based_on?
|
324
|
-
nameValuePairs[domain_class.sql_primary_key_name] = 'LAST_INSERT_ID()'
|
325
|
-
end
|
326
|
-
fieldNameStr = nameValuePairs.keys.join ", "
|
327
|
-
fieldValueStr = nameValuePairs.values.join ", "
|
328
|
-
"insert into #{ domain_class.table_name}(#{fieldNameStr}) " +
|
329
|
-
"values(#{fieldValueStr})"
|
330
|
-
end
|
331
|
-
|
332
|
-
def sql_statements
|
333
|
-
statements = []
|
334
|
-
if @obj.error_messages.size > 0
|
335
|
-
raise DomainObjectInitError, @obj.error_messages, caller
|
336
|
-
end
|
337
|
-
@obj.class.self_and_concrete_superclasses.each { |domain_class|
|
338
|
-
statements << statement_bind_value_pair( domain_class )
|
339
|
-
}
|
340
|
-
statements.reverse
|
341
|
-
end
|
342
|
-
|
343
|
-
def statement_bind_value_pair( domain_class )
|
344
|
-
@bind_values = []
|
345
|
-
if @obj.pk_id == nil
|
346
|
-
statement = insert_sql( domain_class )
|
347
|
-
else
|
348
|
-
if @obj.delete
|
349
|
-
statement = delete_sql( domain_class )
|
350
|
-
else
|
351
|
-
statement = update_sql( domain_class)
|
352
|
-
end
|
353
|
-
end
|
354
|
-
[statement, @bind_values]
|
355
|
-
end
|
356
|
-
|
357
|
-
def update_sql( domain_class )
|
358
|
-
nameValueStrings = []
|
359
|
-
nameValuePairs = get_name_value_pairs( domain_class )
|
360
|
-
nameValuePairs.each { |key, value|
|
361
|
-
nameValueStrings << "#{key}=#{ value }"
|
362
|
-
}
|
363
|
-
allNameValues = nameValueStrings.join ', '
|
364
|
-
"update #{ domain_class.table_name} set #{allNameValues} " +
|
365
|
-
"where #{ domain_class.sql_primary_key_name}=#{@obj.pk_id}"
|
65
|
+
def to_s #:nodoc:
|
66
|
+
db_object.to_s
|
366
67
|
end
|
367
68
|
end
|
368
69
|
|
@@ -379,29 +80,31 @@ module Lafcadio
|
|
379
80
|
# [dbhost] The database host.
|
380
81
|
#
|
381
82
|
# = Instantiating ObjectStore
|
382
|
-
#
|
383
|
-
#
|
384
|
-
# ObjectStore.get_object_store. (Using a ContextualService makes it easier to
|
385
|
-
# make out the ObjectStore for unit tests: See ContextualService for more.)
|
83
|
+
# You can't get an instance of ObjectStore by calling ObjectStore.new.
|
84
|
+
# Instead, you should call ObjectStore.get_object_store.
|
386
85
|
#
|
387
86
|
# = Dynamic method calls
|
388
87
|
# ObjectStore uses reflection to provide a lot of convenience methods for
|
389
88
|
# querying domain objects in a number of ways.
|
390
|
-
# [ObjectStore
|
89
|
+
# [ObjectStore#< domain class >( pk_id )]
|
391
90
|
# Retrieves one domain object by pk_id. For example,
|
392
|
-
# ObjectStore#
|
393
|
-
# will return User 100.
|
394
|
-
#
|
91
|
+
# ObjectStore#user( 100 )
|
92
|
+
# will return User 100. Note that you can also just user DomainObject.[]:
|
93
|
+
# User[100]
|
94
|
+
# [ObjectStore#< plural of domain class >( searchTerm, fieldName = nil )]
|
395
95
|
# Returns a collection of all instances of that domain class matching that
|
396
96
|
# search term. For example,
|
397
|
-
# ObjectStore#
|
97
|
+
# ObjectStore#products( a_product_category )
|
398
98
|
# queries MySQL for all products that belong to that product category. You
|
399
99
|
# can omit +fieldName+ if +searchTerm+ is a non-nil domain object, and the
|
400
100
|
# field connecting the first domain class to the second is named after the
|
401
101
|
# domain class. (For example, the above line assumes that Product has a
|
402
|
-
# field named "
|
102
|
+
# field named "product_category".) Otherwise, it's best to include
|
403
103
|
# +fieldName+:
|
404
|
-
# ObjectStore#
|
104
|
+
# ObjectStore#users( "Jones", "lastName" )
|
105
|
+
# Note that these can also be accessed through DomainObject.get:
|
106
|
+
# Product.get( a_product_category )
|
107
|
+
# User.get( "Jones", "lastName" )
|
405
108
|
#
|
406
109
|
# = Querying
|
407
110
|
# ObjectStore can also be used to generate complex, ad-hoc queries which
|
@@ -409,14 +112,19 @@ module Lafcadio
|
|
409
112
|
# Furthermore, these queries can be run against in-memory data stores, which
|
410
113
|
# is particularly useful for tests.
|
411
114
|
# date = Date.new( 2003, 1, 1 )
|
412
|
-
# ObjectStore#
|
413
|
-
#
|
414
|
-
# invoice.hours.equals( 10 )
|
115
|
+
# ObjectStore#invoices { |invoice|
|
116
|
+
# invoice.date.gte( date ) & invoice.rate.equals( 10 ) &
|
117
|
+
# invoice.hours.equals( 10 )
|
415
118
|
# }
|
416
119
|
# is the same as
|
417
120
|
# select * from invoices
|
418
121
|
# where (date >= '2003-01-01' and rate = 10 and hours = 10)
|
419
|
-
#
|
122
|
+
# Note that you can also use DomainObject.get:
|
123
|
+
# Invoice.get { |invoice|
|
124
|
+
# invoice.date.gte( date ) & invoice.rate.equals( 10 ) &
|
125
|
+
# invoice.hours.equals( 10 )
|
126
|
+
# }
|
127
|
+
# See lafcadio/query.rb for more on the query inference syntax.
|
420
128
|
#
|
421
129
|
# = SQL Logging
|
422
130
|
# Lafcadio uses log4r to log all of its SQL statements. The simplest way to
|
@@ -430,60 +138,43 @@ module Lafcadio
|
|
430
138
|
# = Triggers
|
431
139
|
# Domain classes can be set to fire triggers either before or after commits.
|
432
140
|
# Since these triggers are executed in Ruby, they're easy to test. See
|
433
|
-
# DomainObject#pre_commit_trigger and DomainObject#post_commit_trigger for
|
141
|
+
# DomainObject#pre_commit_trigger and DomainObject#post_commit_trigger for
|
142
|
+
# more.
|
434
143
|
class ObjectStore < ContextualService::Service
|
435
|
-
|
436
|
-
|
437
|
-
end
|
144
|
+
# Returns true if the current stored instance is a MockObjectStore.
|
145
|
+
def self.mock?; get_object_store.mock?; end
|
438
146
|
|
439
|
-
|
440
|
-
@dbBridge = dbBridge == nil ? DbBridge.new : dbBridge
|
441
|
-
@cache = ObjectStore::Cache.new( @dbBridge )
|
442
|
-
end
|
443
|
-
|
444
|
-
# Commits a domain object to the database. You can also simply call
|
445
|
-
# myDomainObject.commit
|
446
|
-
def commit(db_object)
|
447
|
-
@cache.commit( db_object )
|
448
|
-
db_object
|
449
|
-
end
|
147
|
+
@@db_bridge = nil
|
450
148
|
|
451
|
-
#
|
452
|
-
|
453
|
-
|
149
|
+
# Returns the DbBridge; this is useful in case you need to use raw SQL for
|
150
|
+
# a specific query.
|
151
|
+
def self.db_bridge; @@db_bridge ||= DbBridge.new; end
|
152
|
+
|
153
|
+
def self.db_name= (dbName)
|
154
|
+
DbConnection.db_name= dbName
|
454
155
|
end
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
query = Query.new domain_class, pk_id
|
459
|
-
@cache.get_by_query( query )[0] ||
|
460
|
-
( raise( DomainObjectNotFoundError,
|
461
|
-
"Can't find #{domain_class} #{pk_id}", caller ) )
|
156
|
+
|
157
|
+
def initialize #:nodoc:
|
158
|
+
@cache = ObjectStore::Cache.new self.class.db_bridge
|
462
159
|
end
|
463
160
|
|
464
161
|
# Returns all domain objects for the given domain class.
|
465
|
-
def
|
466
|
-
|
467
|
-
# Returns the DbBridge; this is useful in case you need to use raw SQL for a
|
468
|
-
# specific query.
|
469
|
-
def get_db_bridge; @dbBridge; end
|
470
|
-
|
471
|
-
def get_field_name( domain_object )
|
472
|
-
domain_object.domain_class.basename.camel_case_to_underscore
|
162
|
+
def all(domain_class)
|
163
|
+
@cache.get_by_query( Query.new( domain_class ) )
|
473
164
|
end
|
474
165
|
|
475
|
-
|
476
|
-
|
477
|
-
|
166
|
+
# Returns the DbBridge; this is useful in case you need to use raw SQL for
|
167
|
+
# a specific query.
|
168
|
+
def db_bridge; @cache.db_bridge; end
|
169
|
+
|
170
|
+
# Returns the domain object corresponding to the domain class and pk_id.
|
171
|
+
def get( domain_class, pk_id )
|
172
|
+
@cache.get_by_query( Query.new( domain_class, pk_id ) ).first or (
|
173
|
+
raise(
|
174
|
+
DomainObjectNotFoundError, "Can't find #{domain_class} #{pk_id}",
|
175
|
+
caller
|
176
|
+
)
|
478
177
|
)
|
479
|
-
unless fieldName
|
480
|
-
fieldName = domain_class.get_link_field( searchTerm.domain_class ).name
|
481
|
-
end
|
482
|
-
get_subset( Query::Equals.new( fieldName, searchTerm, domain_class ) )
|
483
|
-
end
|
484
|
-
|
485
|
-
def get_map_match( domain_class, mapped ) #:nodoc:
|
486
|
-
Query::Equals.new( get_field_name( mapped ), mapped, domain_class )
|
487
178
|
end
|
488
179
|
|
489
180
|
def get_map_object( domain_class, map1, map2 ) #:nodoc:
|
@@ -491,48 +182,53 @@ module Lafcadio
|
|
491
182
|
raise ArgumentError,
|
492
183
|
"ObjectStore#get_map_object needs two non-nil keys", caller
|
493
184
|
end
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
185
|
+
query = Query.infer( domain_class ) { |dobj|
|
186
|
+
dobj.send(
|
187
|
+
map1.domain_class.basename.camel_case_to_underscore
|
188
|
+
).equals( map1 ) &
|
189
|
+
dobj.send(
|
190
|
+
map2.domain_class.basename.camel_case_to_underscore
|
191
|
+
).equals( map2 )
|
192
|
+
}
|
193
|
+
query( query ).first
|
498
194
|
end
|
499
195
|
|
500
|
-
def
|
501
|
-
|
502
|
-
firstTypeName = searchTerm.class.basename
|
503
|
-
secondTypeName = resultType.basename
|
504
|
-
mapTypeName = firstTypeName + secondTypeName
|
505
|
-
get_filtered( mapTypeName, searchTerm ).collect { |mapObj|
|
506
|
-
mapObj.send( resultType.name.decapitalize )
|
507
|
-
}
|
196
|
+
def group_query( query ) #:nodoc:
|
197
|
+
@cache.group_query( query )
|
508
198
|
end
|
509
|
-
|
199
|
+
|
510
200
|
# Retrieves the maximum value across all instances of one domain class.
|
511
|
-
# ObjectStore#
|
201
|
+
# ObjectStore#max( Client )
|
512
202
|
# returns the highest +pk_id+ in the +clients+ table.
|
513
|
-
# ObjectStore#
|
203
|
+
# ObjectStore#max( Invoice, "rate" )
|
514
204
|
# will return the highest rate for all invoices.
|
515
|
-
def
|
205
|
+
def max( domain_class, field_name = 'pk_id' )
|
516
206
|
qry = Query::Max.new( domain_class, field_name )
|
517
|
-
@
|
207
|
+
@cache.group_query( qry ).only[:max]
|
518
208
|
end
|
519
209
|
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
if pk_ids.is_a?( Array ) && pk_ids.all? { |elt| elt.is_a?( Integer ) }
|
524
|
-
get_subset Query::In.new( 'pk_id', pk_ids, domain_class )
|
210
|
+
def method_missing(methodId, *args) #:nodoc:
|
211
|
+
if [ :commit, :flush, :last_commit_time ].include?( methodId )
|
212
|
+
@cache.send( methodId, *args )
|
525
213
|
else
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
214
|
+
proc = block_given? ? ( proc { |obj| yield( obj ) } ) : nil
|
215
|
+
dispatch = MethodDispatch.new( methodId, proc, *args )
|
216
|
+
if dispatch.symbol
|
217
|
+
dispatch.dispatch self
|
218
|
+
else
|
219
|
+
super
|
220
|
+
end
|
532
221
|
end
|
533
222
|
end
|
534
|
-
|
535
|
-
def
|
223
|
+
|
224
|
+
def mock? #:nodoc:
|
225
|
+
false
|
226
|
+
end
|
227
|
+
|
228
|
+
# Passes a query and selects with it.
|
229
|
+
# qry = Query.infer( User ) { |user| user.fname.equals( 'Francis' ) }
|
230
|
+
# francises = ObjectStore.get_object_store.query( qry )
|
231
|
+
def query(conditionOrQuery)
|
536
232
|
if conditionOrQuery.class <= Query::Condition
|
537
233
|
condition = conditionOrQuery
|
538
234
|
query = Query.new condition.domain_class, condition
|
@@ -542,259 +238,537 @@ module Lafcadio
|
|
542
238
|
@cache.get_by_query( query )
|
543
239
|
end
|
544
240
|
|
545
|
-
def
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
def method_missing(methodId, *args) #:nodoc:
|
550
|
-
proc = block_given? ? ( proc { |obj| yield( obj ) } ) : nil
|
551
|
-
dispatch = MethodDispatch.new( methodId, proc, *args )
|
552
|
-
self.send( dispatch.symbol, *dispatch.args )
|
553
|
-
end
|
554
|
-
|
555
|
-
def mock? #:nodoc:
|
556
|
-
false
|
557
|
-
end
|
558
|
-
|
559
|
-
def query( query ); @dbBridge.group_query( query ); end
|
560
|
-
|
561
|
-
def respond_to?( symbol, include_private = false )
|
562
|
-
begin
|
563
|
-
dispatch = MethodDispatch.new( symbol )
|
564
|
-
rescue NoMethodError
|
241
|
+
def respond_to?( symbol, include_private = false ) #:nodoc:
|
242
|
+
if MethodDispatch.new( symbol ).symbol
|
243
|
+
true
|
244
|
+
else
|
565
245
|
super
|
566
246
|
end
|
567
247
|
end
|
568
248
|
|
569
|
-
|
249
|
+
# As long as the underlying database table sorts transactions, you can use
|
250
|
+
# this to run transactional logic. These transactions will auto commit at
|
251
|
+
# the end of the block, and can be rolled back.
|
252
|
+
# ObjectStore.get_object_store.transaction do |tr|
|
253
|
+
# Client.new( 'name' => 'Big Co.' ).commit
|
254
|
+
# tr.rollback
|
255
|
+
# end # the client will not be saved to the DB
|
256
|
+
def transaction( &action ); @cache.transaction( action ); end
|
570
257
|
|
571
258
|
class Cache #:nodoc:
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
@
|
576
|
-
@
|
259
|
+
attr_reader :db_bridge
|
260
|
+
|
261
|
+
def initialize( db_bridge = DbBridge.new )
|
262
|
+
@db_bridge = db_bridge
|
263
|
+
@domain_class_caches = {}
|
577
264
|
end
|
578
265
|
|
579
266
|
def commit( db_object )
|
580
|
-
|
581
|
-
|
267
|
+
db_object.verify if LafcadioConfig.new()['checkFields'] == 'onCommit'
|
268
|
+
db_object.last_commit_type = get_last_commit db_object
|
269
|
+
db_object.pre_commit_trigger
|
270
|
+
update_dependent_domain_objects( db_object ) if db_object.delete
|
271
|
+
@db_bridge.commit db_object
|
272
|
+
db_object.pk_id = @db_bridge.last_pk_id_inserted unless db_object.pk_id
|
273
|
+
update_after_commit db_object
|
274
|
+
db_object.post_commit_trigger
|
275
|
+
db_object.reset_original_values_hash
|
276
|
+
db_object
|
582
277
|
end
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
278
|
+
|
279
|
+
def cache( domain_class )
|
280
|
+
unless @domain_class_caches[domain_class]
|
281
|
+
@domain_class_caches[domain_class] = DomainClassCache.new(
|
282
|
+
domain_class, @db_bridge
|
283
|
+
)
|
284
|
+
end
|
285
|
+
@domain_class_caches[domain_class]
|
588
286
|
end
|
589
287
|
|
590
|
-
def
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
288
|
+
def get_last_commit( db_object )
|
289
|
+
if db_object.delete
|
290
|
+
DomainObject::COMMIT_DELETE
|
291
|
+
elsif db_object.pk_id
|
292
|
+
DomainObject::COMMIT_EDIT
|
293
|
+
else
|
294
|
+
DomainObject::COMMIT_ADD
|
295
|
+
end
|
596
296
|
end
|
597
297
|
|
598
|
-
|
599
|
-
|
600
|
-
|
298
|
+
def method_missing( meth, *args )
|
299
|
+
simple_dispatch = [
|
300
|
+
:get_by_query, :queries, :save, :set_commit_time,
|
301
|
+
:update_after_commit
|
302
|
+
]
|
303
|
+
if simple_dispatch.include?( meth )
|
304
|
+
cache( args.first.domain_class ).send( meth, *args )
|
305
|
+
elsif [ :[], :last_commit_time ].include?( meth )
|
306
|
+
cache( args.first ).send( meth, *args[1..-1] )
|
307
|
+
elsif [ :group_query, :transaction ].include?( meth )
|
308
|
+
@db_bridge.send( meth, *args )
|
309
|
+
end
|
601
310
|
end
|
602
311
|
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
312
|
+
def update_dependent_domain_class( db_object, aClass, field )
|
313
|
+
object_store = ObjectStore.get_object_store
|
314
|
+
collection = aClass.get( db_object, field.name )
|
315
|
+
collection.each { |dependentObject|
|
316
|
+
if field.delete_cascade
|
317
|
+
dependentObject.delete = true
|
318
|
+
else
|
319
|
+
dependentObject.send( field.name + '=', nil )
|
320
|
+
end
|
321
|
+
object_store.commit dependentObject
|
607
322
|
}
|
608
323
|
end
|
609
324
|
|
610
|
-
def
|
611
|
-
|
325
|
+
def update_dependent_domain_objects( db_object )
|
326
|
+
dependent_classes = db_object.domain_class.dependent_classes
|
327
|
+
dependent_classes.keys.each { |aClass|
|
328
|
+
update_dependent_domain_class(
|
329
|
+
db_object, aClass, dependent_classes[aClass]
|
330
|
+
)
|
331
|
+
}
|
332
|
+
end
|
333
|
+
|
334
|
+
class DomainClassCache < Hash #:nodoc:
|
335
|
+
attr_reader :commit_times, :domain_class, :queries
|
336
|
+
|
337
|
+
def initialize( domain_class, db_bridge )
|
338
|
+
super()
|
339
|
+
@domain_class, @db_bridge = domain_class, db_bridge
|
340
|
+
@commit_times = {}
|
341
|
+
@queries = {}
|
342
|
+
end
|
343
|
+
|
344
|
+
def []( pk_id )
|
345
|
+
dobj = super
|
346
|
+
dobj ? dobj.clone : nil
|
347
|
+
end
|
348
|
+
|
349
|
+
def collect_from_superset( query )
|
350
|
+
if ( pk_ids = find_superset_pk_ids( query ) )
|
351
|
+
db_objects = ( pk_ids.collect { |pk_id|
|
352
|
+
self[ pk_id ]
|
353
|
+
} ).select { |dobj| query.dobj_satisfies?( dobj ) }
|
354
|
+
db_objects = query.order_and_limit_collection db_objects
|
355
|
+
queries[query] = db_objects.collect { |dobj| dobj.pk_id }
|
356
|
+
true
|
357
|
+
else
|
358
|
+
false
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def find_superset_pk_ids( query )
|
612
363
|
superset_query, pk_ids =
|
613
|
-
|
364
|
+
queries.find { |other_query, pk_ids|
|
614
365
|
query.implies?( other_query )
|
615
366
|
}
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
367
|
+
pk_ids
|
368
|
+
end
|
369
|
+
|
370
|
+
# Flushes a domain object.
|
371
|
+
def flush( db_object )
|
372
|
+
delete db_object.pk_id
|
373
|
+
flush_queries
|
374
|
+
end
|
375
|
+
|
376
|
+
def flush_queries
|
377
|
+
queries.keys.each do |query|
|
378
|
+
queries.delete( query ) if query.domain_class == domain_class
|
628
379
|
end
|
629
380
|
end
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
381
|
+
|
382
|
+
def get_by_query( query )
|
383
|
+
unless queries[query]
|
384
|
+
collected = collect_from_superset query
|
385
|
+
if !collected and queries.values
|
386
|
+
query_db query
|
387
|
+
end
|
388
|
+
end
|
389
|
+
collection = []
|
390
|
+
queries[query].each { |pk_id|
|
391
|
+
dobj = self[ pk_id ]
|
392
|
+
collection << dobj if dobj
|
393
|
+
}
|
394
|
+
collection
|
395
|
+
end
|
396
|
+
|
397
|
+
def last_commit_time( pk_id ); commit_times[pk_id]; end
|
398
|
+
|
399
|
+
def query_db( query )
|
400
|
+
newObjects = @db_bridge.select_dobjs query
|
401
|
+
newObjects.each { |dbObj| save dbObj }
|
402
|
+
queries[query] = newObjects.collect { |dobj| dobj.pk_id }
|
403
|
+
end
|
404
|
+
|
405
|
+
# Saves a domain object.
|
406
|
+
def save(db_object)
|
407
|
+
self[db_object.pk_id] = db_object
|
408
|
+
flush_queries
|
409
|
+
end
|
410
|
+
|
411
|
+
def set_commit_time( d_obj ); commit_times[d_obj.pk_id] = Time.now; end
|
412
|
+
|
413
|
+
def update_after_commit( db_object ) #:nodoc:
|
414
|
+
if [ DomainObject::COMMIT_EDIT, DomainObject::COMMIT_ADD ].include?(
|
415
|
+
db_object.last_commit_type
|
416
|
+
)
|
417
|
+
save db_object
|
418
|
+
elsif db_object.last_commit_type == DomainObject::COMMIT_DELETE
|
419
|
+
flush db_object
|
420
|
+
end
|
421
|
+
set_commit_time db_object
|
422
|
+
end
|
636
423
|
end
|
424
|
+
end
|
637
425
|
|
638
|
-
|
639
|
-
|
640
|
-
|
426
|
+
class CommitSqlStatementsAndBinds < Array #:nodoc:
|
427
|
+
attr_reader :bind_values
|
428
|
+
|
429
|
+
def initialize( obj )
|
430
|
+
@obj = obj
|
431
|
+
reversed = []
|
432
|
+
@obj.class.self_and_concrete_superclasses.each { |domain_class|
|
433
|
+
reversed << statement_bind_value_pair( domain_class )
|
434
|
+
}
|
435
|
+
reversed.reverse.each do |statement, binds|
|
436
|
+
self << [ statement, binds ]
|
641
437
|
end
|
642
|
-
@objects[domain_class]
|
643
438
|
end
|
439
|
+
|
440
|
+
def delete_sql( domain_class )
|
441
|
+
"delete from #{ domain_class.table_name} " +
|
442
|
+
"where #{ domain_class.sql_primary_key_name }=#{ @obj.pk_id }"
|
443
|
+
end
|
444
|
+
|
445
|
+
def get_name_value_pairs( domain_class )
|
446
|
+
nameValues = []
|
447
|
+
domain_class.class_fields.each { |field|
|
448
|
+
unless field.instance_of?( PrimaryKeyField )
|
449
|
+
value = @obj.send(field.name)
|
450
|
+
unless field.db_will_automatically_write?
|
451
|
+
nameValues << field.db_field_name
|
452
|
+
nameValues << field.value_for_sql( value )
|
453
|
+
end
|
454
|
+
if field.bind_write?
|
455
|
+
@bind_values << value
|
456
|
+
end
|
457
|
+
end
|
458
|
+
}
|
459
|
+
QueueHash.new( *nameValues )
|
460
|
+
end
|
461
|
+
|
462
|
+
def insert_sql( domain_class )
|
463
|
+
fields = domain_class.class_fields
|
464
|
+
nameValuePairs = get_name_value_pairs( domain_class )
|
465
|
+
if domain_class.is_child_domain_class?
|
466
|
+
nameValuePairs[domain_class.sql_primary_key_name] = 'LAST_INSERT_ID()'
|
467
|
+
end
|
468
|
+
fieldNameStr = nameValuePairs.keys.join ", "
|
469
|
+
fieldValueStr = nameValuePairs.values.join ", "
|
470
|
+
"insert into #{ domain_class.table_name}(#{fieldNameStr}) " +
|
471
|
+
"values(#{fieldValueStr})"
|
472
|
+
end
|
473
|
+
|
474
|
+
def statement_bind_value_pair( domain_class )
|
475
|
+
@bind_values = []
|
476
|
+
if @obj.pk_id == nil
|
477
|
+
statement = insert_sql( domain_class )
|
478
|
+
else
|
479
|
+
if @obj.delete
|
480
|
+
statement = delete_sql( domain_class )
|
481
|
+
else
|
482
|
+
statement = update_sql( domain_class)
|
483
|
+
end
|
484
|
+
end
|
485
|
+
[statement, @bind_values]
|
486
|
+
end
|
487
|
+
|
488
|
+
def update_sql( domain_class )
|
489
|
+
nameValueStrings = []
|
490
|
+
nameValuePairs = get_name_value_pairs( domain_class )
|
491
|
+
nameValuePairs.each { |key, value|
|
492
|
+
nameValueStrings << "#{key}=#{ value }"
|
493
|
+
}
|
494
|
+
allNameValues = nameValueStrings.join ', '
|
495
|
+
"update #{ domain_class.table_name} set #{allNameValues} " +
|
496
|
+
"where #{ domain_class.sql_primary_key_name}=#{@obj.pk_id}"
|
497
|
+
end
|
498
|
+
end
|
644
499
|
|
645
|
-
|
646
|
-
|
647
|
-
|
500
|
+
class DbBridge #:nodoc:
|
501
|
+
@@last_pk_id_inserted = nil
|
502
|
+
|
503
|
+
def self._load(aString)
|
504
|
+
aString =~ /db_conn:/
|
505
|
+
db_conn_str = $'
|
506
|
+
begin
|
507
|
+
db_conn = Marshal.load db_conn_str
|
508
|
+
rescue TypeError
|
509
|
+
db_conn = nil
|
510
|
+
end
|
511
|
+
DbConnection.set_db_connection db_conn
|
512
|
+
new
|
513
|
+
end
|
514
|
+
|
515
|
+
def initialize
|
516
|
+
@db_conn = DbConnection.get_db_connection
|
517
|
+
ObjectSpace.define_finalizer( self, proc { |id|
|
518
|
+
DbConnection.get_db_connection.disconnect
|
519
|
+
} )
|
520
|
+
end
|
521
|
+
|
522
|
+
def _dump(aDepth)
|
523
|
+
dbDump = @dbh.respond_to?( '_dump' ) ? @dbh._dump : @dbh.class.to_s
|
524
|
+
"dbh:#{dbDump}"
|
648
525
|
end
|
649
526
|
|
650
|
-
def
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
527
|
+
def commit(db_object)
|
528
|
+
statements_and_binds = ObjectStore::CommitSqlStatementsAndBinds.new(
|
529
|
+
db_object
|
530
|
+
)
|
531
|
+
statements_and_binds.each do |sql, binds|
|
532
|
+
@db_conn.do( sql, *binds )
|
533
|
+
end
|
534
|
+
if statements_and_binds[0].first =~ /insert/
|
535
|
+
sql = 'select last_insert_id()'
|
536
|
+
result = select_all( sql )
|
537
|
+
@@last_pk_id_inserted = result[0]['last_insert_id()'].to_i
|
655
538
|
end
|
656
|
-
by_domain_class[d_obj.pk_id] = Time.now
|
657
539
|
end
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
hash = hash_by_domain_class( db_object.domain_class )
|
662
|
-
hash[db_object.pk_id] = db_object
|
663
|
-
flush_collection_cache( db_object.domain_class )
|
540
|
+
|
541
|
+
def group_query( query )
|
542
|
+
select_all( query.to_sql ).map { |row| query.result_row( row ) }
|
664
543
|
end
|
544
|
+
|
545
|
+
def last_pk_id_inserted; @@last_pk_id_inserted; end
|
665
546
|
|
666
|
-
def
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
547
|
+
def maybe_log(sql)
|
548
|
+
config = LafcadioConfig.new
|
549
|
+
if config['logSql'] == 'y'
|
550
|
+
sqllog = Log4r::Logger['sql'] || Log4r::Logger.new( 'sql' )
|
551
|
+
filename = File.join(
|
552
|
+
config['logdir'], config['sqlLogFile'] || 'sql'
|
553
|
+
)
|
554
|
+
outputter = Log4r::FileOutputter.new(
|
555
|
+
'outputter', { :filename => filename }
|
556
|
+
)
|
557
|
+
sqllog.outputters = outputter
|
558
|
+
sqllog.info sql
|
672
559
|
end
|
673
|
-
|
560
|
+
end
|
561
|
+
|
562
|
+
def select_all(sql)
|
563
|
+
maybe_log sql
|
564
|
+
begin
|
565
|
+
@db_conn.select_all( sql )
|
566
|
+
rescue DBI::DatabaseError => e
|
567
|
+
raise $!.to_s + ": #{ e.errstr }"
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
def select_dobjs(query)
|
572
|
+
domain_class = query.domain_class
|
573
|
+
select_all( query.to_sql ).collect { |row_hash|
|
574
|
+
domain_class.new( SqlToRubyValues.new( domain_class, row_hash ) )
|
575
|
+
}
|
576
|
+
end
|
577
|
+
|
578
|
+
def transaction( action )
|
579
|
+
tr = Transaction.new @db_conn
|
580
|
+
tr.commit
|
581
|
+
begin
|
582
|
+
action.call tr
|
583
|
+
tr.commit
|
584
|
+
rescue RollbackError
|
585
|
+
# rollback handled by Transaction
|
586
|
+
rescue
|
587
|
+
err_to_raise = $!
|
588
|
+
tr.rollback false
|
589
|
+
raise err_to_raise
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
class Transaction #:nodoc:
|
594
|
+
def initialize( db_conn ); @db_conn = db_conn; end
|
595
|
+
|
596
|
+
def commit; @db_conn.commit; end
|
597
|
+
|
598
|
+
def rollback( raise_error = true )
|
599
|
+
@db_conn.rollback
|
600
|
+
raise RollbackError if raise_error
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
class RollbackError < StandardError #:nodoc:
|
674
605
|
end
|
675
606
|
end
|
676
607
|
|
608
|
+
class DbConnection < ContextualService::Service #:nodoc:
|
609
|
+
@@conn_class = DBI
|
610
|
+
@@db_name = nil
|
611
|
+
|
612
|
+
def self.connection_class=( aClass ); @@conn_class = aClass; end
|
613
|
+
|
614
|
+
def self.db_name=( db_name ); @@db_name = db_name; end
|
615
|
+
|
616
|
+
def initialize; @dbh = load_new_dbh; end
|
617
|
+
|
618
|
+
def disconnect; @dbh.disconnect; end
|
619
|
+
|
620
|
+
def load_new_dbh
|
621
|
+
config = LafcadioConfig.new
|
622
|
+
dbName = @@db_name || config['dbname']
|
623
|
+
dbAndHost = nil
|
624
|
+
if dbName && config['dbhost']
|
625
|
+
dbAndHost = "dbi:Mysql:#{ dbName }:#{ config['dbhost'] }"
|
626
|
+
else
|
627
|
+
dbAndHost = "dbi:#{config['dbconn']}"
|
628
|
+
end
|
629
|
+
dbh = @@conn_class.connect(
|
630
|
+
dbAndHost, config['dbuser'], config['dbpassword']
|
631
|
+
)
|
632
|
+
dbh['AutoCommit'] = false
|
633
|
+
dbh
|
634
|
+
end
|
635
|
+
|
636
|
+
def method_missing( symbol, *args )
|
637
|
+
@dbh.send( symbol, *args )
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
677
641
|
class MethodDispatch #:nodoc:
|
678
642
|
attr_reader :symbol, :args
|
679
643
|
|
680
644
|
def initialize( orig_method, *other_args )
|
681
645
|
@orig_method = orig_method
|
682
646
|
@orig_args = other_args
|
683
|
-
if @orig_args.size > 0
|
684
|
-
@maybe_proc = @orig_args.shift
|
685
|
-
end
|
647
|
+
@maybe_proc = @orig_args.shift if @orig_args.size > 0
|
686
648
|
@methodName = orig_method.id2name
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
649
|
+
dispatch_method
|
650
|
+
end
|
651
|
+
|
652
|
+
def camel_case_method_name
|
653
|
+
@orig_method.id2name.underscore_to_camel_case
|
654
|
+
end
|
655
|
+
|
656
|
+
def dispatch( object_store )
|
657
|
+
target = ( @target or object_store )
|
658
|
+
target.send( @symbol, *@args )
|
659
|
+
end
|
660
|
+
|
661
|
+
def dispatch_all
|
662
|
+
@symbol = :all
|
663
|
+
@args = [ @domain_class ]
|
664
|
+
end
|
665
|
+
|
666
|
+
def dispatch_domain_class_get( searchTerm, fieldName )
|
667
|
+
@symbol = :get
|
668
|
+
@args = [ searchTerm, fieldName ]
|
669
|
+
@target = @domain_class
|
692
670
|
end
|
693
671
|
|
694
|
-
def
|
672
|
+
def dispatch_get_map_object( domain_class )
|
673
|
+
@symbol = :get_map_object
|
674
|
+
@args = [ domain_class, @orig_args[0], @orig_args[1] ]
|
675
|
+
end
|
676
|
+
|
677
|
+
def dispatch_plural
|
695
678
|
if @orig_args.size == 0 && @maybe_proc.nil?
|
696
|
-
|
697
|
-
@args = [ @domain_class ]
|
679
|
+
dispatch_all
|
698
680
|
else
|
699
|
-
searchTerm = @orig_args[0]
|
700
|
-
fieldName = @orig_args[1]
|
681
|
+
searchTerm, fieldName = @orig_args[0..1]
|
701
682
|
if searchTerm.nil? && @maybe_proc.nil? && fieldName.nil?
|
702
|
-
|
703
|
-
|
704
|
-
|
683
|
+
raise_plural_needs_field_arg_if_first_arg_nil
|
684
|
+
elsif !@maybe_proc.nil? && searchTerm.nil?
|
685
|
+
dispatch_plural_by_query_block
|
686
|
+
elsif @maybe_proc.nil? && ( !( searchTerm.nil? && fieldName.nil? ) )
|
687
|
+
dispatch_domain_class_get( searchTerm, fieldName )
|
688
|
+
else
|
689
|
+
raise_plural_cant_have_both_query_block_and_search_term
|
705
690
|
end
|
706
|
-
dispatch_get_plural_by_query_block_or_search_term( searchTerm,
|
707
|
-
fieldName )
|
708
691
|
end
|
709
692
|
end
|
710
693
|
|
711
|
-
def
|
694
|
+
def dispatch_plural_by_query_block
|
712
695
|
inferrer = Query::Inferrer.new( @domain_class ) { |obj|
|
713
696
|
@maybe_proc.call( obj )
|
714
697
|
}
|
715
|
-
@symbol = :
|
698
|
+
@symbol = :query
|
716
699
|
@args = [ inferrer.execute ]
|
717
700
|
end
|
718
701
|
|
719
|
-
def
|
720
|
-
|
721
|
-
|
722
|
-
dispatch_get_plural_by_query_block
|
723
|
-
elsif @maybe_proc.nil? && ( !( searchTerm.nil? && fieldName.nil? ) )
|
724
|
-
@symbol = :get_filtered
|
725
|
-
@args = [ @domain_class.name, searchTerm, fieldName ]
|
726
|
-
else
|
727
|
-
raise( ArgumentError,
|
728
|
-
"Shouldn't send both a query block and a search term",
|
729
|
-
caller )
|
730
|
-
end
|
731
|
-
end
|
732
|
-
|
733
|
-
def dispatch_get_method
|
734
|
-
begin
|
735
|
-
dispatch_get_singular
|
736
|
-
rescue CouldntMatchDomainClassError
|
737
|
-
domain_class_name = camel_case_method_name_after_get.singular
|
702
|
+
def dispatch_method
|
703
|
+
unless ( dispatch_singular )
|
704
|
+
domain_class_name = camel_case_method_name.singular
|
738
705
|
begin
|
739
|
-
@domain_class =
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
raise_no_method_error
|
706
|
+
@domain_class = Module.by_name domain_class_name
|
707
|
+
dispatch_plural
|
708
|
+
rescue NameError
|
709
|
+
# skip it
|
744
710
|
end
|
745
711
|
end
|
746
712
|
end
|
747
713
|
|
748
|
-
def
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
714
|
+
def dispatch_singular
|
715
|
+
begin
|
716
|
+
d_class_name = @orig_method.id2name.underscore_to_camel_case
|
717
|
+
domain_class = Module.by_name d_class_name
|
718
|
+
if @orig_args[0].class <= Integer
|
719
|
+
@symbol = :get
|
720
|
+
@args = [ domain_class, @orig_args[0] ]
|
721
|
+
elsif @orig_args[0].class <= DomainObject
|
722
|
+
dispatch_get_map_object domain_class
|
723
|
+
elsif @orig_args.empty?
|
724
|
+
@symbol = :get
|
725
|
+
end
|
726
|
+
true
|
727
|
+
rescue NameError
|
728
|
+
false
|
758
729
|
end
|
759
730
|
end
|
760
731
|
|
761
|
-
def
|
762
|
-
@orig_method
|
763
|
-
|
732
|
+
def raise_plural_needs_field_arg_if_first_arg_nil
|
733
|
+
msg = "ObjectStore\##{ @orig_method } needs a field name as its " +
|
734
|
+
"second argument if its first argument is nil"
|
735
|
+
raise( ArgumentError, msg, caller )
|
764
736
|
end
|
765
737
|
|
766
|
-
def
|
767
|
-
raise(
|
738
|
+
def raise_plural_cant_have_both_query_block_and_search_term
|
739
|
+
raise(
|
740
|
+
ArgumentError, "Shouldn't send both a query block and a search term",
|
741
|
+
caller
|
742
|
+
)
|
768
743
|
end
|
769
744
|
end
|
770
|
-
end
|
771
|
-
|
772
|
-
class SqlValueConverter #:nodoc:
|
773
|
-
attr_reader :domain_class, :row_hash
|
774
|
-
|
775
|
-
def initialize( domain_class, row_hash )
|
776
|
-
@domain_class = domain_class
|
777
|
-
@row_hash = row_hash
|
778
|
-
end
|
779
745
|
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
746
|
+
class SqlToRubyValues #:nodoc:
|
747
|
+
attr_reader :domain_class, :row_hash
|
748
|
+
|
749
|
+
def initialize( domain_class, row_hash )
|
750
|
+
@domain_class = domain_class
|
751
|
+
@row_hash = row_hash
|
752
|
+
end
|
753
|
+
|
754
|
+
def []( key )
|
755
|
+
if ( field = @domain_class.field key )
|
756
|
+
val = field.value_from_sql( @row_hash[ field.db_field_name ] )
|
757
|
+
if field.instance_of?( PrimaryKeyField ) && val.nil?
|
758
|
+
raise FieldMatchError, error_msg, caller
|
759
|
+
else
|
760
|
+
val
|
761
|
+
end
|
786
762
|
else
|
787
|
-
|
763
|
+
nil
|
788
764
|
end
|
789
|
-
rescue MissingError
|
790
|
-
nil
|
791
765
|
end
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
766
|
+
|
767
|
+
def error_msg
|
768
|
+
"The field \"" + @domain_class.sql_primary_key_name +
|
769
|
+
"\" can\'t be found in the table \"" +
|
770
|
+
@domain_class.table_name + "\"."
|
771
|
+
end
|
798
772
|
end
|
799
773
|
end
|
800
774
|
end
|