sequel 0.3.1 → 0.3.2

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