sequel 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 field values for each record in the dataset (if a field name is
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(field_name = nil, &block)
83
- if field_name
84
- super() {|r| r[field_name]}
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 field.
156
- def min(field)
157
- single_value(:select => [field.MIN.AS(:v)])
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 field.
161
- def max(field)
162
- single_value(:select => [field.MAX.AS(:v)])
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 field.
166
- def sum(field)
167
- single_value(:select => [field.SUM.AS(:v)])
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 field.
171
- def avg(field)
172
- single_value(:select => [field.AVG.AS(:v)])
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
- super
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])
@@ -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 fieldname as a string. Field names specified as
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 field_name(field)
10
- case field
9
+ def column_name(column)
10
+ case column
11
11
  when Symbol, String:
12
- quoted_field_name(field.to_field_name)
12
+ quoted_column_name(column.to_column_name)
13
13
  when Hash:
14
- field.map {|f,a| "#{field_name(f)} AS #{field_name(a)}"}.join(COMMA_SEPARATOR)
14
+ column.map {|f,a| "#{column_name(f)} AS #{column_name(a)}"}.join(COMMA_SEPARATOR)
15
15
  else
16
- field
16
+ column
17
17
  end
18
18
  end
19
19
 
20
- # Adds quoting to field references. This method is just a stub and can
21
- # be overriden in adapters in order to provide correct field quoting
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 quoted_field_name(name)
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 field name (including a table name) if the field
30
+ # Returns a qualified column name (including a table name) if the column
31
31
  # name isn't already qualified.
32
- def qualified_field_name(field, table)
33
- field = field_name(field)
34
- if field =~ QUALIFIED_REGEXP
35
- # field is already qualified
36
- field
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}.#{field}"
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 field names into a comma seperated string of
50
- # field names. If the array is empty, a wildcard (*) is returned.
51
- def field_list(fields)
52
- if fields.empty?
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
- fields.map do |i|
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 field references), Array (as a list of literalized values),
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: quoted_field_name(v.to_field_name)
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 fields changed.
150
- def select(*fields)
151
- clone_merge(:select => fields)
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 field references
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 fields
194
- def group(*fields)
195
- clone_merge(:group => fields)
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 = qualified_field_name(k, table).intern if k.is_a?(Symbol)
334
- v = qualified_field_name(v, @opts[:last_joined_table] || @opts[:from].first).intern if v.is_a?(Symbol)
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
- fields = opts[:select]
384
- select_fields = fields ? field_list(fields) : WILDCARD
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 #{select_fields} FROM #{select_source}" : \
388
- "SELECT #{select_fields} FROM #{select_source}"
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 #{field_list(group)}"
398
+ sql << " GROUP BY #{column_list(group)}"
400
399
  end
401
400
 
402
401
  if order = opts[:order]
403
- sql << " ORDER BY #{field_list(order)}"
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 field names. If no values are given,
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 << field_name(k); vl << literal(v)}
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| "#{field_name(k)} = #{literal(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