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 CHANGED
@@ -1,3 +1,31 @@
1
+ === 0.3.2 (2007-11-01)
2
+
3
+ * Added #to_column_name as alias to #to_field_name, #column_title as alias to #field_title.
4
+
5
+ * Added Dataset#interval method for getting interval between minimum/maximum values for a column.
6
+
7
+ * Fixed Oracle::Database#execute (#84).
8
+
9
+ * Added group_and_count as general implementation for count_by_xxx.
10
+
11
+ * Added count_by magic method.
12
+
13
+ * Added Dataset#range method for getting the minimum/maximum values for a column.
14
+
15
+ * Fixed timestamp translation in SQLite adapter (#83).
16
+
17
+ * Experimental DB2 adapter.
18
+
19
+ * Added Dataset#set as alias to Dataset#update.
20
+
21
+ * Removed long deprecated expressions.rb code.
22
+
23
+ * Better documentation.
24
+
25
+ * Implemented Dataset magic methods: order_by_xxx, group_by_xxx, filter_by_xxx, all_by_xxx, first_by_xxx, last_by_xxx.
26
+
27
+ * Changed Model.create and Model.new to accept a block.
28
+
1
29
  === 0.3.1 (2007-10-30)
2
30
 
3
31
  * Typo fixes (#79).
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
6
6
  include FileUtils
7
7
 
8
8
  NAME = "sequel"
9
- VERS = "0.3.1"
9
+ VERS = "0.3.2"
10
10
  CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
11
11
  RDOC_OPTS = ['--quiet', '--title', "Sequel: Concise ORM for Ruby",
12
12
  "--opname", "index.html",
data/lib/sequel/ado.rb CHANGED
@@ -5,6 +5,15 @@ end
5
5
  require 'win32ole'
6
6
 
7
7
  module Sequel
8
+ # The ADO adapter provides connectivity to ADO databases in Windows. ADO
9
+ # databases can be opened using a URL with the ado schema:
10
+ #
11
+ # DB = Sequel.open('ado://mydb')
12
+ #
13
+ # or using the Sequel.ado method:
14
+ #
15
+ # DB = Sequel.ado('mydb')
16
+ #
8
17
  module ADO
9
18
  class Database < Sequel::Database
10
19
  set_adapter_scheme :ado
@@ -51,8 +60,7 @@ module Sequel
51
60
  @db.synchronize do
52
61
  s = @db.execute sql
53
62
 
54
- fields = s.Fields.extend(Enumerable)
55
- @columns = fields.map {|x| x.Name.to_sym}
63
+ @columns = s.Fields.extend(Enumerable).map {|x| x.Name.to_sym}
56
64
 
57
65
  s.moveFirst
58
66
  s.getRows.transpose.each {|r| yield hash_row(r)}
@@ -71,11 +79,10 @@ module Sequel
71
79
  @db.synchronize do
72
80
  s = @db.execute sql
73
81
 
74
- fields = s.Fields.extend(Enumerable)
75
- @columns = fields.map {|x| x.Name.to_sym}
82
+ @columns = s.Fields.extend(Enumerable).map {|x| x.Name.to_sym}
76
83
 
77
84
  s.moveFirst
78
- s.getRows.transpose.each {|r| r.fields = @columns; yield r}
85
+ s.getRows.transpose.each {|r| r.keys = @columns; yield r}
79
86
  end
80
87
  self
81
88
  end
@@ -1,24 +1,38 @@
1
- # Based on the arrayfields gem by Ara Howard
2
-
1
+ # ArrayKeys provide support for accessing array elements by keys. ArrayKeys are
2
+ # based on the arrayfields gem by Ara Howard, and can be used as substitutes
3
+ # for fetching records tuples as Ruby hashes.
4
+ #
5
+ # The main advantage offered by ArrayKeys over hashes is that the values are
6
+ # always ordered according to the column order in the query. Another purported
7
+ # advantage is that they reduce the memory footprint, but this has turned out
8
+ # to be a false claim.
3
9
  module ArrayKeys
10
+ # The KeySet module contains methods that extend an array of keys to return
11
+ # a key's position in the key set.
4
12
  module KeySet
13
+ # Returns the key's position in the key set. Provides indifferent access
14
+ # for symbols and strings.
5
15
  def key_pos(key)
6
16
  @key_indexes ||= inject({}) {|h, k| h[k.to_sym] = h.size; h}
7
17
  @key_indexes[key] || @key_indexes[key.to_sym] || @key_indexes[key.to_s]
8
18
  end
9
19
 
20
+ # Adds a key to the key set.
10
21
  def add_key(key)
11
22
  self << key
12
23
  @key_indexes[key] = @key_indexes.size
13
24
  end
14
25
 
26
+ # Removes a key from the key set by its index.
15
27
  def del_key(idx)
16
28
  delete_at(idx)
17
29
  @key_indexes = nil # reset key indexes
18
30
  end
19
31
  end
20
32
 
33
+ # The KeyAccess provides a large part of the Hash API for arrays with keys.
21
34
  module KeyAccess
35
+ # Returns a value referenced by an array index or a key.
22
36
  def [](idx, *args)
23
37
  if String === idx or Symbol === idx
24
38
  (idx = @keys.key_pos(idx)) ? super(idx, *args) : nil
@@ -27,6 +41,7 @@ module ArrayKeys
27
41
  end
28
42
  end
29
43
 
44
+ # Sets the value referenced by an array index or a key.
30
45
  def []=(idx,*args)
31
46
  if String === idx or Symbol === idx
32
47
  idx = @keys.key_pos(idx) || @keys.add_key(idx.to_sym)
@@ -34,14 +49,17 @@ module ArrayKeys
34
49
  super(idx, *args)
35
50
  end
36
51
 
52
+ # Stores a value by index or key.
37
53
  def store(k, v); self[k] = v; end
38
54
 
55
+ # Slices the array, and returns an array with its keys sliced accordingly.
39
56
  def slice(*args)
40
57
  s = super(*args)
41
58
  s.keys = @keys.slice(*args)
42
59
  s
43
60
  end
44
61
 
62
+ # Converts the array into a hash.
45
63
  def to_hash
46
64
  h = {}
47
65
  each_with_index {|v, i| h[@keys[i].to_sym] = v}
@@ -49,43 +67,60 @@ module ArrayKeys
49
67
  end
50
68
  alias_method :to_h, :to_hash
51
69
 
70
+ # Iterates over each key-value pair in the array.
52
71
  def each_pair
53
72
  each_with_index {|v, i| yield @keys[i], v}
54
73
  end
55
74
 
75
+ # Iterates over the array's associated keys.
56
76
  def each_key(&block)
57
77
  @keys.each(&block)
58
78
  end
59
79
 
80
+ # Iterates over the array's values.
60
81
  def each_value(&block)
61
82
  each(&block)
62
83
  end
63
84
 
85
+ # Deletes a value by its key.
64
86
  def delete(key, *args)
65
87
  if (idx = @keys.key_pos(key))
66
88
  delete_at(idx)
67
89
  end
68
90
  end
69
91
 
92
+ # Deletes a value by its index.
70
93
  def delete_at(idx)
71
94
  super(idx)
72
95
  @keys = @keys.clone
73
96
  @keys.del_key(idx)
74
97
  end
75
98
 
99
+ # Returns true if the array's key set contains the given key.
76
100
  def include?(k)
77
101
  @keys.include?(k) || @keys.include?(k.to_sym) || @keys.include?(k.to_s)
78
102
  end
79
103
 
104
+ # Returns true if the array's key set contains the given key.
80
105
  def has_key?(k)
81
106
  @keys.include?(k) || @keys.include?(k.to_sym) || @keys.include?(k.to_s)
82
107
  end
83
108
  alias_method :member?, :has_key?
84
109
  alias_method :key?, :has_key?
85
110
 
111
+ # Returns true if the array contains the given value.
86
112
  def has_value?(k); orig_include?(k); end
87
113
  alias_method :value?, :has_value?
88
114
 
115
+ # Fetches a value by its key and optionally passes it through the given
116
+ # block:
117
+ #
118
+ # row.fetch(:name) {|v| v.to_sym}
119
+ #
120
+ # You can also give a default value
121
+ #
122
+ # row.fetch(:name, 'untitled')
123
+ #
89
124
  def fetch(k, *args, &block)
90
125
  if idx = @keys.key_pos(k)
91
126
  v = at idx
@@ -95,26 +130,35 @@ module ArrayKeys
95
130
  block ? block[v] : v
96
131
  end
97
132
 
133
+ # Returns self.
98
134
  def values
99
135
  self
100
136
  end
101
137
 
138
+ # Creates a copy of self with the same key set.
102
139
  def dup
103
140
  copy = super
104
141
  copy.keys = @keys
105
142
  copy
106
143
  end
107
144
 
145
+ # Creates a copy of self with a copy of the key set.
108
146
  def clone
109
147
  copy = super
110
148
  copy.keys = @keys.clone
111
149
  copy
112
150
  end
113
151
 
152
+ # Returns an array merged from self and the given array.
114
153
  def merge(values, &block)
115
154
  clone.merge!(values, &block)
116
155
  end
117
156
 
157
+ # Merges the given array with self, optionally passing the values from self
158
+ # through the given block:
159
+ #
160
+ # row.merge!(new_values) {|k, old, new| (k == :name) ? old : new}
161
+ #
118
162
  def merge!(values, &block)
119
163
  values.each_pair do |k, v|
120
164
  self[k] = (has_key?(k) && block) ? block[k, self[k], v] : v
@@ -125,8 +169,12 @@ module ArrayKeys
125
169
  alias_method :update!, :merge!
126
170
  end
127
171
 
172
+ # The ArrayExtensions module provides extensions for the Array class.
128
173
  module ArrayExtensions
129
174
  attr_reader :keys
175
+
176
+ # Sets the key set for the array. Once a key set has been set for an array,
177
+ # it is extended with the KeyAccess API
130
178
  def keys=(keys)
131
179
  extend ArrayKeys::KeyAccess if keys
132
180
  @keys = keys.frozen? ? keys.dup : keys
@@ -139,11 +187,17 @@ module ArrayKeys
139
187
  alias_method :fields=, :keys=
140
188
  end
141
189
 
190
+ # The DatasetExtensions module provides extensions that modify
191
+ # a dataset to return Array tuples instead of Hash tuples.
142
192
  module DatasetExtensions
193
+ # Fetches a dataset's records, converting each tuple into an array with
194
+ # keys.
143
195
  def array_tuples_each(opts = nil, &block)
144
196
  fetch_rows(select_sql(opts)) {|h| block[Array.from_hash(h)]}
145
197
  end
146
198
 
199
+ # Provides the corresponding behavior to Sequel::Dataset#update_each_method,
200
+ # using array tuples.
147
201
  def array_tuples_update_each_method
148
202
  # warning: ugly code generation ahead
149
203
  if @row_proc && @transform
@@ -183,11 +237,13 @@ module ArrayKeys
183
237
  end
184
238
  end
185
239
 
240
+ # Array extensions.
186
241
  class Array
187
242
  alias_method :orig_include?, :include?
188
243
 
189
244
  include ArrayKeys::ArrayExtensions
190
245
 
246
+ # Converts a hash into an array with keys.
191
247
  def self.from_hash(h)
192
248
  a = []; a.keys = []
193
249
  a.merge!(h)
@@ -195,6 +251,8 @@ class Array
195
251
  end
196
252
 
197
253
  module Sequel
254
+ # Modifies all dataset classes to fetch records as arrays with keys. By
255
+ # default records are fetched as hashes.
198
256
  def self.use_array_tuples
199
257
  Dataset.dataset_classes.each do |c|
200
258
  c.class_eval do
@@ -212,6 +270,7 @@ module Sequel
212
270
  end
213
271
  end
214
272
 
273
+ # Modifies all dataset classes to fetch records as hashes.
215
274
  def self.use_hash_tuples
216
275
  Dataset.dataset_classes.each do |c|
217
276
  c.class_eval do
@@ -68,6 +68,10 @@ module Sequel
68
68
  raise e.is_a?(StandardError) ? e : e.message
69
69
  end
70
70
 
71
+ # Removes all connection currently available, optionally yielding each
72
+ # connection to the given block. This method has the effect of
73
+ # disconnecting from the database. Once a connection is requested using
74
+ # #hold, the connection pool creates new connections to the database.
71
75
  def disconnect(&block)
72
76
  @mutex.synchronize do
73
77
  @available_connections.each {|c| block[c]} if block
@@ -138,6 +142,8 @@ module Sequel
138
142
  raise e.is_a?(StandardError) ? e : e.message
139
143
  end
140
144
 
145
+ # Disconnects from the database. Once a connection is requested using
146
+ # #hold, the connection is reestablished.
141
147
  def disconnect(&block)
142
148
  block[@conn] if block && @conn
143
149
  @conn = nil
@@ -59,23 +59,24 @@ class String
59
59
  Time.parse(self)
60
60
  end
61
61
 
62
- # Converts a string into a field name.
62
+ # Converts a string into a column name.
63
63
  def to_field_name
64
64
  self
65
65
  end
66
+ alias_method :to_column_name, :to_field_name
66
67
  end
67
68
 
68
- # Methods to format field names and associated constructs. This module is
69
+ # Methods to format column names and associated constructs. This module is
69
70
  # included in String and Symbol.
70
71
  module FieldCompositionMethods
71
72
  # Constructs a DESC clause for use in an ORDER BY clause.
72
73
  def DESC
73
- "#{to_field_name} DESC".lit
74
+ "#{to_column_name} DESC".lit
74
75
  end
75
76
 
76
- # Constructs an AS clause for field aliasing.
77
+ # Constructs an AS clause for column aliasing.
77
78
  def AS(target)
78
- "#{to_field_name} AS #{target}".lit
79
+ "#{to_column_name} AS #{target}".lit
79
80
  end
80
81
 
81
82
  # Constructs a qualified wildcard (*) clause.
@@ -86,17 +87,19 @@ module FieldCompositionMethods
86
87
  FIELD_TITLE_RE1 = /^(.*)\sAS\s(.+)$/i.freeze
87
88
  FIELD_TITLE_RE2 = /^([^\.]+)\.([^\.]+)$/.freeze
88
89
 
89
- # Returns the field name. If the field name is aliased, the alias is
90
+ # Returns the column name. If the column name is aliased, the alias is
90
91
  # returned.
91
92
  def field_title
92
- case s = to_field_name
93
+ case s = to_column_name
93
94
  when FIELD_TITLE_RE1, FIELD_TITLE_RE2: $2
94
95
  else
95
96
  s
96
97
  end
97
98
  end
99
+ alias_method :column_title, :field_title
98
100
  end
99
101
 
102
+ # String extensions
100
103
  class String
101
104
  include FieldCompositionMethods
102
105
  end
@@ -110,14 +113,14 @@ class Symbol
110
113
  FIELD_REF_RE2 = /^(\w+)___(\w+)$/.freeze
111
114
  FIELD_REF_RE3 = /^(\w+)__(\w+)$/.freeze
112
115
 
113
- # Converts a symbol into a field name. This method supports underscore
116
+ # Converts a symbol into a column name. This method supports underscore
114
117
  # notation in order to express qualified (two underscores) and aliased
115
- # (three underscores) fields:
118
+ # (three underscores) columns:
116
119
  #
117
- # :abc.to_field_name #=> "abc"
118
- # :abc___a.to_field_name #=> "abc AS a"
119
- # :items__abc.to_field_name #=> "items.abc"
120
- # :items__abc___a.to_field_name #=> "items.abc AS a"
120
+ # :abc.to_column_name #=> "abc"
121
+ # :abc___a.to_column_name #=> "abc AS a"
122
+ # :items__abc.to_column_name #=> "items.abc"
123
+ # :items__abc___a.to_column_name #=> "items.abc AS a"
121
124
  #
122
125
  def to_field_name
123
126
  s = to_s
@@ -129,12 +132,13 @@ class Symbol
129
132
  s
130
133
  end
131
134
  end
135
+ alias_method :to_column_name, :to_field_name
132
136
 
133
137
  # Converts missing method calls into functions on columns, if the
134
138
  # method name is made of all upper case letters.
135
139
  def method_missing(sym)
136
140
  ((s = sym.to_s) =~ /^([A-Z]+)$/) ? \
137
- "#{s.downcase}(#{to_field_name})".lit : super
141
+ "#{s.downcase}(#{to_column_name})".lit : super
138
142
  end
139
143
 
140
144
  # Formats an SQL function with optional parameters
@@ -29,22 +29,28 @@ module Sequel
29
29
  @logger = opts[:logger]
30
30
  end
31
31
 
32
+ # Connects to the database. This method should be overriden by descendants.
32
33
  def connect
33
34
  raise NotImplementedError, "#connect should be overriden by adapters"
34
35
  end
35
36
 
37
+ # Disconnects from the database. This method should be overriden by
38
+ # descendants.
36
39
  def disconnect
37
40
  raise NotImplementedError, "#disconnect should be overriden by adapters"
38
41
  end
39
42
 
43
+ # Returns true if the database is using a multi-threaded connection pool.
40
44
  def multi_threaded?
41
45
  !@single_threaded
42
46
  end
43
47
 
48
+ # Returns true if the database is using a single-threaded connection pool.
44
49
  def single_threaded?
45
50
  @single_threaded
46
51
  end
47
52
 
53
+ # Returns the URI identifying the database.
48
54
  def uri
49
55
  uri = URI::Generic.new(
50
56
  self.class.adapter_scheme.to_s,
@@ -68,6 +74,24 @@ module Sequel
68
74
  ds = Sequel::Dataset.new(self)
69
75
  end
70
76
 
77
+ # Fetches records for an arbitrary SQL statement. If a block is given,
78
+ # it is used to iterate over the records:
79
+ #
80
+ # DB.fetch('SELECT * FROM items') {|r| p r}
81
+ #
82
+ # If a block is not given, the method returns a dataset instance:
83
+ #
84
+ # DB.fetch('SELECT * FROM items').print
85
+ #
86
+ # Fetch can also perform parameterized queries for protection against SQL
87
+ # injection:
88
+ #
89
+ # DB.fetch('SELECT * FROM items WHERE name = ?', my_name).print
90
+ #
91
+ # A short-hand form for Database#fetch is Database#[]:
92
+ #
93
+ # DB['SELECT * FROM items'].each {|r| p r}
94
+ #
71
95
  def fetch(sql, *args, &block)
72
96
  ds = dataset
73
97
  sql = sql.gsub('?') {|m| ds.literal(args.shift)}
@@ -97,10 +121,22 @@ module Sequel
97
121
  # Returns a new dataset with the select method invoked.
98
122
  def select(*args); dataset.select(*args); end
99
123
 
124
+ # Returns a dataset from the database. If the first argument is a string,
125
+ # the method acts as an alias for Database#fetch, returning a dataset for
126
+ # arbitrary SQL:
127
+ #
128
+ # DB['SELECT * FROM items WHERE name = ?', my_name].print
129
+ #
130
+ # Otherwise, the dataset returned has its from option set to the given
131
+ # arguments:
132
+ #
133
+ # DB[:items].sql #=> "SELECT * FROM items"
134
+ #
100
135
  def [](*args)
101
136
  (String === args.first) ? fetch(*args) : from(*args)
102
137
  end
103
-
138
+
139
+ # Raises a NotImplementedError. This method is overriden in descendants.
104
140
  def execute(sql)
105
141
  raise NotImplementedError
106
142
  end
@@ -142,13 +178,12 @@ module Sequel
142
178
  create_table_sql_list(*g.create_info).each {|sta| execute(sta)}
143
179
  end
144
180
 
145
- # Drops a table.
181
+ # Drops one or more tables corresponding to the given table names.
146
182
  def drop_table(*names)
147
183
  execute(names.map {|n| drop_table_sql(n)}.join)
148
184
  end
149
185
 
150
- # Performs a brute-force check for the existance of a table. This method is
151
- # usually overriden in descendants.
186
+ # Returns true if the given table exists.
152
187
  def table_exists?(name)
153
188
  if respond_to?(:tables)
154
189
  tables.include?(name.to_sym)
@@ -257,6 +292,7 @@ module Sequel
257
292
 
258
293
  @@single_threaded = false
259
294
 
295
+ # Sets the default single_threaded mode for new databases.
260
296
  def self.single_threaded=(value)
261
297
  @@single_threaded = value
262
298
  end