sequel 0.2.1.1 → 0.3.0

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,79 @@
1
+ === 0.3 (2007-10-20)
2
+
3
+ * Added stock transforms to Dataset#transform. Refactored Model.serialize.
4
+
5
+ * Added Database#logger= method for setting the database logger object.
6
+
7
+ * Fixed Model.[] to act as shortcut to Model.find when a hash is given (#71).
8
+
9
+ * Added support for old and new decimal types in MySQL adapter, and updated MYSQL_TYPES with MySQL 5.0 constants (#72).
10
+
11
+ * Implemented Database#disconnect method for all adapters.
12
+
13
+ * Fixed small bug in ArrayKeys module.
14
+
15
+ * Implemented model caching by primary key.
16
+
17
+ * Separated Model.find and Model.[] functionality. Model.find takes a filter. Model.[] is strictly for finding by primary keys.
18
+
19
+ * Enhanced Dataset#first to accept a filter block. Model#find can also now accept a filter block.
20
+
21
+ * Changed Database#[] to act as shortcut to #fetch if a string is given.
22
+
23
+ * Renamed Database#each to #fetch. If no block is given, the method returns an enumerator.
24
+
25
+ * Changed Dataset#join methods to correctly literalize values in join conditions (#70).
26
+
27
+ * Fixed #filter with ranges to correctly literalize field names (#69).
28
+
29
+ * Implemented Database#each method for quickly retrieving records with arbitrary SQL (thanks Aman Gupta).
30
+
31
+ * Fixed bug in postgres adapter where a LiteralString would be literalized as a regular String.
32
+
33
+ * Fixed SQLite insert with subquery (#68).
34
+
35
+ * Reverted back to hashes as default mode. Added Sequel.use_array_tuples and Sequel.use_hash_tuples methods.
36
+
37
+ * Fixed problem with arrays with keys when using #delete.
38
+
39
+ * Implemented ArrayKeys as substitute for ArrayFields.
40
+
41
+ * Added Dataset#each_hash method.
42
+
43
+ * Rewrote SQLite::Database#transaction to use sqlite3-ruby library implementation of transactions.
44
+
45
+ * Fixed Model.destroy_all to work correctly in cases where no before_destroy hook is defined and an after_destroy hook is defined.
46
+
47
+ * Restored Model.has_hooks? implementation.
48
+
49
+ * Changed Database#<< to strip comments and whitespace only when an array is given.
50
+
51
+ * Changed Schema::Generator#primary_key to accept calls with the type argument omitted.
52
+
53
+ * Hooks can now be prepended or appended by choice.
54
+
55
+ * Changed Model.subset to define filter method on the underlying dataset instead of the model class.
56
+
57
+ * Fixed Dataset#transform to work with array fields.
58
+
59
+ * Added Dataset#to_csv method.
60
+
61
+ * PrettyTable can now extract column names from arrayfields.
62
+
63
+ * Converted ado, dbi, odbc adapters to use arrayfields instead of hashes.
64
+
65
+ * Fixed composite key support.
66
+
67
+ * Fixed Dataset#insert_sql, update_sql to support array fields.
68
+
69
+ * Converted sqlite, mysql, postgres adapters to use arrayfields instead of hashes.
70
+
71
+ * Extended Dataset#from to auto alias sub-queries.
72
+
73
+ * Extended Dataset#from to accept hash for aliasing tables.
74
+
75
+ * Added before_update, after_update hooks.
76
+
1
77
  === 0.2.1.1 (2007-10-07)
2
78
 
3
79
  * Added Date literalization to sqlite adapter (#60).
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
6
6
  include FileUtils
7
7
 
8
8
  NAME = "sequel"
9
- VERS = "0.2.1.1"
9
+ VERS = "0.3.0"
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",
@@ -1,7 +1,7 @@
1
1
  require 'metaid'
2
2
 
3
3
  files = %w[
4
- core_ext error connection_pool pretty_table
4
+ core_ext array_keys error connection_pool pretty_table
5
5
  dataset migration model schema database
6
6
  ]
7
7
  dir = File.join(File.dirname(__FILE__), 'sequel')
@@ -21,6 +21,10 @@ module Sequel
21
21
  handle.Open(dbname)
22
22
  handle
23
23
  end
24
+
25
+ def disconnect
26
+ # how do we disconnect? couldn't find anything in the docs
27
+ end
24
28
 
25
29
  def dataset(opts = nil)
26
30
  ADO::Dataset.new(self, opts)
@@ -63,6 +67,19 @@ module Sequel
63
67
  end
64
68
  end
65
69
 
70
+ def array_tuples_fetch_rows(sql, &block)
71
+ @db.synchronize do
72
+ s = @db.execute sql
73
+
74
+ fields = s.Fields.extend(Enumerable)
75
+ @columns = fields.map {|x| x.Name.to_sym}
76
+
77
+ s.moveFirst
78
+ s.getRows.transpose.each {|r| r.fields = @columns; yield r}
79
+ end
80
+ self
81
+ end
82
+
66
83
  def insert(*values)
67
84
  @db.do insert_sql(*values)
68
85
  end
@@ -0,0 +1,233 @@
1
+ # Based on the arrayfields gem by Ara Howard
2
+
3
+ module ArrayKeys
4
+ module KeySet
5
+ def key_pos(key)
6
+ @key_indexes ||= inject({}) {|h, k| h[k.to_sym] = h.size; h}
7
+ @key_indexes[key] || @key_indexes[key.to_sym] || @key_indexes[key.to_s]
8
+ end
9
+
10
+ def add_key(key)
11
+ self << key
12
+ @key_indexes[key] = @key_indexes.size
13
+ end
14
+
15
+ def del_key(idx)
16
+ delete_at(idx)
17
+ @key_indexes = nil # reset key indexes
18
+ end
19
+ end
20
+
21
+ module KeyAccess
22
+ def [](idx, *args)
23
+ if String === idx or Symbol === idx
24
+ (idx = @keys.key_pos(idx)) ? super(idx, *args) : nil
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ def []=(idx,*args)
31
+ if String === idx or Symbol === idx
32
+ idx = @keys.key_pos(idx) || @keys.add_key(idx.to_sym)
33
+ end
34
+ super(idx, *args)
35
+ end
36
+
37
+ def store(k, v); self[k] = v; end
38
+
39
+ def slice(*args)
40
+ s = super(*args)
41
+ s.keys = @keys.slice(*args)
42
+ s
43
+ end
44
+
45
+ def to_hash
46
+ h = {}
47
+ each_with_index {|v, i| h[@keys[i].to_sym] = v}
48
+ h
49
+ end
50
+ alias_method :to_h, :to_hash
51
+
52
+ def each_pair
53
+ each_with_index {|v, i| yield @keys[i], v}
54
+ end
55
+
56
+ def each_key(&block)
57
+ @keys.each(&block)
58
+ end
59
+
60
+ def each_value(&block)
61
+ each(&block)
62
+ end
63
+
64
+ def delete(key, *args)
65
+ if (idx = @keys.key_pos(key))
66
+ delete_at(idx)
67
+ end
68
+ end
69
+
70
+ def delete_at(idx)
71
+ super(idx)
72
+ @keys = @keys.clone
73
+ @keys.del_key(idx)
74
+ end
75
+
76
+ def include?(k)
77
+ @keys.include?(k) || @keys.include?(k.to_sym) || @keys.include?(k.to_s)
78
+ end
79
+
80
+ def has_key?(k)
81
+ @keys.include?(k) || @keys.include?(k.to_sym) || @keys.include?(k.to_s)
82
+ end
83
+ alias_method :member?, :has_key?
84
+ alias_method :key?, :has_key?
85
+
86
+ def has_value?(k); orig_include?(k); end
87
+ alias_method :value?, :has_value?
88
+
89
+ def fetch(k, *args, &block)
90
+ if idx = @keys.key_pos(k)
91
+ v = at idx
92
+ else
93
+ !args.empty? ? (v = args.first) : (raise IndexError, "key not found")
94
+ end
95
+ block ? block[v] : v
96
+ end
97
+
98
+ def values
99
+ self
100
+ end
101
+
102
+ def dup
103
+ copy = super
104
+ copy.keys = @keys
105
+ copy
106
+ end
107
+
108
+ def clone
109
+ copy = super
110
+ copy.keys = @keys.clone
111
+ copy
112
+ end
113
+
114
+ def merge(values, &block)
115
+ clone.merge!(values, &block)
116
+ end
117
+
118
+ def merge!(values, &block)
119
+ values.each_pair do |k, v|
120
+ self[k] = (has_key?(k) && block) ? block[k, self[k], v] : v
121
+ end
122
+ self
123
+ end
124
+ alias_method :update, :merge!
125
+ alias_method :update!, :merge!
126
+ end
127
+
128
+ module ArrayExtensions
129
+ attr_reader :keys
130
+ def keys=(keys)
131
+ extend ArrayKeys::KeyAccess if keys
132
+ @keys = keys.frozen? ? keys.dup : keys
133
+ unless @keys.respond_to?(:key_pos)
134
+ @keys.extend(ArrayKeys::KeySet)
135
+ end
136
+ end
137
+
138
+ alias_method :fields, :keys
139
+ alias_method :fields=, :keys=
140
+ end
141
+
142
+ module DatasetExtensions
143
+ def array_tuples_each(opts = nil, &block)
144
+ fetch_rows(select_sql(opts)) {|h| block[Array.from_hash(h)]}
145
+ end
146
+
147
+ def array_tuples_update_each_method
148
+ # warning: ugly code generation ahead
149
+ if @row_proc && @transform
150
+ class << self
151
+ def each(opts = nil, &block)
152
+ if opts && opts[:naked]
153
+ fetch_rows(select_sql(opts)) {|r| block[transform_load(Array.from_hash(r))]}
154
+ else
155
+ fetch_rows(select_sql(opts)) {|r| block[@row_proc[transform_load(Array.from_hash(r))]]}
156
+ end
157
+ end
158
+ end
159
+ elsif @row_proc
160
+ class << self
161
+ def each(opts = nil, &block)
162
+ if opts && opts[:naked]
163
+ fetch_rows(select_sql(opts)) {|r| block[Array.from_hash(r)]}
164
+ else
165
+ fetch_rows(select_sql(opts)) {|r| block[@row_proc[Array.from_hash(r)]]}
166
+ end
167
+ end
168
+ end
169
+ elsif @transform
170
+ class << self
171
+ def each(opts = nil, &block)
172
+ fetch_rows(select_sql(opts)) {|r| block[transform_load(Array.from_hash(r))]}
173
+ end
174
+ end
175
+ else
176
+ class << self
177
+ def each(opts = nil, &block)
178
+ fetch_rows(select_sql(opts)) {|r| block[Array.from_hash(r)]}
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+
186
+ class Array
187
+ alias_method :orig_include?, :include?
188
+
189
+ include ArrayKeys::ArrayExtensions
190
+
191
+ def self.from_hash(h)
192
+ a = []; a.keys = []
193
+ a.merge!(h)
194
+ end
195
+ end
196
+
197
+ module Sequel
198
+ def self.use_array_tuples
199
+ Dataset.dataset_classes.each do |c|
200
+ c.class_eval do
201
+ if method_defined?(:array_tuples_fetch_rows)
202
+ alias_method :hash_tuples_fetch_rows, :fetch_rows
203
+ alias_method :fetch_rows, :array_tuples_fetch_rows
204
+ else
205
+ alias_method :orig_each, :each
206
+ alias_method :orig_update_each_method, :update_each_method
207
+ include ArrayKeys::DatasetExtensions
208
+ alias_method :each, :array_tuples_each
209
+ alias_method :update_each_method, :array_tuples_update_each_method
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ def self.use_hash_tuples
216
+ Dataset.dataset_classes.each do |c|
217
+ c.class_eval do
218
+ if method_defined?(:hash_tuples_fetch_rows)
219
+ alias_method :fetch_rows, :hash_tuples_fetch_rows
220
+ else
221
+ if method_defined?(:orig_each)
222
+ alias_method :each, :orig_each
223
+ undef_method :orig_each
224
+ end
225
+ if method_defined?(:orig_update_each_method)
226
+ alias_method :update_each_method, :orig_update_each_method
227
+ undef_method :orig_update_each_method
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
@@ -68,6 +68,14 @@ module Sequel
68
68
  raise e.is_a?(StandardError) ? e : e.message
69
69
  end
70
70
 
71
+ def disconnect(&block)
72
+ @mutex.synchronize do
73
+ @available_connections.each {|c| block[c]} if block
74
+ @available_connections = []
75
+ @created_count = @allocated.size
76
+ end
77
+ end
78
+
71
79
  private
72
80
  # Returns the connection owned by the supplied thread, if any.
73
81
  def owned_connection(thread)
@@ -112,6 +120,7 @@ module Sequel
112
120
  # in single-threaded applications. ConnectionPool imposes a substantial
113
121
  # performance penalty, so SingleThreadedPool is used to gain some speed.
114
122
  class SingleThreadedPool
123
+ attr_reader :conn
115
124
  attr_writer :connection_proc
116
125
 
117
126
  # Initializes the instance with the supplied block as the connection_proc.
@@ -128,5 +137,10 @@ module Sequel
128
137
  # if the error is not a StandardError it is converted into RuntimeError.
129
138
  raise e.is_a?(StandardError) ? e : e.message
130
139
  end
140
+
141
+ def disconnect(&block)
142
+ block[@conn] if block && @conn
143
+ @conn = nil
144
+ end
131
145
  end
132
146
  end
@@ -70,17 +70,17 @@ end
70
70
  module FieldCompositionMethods
71
71
  # Constructs a DESC clause for use in an ORDER BY clause.
72
72
  def DESC
73
- "#{to_field_name} DESC"
73
+ "#{to_field_name} DESC".lit
74
74
  end
75
75
 
76
76
  # Constructs an AS clause for field aliasing.
77
77
  def AS(target)
78
- "#{to_field_name} AS #{target}"
78
+ "#{to_field_name} AS #{target}".lit
79
79
  end
80
80
 
81
81
  # Constructs a qualified wildcard (*) clause.
82
82
  def ALL
83
- "#{to_s}.*"
83
+ "#{to_s}.*".lit
84
84
  end
85
85
 
86
86
  FIELD_TITLE_RE1 = /^(.*)\sAS\s(.+)$/i.freeze
@@ -5,7 +5,8 @@ module Sequel
5
5
  # The Database class is meant to be subclassed by database adapters in order
6
6
  # to provide the functionality needed for executing queries.
7
7
  class Database
8
- attr_reader :opts, :pool, :logger
8
+ attr_reader :opts, :pool
9
+ attr_accessor :logger
9
10
 
10
11
  # Constructs a new instance of a database connection with the specified
11
12
  # options hash.
@@ -32,6 +33,10 @@ module Sequel
32
33
  raise NotImplementedError, "#connect should be overriden by adapters"
33
34
  end
34
35
 
36
+ def disconnect
37
+ raise NotImplementedError, "#disconnect should be overriden by adapters"
38
+ end
39
+
35
40
  def multi_threaded?
36
41
  !@single_threaded
37
42
  end
@@ -63,6 +68,17 @@ module Sequel
63
68
  ds = Sequel::Dataset.new(self)
64
69
  end
65
70
 
71
+ def fetch(sql, *args, &block)
72
+ ds = dataset
73
+ sql = sql.gsub('?') {|m| ds.literal(args.shift)}
74
+ if block
75
+ ds.fetch_rows(sql, &block)
76
+ else
77
+ Enumerable::Enumerator.new(ds, :fetch_rows, sql)
78
+ end
79
+ end
80
+ alias_method :>>, :fetch
81
+
66
82
  # Converts a query block into a dataset. For more information see
67
83
  # Dataset#query.
68
84
  def query(&block)
@@ -79,16 +95,18 @@ module Sequel
79
95
  # Returns a new dataset with the select method invoked.
80
96
  def select(*args); dataset.select(*args); end
81
97
 
82
- alias_method :[], :from
83
-
98
+ def [](*args)
99
+ (String === args.first) ? fetch(*args) : from(*args)
100
+ end
101
+
84
102
  def execute(sql)
85
103
  raise NotImplementedError
86
104
  end
87
105
 
88
- # Executes the supplied SQL. The SQL can be supplied as a string or as an
89
- # array of strings. Comments and excessive white space are removed. See
90
- # also Array#to_sql.
91
- def <<(sql); execute(sql.to_sql); end
106
+ # Executes the supplied SQL statement. The SQL can be supplied as a string
107
+ # or as an array of strings. If an array is give, comments and excessive
108
+ # white space are removed. See also Array#to_sql.
109
+ def <<(sql); execute((Array === sql) ? sql.to_sql : sql); end
92
110
 
93
111
  # Acquires a database connection, yielding it to the passed block.
94
112
  def synchronize(&block)