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 +76 -0
- data/Rakefile +1 -1
- data/lib/sequel.rb +1 -1
- data/lib/sequel/ado.rb +17 -0
- data/lib/sequel/array_keys.rb +233 -0
- data/lib/sequel/connection_pool.rb +14 -0
- data/lib/sequel/core_ext.rb +3 -3
- data/lib/sequel/database.rb +25 -7
- data/lib/sequel/dataset.rb +46 -15
- data/lib/sequel/dataset/convenience.rb +27 -2
- data/lib/sequel/dataset/sequelizer.rb +2 -2
- data/lib/sequel/dataset/sql.rb +49 -18
- data/lib/sequel/dbi.rb +17 -0
- data/lib/sequel/model.rb +276 -82
- data/lib/sequel/model/base.rb +41 -30
- data/lib/sequel/model/caching.rb +42 -0
- data/lib/sequel/model/hooks.rb +113 -27
- data/lib/sequel/model/record.rb +78 -21
- data/lib/sequel/model/relations.rb +5 -0
- data/lib/sequel/model/schema.rb +11 -1
- data/lib/sequel/mysql.rb +61 -17
- data/lib/sequel/odbc.rb +42 -1
- data/lib/sequel/postgres.rb +45 -0
- data/lib/sequel/pretty_table.rb +14 -11
- data/lib/sequel/schema/schema_generator.rb +9 -3
- data/lib/sequel/sqlite.rb +33 -1
- data/spec/adapters/mysql_spec.rb +69 -15
- data/spec/adapters/postgres_spec.rb +66 -12
- data/spec/adapters/sqlite_spec.rb +113 -1
- data/spec/array_keys_spec.rb +544 -0
- data/spec/connection_pool_spec.rb +83 -0
- data/spec/database_spec.rb +81 -2
- data/spec/dataset_spec.rb +227 -9
- data/spec/model_spec.rb +392 -68
- data/spec/schema_spec.rb +7 -0
- metadata +5 -2
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.
|
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",
|
data/lib/sequel.rb
CHANGED
data/lib/sequel/ado.rb
CHANGED
@@ -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
|
data/lib/sequel/core_ext.rb
CHANGED
@@ -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
|
data/lib/sequel/database.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
89
|
-
# array of strings.
|
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)
|