iotaz 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/PERSISTENT +137 -0
- data/doc/README +470 -0
- data/examples/example01.rb +105 -0
- data/examples/example02.rb +105 -0
- data/lib/iotaz.rb +35 -0
- data/lib/iotaz/DatabaseInterfaceFactory.rb +92 -0
- data/lib/iotaz/FirebirdInterface.rb +788 -0
- data/lib/iotaz/IotazError.rb +81 -0
- data/lib/iotaz/MetaData.rb +646 -0
- data/lib/iotaz/ObjectPool.rb +299 -0
- data/lib/iotaz/Persistent.rb +155 -0
- data/lib/iotaz/Query.rb +566 -0
- data/lib/iotaz/Session.rb +379 -0
- data/lib/iotaz/WorkSet.rb +295 -0
- data/test/ActionTest.rb +31 -0
- data/test/AtomTest.rb +90 -0
- data/test/AttributeTest.rb +64 -0
- data/test/DatabaseInterfaceFactoryTest.rb +41 -0
- data/test/FirebirdInterfaceTest.rb +276 -0
- data/test/GeneratedAttributeTest.rb +124 -0
- data/test/IotazErrorTest.rb +23 -0
- data/test/IotazMetaDataTest.rb +87 -0
- data/test/ObjectPoolTest.rb +106 -0
- data/test/PersistentTest.rb +102 -0
- data/test/QueryFieldTest.rb +152 -0
- data/test/QueryRowTest.rb +42 -0
- data/test/QueryTest.rb +59 -0
- data/test/SessionTest.rb +162 -0
- data/test/UnitTest.rb +17 -0
- data/test/ValueCallbackTest.rb +44 -0
- data/test/dbinfo.rb +7 -0
- metadata +75 -0
data/test/ActionTest.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rubygems'
|
5
|
+
require_gem 'iotaz'
|
6
|
+
|
7
|
+
include Iotaz
|
8
|
+
|
9
|
+
class ActionTest < Test::Unit::TestCase
|
10
|
+
def test01
|
11
|
+
a = Action.new('SELECT * FROM SOME_TABLE')
|
12
|
+
|
13
|
+
assert(a.sql == 'SELECT * FROM SOME_TABLE')
|
14
|
+
begin
|
15
|
+
a.get_parameters
|
16
|
+
assert(false, 'Fetched parameter set from empty action.')
|
17
|
+
rescue IotazError
|
18
|
+
end
|
19
|
+
|
20
|
+
a.add_parameters(1, 'One', 1.0)
|
21
|
+
a.add_parameters(2, 'Two', 2.0)
|
22
|
+
|
23
|
+
assert(a.get_parameters == [1, 'One', 1.0])
|
24
|
+
assert(a.get_parameters == [2, 'Two', 2.0])
|
25
|
+
begin
|
26
|
+
a.get_parameters
|
27
|
+
assert(false, 'Fetched parameter set from emptied action.')
|
28
|
+
rescue Exception
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/test/AtomTest.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rubygems'
|
5
|
+
require_gem 'iotaz'
|
6
|
+
|
7
|
+
include Iotaz
|
8
|
+
|
9
|
+
# Dummy value class for testing.
|
10
|
+
class DummyValue
|
11
|
+
attr_accessor :first, :second
|
12
|
+
|
13
|
+
def DummyValue.iotaz_meta_data
|
14
|
+
IotazMetaData.scan(DummyValue)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Dummy database interface class for testing.
|
19
|
+
class DummyInterface
|
20
|
+
def initialize
|
21
|
+
@actions = 0
|
22
|
+
@callbacks = 0
|
23
|
+
@cache = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def start_transaction
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute_action(entry, transaction)
|
31
|
+
@actions += 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def execute_callback(callback, transaction)
|
35
|
+
@callbacks += 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def clean_cache(items)
|
39
|
+
@cache += items.size
|
40
|
+
end
|
41
|
+
|
42
|
+
def commit(transaction)
|
43
|
+
end
|
44
|
+
|
45
|
+
def rollback(transaction)
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :actions, :callbacks, :cache
|
49
|
+
end
|
50
|
+
|
51
|
+
class AtomTest < Test::Unit::TestCase
|
52
|
+
def setup
|
53
|
+
@objects = []
|
54
|
+
@actions = []
|
55
|
+
|
56
|
+
@objects.push(DummyValue.new)
|
57
|
+
@objects.push(DummyValue.new)
|
58
|
+
@objects.push(DummyValue.new)
|
59
|
+
|
60
|
+
@actions.push(Action.new("Action #1"))
|
61
|
+
@actions.push(Action.new("Action #2"))
|
62
|
+
@actions.push(Action.new("Action #3"))
|
63
|
+
|
64
|
+
@actions[0].add_parameters([])
|
65
|
+
@actions[1].add_parameters([])
|
66
|
+
@actions[2].add_parameters([])
|
67
|
+
end
|
68
|
+
|
69
|
+
def test01
|
70
|
+
atom = Atom.new
|
71
|
+
|
72
|
+
assert(atom.action_count == 0)
|
73
|
+
assert(atom.callback_count == 0)
|
74
|
+
|
75
|
+
atom.add(@actions[0])
|
76
|
+
atom.add(@actions[1], ValueCallback.new(@objects[0], 'first', 'The SQL.'))
|
77
|
+
atom.add(@actions[2], ValueCallback.new(@objects[1], 'first', 'The SQL.'),
|
78
|
+
ValueCallback.new(@objects[2], 'second', 'The SQL.'))
|
79
|
+
|
80
|
+
assert(atom.action_count == 3)
|
81
|
+
assert(atom.callback_count == 3)
|
82
|
+
|
83
|
+
interface = DummyInterface.new
|
84
|
+
atom.commit(interface)
|
85
|
+
|
86
|
+
assert(interface.actions == 3)
|
87
|
+
assert(interface.callbacks == 3)
|
88
|
+
assert(interface.cache == 0)
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rubygems'
|
5
|
+
require_gem 'iotaz'
|
6
|
+
|
7
|
+
include Iotaz
|
8
|
+
|
9
|
+
class AttributeTest < Test::Unit::TestCase
|
10
|
+
def test01
|
11
|
+
a = Attribute.new('att01')
|
12
|
+
|
13
|
+
assert(a.name == 'att01')
|
14
|
+
assert(a.type == nil)
|
15
|
+
assert(a.column == 'att01')
|
16
|
+
assert(a.accessor == 'att01')
|
17
|
+
assert(a.mutator == 'att01=')
|
18
|
+
|
19
|
+
a = Attribute.new('att02', 'ATT_02')
|
20
|
+
|
21
|
+
assert(a.name == 'att02')
|
22
|
+
assert(a.type == nil)
|
23
|
+
assert(a.column == 'ATT_02')
|
24
|
+
assert(a.accessor == 'att02')
|
25
|
+
assert(a.mutator == 'att02=')
|
26
|
+
|
27
|
+
a = Attribute.new('att03', 'COLUMN_03', 'string')
|
28
|
+
|
29
|
+
assert(a.name == 'att03')
|
30
|
+
assert(a.type == 'string')
|
31
|
+
assert(a.column == 'COLUMN_03')
|
32
|
+
assert(a.accessor == 'att03')
|
33
|
+
assert(a.mutator == 'att03=')
|
34
|
+
end
|
35
|
+
|
36
|
+
def test02
|
37
|
+
a1 = Attribute.new('name')
|
38
|
+
a2 = Attribute.new('name')
|
39
|
+
a3 = Attribute.new('name', 'DIFFERENT')
|
40
|
+
|
41
|
+
assert(a1 == a1)
|
42
|
+
assert(a1 == a2)
|
43
|
+
assert(a1 != a3)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test03
|
47
|
+
a = Attribute.new('test_value')
|
48
|
+
|
49
|
+
a.name = 'alternative'
|
50
|
+
assert(a.name == 'alternative')
|
51
|
+
|
52
|
+
a.type = 'INTEGER'
|
53
|
+
assert(a.type == 'INTEGER')
|
54
|
+
|
55
|
+
a.column = 'COL_NAME'
|
56
|
+
assert(a.column == 'COL_NAME')
|
57
|
+
|
58
|
+
a.accessor = 'blah'
|
59
|
+
assert(a.accessor = 'blah')
|
60
|
+
|
61
|
+
a.mutator = 'blah_de_blah'
|
62
|
+
assert(a.mutator == 'blah_de_blah')
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'dbinfo'
|
5
|
+
require 'rubygems'
|
6
|
+
require_gem 'iotaz'
|
7
|
+
|
8
|
+
include Iotaz
|
9
|
+
|
10
|
+
class DatabaseInterfaceFactoryTest < Test::Unit::TestCase
|
11
|
+
def setup
|
12
|
+
@configurations = [{}, {}, {}]
|
13
|
+
|
14
|
+
@configurations[1]['iotaz.database'] = 'unknown'
|
15
|
+
@configurations[2]['iotaz.database'] = 'firebird'
|
16
|
+
@configurations[2]['iotaz.firebird.user'] = DB_USER_NAME
|
17
|
+
@configurations[2]['iotaz.firebird.password'] = DB_PASSWORD
|
18
|
+
@configurations[2]['iotaz.firebird.database'] = './nonexistent.fdb'
|
19
|
+
@configurations[2]['iotaz.firebird.connections.min'] = 0
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def test01
|
24
|
+
begin
|
25
|
+
DatabaseInterfaceFactory.new(@configurations[0])
|
26
|
+
assert(false,
|
27
|
+
'Able to create an interface factory without specifying a database.')
|
28
|
+
rescue IotazError
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
DatabaseInterfaceFactory.new(@configurations[0])
|
33
|
+
assert(false,
|
34
|
+
'Able to create an interface factory using an illegal database.')
|
35
|
+
rescue IotazError
|
36
|
+
end
|
37
|
+
|
38
|
+
factory = DatabaseInterfaceFactory.new(@configurations[2])
|
39
|
+
assert(factory.create.class == FirebirdInterface)
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,276 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'dbinfo'
|
5
|
+
require 'rubygems'
|
6
|
+
require_gem 'iotaz'
|
7
|
+
require_gem 'fireruby'
|
8
|
+
|
9
|
+
include Iotaz
|
10
|
+
|
11
|
+
# Class for use in testing.
|
12
|
+
class Account
|
13
|
+
attr_accessor :id, :number, :customer, :created, :updated
|
14
|
+
|
15
|
+
def initialize(number, customer)
|
16
|
+
@number = number
|
17
|
+
@customer = customer
|
18
|
+
end
|
19
|
+
|
20
|
+
def Account.iotaz_meta_data
|
21
|
+
IotazMetaData.scan(Account)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class FirebirdInterfaceTest < Test::Unit::TestCase
|
26
|
+
DB_FILE = './firebird_interface_test.fdb'
|
27
|
+
TABLE_SQL = 'create table Account (id integer not null primary key, '\
|
28
|
+
'number varchar(10) not null, customer varchar(50), '\
|
29
|
+
'created timestamp not null, updated timestamp)'
|
30
|
+
GENERATOR_SQL = 'create generator ACCOUNT_ID_SQ'
|
31
|
+
|
32
|
+
def setup
|
33
|
+
@configurations = []
|
34
|
+
|
35
|
+
# Full and proper configuration.
|
36
|
+
@configurations.push({'iotaz.database' => 'firebird',
|
37
|
+
'iotaz.firebird.user' => DB_USER_NAME,
|
38
|
+
'iotaz.firebird.password' => DB_PASSWORD,
|
39
|
+
'iotaz.firebird.database' => DB_FILE,
|
40
|
+
'iotaz.firebird.connections.min' => 0})
|
41
|
+
|
42
|
+
# Configuration without the database user name.
|
43
|
+
@configurations.push({'iotaz.database' => 'firebird',
|
44
|
+
'iotaz.firebird.password' => DB_PASSWORD,
|
45
|
+
'iotaz.firebird.database' => DB_FILE,
|
46
|
+
'iotaz.firebird.connections.min' => 0})
|
47
|
+
|
48
|
+
# Configuration without the database user password.
|
49
|
+
@configurations.push({'iotaz.database' => 'firebird',
|
50
|
+
'iotaz.firebird.user' => DB_USER_NAME,
|
51
|
+
'iotaz.firebird.database' => DB_FILE,
|
52
|
+
'iotaz.firebird.connections.min' => 0})
|
53
|
+
|
54
|
+
# Configuration without the database file specification.
|
55
|
+
@configurations.push({'iotaz.database' => 'firebird',
|
56
|
+
'iotaz.firebird.user' => DB_USER_NAME,
|
57
|
+
'iotaz.firebird.password' => DB_PASSWORD,
|
58
|
+
'iotaz.firebird.connections.min' => 0})
|
59
|
+
|
60
|
+
if File.exist?(DB_FILE)
|
61
|
+
Database.new(DB_FILE).drop(DB_USER_NAME, DB_PASSWORD)
|
62
|
+
end
|
63
|
+
db = Database.create(DB_FILE, DB_USER_NAME, DB_PASSWORD, 1024, 'ASCII')
|
64
|
+
db.connect(DB_USER_NAME, DB_PASSWORD) do |cxn|
|
65
|
+
cxn.execute_immediate(GENERATOR_SQL)
|
66
|
+
cxn.execute_immediate(TABLE_SQL)
|
67
|
+
end
|
68
|
+
|
69
|
+
@interface = FirebirdInterface.new(@configurations[0])
|
70
|
+
end
|
71
|
+
|
72
|
+
def teardown
|
73
|
+
@interface.shutdown
|
74
|
+
if File.exist?(DB_FILE)
|
75
|
+
Database.new(DB_FILE).drop(DB_USER_NAME, DB_PASSWORD)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Test the constructor.
|
80
|
+
def test01
|
81
|
+
FirebirdInterface.new(@configurations[0])
|
82
|
+
|
83
|
+
begin
|
84
|
+
FirebirdInterface.new(@configurations[1])
|
85
|
+
assert(false,
|
86
|
+
'Able to successful create an interface with a database user.')
|
87
|
+
rescue IotazError
|
88
|
+
end
|
89
|
+
|
90
|
+
begin
|
91
|
+
FirebirdInterface.new(@configurations[2])
|
92
|
+
assert(false,
|
93
|
+
'Able to successful create an interface with a database password.')
|
94
|
+
rescue IotazError
|
95
|
+
end
|
96
|
+
|
97
|
+
begin
|
98
|
+
FirebirdInterface.new(@configurations[3])
|
99
|
+
assert(false,
|
100
|
+
'Able to successful create an interface with a database file.')
|
101
|
+
rescue IotazError
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Test the transactional elements.
|
106
|
+
def test02
|
107
|
+
transaction = @interface.start_transaction
|
108
|
+
|
109
|
+
assert(transaction.class == Array)
|
110
|
+
assert(transaction.size == 2)
|
111
|
+
assert(transaction[0].class == FireRuby::Connection)
|
112
|
+
assert(transaction[1] == nil)
|
113
|
+
|
114
|
+
@interface.commit(transaction)
|
115
|
+
assert(transaction[0] == nil)
|
116
|
+
assert(transaction[1] == nil)
|
117
|
+
|
118
|
+
transaction = @interface.start_transaction
|
119
|
+
assert(transaction[0].class == FireRuby::Connection)
|
120
|
+
assert(transaction[1] == nil)
|
121
|
+
|
122
|
+
@interface.rollback(transaction)
|
123
|
+
assert(transaction[0] == nil)
|
124
|
+
assert(transaction[1] == nil)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Tes the SQL generation code.
|
128
|
+
def test03
|
129
|
+
account = Account.new('ACC001', 'The Customer')
|
130
|
+
|
131
|
+
# Check SQL for inserts.
|
132
|
+
sql, callbacks, parameters = @interface.get_insert_sql(account)
|
133
|
+
|
134
|
+
assert(/^insert into Account\(/.match(sql) != nil)
|
135
|
+
assert(sql.include?(' values('))
|
136
|
+
index = sql.index(' values(') + 1
|
137
|
+
insert = sql[0, index]
|
138
|
+
values = sql[index, sql.length - insert.length]
|
139
|
+
assert(/[ (]id/.match(insert) != nil)
|
140
|
+
assert(/[ (]number/.match(insert) != nil)
|
141
|
+
assert(/[ (]customer/.match(insert) != nil)
|
142
|
+
assert(/[ (]created/.match(insert) != nil)
|
143
|
+
assert(values.count('?') == 3)
|
144
|
+
assert(/'NOW'/.match(values) != nil)
|
145
|
+
|
146
|
+
assert(callbacks.size == 1)
|
147
|
+
assert(callbacks[0].sql == 'select created from Account where id = ?')
|
148
|
+
assert(parameters.size == 3)
|
149
|
+
assert(parameters.include?(account.id))
|
150
|
+
assert(parameters.include?('ACC001'))
|
151
|
+
assert(parameters.include?('The Customer'))
|
152
|
+
|
153
|
+
# Check SQL for updates.
|
154
|
+
sql, callbacks, parameters = @interface.get_update_sql(account)
|
155
|
+
|
156
|
+
assert(/^update Account set /.match(sql) != nil)
|
157
|
+
assert(sql.include?('number = ?'))
|
158
|
+
assert(sql.include?('customer = ?'))
|
159
|
+
assert(sql.include?("updated = 'NOW'"))
|
160
|
+
assert(/where id = \?$/.match(sql) != nil)
|
161
|
+
|
162
|
+
assert(callbacks.size == 1)
|
163
|
+
assert(callbacks[0].sql == 'select updated from Account where id = ?')
|
164
|
+
assert(parameters.size == 3)
|
165
|
+
assert(parameters.include?(account.id))
|
166
|
+
assert(parameters.include?('ACC001'))
|
167
|
+
assert(parameters.include?('The Customer'))
|
168
|
+
|
169
|
+
# Check the SQL for fetches.
|
170
|
+
sql = @interface.get_fetch_sql(Account)
|
171
|
+
|
172
|
+
index = sql.index(' where ') + 1
|
173
|
+
select = sql[0, index]
|
174
|
+
where = sql[index, sql.length - select.length]
|
175
|
+
assert(select.include?('id'))
|
176
|
+
assert(select.include?('number'))
|
177
|
+
assert(select.include?('customer'))
|
178
|
+
assert(select.include?('created'))
|
179
|
+
assert(select.include?('updated'))
|
180
|
+
assert(where == 'where id = ?')
|
181
|
+
|
182
|
+
# Check SQL for deletes.
|
183
|
+
sql = @interface.get_delete_sql(account)
|
184
|
+
|
185
|
+
assert(sql == 'delete from Account where id = ?')
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
# Test the execute methods.
|
190
|
+
def test04
|
191
|
+
# Test the FirebirdInterface#execute_action method.
|
192
|
+
sql = "insert into account values(?, ?, ?, 'NOW', null)"
|
193
|
+
action = Action.new(sql)
|
194
|
+
action.add_parameters(2500, 'ACC002', 'Customer Name')
|
195
|
+
transaction = @interface.start_transaction
|
196
|
+
begin
|
197
|
+
@interface.execute_action(action, transaction)
|
198
|
+
|
199
|
+
select = 'select count(*) from account where id = 2500'
|
200
|
+
d = transaction[1].execute(select)
|
201
|
+
r = d.fetch
|
202
|
+
d.close
|
203
|
+
assert(r[0] == 1)
|
204
|
+
|
205
|
+
@interface.commit(transaction)
|
206
|
+
rescue Exception => error
|
207
|
+
@interface.rollback(transaction)
|
208
|
+
raise error
|
209
|
+
end
|
210
|
+
|
211
|
+
# Test the FirebirdInterface#execute_callback method.
|
212
|
+
sql = 'select created from account where id = ?'
|
213
|
+
account = Account.new('ACC002', 'Customer Name')
|
214
|
+
callback = ValueCallback.new(account, 'created', sql)
|
215
|
+
account.id = 2500
|
216
|
+
transaction = @interface.start_transaction
|
217
|
+
begin
|
218
|
+
data = @interface.execute_callback(callback, transaction)
|
219
|
+
assert(data != nil)
|
220
|
+
assert(data.class == Time)
|
221
|
+
|
222
|
+
@interface.commit(transaction)
|
223
|
+
rescue Exception => error
|
224
|
+
@interface.rollback(transaction)
|
225
|
+
raise error
|
226
|
+
end
|
227
|
+
|
228
|
+
# Test the FirebirdInterface#execute_select method.
|
229
|
+
sql = 'select * from account'
|
230
|
+
transaction = @interface.start_transaction
|
231
|
+
begin
|
232
|
+
array = @interface.execute_select(sql, [], transaction)
|
233
|
+
|
234
|
+
assert(array != nil)
|
235
|
+
assert(array.class == Array)
|
236
|
+
assert(array.size == 1)
|
237
|
+
assert(array[0]['ID'] == 2500)
|
238
|
+
assert(array[0]['NUMBER'] == 'ACC002')
|
239
|
+
assert(array[0]['CUSTOMER'] == 'Customer Name')
|
240
|
+
assert(array[0]['CREATED'].class == Time)
|
241
|
+
assert(array[0]['UPDATED'] == nil)
|
242
|
+
|
243
|
+
@interface.commit(transaction)
|
244
|
+
rescue Exception => error
|
245
|
+
@interface.rollback(transaction)
|
246
|
+
raise error
|
247
|
+
end
|
248
|
+
|
249
|
+
# Test the FirebirdInterface#execute_query method.
|
250
|
+
query = Query.new(Account)
|
251
|
+
query.field('number').equal_to('ACC002')
|
252
|
+
transaction = @interface.start_transaction
|
253
|
+
begin
|
254
|
+
rows = @interface.execute_query(query, transaction)
|
255
|
+
|
256
|
+
assert(rows != nil)
|
257
|
+
assert(rows[0].class == QueryRow)
|
258
|
+
assert(rows.size == 1)
|
259
|
+
assert(rows[0]['ID'] == 2500)
|
260
|
+
assert(rows[0]['NUMBER'] == 'ACC002')
|
261
|
+
assert(rows[0]['CUSTOMER'] == 'Customer Name')
|
262
|
+
assert(rows[0]['CREATED'].class == Time)
|
263
|
+
assert(rows[0]['UPDATED'] == nil)
|
264
|
+
|
265
|
+
@interface.commit(transaction)
|
266
|
+
rescue Exception => error
|
267
|
+
@interface.rollback(transaction)
|
268
|
+
raise error
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# Test miscellaneous methods.
|
273
|
+
def test05
|
274
|
+
assert(@interface.get_sequence_value('ACCOUNT_ID_SQ') == 1)
|
275
|
+
end
|
276
|
+
end
|