sequel 0.2.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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)