iotaz 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ #--
4
+ # Copyright (C) Peter Wood, 2005
5
+ #
6
+ # The contents of this file are subject to the Mozilla Public License Version
7
+ # 1.1 (the "License"); you may not use this file except in compliance with the
8
+ # License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.mozilla.org/MPL/
11
+ #
12
+ # Software distributed under the License is distributed on an "AS IS" basis,
13
+ # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14
+ # the specificlanguage governing rights and limitations under the License.
15
+ #
16
+ # The Original Code is the FireRuby extension for the Ruby language.
17
+ #
18
+ # The Initial Developer of the Original Code is Peter Wood. All Rights
19
+ # Reserved.
20
+ #++
21
+ #
22
+
23
+ require 'rubygems'
24
+ require_gem 'iotaz'
25
+ require_gem 'fireruby'
26
+
27
+ include FireRuby
28
+ include Iotaz
29
+
30
+ TABLE_SQL = 'create table account (id integer not null primary key,'\
31
+ 'number varchar(10) not null, customer varchar(30), '\
32
+ 'created timestamp not null, updated timestamp)'
33
+ DB_FILE = './accounts.fdb'
34
+ DB_USER_NAME = 'sysdba'
35
+ DB_PASSWORD = 'masterkey'
36
+ CONFIGURATION = {'iotaz.database' => 'firebird',
37
+ 'iotaz.firebird.user' => DB_USER_NAME,
38
+ 'iotaz.firebird.password' => DB_PASSWORD,
39
+ 'iotaz.firebird.database' => DB_FILE}
40
+
41
+ # The definition for the persistable class.
42
+ class Account
43
+ attr_accessor :id, :number, :customer, :created, :updated
44
+
45
+ def initialize(number, customer)
46
+ @number = number
47
+ @customer = customer
48
+ end
49
+
50
+ def to_s
51
+ "Id: #{@id}\nNumber: #{@number}\nCustomer: #{@customer}\n"\
52
+ "Created: #{@created}\nUpdated: #{@updated}"
53
+ end
54
+
55
+ def Account.iotaz_meta_data
56
+ IotazMetaData.scan(Account)
57
+ end
58
+
59
+ private :id=, :created=, :updated=
60
+ end
61
+
62
+ def create(session)
63
+ account = Account.new('ACC/001', 'Test Customer')
64
+ session.save(account)
65
+ puts "Save->\n#{account}"
66
+ account.id
67
+ end
68
+
69
+ def update(id, session)
70
+ account = session.load(Account, id)
71
+ account.customer = 'Updated Customer'
72
+ session.save(account)
73
+ puts "Update->\n#{account}"
74
+ end
75
+
76
+ def delete(id, session)
77
+ account = session.load(Account, id)
78
+ session.delete(account)
79
+ puts "Account record deleted."
80
+ end
81
+
82
+ begin
83
+ # Check if the database exists.
84
+ database = nil
85
+ if File.exist?(DB_FILE) == false
86
+ database = Database.create(DB_FILE, DB_USER_NAME, DB_PASSWORD, 1024, nil)
87
+ database.connect(DB_USER_NAME, DB_PASSWORD) do |connection|
88
+ connection.execute_immediate(TABLE_SQL)
89
+ Generator.create('ACCOUNT_ID_SQ', connection)
90
+ end
91
+ end
92
+
93
+ factory = SessionFactory.new(CONFIGURATION)
94
+ session = factory.start
95
+
96
+ id = create(session)
97
+ update(id, session)
98
+ delete(id, session)
99
+
100
+ session.terminate
101
+
102
+ rescue Exception => error
103
+ puts error.message
104
+ error.backtrace.each {|step| puts " #{step}"}
105
+ end
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ #--
4
+ # Copyright (C) Peter Wood, 2005
5
+ #
6
+ # The contents of this file are subject to the Mozilla Public License Version
7
+ # 1.1 (the "License"); you may not use this file except in compliance with the
8
+ # License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.mozilla.org/MPL/
11
+ #
12
+ # Software distributed under the License is distributed on an "AS IS" basis,
13
+ # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14
+ # the specificlanguage governing rights and limitations under the License.
15
+ #
16
+ # The Original Code is the FireRuby extension for the Ruby language.
17
+ #
18
+ # The Initial Developer of the Original Code is Peter Wood. All Rights
19
+ # Reserved.
20
+ #++
21
+ #
22
+
23
+ require 'rubygems'
24
+ require_gem 'iotaz'
25
+ require_gem 'fireruby'
26
+
27
+ include FireRuby
28
+ include Iotaz
29
+
30
+ TABLE_SQL = 'create table account (id integer not null primary key,'\
31
+ 'number varchar(10) not null, customer varchar(30), '\
32
+ 'created timestamp not null, updated timestamp)'
33
+ DB_FILE = './accounts.fdb'
34
+ DB_USER_NAME = 'sysdba'
35
+ DB_PASSWORD = 'masterkey'
36
+ CONFIGURATION = {'iotaz.database' => 'firebird',
37
+ 'iotaz.firebird.user' => DB_USER_NAME,
38
+ 'iotaz.firebird.password' => DB_PASSWORD,
39
+ 'iotaz.firebird.database' => DB_FILE}
40
+
41
+ # The definition for the persistable class.
42
+ class Account
43
+ include Persistent
44
+
45
+ sequence_attr :id
46
+ persistent_attr :number
47
+ persistent_attr :customer
48
+ timestamp_attr :created
49
+ timestamp_attr :updated, nil, false, true
50
+
51
+ def initialize(number, customer)
52
+ @number = number
53
+ @customer = customer
54
+ end
55
+
56
+ def to_s
57
+ "Id: #{@id}\nNumber: #{@number}\nCustomer: #{@customer}\n"\
58
+ "Created: #{@created}\nUpdated: #{@updated}"
59
+ end
60
+ end
61
+
62
+ def create(session)
63
+ account = Account.new('ACC/001', 'Test Customer')
64
+ session.save(account)
65
+ puts "Save->\n#{account}"
66
+ account.id
67
+ end
68
+
69
+ def update(id, session)
70
+ account = session.load(Account, id)
71
+ account.customer = 'Updated Customer'
72
+ session.save(account)
73
+ puts "Update->\n#{account}"
74
+ end
75
+
76
+ def delete(id, session)
77
+ account = session.load(Account, id)
78
+ session.delete(account)
79
+ puts "Account record deleted."
80
+ end
81
+
82
+ begin
83
+ # Check if the database exists.
84
+ database = nil
85
+ if File.exist?(DB_FILE) == false
86
+ database = Database.create(DB_FILE, DB_USER_NAME, DB_PASSWORD, 1024, nil)
87
+ database.connect(DB_USER_NAME, DB_PASSWORD) do |connection|
88
+ connection.execute_immediate(TABLE_SQL)
89
+ Generator.create('ACCOUNT_ID_SQ', connection)
90
+ end
91
+ end
92
+
93
+ factory = SessionFactory.new(CONFIGURATION)
94
+ session = factory.start
95
+
96
+ id = create(session)
97
+ update(id, session)
98
+ delete(id, session)
99
+
100
+ session.terminate
101
+
102
+ rescue Exception => error
103
+ puts error.message
104
+ error.backtrace.each {|step| puts " #{step}"}
105
+ end
data/lib/iotaz.rb ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ #--
4
+ # Copyright � Peter Wood, 2005
5
+ #
6
+ # The contents of this file are subject to the Mozilla Public License Version
7
+ # 1.1 (the "License"); you may not use this file except in compliance with the
8
+ # License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.mozilla.org/MPL/
11
+ #
12
+ # Software distributed under the License is distributed on an "AS IS" basis,
13
+ # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14
+ # the specificlanguage governing rights and limitations under the License.
15
+ #
16
+ # The Original Code is the FireRuby extension for the Ruby language.
17
+ #
18
+ # The Initial Developer of the Original Code is Peter Wood. All Rights
19
+ # Reserved.
20
+ #++
21
+ #
22
+
23
+ require 'iotaz/DatabaseInterfaceFactory'
24
+ require 'iotaz/FirebirdInterface'
25
+ require 'iotaz/IotazError'
26
+ require 'iotaz/MetaData'
27
+ require 'iotaz/ObjectPool'
28
+ require 'iotaz/Persistent'
29
+ require 'iotaz/Query'
30
+ require 'iotaz/Session'
31
+ require 'iotaz/WorkSet'
32
+
33
+ module Iotaz
34
+ IOTAZ_VERSION = [0, 1, 0].join('.')
35
+ end
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ #--
4
+ # Copyright � Peter Wood, 2005
5
+ #
6
+ # The contents of this file are subject to the Mozilla Public License Version
7
+ # 1.1 (the "License"); you may not use this file except in compliance with the
8
+ # License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.mozilla.org/MPL/
11
+ #
12
+ # Software distributed under the License is distributed on an "AS IS" basis,
13
+ # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14
+ # the specificlanguage governing rights and limitations under the License.
15
+ #
16
+ # The Original Code is the FireRuby extension for the Ruby language.
17
+ #
18
+ # The Initial Developer of the Original Code is Peter Wood. All Rights
19
+ # Reserved.
20
+ #++
21
+ #
22
+
23
+ require 'thread'
24
+ require 'iotaz/IotazError'
25
+ require 'iotaz/FirebirdInterface'
26
+
27
+ module Iotaz
28
+ #
29
+ # This class represents a factory object that will be used to manufacture
30
+ # the database interfaces used by the library.
31
+ #
32
+ class DatabaseInterfaceFactory
33
+ # The list of supported databases.
34
+ @@databases = ['FIREBIRD']
35
+
36
+
37
+ #
38
+ # This is the constructor for the DatabaseInterfaceFactory class. With
39
+ # regards to the configuration passed to this class, the factory is
40
+ # only interested in one value - iotaz.database. For the moment the only
41
+ # supported value for this is 'firebird'.
42
+ #
43
+ # ==== Parameters
44
+ # configuration:: A reference to a Hash object containing the
45
+ # configuration information for use by the factory
46
+ # object and the database interfaces that it will
47
+ # produce.
48
+ #
49
+ # ==== Exceptions
50
+ # IotazError:: Generated whenever the iotaz.database configuration entry is
51
+ # not present or is set to an unsupported value.
52
+ #
53
+ def initialize(configuration)
54
+ if configuration.key?('iotaz.database')
55
+ name = configuration['iotaz.database'].upcase
56
+ if @@databases.include?(name) == false
57
+ raise IotazError.new("Unsupported database name specified for "\
58
+ "database interface factory. '{0}' is not "\
59
+ "the name of a supported database.")
60
+ end
61
+ else
62
+ raise IotazError.new('Database name missing from Iotaz configuration '\
63
+ 'data.')
64
+ end
65
+
66
+ @configuration = configuration
67
+ end
68
+
69
+
70
+ #
71
+ # This method manufactures a new database interface object using the
72
+ # configuration information provided to the factory.
73
+ #
74
+ # ==== Exceptions
75
+ # IotazError:: Generated if any of the required configuration information
76
+ # for the interface object is invalid.
77
+ #
78
+ def create
79
+ name = @configuration['iotaz.database'].upcase
80
+ interface = nil
81
+ case name
82
+ when 'FIREBIRD' :
83
+ interface = FirebirdInterface.new(@configuration)
84
+
85
+ else
86
+ raise IotazError.new("Error manufacturing database interface. "\
87
+ "Unknown database name '{0}' encountered.")
88
+ end
89
+ interface
90
+ end
91
+ end # End of the DatabaseInterfaceFactory class.
92
+ end # End of the Iotaz module.
@@ -0,0 +1,788 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ #--
4
+ # Copyright � Peter Wood, 2005
5
+ #
6
+ # The contents of this file are subject to the Mozilla Public License Version
7
+ # 1.1 (the "License"); you may not use this file except in compliance with the
8
+ # License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.mozilla.org/MPL/
11
+ #
12
+ # Software distributed under the License is distributed on an "AS IS" basis,
13
+ # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14
+ # the specificlanguage governing rights and limitations under the License.
15
+ #
16
+ # The Original Code is the FireRuby extension for the Ruby language.
17
+ #
18
+ # The Initial Developer of the Original Code is Peter Wood. All Rights
19
+ # Reserved.
20
+ #++
21
+ #
22
+
23
+ require 'iotaz/IotazError'
24
+ require 'iotaz/ObjectPool'
25
+ require 'iotaz/Session'
26
+ require 'stringio'
27
+ require 'rubygems'
28
+ require_gem 'fireruby'
29
+
30
+ include FireRuby
31
+
32
+ module Iotaz
33
+ #
34
+ # This class manufactures or destroys connections to a specific Firebird
35
+ # database. This class is used internally by the connection pool within
36
+ # the FirebirdInterface class.
37
+ #
38
+ class FirebirdConnectionFactory
39
+ #
40
+ # This is the constructor fo the FirebirdConnectionFactory class.
41
+ #
42
+ # ==== Parameters
43
+ # user:: The user name to connect as.
44
+ # password:: The user password to be used to obtain connections.
45
+ # database:: The database file specification to be used to obtain
46
+ # connections.
47
+ #
48
+ def initialize(user, password, database)
49
+ @database = Database.new(database)
50
+ @user = user
51
+ @password = password
52
+ end
53
+
54
+
55
+ #
56
+ # This method is used to manufacture a new connection to the database.
57
+ #
58
+ # ==== Exceptions
59
+ # IotazError:: Generated whenever a problem occurs obtaining a new
60
+ # database connection.
61
+ #
62
+ def create
63
+ @database.connect(@user, @password)
64
+ rescue FireRubyException => error
65
+ raise IotazError.new('Error opening new database connection. Cause: {0}',
66
+ error.message)
67
+ end
68
+
69
+
70
+ #
71
+ # This method frees the resources associated with a connection previously
72
+ # created by the factory.
73
+ #
74
+ # ==== Exceptions
75
+ # IotazError:: Generated whenever a problem occurs closing the Firebird
76
+ # connection.
77
+ #
78
+ def destroy(connection)
79
+ connection.close
80
+ rescue FireRubyException => error
81
+ raise IotazError.new('Error closing database connection. Cause: {0}',
82
+ error.message)
83
+ end
84
+ end # End of the FirebirdConnectionFactory class
85
+
86
+
87
+ #
88
+ # This class represents the database interface for the Firebird database.
89
+ # The class makes use of the FireRuby library to interface with the database.
90
+ # The class currently recognises the following keys within the configuration
91
+ # data that is passed to it's constructor...
92
+ #
93
+ # - 'iotaz.firebird.user':
94
+ # The user name to be used in connecting to the database. A value for this
95
+ # entry must be specified.
96
+ #
97
+ # - 'iotaz.firebird.password':
98
+ # The password to be used to connect to the database. A value for this
99
+ # entry must be specified.
100
+ #
101
+ # - 'iotaz.firebird.database':
102
+ # The database file specification to be used to connect to the database. A
103
+ # value for this entry must be specified.
104
+ #
105
+ # - 'iotaz.firebird.connections.max':
106
+ # The maximum number of connections to be pooled by the interface. If not
107
+ # specified a default of 5 is used.
108
+ #
109
+ # - 'iotaz.firebird.connections.min':
110
+ # The minimum number of connections that the interface will have available
111
+ # in its connection pool. If not specified a default of 2 is used.
112
+ #
113
+ # - 'iotaz.firebird.connections.idle':
114
+ # The amount of time, in seconds, that a pooled connection can remain idle
115
+ # before it can be considered for automatic release. If not specified a
116
+ # default of 1800 is used.
117
+ #
118
+ # - 'iotaz.firebird.connections.interval':
119
+ # The interval, in seconds, that the interface will wait between attempts
120
+ # to clean up idle connections. If not specified a default of 300 is used.
121
+ #
122
+ class FirebirdInterface
123
+ # A definition for the configuration keys recognised by the class.
124
+ KEYS = ['iotaz.firebird.user',
125
+ 'iotaz.firebird.password',
126
+ 'iotaz.firebird.database',
127
+ 'iotaz.firebird.connections.max',
128
+ 'iotaz.firebird.connections.min',
129
+ 'iotaz.firebird.connections.idle',
130
+ 'iotaz.firebird.connections.interval']
131
+
132
+
133
+ #
134
+ # This is the constructor for the FirebirdInterface class.
135
+ #
136
+ # ==== Parameters
137
+ # config:: A hash containing the configuration elements for the interface.
138
+ #
139
+ # ==== Exceptions
140
+ # IotazError:: Generated whenever a required configuration element is not
141
+ # provided.
142
+ #
143
+ def initialize(config)
144
+ # Check for required values.
145
+ if config.key?(KEYS[0]) == false
146
+ raise IotazError.new("Require database interface configuration "\
147
+ "element '{0}' not specified.", KEYS[0])
148
+ end
149
+ if config.key?(KEYS[1]) == false
150
+ raise IotazError.new("Require database interface configuration "\
151
+ "element '{0}' not specified.", KEYS[1])
152
+ end
153
+ if config.key?('iotaz.firebird.database') == false
154
+ raise IotazError.new("Require database interface configuration "\
155
+ "element '{0}' not specified.", KEYS[2])
156
+ end
157
+
158
+ # Extract the required elements.
159
+ user = config[KEYS[0]]
160
+ password = config[KEYS[1]]
161
+ database = config[KEYS[2]]
162
+
163
+ # Create the interface connection pool.
164
+ factory = FirebirdConnectionFactory.new(user, password, database)
165
+ maximum = config.key?(KEYS[3]) ? config[KEYS[3]].to_i : 5
166
+ minimum = config.key?(KEYS[4]) ? config[KEYS[3]].to_i : 2
167
+ idle = config.key?(KEYS[5]) ? config[KEYS[3]].to_i : 1800
168
+ interval = config.key?(KEYS[6]) ? config[KEYS[3]].to_i : 300
169
+ @pool = ObjectPool.new(factory, maximum, minimum, idle, interval)
170
+ at_exit {@pool.shutdown}
171
+ end
172
+
173
+
174
+ #
175
+ # This method generates the SQL statement for the insertion of a new
176
+ # row into a database table based on a meta-data set and an object
177
+ # value. The function returns three values, the first is the SQL statement
178
+ # for the insertion. The second is an array of the callback objects that
179
+ # will be used to populate data values that will be available after the
180
+ # SQL execution. The third is an array of the parameter values to be used
181
+ # in conjunction with the SQL.
182
+ #
183
+ # ==== Parameters
184
+ # object:: A reference to the object to generate the insertion SQL
185
+ # statement for. This must be an instance of a class that
186
+ # implements the iotaz_meta_data method.
187
+ #
188
+ def get_insert_sql(object)
189
+ text = [StringIO.new, StringIO.new]
190
+ callbacks = []
191
+ parameters = []
192
+ metadata = object.class.iotaz_meta_data
193
+
194
+ # Insert the initial callbacks for the aspects of the SQL statement.
195
+ text[0] << "insert into #{metadata.table}("
196
+ text[1] << "values("
197
+
198
+ # Iterate over the attribute definitions.
199
+ added = false
200
+ metadata.each do |attribute|
201
+ # Check if the attribute is to be generated.
202
+ if attribute.is_generated?
203
+ # Check if the value is generated for inserts.
204
+ if attribute.events.include?("INSERT")
205
+ name = attribute.name
206
+ source = attribute.source
207
+ case attribute.type
208
+ when 'SEQUENCE' :
209
+ # Added the details to the SQL statement.
210
+ text[0] << ", " if added
211
+ text[0] << attribute.column
212
+ text[1] << ", " if added
213
+ text[1] << "?"
214
+ number = get_sequence_value(source)
215
+ parameters.push(number)
216
+ attribute.set(object, number)
217
+ added = true if added == false
218
+ when 'DATE', 'TIME', 'TIMESTAMP' :
219
+ # Generate an value callback to get the value assigned.
220
+ sql = get_value_select(attribute.name, object)
221
+ callbacks.push(ValueCallback.new(object,
222
+ attribute.name,
223
+ sql))
224
+
225
+ # Update the SQL statement.
226
+ text[0] << ", " if added
227
+ text[0] << attribute.column
228
+ text[1] << ", " if added
229
+ text[1] << "'#{attribute.source}'"
230
+ added = true if added == false
231
+ end
232
+ end
233
+ else
234
+ # Add the column name.
235
+ text[0] << ", " if added
236
+ text[0] << attribute.column
237
+
238
+ # Add the column value.
239
+ text[1] << ", " if added
240
+ text[1] << "?"
241
+ parameters.push(attribute.get(object))
242
+ added = true if added == false
243
+ end
244
+ end
245
+
246
+ # Terminate the aspects of the SQL statement.
247
+ text[0] << ") "
248
+ text[1] << ")"
249
+
250
+ #puts "INSERT SQL: #{text[0].string}#{text[1].string}"
251
+ #puts "PARAMETERS: #{parameters.join(', ')}"
252
+ [text[0].string + text[1].string, callbacks, parameters]
253
+ end
254
+
255
+
256
+ #
257
+ # This method generates the SQL statement for the update of an existing
258
+ # row into a database table based on a meta-data set and an object
259
+ # value. The function returns two values, the firsr is the SQL statement
260
+ # for the update. The second is a hash of generated values, mapping
261
+ # attribute names to generated values. This hash will either contain
262
+ # the generated value or a ValueCallback if the value will only be
263
+ # available after the statement has been executed.
264
+ #
265
+ # ==== Parameters
266
+ # object:: A reference to the object to generate the insertion SQL
267
+ # statement for. This must be an instance of a class that
268
+ # implements the iotaz_meta_data method.
269
+ #
270
+ def get_update_sql(object)
271
+ sql = StringIO.new
272
+ values = []
273
+ parameters = []
274
+ metadata = object.class.iotaz_meta_data
275
+
276
+ # Insert the initial values for the aspects of the SQL statement.
277
+ sql << "update #{metadata.table} set "
278
+
279
+ # Iterate over the attribute definitions.
280
+ added = false
281
+ metadata.each do |attribute|
282
+ # Check if the attribute is to be generated.
283
+ if attribute.is_generated?
284
+ # Check if the value is generated for inserts.
285
+ if attribute.events.include?("UPDATE")
286
+ name = attribute.name
287
+ source = attribute.source
288
+ case attribute.type
289
+ when 'SEQUENCE' :
290
+ # Added the details to the SQL statement.
291
+ sql << ", " if added
292
+ sql << attribute.column << " = ?"
293
+ number = get_sequence_value(source)
294
+ parameters.push(number)
295
+ attribute.set(object, number)
296
+ added = true if added == false
297
+
298
+ when 'DATE', 'TIME', 'TIMESTAMP' :
299
+ # Generate an value callback to get the value assigned.
300
+ statement = get_value_select(attribute.name, object)
301
+ values.push(ValueCallback.new(object,
302
+ attribute.name,
303
+ statement))
304
+
305
+ # Update the SQL statement.
306
+ sql << ", " if added
307
+ sql << attribute.column << " = '#{attribute.source}'"
308
+ added = true if added == false
309
+ end
310
+ end
311
+ else
312
+ # Add the column name and value.
313
+ sql << ", " if added
314
+ sql << attribute.column << " = ?"
315
+ parameters.push(attribute.get(object))
316
+ added = true if added == false
317
+ end
318
+ end
319
+
320
+ # Terminate the aspects of the SQL statement.
321
+ sql << " " << get_key_sql(object)
322
+ parameters.concat(get_key_values(object))
323
+
324
+ #puts "UPDATE SQL: #{sql.string}"
325
+ #puts "PARAMETERS: #{parameters.join(', ')}"
326
+ [sql.string, values, parameters]
327
+ end
328
+
329
+
330
+ #
331
+ # This method produces the SQL select statement that can be used to
332
+ # fetch an object instance for a given class from the database.
333
+ #
334
+ # ==== Parameters
335
+ # klass:: A reference to the class of the object that will be fetched
336
+ # from the database.
337
+ #
338
+ def get_fetch_sql(klass)
339
+ metadata = klass.iotaz_meta_data
340
+ sql = StringIO.new
341
+ added = false
342
+
343
+ # Load details of the attributes.
344
+ sql << "select "
345
+ metadata.each do |attribute|
346
+ sql << ', ' if added
347
+ sql << attribute.column
348
+ added = true if added == false
349
+ end
350
+ sql << " from #{metadata.table} #{get_key_sql(klass)}"
351
+
352
+ #puts "FETCH SQL: #{sql.string}"
353
+ sql.string
354
+ end
355
+
356
+
357
+ #
358
+ # This method produces the SQL statement that can be used to delete the
359
+ # database record for an object.
360
+ #
361
+ # ==== Parameters
362
+ # object:: Either a reference to an object or to a Class instance. If it
363
+ # is a Class instance then the class must implement the
364
+ # iotaz_meta_data method. If it's just a normal object then
365
+ # the objects class must implement the method.
366
+ #
367
+ def get_delete_sql(object)
368
+ metadata = nil
369
+ if object.instance_of?(Class)
370
+ metadata = object.iotaz_meta_data
371
+ else
372
+ metadata = object.class.iotaz_meta_data
373
+ end
374
+ #puts "DELETE SQL: delete from #{metadata.table} #{get_key_sql(object)}"
375
+ "delete from #{metadata.table} #{get_key_sql(object)}"
376
+ end
377
+
378
+
379
+ #
380
+ # This method initiates a transaction within the database. The value
381
+ # that is returned by the method should not be interpreted outside of
382
+ # this class.
383
+ #
384
+ def start_transaction
385
+ [@pool.acquire, nil]
386
+ end
387
+
388
+
389
+ #
390
+ # This method commits a transaction previously created using a call to the
391
+ # FirebirdInterface#start_transaction method.
392
+ #
393
+ # ==== Parameters
394
+ # transaction:: A reference to the transaction details to be committed.
395
+ #
396
+ # ==== Exception
397
+ # IotazError:: Generated whenever a problem occurs committing the work
398
+ # for the transaction.
399
+ #
400
+ def commit(transaction)
401
+ begin
402
+ transaction[1].commit if transaction[1] != nil
403
+ @pool.release(transaction[0])
404
+ transaction[0] = transaction[1] = nil
405
+ rescue FireRubyException => error
406
+ raise IotazError.new("Exception caught committing transaction. "\
407
+ "Cause: {0}", error.message)
408
+ end
409
+ end
410
+
411
+
412
+ #
413
+ # This method rolls back a transaction created using a call to the
414
+ # FirebirdInterface#start_transaction method.
415
+ #
416
+ # ==== Parameters
417
+ # transaction:: A reference to the transaction details to be rolled back.
418
+ #
419
+ # ==== Exceptions
420
+ # IotazError:: Generated whenever a problem occurs rolling back the work
421
+ # for the transaction.
422
+ #
423
+ def rollback(transaction)
424
+ begin
425
+ transaction[1].rollback if transaction[1] != nil
426
+ @pool.release(transaction[0])
427
+ transaction[0] = transaction[1] = nil
428
+ rescue FireRubyException => error
429
+ raise IotazError.new("Exception caught rolling back transaction. "\
430
+ "Cause: {0}", error.message)
431
+ end
432
+ end
433
+
434
+
435
+ #
436
+ # This method executes a single unit of work represented by an Action
437
+ # object.
438
+ #
439
+ # ==== Parameters
440
+ # action:: A reference to the action to be executed.
441
+ # transaction:: A reference to the transaction entity to be used in the
442
+ # execution. This should have been previously obtained
443
+ # from a call to the start_transaction method.
444
+ #
445
+ # ==== Exceptions
446
+ # IotazError:: Generated whenever a problem occurs executing the work for
447
+ # the action.
448
+ #
449
+ def execute_action(action, transaction)
450
+ begin
451
+ # Check if a transaction needs to be started.
452
+ if transaction[1] == nil
453
+ transaction[1] = transaction[0].start_transaction
454
+ end
455
+
456
+ # Check if we've cached anything for this action.
457
+ statement = nil
458
+ if action.cache == nil
459
+ statement = Statement.new(transaction[0], transaction[1],
460
+ action.sql, 3)
461
+ action.cache = statement
462
+ else
463
+ statement = action.cache
464
+ end
465
+
466
+ # Execute the statement on a parameter set.
467
+ parameters = action.get_parameters
468
+ result = statement.execute_for(parameters)
469
+ result.close if result != nil
470
+ rescue FireRubyException => error
471
+ raise IotazError.new("Exception caught executing action. Cause: {0}",
472
+ error.message)
473
+ end
474
+ end
475
+
476
+
477
+ #
478
+ # This method executes a select statement to fetch the data for a value
479
+ # callback.
480
+ #
481
+ # ==== Parameters
482
+ # callback:: A reference to the ValueCallback object to be executed.
483
+ # transaction:: A reference to the transaction entity to be used in the
484
+ # execution. This should have been previously obtained
485
+ # from a call to the start_transaction method.
486
+ #
487
+ # ==== Exceptions
488
+ # IotazError:: Generated whenever a problem occurs executing the SQL for
489
+ # the callback object.
490
+ #
491
+ def execute_callback(callback, transaction)
492
+ data = nil
493
+ begin
494
+ # Check if a transaction has actually been started.
495
+ if transaction[1] == nil
496
+ transaction[1] = transaction[0].start_transaction
497
+ end
498
+
499
+ # Execute the fetch and check the result.
500
+ statement = nil
501
+ if callback.cache == nil
502
+ statement = Statement.new(transaction[0], transaction[1],
503
+ callback.sql, 3)
504
+ callback.cache = statement
505
+ else
506
+ statement = callback.cache
507
+ end
508
+
509
+ result = statement.execute_for(callback.parameters)
510
+ if result == nil
511
+ raise IotazError.new("Value callback SQL statement did not "\
512
+ "return a result set.")
513
+ end
514
+
515
+ # Extract the data and clean up.
516
+ data = result.fetch[0]
517
+ result.close
518
+ rescue FireRubyException => error
519
+ raise IotazError.new("Error executing value callback. Cause: {0}",
520
+ error.message)
521
+ end
522
+ data
523
+ end
524
+
525
+
526
+ #
527
+ # This method executes a SQL query, passed in the form of a Query object,
528
+ # against the database.
529
+ #
530
+ # ==== Parameters
531
+ # query:: A reference to the Query object that will provide the
532
+ # details for the query to execute.
533
+ # transaction:: A reference to the transaction entity, previously created
534
+ # by a call to the start_transaction method, that will be
535
+ # used to execute the query.
536
+ # maximum:: A reference to an integer indicating the maximum number
537
+ # of rows to fetch. This defaults to -1 to indicate that
538
+ # all rows for the query should be returned.
539
+ #
540
+ def execute_query(query, transaction, maximum=-1)
541
+ rows = []
542
+
543
+ # Check that the transaction entity is fully initialized.
544
+ if transaction[1] == nil
545
+ transaction[1] = transaction[0].start_transaction
546
+ end
547
+
548
+ # Extract the query details.
549
+ sql, parameters = query.to_sql
550
+
551
+ # Create the statement.
552
+ statement = Statement.new(transaction[0], transaction[1], sql, 3)
553
+ begin
554
+ # Get the result set.
555
+ results = statement.execute_for(parameters)
556
+ begin
557
+ if maximum > 0
558
+ 1.upto(maximum) do |index|
559
+ entry = results.fetch
560
+ if entry != nil
561
+ row = QueryRow.new
562
+ 0.upto(entry.column_count - 1) do |index|
563
+ row[entry.column_name(index)] = entry[index]
564
+ end
565
+ rows.push(row)
566
+ else
567
+ break;
568
+ end
569
+ end
570
+ else
571
+ results.each do |entry|
572
+ row = QueryRow.new
573
+ 0.upto(entry.column_count - 1) do |index|
574
+ row[entry.column_name(index)] = entry[index]
575
+ end
576
+ rows.push(row)
577
+ end
578
+ end
579
+ ensure
580
+ results.close
581
+ end
582
+ ensure
583
+ statement.close
584
+ end
585
+
586
+ rows
587
+ end
588
+
589
+
590
+ #
591
+ # This method executes a SQL select statement, returning the rows fetched
592
+ # as a result. This method returns an array of hashes. Each contained
593
+ # hash represents a row of data from the select statement, mapping
594
+ # column names onto column values. Column names are set to be all upper
595
+ # case to simplify comparisons.
596
+ #
597
+ # ==== Parameters
598
+ # sql:: A string containing the SQL statement to be executed.
599
+ # parameters:: An array containing the parameters to be used in
600
+ # executing the SQL.
601
+ # transaction:: A reference to the transaction entity that will be used
602
+ # in executing the select.
603
+ # maximum:: An integer specifying the maximum number of rows to be
604
+ # fetched. This defaults to -1 to indicate that all rows
605
+ # should be fetched.
606
+ #
607
+ # ==== Exceptions
608
+ # IotazError:: Generated whenever a invalid number of parameters are
609
+ # specified, the SQL statement provided does not represent a
610
+ # select or an error occurs accessing the database.
611
+ #
612
+ def execute_select(sql, parameters, transaction, maximum=-1)
613
+ rows = []
614
+
615
+ # Check if a transaction is needed.
616
+ if transaction[1] == nil
617
+ transaction[1] = transaction[0].start_transaction
618
+ end
619
+
620
+ # Create the statement to execute the SQL.
621
+ statement = Statement.new(transaction[0], transaction[1], sql, 3)
622
+ if statement.is_query? == false
623
+ raise IotazError.new("Invalid SQL query. '{0}' is not a SQL select "\
624
+ "statement.", sql)
625
+ end
626
+
627
+ results = nil
628
+ begin
629
+ results = statement.execute_for(parameters)
630
+ if maximum > 0
631
+ count = 0
632
+ while count < maximum and rows.size == count
633
+ row = results.fetch
634
+ if row != nil
635
+ map = Hash.new
636
+ row.each_index do |index|
637
+ map[results.column_name(index).upcase] = row[index]
638
+ end
639
+ rows.push(map)
640
+ end
641
+ count += 1
642
+ end
643
+ else
644
+ results.each do |row|
645
+ map = Hash.new
646
+
647
+ 0.upto(row.column_count - 1) do |index|
648
+ map[row.column_name(index).upcase] = row[index]
649
+ end
650
+ rows.push(map)
651
+ end
652
+ end
653
+ ensure
654
+ results.close if results != nil
655
+ end
656
+
657
+ rows
658
+ rescue FireRubyException => error
659
+ raise IotazError.new('Error executing select. Cause: {0}',
660
+ error.message)
661
+ end
662
+
663
+
664
+ #
665
+ # This method is used to clean up the items assigned to Action cache
666
+ # values as part of the work of executing the action.
667
+ #
668
+ # ==== Parameters
669
+ # cache:: An array of the cache items that are to be cleaned up.
670
+ #
671
+ # ==== Exceptions
672
+ # IotazError:: Generated whenever a problem is encountered cleaning up a
673
+ # cache item.
674
+ #
675
+ def clean_cache(cache)
676
+ begin
677
+ cache.each {|entry| entry.close if entry != nil}
678
+ rescue FireRubyException => error
679
+ raise IotazError.new("Error cleaning up cache item. Cause: {0}",
680
+ error.message)
681
+ end
682
+ end
683
+
684
+
685
+ #
686
+ # This method fetches the next value from a name Firebird generator.
687
+ #
688
+ # ==== Parameters
689
+ # sequence:: The name of the generator to fetch the value.
690
+ #
691
+ def get_sequence_value(sequence)
692
+ value = nil
693
+ @pool.acquire do |connection|
694
+ sequence = Generator.new(sequence, connection)
695
+ value = sequence.next(1)
696
+ end
697
+ value
698
+ end
699
+
700
+
701
+ #
702
+ # This method produces a SQL select statement to fetch a value from
703
+ # a specific database row.
704
+ #
705
+ # ==== Parameters
706
+ # name:: The name of the attribute to generate the select for.
707
+ # object:: The object to get the key values from. This must be an
708
+ # instance of a class that implements the iotaz_meta_data
709
+ # method.
710
+ #
711
+ def get_value_select(name, object)
712
+ metadata = object.class.iotaz_meta_data
713
+ attribute = metadata.get_attribute(name)
714
+ "select #{attribute.column} from #{metadata.table} #{get_key_sql(object)}"
715
+ end
716
+
717
+
718
+ #
719
+ # This method generates the where clause aspects of a SQL statement to
720
+ # uniquely pick out a database row for a given object. The SQL fragement
721
+ # returned by the method will not fill out the key ookup fields, providing
722
+ # substitution entries instead. This is done as the lookup of the key
723
+ # values is left to the latest possible moment.
724
+ #
725
+ # ==== Parameters
726
+ # object:: Either a reference to the object to generate the SQL statement
727
+ # clauses for or a Class instance. If an object is specified
728
+ # then its class must provides the iotaz_meta_data method. If
729
+ # its a class then it must have a iotaz_meta_data method.
730
+ #
731
+ def get_key_sql(object)
732
+ out = StringIO.new
733
+ metadata = nil
734
+ if object.instance_of?(Class)
735
+ metadata = object.iotaz_meta_data
736
+ else
737
+ metadata = object.class.iotaz_meta_data
738
+ end
739
+
740
+ added = false
741
+ out << 'where '
742
+ metadata.keys.each do |key|
743
+ attribute = metadata.get_attribute(key)
744
+ out << " and " if added
745
+ out << attribute.column << " = ?"
746
+ added = true if added == false
747
+ end
748
+
749
+ out.string
750
+ end
751
+
752
+
753
+ #
754
+ # This method looks up the values of the parameters associated with the
755
+ # keys for an object. The method returns an array of the values for the
756
+ # key attributes for the object.
757
+ #
758
+ # ==== Parameters
759
+ # object:: The object to look up the key values for. This must be an
760
+ # instance of a class that implements the iotaz_meta_data
761
+ # method.
762
+ #
763
+ def get_key_values(object)
764
+ values = []
765
+ metadata = object.class.iotaz_meta_data
766
+
767
+ metadata.keys.each do |key|
768
+ values.push(metadata.get_attribute(key).get(object))
769
+ end
770
+
771
+ values
772
+ end
773
+
774
+
775
+ #
776
+ # This method shutdown an instance of the FirebirdInterface class. The
777
+ # object that this method is called for should not be used again after
778
+ # making the call.
779
+ #
780
+ def shutdown
781
+ @pool.shutdown
782
+ end
783
+
784
+
785
+ # Method access alterations.
786
+ private :get_value_select
787
+ end # End of the Firebird interface class.
788
+ end # End of the Iotaz module.