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.
- 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/lib/iotaz/Query.rb
ADDED
@@ -0,0 +1,566 @@
|
|
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 specific language 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 'stringio'
|
24
|
+
require 'iotaz/IotazError'
|
25
|
+
|
26
|
+
module Iotaz
|
27
|
+
#
|
28
|
+
# This class represents a field that constitutes a component within a Query
|
29
|
+
# object.
|
30
|
+
#
|
31
|
+
class QueryField
|
32
|
+
#
|
33
|
+
# This is a constructor for the QueryField class.
|
34
|
+
#
|
35
|
+
# ==== Parameters
|
36
|
+
# name:: A string containing the name of the attribute to be made
|
37
|
+
# into a QueryField
|
38
|
+
# metadata:: A reference to the class IotazMetaData object that contains
|
39
|
+
# the attribute details.
|
40
|
+
# fetched:: A boolean flag to indicate whether the field should be
|
41
|
+
# part of the list of values fetched. This defaults to true.
|
42
|
+
#
|
43
|
+
def initialize(name, metadata, fetched=true)
|
44
|
+
@name = name
|
45
|
+
@metadata = metadata
|
46
|
+
@fetched = fetched
|
47
|
+
@comparison = nil
|
48
|
+
@value = nil
|
49
|
+
if @metadata.get_attribute(name) == nil
|
50
|
+
raise IotazError.new("'{0}' is not an attribute of the {1} class "\
|
51
|
+
"and therefore cannot be used to create "\
|
52
|
+
"query field.", name, metadata.name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
#
|
58
|
+
# Attribute accessor that fetches the column name for a QueryField.
|
59
|
+
#
|
60
|
+
def column
|
61
|
+
@metadata.get_attribute(@name).column
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
#
|
66
|
+
# Attribute accessor that fetches the column table name for a QueryField.
|
67
|
+
#
|
68
|
+
def table
|
69
|
+
@metadata.table
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
#
|
74
|
+
# This method attempts to perform a field name match for a QueryField
|
75
|
+
# object. A name matches a QueryField name if the name provided...
|
76
|
+
#
|
77
|
+
# - Matches the name of the underlying attribute.
|
78
|
+
#
|
79
|
+
# - Matches the name of the underlying attribute qualified by its class
|
80
|
+
# name.
|
81
|
+
#
|
82
|
+
# - Matches the name fo the underlying attributes column name.
|
83
|
+
#
|
84
|
+
# - Matches the name for the underlying attributes column name qualified
|
85
|
+
# with it's table name.
|
86
|
+
#
|
87
|
+
def name_match?(name)
|
88
|
+
attribute = @metadata.get_attribute(@name)
|
89
|
+
@name == name or
|
90
|
+
"#{@metadata.name}.#{@name}" == name or
|
91
|
+
"#{attribute.column}" == name or
|
92
|
+
"#{@metadata.table}.#{attribute.column}" == name
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
#
|
97
|
+
# This method sets a query field for a straight equivalence comaparison
|
98
|
+
# to another value. If the value passed to this method is nil then this
|
99
|
+
# changes the comparison to an is null check.
|
100
|
+
#
|
101
|
+
# ==== Parameters
|
102
|
+
# value:: The value to compare the field to.
|
103
|
+
#
|
104
|
+
def equal_to(value)
|
105
|
+
@comparison = value == nil ? 'is null' : '= ?'
|
106
|
+
@value = value
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
#
|
111
|
+
# This method sets a query field for a straight non-equivalence
|
112
|
+
# comaparison to another value. If the value passed to this method is nil
|
113
|
+
# then this changes the comparison to an is not null check.
|
114
|
+
#
|
115
|
+
# ==== Parameters
|
116
|
+
# value:: The value to compare the field to.
|
117
|
+
#
|
118
|
+
def not_equal_to(value)
|
119
|
+
@comparison = value == nil ? 'is not null' : '!= ?'
|
120
|
+
@value = value
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
#
|
125
|
+
# This method sets a query feld for a greater than comparison to another
|
126
|
+
# value.
|
127
|
+
#
|
128
|
+
# ==== Parameters
|
129
|
+
# value:: The value to perform the comparison against. This must not be
|
130
|
+
# nil.
|
131
|
+
#
|
132
|
+
# ==== Exceptions
|
133
|
+
# IotazError:: Generated whenever a nil value is specified to the method.
|
134
|
+
#
|
135
|
+
def greater_than(value)
|
136
|
+
if value == nil
|
137
|
+
raise IotazError.new("Nil value specified for greater than "\
|
138
|
+
"comparison on the {0} query field.",
|
139
|
+
@attribute.name)
|
140
|
+
end
|
141
|
+
@comparison = '> ?'
|
142
|
+
@value = value
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
#
|
147
|
+
# This method sets a query feld for a less than comparison to another
|
148
|
+
# value.
|
149
|
+
#
|
150
|
+
# ==== Parameters
|
151
|
+
# value:: The value to perform the comparison against. This must not be
|
152
|
+
# nil.
|
153
|
+
#
|
154
|
+
# ==== Exceptions
|
155
|
+
# IotazError:: Generated whenever a nil value is specified to the method.
|
156
|
+
#
|
157
|
+
def less_than(value)
|
158
|
+
if value == nil
|
159
|
+
raise IotazError.new("Nil value specified for less than "\
|
160
|
+
"comparison on the {0} query field.",
|
161
|
+
@attribute.name)
|
162
|
+
end
|
163
|
+
@comparison = '< ?'
|
164
|
+
@value = value
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
#
|
169
|
+
# This method sets a query feld for a greater than comparison to another
|
170
|
+
# value.
|
171
|
+
#
|
172
|
+
# ==== Parameters
|
173
|
+
# value:: The value to perform the comparison against. This must not be
|
174
|
+
# nil.
|
175
|
+
#
|
176
|
+
# ==== Exceptions
|
177
|
+
# IotazError:: Generated whenever a nil value is specified to the method.
|
178
|
+
#
|
179
|
+
def greater_than_or_equal_to(value)
|
180
|
+
if value == nil
|
181
|
+
raise IotazError.new("Nil value specified for greater than or "\
|
182
|
+
"equal to comparison on the {0} query field.",
|
183
|
+
@attribute.name)
|
184
|
+
end
|
185
|
+
@comparison = '>= ?'
|
186
|
+
@value = value
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
#
|
191
|
+
# This method sets a query feld for a less than comparison to another
|
192
|
+
# value.
|
193
|
+
#
|
194
|
+
# ==== Parameters
|
195
|
+
# value:: The value to perform the comparison against. This must not be
|
196
|
+
# nil.
|
197
|
+
#
|
198
|
+
# ==== Exceptions
|
199
|
+
# IotazError:: Generated whenever a nil value is specified to the method.
|
200
|
+
#
|
201
|
+
def less_than_or_equal_to(value)
|
202
|
+
if value == nil
|
203
|
+
raise IotazError.new("Nil value specified for less than or equal"\
|
204
|
+
"to comparison on the {0} query field.",
|
205
|
+
@attribute.name)
|
206
|
+
end
|
207
|
+
@comparison = '<= ?'
|
208
|
+
@value = value
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
#
|
213
|
+
# This method sets a query for a between comparison to two other values.
|
214
|
+
#
|
215
|
+
# ==== Parameters
|
216
|
+
# lower:: A reference to the value that represents the upper edge of the
|
217
|
+
# comparison. This value must not be nil.
|
218
|
+
# upper:: A reference to the value that represents the upper edge of the
|
219
|
+
# comparison. This value must not be nil.
|
220
|
+
#
|
221
|
+
def between(lower, upper)
|
222
|
+
if lower == nil or upper == nil
|
223
|
+
raise IotazError.new("Nil value specified for between comparison "\
|
224
|
+
"on the {0} query field.", @attribute.name)
|
225
|
+
end
|
226
|
+
@comparison = 'between ? and ?'
|
227
|
+
@value = [lower, upper]
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
#
|
232
|
+
# This method sets a query for a not between comparison to two other
|
233
|
+
# values.
|
234
|
+
#
|
235
|
+
# ==== Parameters
|
236
|
+
# lower:: A reference to the value that represents the upper edge of the
|
237
|
+
# comparison. This value must not be nil.
|
238
|
+
# upper:: A reference to the value that represents the upper edge of the
|
239
|
+
# comparison. This value must not be nil.
|
240
|
+
#
|
241
|
+
def not_between(lower, upper)
|
242
|
+
if lower == nil or upper == nil
|
243
|
+
raise IotazError.new("Nil value specified for not between "\
|
244
|
+
"comparison on the {0} query field.",
|
245
|
+
@attribute.name)
|
246
|
+
end
|
247
|
+
@comparison = 'not between ? and ?'
|
248
|
+
@value = [lower, upper]
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
#
|
253
|
+
# This method sets a query for an in comparison to a set of values.
|
254
|
+
#
|
255
|
+
# ==== Parameters
|
256
|
+
# values:: An array containing the full set of values to be compared
|
257
|
+
# with. This may not be empty.
|
258
|
+
#
|
259
|
+
def in(*values)
|
260
|
+
if values.size == 0
|
261
|
+
raise IotazError.new("Empty values set specified for in "\
|
262
|
+
"comparison on the {0} query field.",
|
263
|
+
@attribute.name)
|
264
|
+
end
|
265
|
+
|
266
|
+
list = []
|
267
|
+
values.each do |entry|
|
268
|
+
text = entry.class == String ? "'#{entry}'" : entry
|
269
|
+
list.push(text)
|
270
|
+
end
|
271
|
+
|
272
|
+
@comparison = "in (#{list.join(', ')})"
|
273
|
+
@value = nil
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
#
|
278
|
+
# This method sets a query for a not in comparison to a set of values.
|
279
|
+
#
|
280
|
+
# ==== Parameters
|
281
|
+
# values:: An array containing the full set of values to be compared
|
282
|
+
# with. This may not be empty.
|
283
|
+
#
|
284
|
+
def not_in(*values)
|
285
|
+
if values.size == 0
|
286
|
+
raise IotazError.new("Empty values set specified for not in "\
|
287
|
+
"comparison on the {0} query field.",
|
288
|
+
@attribute.name)
|
289
|
+
end
|
290
|
+
|
291
|
+
list = []
|
292
|
+
values.each do |entry|
|
293
|
+
text = entry.class == String ? "'#{entry}'" : entry
|
294
|
+
list.push(text)
|
295
|
+
end
|
296
|
+
|
297
|
+
@comparison = "not in (#{list.join(', ')})"
|
298
|
+
@value = nil
|
299
|
+
end
|
300
|
+
|
301
|
+
|
302
|
+
# Attribute accessor.
|
303
|
+
attr_reader :name, :fetched, :comparison, :value
|
304
|
+
|
305
|
+
# Attribute mutator.
|
306
|
+
attr_writer :fetched
|
307
|
+
|
308
|
+
# Method aliases.
|
309
|
+
alias :fetched? :fetched
|
310
|
+
end # Endo of the QueryField class.
|
311
|
+
|
312
|
+
|
313
|
+
#
|
314
|
+
# This class models a SQL query against one or more database tables.
|
315
|
+
#
|
316
|
+
class Query
|
317
|
+
#
|
318
|
+
# This is the constructor for the Query class.
|
319
|
+
#
|
320
|
+
# ==== Parameters
|
321
|
+
# klass:: A reference to a Class object representing the first table
|
322
|
+
# element of the query.
|
323
|
+
# fields:: A array of the name of class attributes that are to be
|
324
|
+
# included in the results for the query. If this is empty
|
325
|
+
# then all fields are marked as fetched.
|
326
|
+
#
|
327
|
+
def initialize(klass, *fields)
|
328
|
+
# Initialise instances values.
|
329
|
+
@definitions = []
|
330
|
+
@fields = []
|
331
|
+
|
332
|
+
# Process the class attributes as fields.
|
333
|
+
@definitions.push(klass.iotaz_meta_data)
|
334
|
+
@definitions[0].each do |attribute|
|
335
|
+
flag = fields.size > 0 ? fields.include?(attribute.name) : true
|
336
|
+
@fields.push(QueryField.new(attribute.name, @definitions[0], flag))
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
|
341
|
+
#
|
342
|
+
# This method retrieves a reference to a QueryField object within a
|
343
|
+
# Query instance. Fields my be referenced in a number of ways. They can
|
344
|
+
# be referenced by their attribute name, they can be referenced to their
|
345
|
+
# attribute name qualified by a class name, they can be referenced by
|
346
|
+
# their column name or they can be referenced by their column name
|
347
|
+
# qualified by their table name. The method returns nil if the specified
|
348
|
+
# field does not exist.
|
349
|
+
#
|
350
|
+
# ==== Parameters
|
351
|
+
# name:: The name of the field to be retrieved.
|
352
|
+
#
|
353
|
+
# ==== Exceptions
|
354
|
+
# IotazError:: Generated whenever the field name specified is ambiguous
|
355
|
+
# and can potential refer to multiple fields.
|
356
|
+
#
|
357
|
+
def field(name)
|
358
|
+
field = get_fields(name)
|
359
|
+
if field.class == Array
|
360
|
+
raise IotazError.new("Ambiguous field name. The name '{0}' cannot "\
|
361
|
+
"be resolved to a unique field.", name)
|
362
|
+
end
|
363
|
+
field
|
364
|
+
end
|
365
|
+
|
366
|
+
|
367
|
+
#
|
368
|
+
# This method generates the SQL statement for a Query object and assembles
|
369
|
+
# the list of parameters that will be needed to execute it. The method
|
370
|
+
# returns two elements, a string containing the SQL and an array
|
371
|
+
# containing the parameters.
|
372
|
+
#
|
373
|
+
def to_sql
|
374
|
+
parameters = []
|
375
|
+
select = StringIO.new
|
376
|
+
from = StringIO.new
|
377
|
+
where = StringIO.new
|
378
|
+
|
379
|
+
fields = 0
|
380
|
+
tables = []
|
381
|
+
clauses = 0
|
382
|
+
|
383
|
+
select << 'select '
|
384
|
+
from << ' from '
|
385
|
+
where << ' where '
|
386
|
+
|
387
|
+
@fields.each do |field|
|
388
|
+
# Check if the field is retrieved.
|
389
|
+
if field.fetched?
|
390
|
+
select << ', ' if fields > 0
|
391
|
+
select << field.column
|
392
|
+
fields += 1
|
393
|
+
end
|
394
|
+
|
395
|
+
# Check if the fields table has already been added,
|
396
|
+
pattern = Regexp.new(field.table)
|
397
|
+
if tables.include?(field.table) == false
|
398
|
+
from << ", " if tables.size > 0
|
399
|
+
from << field.table
|
400
|
+
tables.push(field.table)
|
401
|
+
end
|
402
|
+
|
403
|
+
# Check if the field has a comparison.
|
404
|
+
if field.comparison != nil
|
405
|
+
where << ' and ' if clauses > 0
|
406
|
+
where << field.column << ' ' << field.comparison
|
407
|
+
if field.value.class == Array
|
408
|
+
parameters = parameters.concat(field.value)
|
409
|
+
else
|
410
|
+
parameters.push(field.value) if field.value != nil
|
411
|
+
end
|
412
|
+
clauses += 1
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# Generate the SQL statement.
|
417
|
+
sql = select.string + from.string
|
418
|
+
sql += where.string if clauses > 0
|
419
|
+
|
420
|
+
[sql, parameters]
|
421
|
+
end
|
422
|
+
|
423
|
+
|
424
|
+
#
|
425
|
+
# This method is used for field name matching. The method will return
|
426
|
+
# one of three possible values. If a specified name uniquely matches a
|
427
|
+
# field within the Query object then this will be returned. If it matches
|
428
|
+
# more than one field then an array of fields will be returned. If it
|
429
|
+
# matches not fields then nil will be returned.
|
430
|
+
#
|
431
|
+
# ==== Parameters
|
432
|
+
# name:: A reference to the name of the field to be retrieved. See the
|
433
|
+
# description for the field method for details of the values that
|
434
|
+
# are accepted for this parameter.
|
435
|
+
#
|
436
|
+
def get_fields(name)
|
437
|
+
result = nil
|
438
|
+
|
439
|
+
matches = @fields.collect do |field|
|
440
|
+
field.name_match?(name) ? field : nil
|
441
|
+
end
|
442
|
+
matches.compact!
|
443
|
+
|
444
|
+
if matches.size > 0
|
445
|
+
result = matches.size == 1 ? matches[0] : matches
|
446
|
+
end
|
447
|
+
|
448
|
+
result
|
449
|
+
end
|
450
|
+
|
451
|
+
|
452
|
+
# Method access alterations.
|
453
|
+
private :get_fields
|
454
|
+
end # End of the Query class.
|
455
|
+
|
456
|
+
|
457
|
+
#
|
458
|
+
# This class models a row of data returned from the execution of a query.
|
459
|
+
# The class provides a mapping between the (case insensitive) column names
|
460
|
+
# and the column values for the row.
|
461
|
+
#
|
462
|
+
class QueryRow
|
463
|
+
#
|
464
|
+
# This is the constructor for the QueryRow class.
|
465
|
+
#
|
466
|
+
def initialize
|
467
|
+
@entries = Hash.new
|
468
|
+
end
|
469
|
+
|
470
|
+
#
|
471
|
+
# This method fetches a count of the number of columns within a QueryRow
|
472
|
+
# object.
|
473
|
+
#
|
474
|
+
def size
|
475
|
+
@entries.size
|
476
|
+
end
|
477
|
+
|
478
|
+
|
479
|
+
#
|
480
|
+
# This method test whether a given column name exists within a QueryRow
|
481
|
+
# object.
|
482
|
+
#
|
483
|
+
# ==== Parameters
|
484
|
+
# name:: The name of the column to check for.
|
485
|
+
#
|
486
|
+
def exists?(name)
|
487
|
+
@entries.key?(name.upcase)
|
488
|
+
end
|
489
|
+
|
490
|
+
|
491
|
+
#
|
492
|
+
# This method fetches an array of the column names for a QueryRow object.
|
493
|
+
#
|
494
|
+
def columns
|
495
|
+
@entries.keys
|
496
|
+
end
|
497
|
+
|
498
|
+
|
499
|
+
#
|
500
|
+
# This method fetches an array of the column values for a QueryRow object.
|
501
|
+
#
|
502
|
+
def values
|
503
|
+
@entries.values
|
504
|
+
end
|
505
|
+
|
506
|
+
|
507
|
+
#
|
508
|
+
# This method provides an iterator for the contents of a QueryRow object.
|
509
|
+
# The block passed to this method should take two parameters. The first
|
510
|
+
# will be the column name and the second will be the column value.
|
511
|
+
#
|
512
|
+
def each
|
513
|
+
if block_given?
|
514
|
+
@entries.each {|name, value| yield(name, value)}
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
|
519
|
+
#
|
520
|
+
# This method adds or updates a new column value to a QueryRow object.
|
521
|
+
# Note that the column names used by this class are case insensitive so
|
522
|
+
# 'COLUMN' is the same as 'Column', 'column' or 'cOLumn'.
|
523
|
+
#
|
524
|
+
# ==== Parameters
|
525
|
+
# name:: A reference to the column name to be stored or updated.
|
526
|
+
# value:: A reference to the column value to be stored.
|
527
|
+
#
|
528
|
+
def []=(name, value)
|
529
|
+
@entries[name.upcase] = value
|
530
|
+
end
|
531
|
+
|
532
|
+
|
533
|
+
#
|
534
|
+
# This method fetches the value for a specified column. If the specified
|
535
|
+
# column does not exist then an exception is thrown.
|
536
|
+
#
|
537
|
+
# ==== Parameters
|
538
|
+
# name:: A reference to a string containing the name of the column to
|
539
|
+
# fetch the value for.
|
540
|
+
#
|
541
|
+
# ==== Exception
|
542
|
+
# IotazError:: Generated whenever the column name specified does not
|
543
|
+
# exist within the QueryRow object.
|
544
|
+
#
|
545
|
+
def [](name)
|
546
|
+
if @entries.key?(name.upcase) == false
|
547
|
+
raise IotazError.new("Column not found. A '{0}' column does not "\
|
548
|
+
"exist in the query row data.", name)
|
549
|
+
end
|
550
|
+
@entries[name.upcase]
|
551
|
+
end
|
552
|
+
|
553
|
+
|
554
|
+
#
|
555
|
+
# This method provides a string description for a QueryRow object.
|
556
|
+
#
|
557
|
+
def to_s
|
558
|
+
out = StringIO.new
|
559
|
+
@entries.each do |column, value|
|
560
|
+
out << ', ' if out.string.length > 0
|
561
|
+
out << "#{column} = #{value}"
|
562
|
+
end
|
563
|
+
out.string
|
564
|
+
end
|
565
|
+
end # End of the QueryRow class.
|
566
|
+
end # End of the Iotaz module.
|