iotaz 0.1.0

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.
@@ -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.