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