sequel 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +28 -0
- data/Rakefile +1 -1
- data/lib/sequel/ado.rb +12 -5
- data/lib/sequel/array_keys.rb +61 -2
- data/lib/sequel/connection_pool.rb +6 -0
- data/lib/sequel/core_ext.rb +18 -14
- data/lib/sequel/database.rb +40 -4
- data/lib/sequel/dataset.rb +9 -3
- data/lib/sequel/dataset/convenience.rb +66 -19
- data/lib/sequel/dataset/sequelizer.rb +4 -4
- data/lib/sequel/dataset/sql.rb +41 -42
- data/lib/sequel/db2.rb +160 -0
- data/lib/sequel/dbi.rb +1 -1
- data/lib/sequel/error.rb +11 -0
- data/lib/sequel/model.rb +28 -22
- data/lib/sequel/model/record.rb +3 -1
- data/lib/sequel/mysql.rb +9 -9
- data/lib/sequel/odbc.rb +1 -1
- data/lib/sequel/oracle.rb +2 -2
- data/lib/sequel/postgres.rb +18 -20
- data/lib/sequel/pretty_table.rb +2 -2
- data/lib/sequel/schema/schema_sql.rb +3 -3
- data/lib/sequel/sqlite.rb +4 -0
- data/spec/adapters/mysql_spec.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +8 -0
- data/spec/core_ext_spec.rb +23 -23
- data/spec/dataset_spec.rb +204 -19
- data/spec/model_spec.rb +71 -1
- data/spec/schema_generator_spec.rb +3 -3
- metadata +4 -3
- data/lib/sequel/expressions.rb +0 -76
data/lib/sequel/dataset.rb
CHANGED
@@ -97,7 +97,7 @@ module Sequel
|
|
97
97
|
new_dataset
|
98
98
|
end
|
99
99
|
|
100
|
-
def set_options(opts)
|
100
|
+
def set_options(opts) #:nodoc:
|
101
101
|
@opts = opts
|
102
102
|
@columns = nil
|
103
103
|
end
|
@@ -152,6 +152,11 @@ module Sequel
|
|
152
152
|
def <<(*args)
|
153
153
|
insert(*args)
|
154
154
|
end
|
155
|
+
|
156
|
+
# Updates the dataset with the given values.
|
157
|
+
def set(*args)
|
158
|
+
update(*args)
|
159
|
+
end
|
155
160
|
|
156
161
|
# Iterates over the records in the dataset
|
157
162
|
def each(opts = nil, &block)
|
@@ -307,6 +312,7 @@ module Sequel
|
|
307
312
|
end
|
308
313
|
end
|
309
314
|
update_each_method
|
315
|
+
self
|
310
316
|
end
|
311
317
|
|
312
318
|
# Applies the value transform for data loaded from the database.
|
@@ -383,11 +389,11 @@ module Sequel
|
|
383
389
|
|
384
390
|
@@dataset_classes = []
|
385
391
|
|
386
|
-
def self.dataset_classes
|
392
|
+
def self.dataset_classes #:nodoc:
|
387
393
|
@@dataset_classes
|
388
394
|
end
|
389
395
|
|
390
|
-
def self.inherited(c)
|
396
|
+
def self.inherited(c) #:nodoc:
|
391
397
|
@@dataset_classes << c
|
392
398
|
end
|
393
399
|
end
|
@@ -77,11 +77,11 @@ module Sequel
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
-
# Maps
|
80
|
+
# Maps column values for each record in the dataset (if a column name is
|
81
81
|
# given), or performs the stock mapping functionality of Enumerable.
|
82
|
-
def map(
|
83
|
-
if
|
84
|
-
super() {|r| r[
|
82
|
+
def map(column_name = nil, &block)
|
83
|
+
if column_name
|
84
|
+
super() {|r| r[column_name]}
|
85
85
|
else
|
86
86
|
super(&block)
|
87
87
|
end
|
@@ -152,24 +152,43 @@ module Sequel
|
|
152
152
|
b - a + 1
|
153
153
|
end
|
154
154
|
|
155
|
-
# Returns the minimum value for the given
|
156
|
-
def min(
|
157
|
-
single_value(:select => [
|
155
|
+
# Returns the minimum value for the given column.
|
156
|
+
def min(column)
|
157
|
+
single_value(:select => [column.MIN.AS(:v)])
|
158
158
|
end
|
159
159
|
|
160
|
-
# Returns the maximum value for the given
|
161
|
-
def max(
|
162
|
-
single_value(:select => [
|
160
|
+
# Returns the maximum value for the given column.
|
161
|
+
def max(column)
|
162
|
+
single_value(:select => [column.MAX.AS(:v)])
|
163
163
|
end
|
164
164
|
|
165
|
-
# Returns the sum for the given
|
166
|
-
def sum(
|
167
|
-
single_value(:select => [
|
165
|
+
# Returns the sum for the given column.
|
166
|
+
def sum(column)
|
167
|
+
single_value(:select => [column.SUM.AS(:v)])
|
168
168
|
end
|
169
169
|
|
170
|
-
# Returns the average value for the given
|
171
|
-
def avg(
|
172
|
-
single_value(:select => [
|
170
|
+
# Returns the average value for the given column.
|
171
|
+
def avg(column)
|
172
|
+
single_value(:select => [column.AVG.AS(:v)])
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns a dataset grouped by the given column with count by group.
|
176
|
+
def group_and_count(column)
|
177
|
+
group(column).select(column, :count[column].AS(:count)).order(:count)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns a Range object made from the minimum and maximum values for the
|
181
|
+
# given column.
|
182
|
+
def range(column)
|
183
|
+
r = select(column.MIN.AS(:v1), column.MAX.AS(:v2)).first
|
184
|
+
r && (r[:v1]..r[:v2])
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns the interval between minimum and maximum values for the given
|
188
|
+
# column.
|
189
|
+
def interval(column)
|
190
|
+
r = select("(max(#{literal(column)}) - min(#{literal(column)})) AS v".lit).first
|
191
|
+
r && r[:v]
|
173
192
|
end
|
174
193
|
|
175
194
|
# Pretty prints the records in the dataset as plain-text table.
|
@@ -216,7 +235,7 @@ module Sequel
|
|
216
235
|
end
|
217
236
|
end
|
218
237
|
|
219
|
-
module QueryBlockCopy
|
238
|
+
module QueryBlockCopy #:nodoc:
|
220
239
|
def each(*args); raise SequelError, "#each cannot be invoked inside a query block."; end
|
221
240
|
def insert(*args); raise SequelError, "#insert cannot be invoked inside a query block."; end
|
222
241
|
def update(*args); raise SequelError, "#update cannot be invoked inside a query block."; end
|
@@ -244,7 +263,9 @@ module Sequel
|
|
244
263
|
end
|
245
264
|
|
246
265
|
MUTATION_RE = /^(.+)!$/.freeze
|
247
|
-
|
266
|
+
|
267
|
+
# Provides support for mutation methods (filter!, order!, etc.) and magic
|
268
|
+
# methods.
|
248
269
|
def method_missing(m, *args, &block)
|
249
270
|
if m.to_s =~ MUTATION_RE
|
250
271
|
m = $1.to_sym
|
@@ -253,10 +274,36 @@ module Sequel
|
|
253
274
|
super if copy.class != self.class
|
254
275
|
@opts.merge!(copy.opts)
|
255
276
|
self
|
277
|
+
elsif magic_method_missing(m)
|
278
|
+
send(m, *args)
|
256
279
|
else
|
257
|
-
|
280
|
+
super
|
258
281
|
end
|
259
282
|
end
|
283
|
+
|
284
|
+
MAGIC_METHODS = {
|
285
|
+
/^order_by_(.+)$/ => proc {|c| proc {order(c)}},
|
286
|
+
/^first_by_(.+)$/ => proc {|c| proc {order(c).first}},
|
287
|
+
/^last_by_(.+)$/ => proc {|c| proc {order(c).last}},
|
288
|
+
/^filter_by_(.+)$/ => proc {|c| proc {|v| filter(c => v)}},
|
289
|
+
/^all_by_(.+)$/ => proc {|c| proc {|v| filter(c => v).all}},
|
290
|
+
/^find_by_(.+)$/ => proc {|c| proc {|v| filter(c => v).first}},
|
291
|
+
/^group_by_(.+)$/ => proc {|c| proc {group(c)}},
|
292
|
+
/^count_by_(.+)$/ => proc {|c| proc {group_and_count(c)}}
|
293
|
+
}
|
294
|
+
|
295
|
+
# Checks if the given method name represents a magic method and
|
296
|
+
# defines it. Otherwise, nil is returned.
|
297
|
+
def magic_method_missing(m)
|
298
|
+
method_name = m.to_s
|
299
|
+
MAGIC_METHODS.each_pair do |r, p|
|
300
|
+
if method_name =~ r
|
301
|
+
impl = p[$1.to_sym]
|
302
|
+
return Dataset.class_def(m, &impl)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
nil
|
306
|
+
end
|
260
307
|
end
|
261
308
|
end
|
262
309
|
end
|
@@ -154,15 +154,15 @@ class Sequel::Dataset
|
|
154
154
|
end
|
155
155
|
end
|
156
156
|
|
157
|
-
def fcall_expr(e, b)
|
157
|
+
def fcall_expr(e, b) #:nodoc:
|
158
158
|
ext_expr(e, b)
|
159
159
|
end
|
160
160
|
|
161
|
-
def vcall_expr(e, b)
|
161
|
+
def vcall_expr(e, b) #:nodoc:
|
162
162
|
eval(e[1].to_s, b)
|
163
163
|
end
|
164
164
|
|
165
|
-
def iter_expr(e, b)
|
165
|
+
def iter_expr(e, b) #:nodoc:
|
166
166
|
if e[1] == [:fcall, :proc]
|
167
167
|
eval_expr(e[3], b) # inline proc
|
168
168
|
else
|
@@ -223,7 +223,7 @@ class Sequel::Dataset
|
|
223
223
|
end
|
224
224
|
end
|
225
225
|
|
226
|
-
def pt_expr(e, b)
|
226
|
+
def pt_expr(e, b) #:nodoc:
|
227
227
|
case e[0]
|
228
228
|
when :not # negation: !x, (x != y), (x !~ y)
|
229
229
|
if (e[1][0] == :lit) && (Symbol === e[1][1])
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -3,56 +3,56 @@ module Sequel
|
|
3
3
|
# The Dataset SQL module implements all the dataset methods concerned with
|
4
4
|
# generating SQL statements for retrieving and manipulating records.
|
5
5
|
module SQL
|
6
|
-
# Returns a valid SQL
|
6
|
+
# Returns a valid SQL column name as a string. Column names specified as
|
7
7
|
# symbols can include double underscores to denote a dot separator, e.g.
|
8
8
|
# :posts__id will be converted into posts.id.
|
9
|
-
def
|
10
|
-
case
|
9
|
+
def column_name(column)
|
10
|
+
case column
|
11
11
|
when Symbol, String:
|
12
|
-
|
12
|
+
quoted_column_name(column.to_column_name)
|
13
13
|
when Hash:
|
14
|
-
|
14
|
+
column.map {|f,a| "#{column_name(f)} AS #{column_name(a)}"}.join(COMMA_SEPARATOR)
|
15
15
|
else
|
16
|
-
|
16
|
+
column
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
# Adds quoting to
|
21
|
-
# be overriden in adapters in order to provide correct
|
20
|
+
# Adds quoting to column references. This method is just a stub and can
|
21
|
+
# be overriden in adapters in order to provide correct column quoting
|
22
22
|
# behavior.
|
23
|
-
def
|
23
|
+
def quoted_column_name(name)
|
24
24
|
name
|
25
25
|
end
|
26
26
|
|
27
27
|
ALIASED_REGEXP = /^(.*)\s(.*)$/.freeze
|
28
28
|
QUALIFIED_REGEXP = /^(.*)\.(.*)$/.freeze
|
29
29
|
|
30
|
-
# Returns a qualified
|
30
|
+
# Returns a qualified column name (including a table name) if the column
|
31
31
|
# name isn't already qualified.
|
32
|
-
def
|
33
|
-
|
34
|
-
if
|
35
|
-
#
|
36
|
-
|
32
|
+
def qualified_column_name(column, table)
|
33
|
+
column = column_name(column)
|
34
|
+
if column =~ QUALIFIED_REGEXP
|
35
|
+
# column is already qualified
|
36
|
+
column
|
37
37
|
else
|
38
38
|
# check if the table is aliased
|
39
39
|
if table =~ ALIASED_REGEXP
|
40
40
|
table = $2
|
41
41
|
end
|
42
|
-
"#{table}.#{
|
42
|
+
"#{table}.#{column}"
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
46
|
WILDCARD = '*'.freeze
|
47
47
|
COMMA_SEPARATOR = ", ".freeze
|
48
48
|
|
49
|
-
# Converts an array of
|
50
|
-
#
|
51
|
-
def
|
52
|
-
if
|
49
|
+
# Converts an array of column names into a comma seperated string of
|
50
|
+
# column names. If the array is empty, a wildcard (*) is returned.
|
51
|
+
def column_list(columns)
|
52
|
+
if columns.empty?
|
53
53
|
WILDCARD
|
54
54
|
else
|
55
|
-
|
55
|
+
columns.map do |i|
|
56
56
|
i.is_a?(Hash) ? i.map {|kv| "#{literal(kv[0])} AS #{kv[1]}"} : literal(i)
|
57
57
|
end.join(COMMA_SEPARATOR)
|
58
58
|
end
|
@@ -87,7 +87,7 @@ module Sequel
|
|
87
87
|
# Returns a literal representation of a value to be used as part
|
88
88
|
# of an SQL expression. The stock implementation supports literalization
|
89
89
|
# of String (with proper escaping to prevent SQL injections), numbers,
|
90
|
-
# Symbol (as
|
90
|
+
# Symbol (as column references), Array (as a list of literalized values),
|
91
91
|
# Time (as an SQL TIMESTAMP), Date (as an SQL DATE), Dataset (as a
|
92
92
|
# subquery) and nil (AS NULL).
|
93
93
|
#
|
@@ -105,7 +105,7 @@ module Sequel
|
|
105
105
|
when NilClass: NULL
|
106
106
|
when TrueClass: TRUE
|
107
107
|
when FalseClass: FALSE
|
108
|
-
when Symbol:
|
108
|
+
when Symbol: quoted_column_name(v.to_column_name)
|
109
109
|
when Array: v.empty? ? NULL : v.map {|i| literal(i)}.join(COMMA_SEPARATOR)
|
110
110
|
when Time: v.strftime(TIMESTAMP_FORMAT)
|
111
111
|
when Date: v.strftime(DATE_FORMAT)
|
@@ -146,9 +146,9 @@ module Sequel
|
|
146
146
|
clone_merge(:from => source)
|
147
147
|
end
|
148
148
|
|
149
|
-
# Returns a copy of the dataset with the selected
|
150
|
-
def select(*
|
151
|
-
clone_merge(:select =>
|
149
|
+
# Returns a copy of the dataset with the selected columns changed.
|
150
|
+
def select(*columns)
|
151
|
+
clone_merge(:select => columns)
|
152
152
|
end
|
153
153
|
|
154
154
|
# Returns a copy of the dataset with the distinct option.
|
@@ -172,7 +172,7 @@ module Sequel
|
|
172
172
|
|
173
173
|
DESC_ORDER_REGEXP = /(.*)\sDESC/i.freeze
|
174
174
|
|
175
|
-
# Inverts the given order by breaking it into a list of
|
175
|
+
# Inverts the given order by breaking it into a list of column references
|
176
176
|
# and inverting them.
|
177
177
|
#
|
178
178
|
# dataset.invert_order('id DESC') #=> "id"
|
@@ -190,9 +190,9 @@ module Sequel
|
|
190
190
|
end
|
191
191
|
|
192
192
|
# Returns a copy of the dataset with the results grouped by the value of
|
193
|
-
# the given
|
194
|
-
def group(*
|
195
|
-
clone_merge(:group =>
|
193
|
+
# the given columns
|
194
|
+
def group(*columns)
|
195
|
+
clone_merge(:group => columns)
|
196
196
|
end
|
197
197
|
|
198
198
|
alias_method :group_by, :group
|
@@ -330,8 +330,8 @@ module Sequel
|
|
330
330
|
|
331
331
|
join_conditions = {}
|
332
332
|
expr.each do |k, v|
|
333
|
-
k =
|
334
|
-
v =
|
333
|
+
k = qualified_column_name(k, table).intern if k.is_a?(Symbol)
|
334
|
+
v = qualified_column_name(v, @opts[:last_joined_table] || @opts[:from].first).intern if v.is_a?(Symbol)
|
335
335
|
join_conditions[k] = v
|
336
336
|
end
|
337
337
|
" #{join_type} #{table} ON #{expression_list(join_conditions)}"
|
@@ -359,7 +359,6 @@ module Sequel
|
|
359
359
|
# Returns an INNER joined dataset.
|
360
360
|
def inner_join(table, expr); join_table(:inner, table, expr); end
|
361
361
|
alias join inner_join
|
362
|
-
|
363
362
|
|
364
363
|
# Inserts multiple values. If a block is given it is invoked for each
|
365
364
|
# item in the given array before inserting it.
|
@@ -380,12 +379,12 @@ module Sequel
|
|
380
379
|
return sql
|
381
380
|
end
|
382
381
|
|
383
|
-
|
384
|
-
|
382
|
+
columns = opts[:select]
|
383
|
+
select_columns = columns ? column_list(columns) : WILDCARD
|
385
384
|
select_source = source_list(opts[:from])
|
386
385
|
sql = opts[:distinct] ? \
|
387
|
-
"SELECT DISTINCT #{
|
388
|
-
"SELECT #{
|
386
|
+
"SELECT DISTINCT #{select_columns} FROM #{select_source}" : \
|
387
|
+
"SELECT #{select_columns} FROM #{select_source}"
|
389
388
|
|
390
389
|
if join = opts[:join]
|
391
390
|
sql << join
|
@@ -396,11 +395,11 @@ module Sequel
|
|
396
395
|
end
|
397
396
|
|
398
397
|
if group = opts[:group]
|
399
|
-
sql << " GROUP BY #{
|
398
|
+
sql << " GROUP BY #{column_list(group)}"
|
400
399
|
end
|
401
400
|
|
402
401
|
if order = opts[:order]
|
403
|
-
sql << " ORDER BY #{
|
402
|
+
sql << " ORDER BY #{column_list(order)}"
|
404
403
|
end
|
405
404
|
|
406
405
|
if having = opts[:having]
|
@@ -430,7 +429,7 @@ module Sequel
|
|
430
429
|
alias sql select_sql
|
431
430
|
|
432
431
|
# Formats an INSERT statement using the given values. If a hash is given,
|
433
|
-
# the resulting statement includes
|
432
|
+
# the resulting statement includes column names. If no values are given,
|
434
433
|
# the resulting statement includes a DEFAULT VALUES clause.
|
435
434
|
#
|
436
435
|
# dataset.insert_sql() #=> 'INSERT INTO items DEFAULT VALUES'
|
@@ -461,7 +460,7 @@ module Sequel
|
|
461
460
|
"INSERT INTO #{@opts[:from]} DEFAULT VALUES;"
|
462
461
|
else
|
463
462
|
fl, vl = [], []
|
464
|
-
values.each {|k, v| fl <<
|
463
|
+
values.each {|k, v| fl << column_name(k); vl << literal(v)}
|
465
464
|
"INSERT INTO #{@opts[:from]} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)});"
|
466
465
|
end
|
467
466
|
when Dataset
|
@@ -489,7 +488,7 @@ module Sequel
|
|
489
488
|
values = values.to_hash
|
490
489
|
end
|
491
490
|
values = transform_save(values) if @transform
|
492
|
-
set_list = values.map {|k, v| "#{
|
491
|
+
set_list = values.map {|k, v| "#{column_name(k)} = #{literal(v)}"}.
|
493
492
|
join(COMMA_SEPARATOR)
|
494
493
|
sql = "UPDATE #{@opts[:from]} SET #{set_list}"
|
495
494
|
|
data/lib/sequel/db2.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
if !Object.const_defined?('Sequel')
|
2
|
+
require File.join(File.dirname(__FILE__), '../sequel')
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'db2/db2cli'
|
6
|
+
|
7
|
+
module Sequel
|
8
|
+
module DB2
|
9
|
+
class Database < Sequel::Database
|
10
|
+
set_adapter_scheme :db2
|
11
|
+
include DB2CLI
|
12
|
+
|
13
|
+
# AUTO_INCREMENT = 'IDENTITY(1,1)'.freeze
|
14
|
+
#
|
15
|
+
# def auto_increment_sql
|
16
|
+
# AUTO_INCREMENT
|
17
|
+
# end
|
18
|
+
|
19
|
+
def check_error(rc, msg)
|
20
|
+
case rc
|
21
|
+
when SQL_SUCCESS, SQL_SUCCESS_WITH_INFO: nil
|
22
|
+
else
|
23
|
+
raise SequelError, msg
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
rc, @@env = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE)
|
28
|
+
check_error(rc, "Could not allocate DB2 environment")
|
29
|
+
|
30
|
+
def connect
|
31
|
+
rc, dbc = SQLAllocHandle(SQL_HANDLE_DBC, @@env)
|
32
|
+
check_error(rc, "Could not allocate database connection")
|
33
|
+
|
34
|
+
rc = SQLConnect(dbc, @opts[:database], @opts[:user], @opts[:password])
|
35
|
+
check_error(rc, "Could not connect to database")
|
36
|
+
|
37
|
+
dbc
|
38
|
+
end
|
39
|
+
|
40
|
+
def disconnect
|
41
|
+
@pool.disconnect do |conn|
|
42
|
+
rc = SQLDisconnect(conn)
|
43
|
+
check_error(rc, "Could not disconnect from database")
|
44
|
+
|
45
|
+
rc = SQLFreeHandle(SQL_HANDLE_DBC, conn)
|
46
|
+
check_error(rc, "Could not free Database handle")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_connection
|
51
|
+
@pool.hold {|conn|}
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def dataset(opts = nil)
|
56
|
+
DB2::Dataset.new(self, opts)
|
57
|
+
end
|
58
|
+
|
59
|
+
def execute(sql, &block)
|
60
|
+
@logger.info(sql) if @logger
|
61
|
+
@pool.hold do |conn|
|
62
|
+
rc, sth = SQLAllocHandle(SQL_HANDLE_STMT, @handle)
|
63
|
+
check_error(rc, "Could not allocate statement")
|
64
|
+
|
65
|
+
begin
|
66
|
+
rc = SQLExecDirect(sth, sql)
|
67
|
+
check_error(rc, "Could not execute statement")
|
68
|
+
|
69
|
+
block[sth] if block
|
70
|
+
|
71
|
+
rc, rpc = SQLRowCount(sth)
|
72
|
+
check_error(rc, "Could not get RPC")
|
73
|
+
rpc
|
74
|
+
ensure
|
75
|
+
rc = SQLFreeHandle(SQL_HANDLE_STMT, sth)
|
76
|
+
check_error(rc, "Could not free statement")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
alias_method :do, :execute
|
81
|
+
end
|
82
|
+
|
83
|
+
class Dataset < Sequel::Dataset
|
84
|
+
def literal(v)
|
85
|
+
case v
|
86
|
+
when Time: literal(v.iso8601)
|
87
|
+
else
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def fetch_rows(sql, &block)
|
93
|
+
@db.synchronize do
|
94
|
+
@db.execute(sql) do |sth|
|
95
|
+
@column_info = get_column_info(sth)
|
96
|
+
@columns = @column_info.map {|c| c[:name]}
|
97
|
+
while (rc = SQLFetch(@handle)) != SQL_NO_DATA_FOUND
|
98
|
+
@db.check_error(rc, "Could not fetch row")
|
99
|
+
yield hash_row(sth)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
MAX_COL_SIZE = 256
|
107
|
+
|
108
|
+
def get_column_info(sth)
|
109
|
+
rc, column_count = SQLNumResultCols(sth)
|
110
|
+
@db.check_error(rc, "Could not get number of result columns")
|
111
|
+
|
112
|
+
(1..column_count).map do |i|
|
113
|
+
rc, name, buflen, datatype, size, digits, nullable = SQLDescribeCol(sth, i, MAX_COL_SIZE)
|
114
|
+
@b.check_error(rc, "Could not describe column")
|
115
|
+
|
116
|
+
{:name => name, :db2_type => datatype, :precision => size}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def hash_row(sth)
|
121
|
+
row = {}
|
122
|
+
@column_info.each_with_index do |c, i|
|
123
|
+
rc, v = SQLGetData(sth, i+1, c[:db2_type], c[:precision])
|
124
|
+
@db.check_error(rc, "Could not get data")
|
125
|
+
|
126
|
+
@row[c[:name]] = convert_type(v)
|
127
|
+
end
|
128
|
+
row
|
129
|
+
end
|
130
|
+
|
131
|
+
def convert_type(v)
|
132
|
+
case v
|
133
|
+
when DB2CLI::Date
|
134
|
+
DBI::Date.new(v.year, v.month, v.day)
|
135
|
+
when DB2CLI::Time
|
136
|
+
DBI::Time.new(v.hour, v.minute, v.second)
|
137
|
+
when DB2CLI::Timestamp
|
138
|
+
DBI::Timestamp.new(v.year, v.month, v.day,
|
139
|
+
v.hour, v.minute, v.second, v.fraction)
|
140
|
+
when DB2CLI::Null
|
141
|
+
nil
|
142
|
+
else
|
143
|
+
v
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def insert(*values)
|
148
|
+
@db.do insert_sql(*values)
|
149
|
+
end
|
150
|
+
|
151
|
+
def update(values, opts = nil)
|
152
|
+
@db.do update_sql(values, opts)
|
153
|
+
end
|
154
|
+
|
155
|
+
def delete(opts = nil)
|
156
|
+
@db.do delete_sql(opts)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|