sequel_core 1.4.0 → 1.5.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 +74 -0
- data/COPYING +1 -0
- data/README +17 -6
- data/Rakefile +16 -21
- data/lib/sequel_core.rb +18 -28
- data/lib/sequel_core/adapters/ado.rb +3 -15
- data/lib/sequel_core/adapters/dbi.rb +1 -14
- data/lib/sequel_core/adapters/informix.rb +3 -3
- data/lib/sequel_core/adapters/jdbc.rb +2 -2
- data/lib/sequel_core/adapters/mysql.rb +39 -59
- data/lib/sequel_core/adapters/odbc.rb +18 -38
- data/lib/sequel_core/adapters/openbase.rb +1 -17
- data/lib/sequel_core/adapters/oracle.rb +1 -19
- data/lib/sequel_core/adapters/postgres.rb +20 -60
- data/lib/sequel_core/adapters/sqlite.rb +4 -8
- data/lib/sequel_core/connection_pool.rb +150 -0
- data/lib/sequel_core/core_ext.rb +41 -0
- data/lib/sequel_core/core_sql.rb +35 -38
- data/lib/sequel_core/database.rb +20 -17
- data/lib/sequel_core/dataset.rb +49 -80
- data/lib/sequel_core/dataset/callback.rb +11 -13
- data/lib/sequel_core/dataset/convenience.rb +18 -136
- data/lib/sequel_core/dataset/pagination.rb +81 -0
- data/lib/sequel_core/dataset/sequelizer.rb +5 -4
- data/lib/sequel_core/dataset/sql.rb +43 -33
- data/lib/sequel_core/deprecated.rb +200 -0
- data/lib/sequel_core/exceptions.rb +0 -14
- data/lib/sequel_core/object_graph.rb +199 -0
- data/lib/sequel_core/pretty_table.rb +27 -24
- data/lib/sequel_core/schema/generator.rb +16 -4
- data/lib/sequel_core/schema/sql.rb +5 -3
- data/lib/sequel_core/worker.rb +1 -1
- data/spec/adapters/informix_spec.rb +1 -47
- data/spec/adapters/mysql_spec.rb +85 -54
- data/spec/adapters/oracle_spec.rb +1 -57
- data/spec/adapters/postgres_spec.rb +66 -49
- data/spec/adapters/sqlite_spec.rb +4 -29
- data/spec/connection_pool_spec.rb +358 -0
- data/spec/core_sql_spec.rb +24 -19
- data/spec/database_spec.rb +13 -9
- data/spec/dataset_spec.rb +59 -78
- data/spec/object_graph_spec.rb +202 -0
- data/spec/pretty_table_spec.rb +1 -9
- data/spec/schema_generator_spec.rb +7 -1
- data/spec/schema_spec.rb +27 -0
- data/spec/sequelizer_spec.rb +2 -2
- data/spec/spec_helper.rb +4 -2
- metadata +16 -57
- data/lib/sequel_core/array_keys.rb +0 -322
- data/lib/sequel_core/model.rb +0 -8
- data/spec/array_keys_spec.rb +0 -682
data/lib/sequel_core/core_ext.rb
CHANGED
@@ -17,9 +17,50 @@ end
|
|
17
17
|
|
18
18
|
# Object extensions
|
19
19
|
class Object
|
20
|
+
# Returns true if the object is a object of one of the classes
|
20
21
|
def is_one_of?(*classes)
|
21
22
|
classes.each {|c| return c if is_a?(c)}
|
22
23
|
nil
|
23
24
|
end
|
25
|
+
|
26
|
+
# Objects are blank if they respond true to empty?
|
27
|
+
def blank?
|
28
|
+
nil? || (respond_to?(:empty?) && empty?)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Numeric
|
33
|
+
# Numerics are never blank (not even 0)
|
34
|
+
def blank?
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class NilClass
|
40
|
+
# nil is always blank
|
41
|
+
def blank?
|
42
|
+
true
|
43
|
+
end
|
24
44
|
end
|
25
45
|
|
46
|
+
class TrueClass
|
47
|
+
# true is never blank
|
48
|
+
def blank?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class FalseClass
|
54
|
+
# false is always blank
|
55
|
+
def blank?
|
56
|
+
true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class String
|
61
|
+
BLANK_STRING_REGEXP = /\A\s*\z/
|
62
|
+
# Strings are blank if they are empty or include only whitespace
|
63
|
+
def blank?
|
64
|
+
empty? || BLANK_STRING_REGEXP.match(self)
|
65
|
+
end
|
66
|
+
end
|
data/lib/sequel_core/core_sql.rb
CHANGED
@@ -11,7 +11,7 @@ end
|
|
11
11
|
module Sequel
|
12
12
|
# LiteralString is used to represent literal SQL expressions. An
|
13
13
|
# LiteralString is copied verbatim into an SQL statement. Instances of
|
14
|
-
# LiteralString can be created by calling String#
|
14
|
+
# LiteralString can be created by calling String#lit.
|
15
15
|
class LiteralString < ::String
|
16
16
|
end
|
17
17
|
end
|
@@ -21,7 +21,7 @@ class String
|
|
21
21
|
# Converts a string into an SQL string by removing comments.
|
22
22
|
# See also Array#to_sql.
|
23
23
|
def to_sql
|
24
|
-
split(
|
24
|
+
split("\n").to_sql
|
25
25
|
end
|
26
26
|
|
27
27
|
# Splits a string into separate SQL statements, removing comments
|
@@ -67,7 +67,27 @@ end
|
|
67
67
|
|
68
68
|
module Sequel
|
69
69
|
module SQL
|
70
|
+
module ColumnMethods
|
71
|
+
AS = 'AS'.freeze
|
72
|
+
DESC = 'DESC'.freeze
|
73
|
+
ASC = 'ASC'.freeze
|
74
|
+
|
75
|
+
def as(a); ColumnExpr.new(self, AS, a); end
|
76
|
+
|
77
|
+
def desc; ColumnExpr.new(self, DESC); end
|
78
|
+
|
79
|
+
def asc; ColumnExpr.new(self, ASC); end
|
80
|
+
|
81
|
+
def cast_as(t)
|
82
|
+
if t.is_a?(Symbol)
|
83
|
+
t = t.to_s.lit
|
84
|
+
end
|
85
|
+
Sequel::SQL::Function.new(:cast, self.as(t))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
70
89
|
class Expression
|
90
|
+
include ColumnMethods
|
71
91
|
def lit; self; end
|
72
92
|
end
|
73
93
|
|
@@ -91,8 +111,15 @@ module Sequel
|
|
91
111
|
end
|
92
112
|
|
93
113
|
class Function < Expression
|
114
|
+
attr_reader :f, :args
|
94
115
|
def initialize(f, *args); @f, @args = f, args; end
|
95
116
|
|
117
|
+
# Functions are considered equivalent if they
|
118
|
+
# have the same class, function, and arguments.
|
119
|
+
def ==(x)
|
120
|
+
x.class == self.class && @f == x.f && @args == x.args
|
121
|
+
end
|
122
|
+
|
96
123
|
def to_s(ds)
|
97
124
|
args = @args.empty? ? '' : ds.literal(@args)
|
98
125
|
"#{@f}(#{args})"
|
@@ -119,39 +146,19 @@ module Sequel
|
|
119
146
|
def initialize(t); @t = t; end
|
120
147
|
def to_s(ds); "#{@t}.*"; end
|
121
148
|
end
|
122
|
-
|
123
|
-
module ColumnMethods
|
124
|
-
AS = 'AS'.freeze
|
125
|
-
DESC = 'DESC'.freeze
|
126
|
-
ASC = 'ASC'.freeze
|
127
|
-
|
128
|
-
def as(a); ColumnExpr.new(self, AS, a); end
|
129
|
-
alias_method :AS, :as
|
130
|
-
|
131
|
-
def desc; ColumnExpr.new(self, DESC); end
|
132
|
-
alias_method :DESC, :desc
|
133
|
-
|
134
|
-
def asc; ColumnExpr.new(self, ASC); end
|
135
|
-
alias_method :ASC, :asc
|
136
|
-
|
137
|
-
def all; Sequel::SQL::ColumnAll.new(self); end
|
138
|
-
alias_method :ALL, :all
|
139
|
-
|
140
|
-
def cast_as(t)
|
141
|
-
if t.is_a?(Symbol)
|
142
|
-
t = t.to_s.lit
|
143
|
-
end
|
144
|
-
Sequel::SQL::Function.new(:cast, self.as(t))
|
145
|
-
end
|
146
|
-
end
|
147
149
|
end
|
148
150
|
end
|
149
151
|
|
150
|
-
class
|
152
|
+
class String
|
151
153
|
include Sequel::SQL::ColumnMethods
|
152
154
|
end
|
153
155
|
|
154
156
|
class Symbol
|
157
|
+
include Sequel::SQL::ColumnMethods
|
158
|
+
def *
|
159
|
+
Sequel::SQL::ColumnAll.new(self);
|
160
|
+
end
|
161
|
+
|
155
162
|
def [](*args); Sequel::SQL::Function.new(self, *args); end
|
156
163
|
def |(sub)
|
157
164
|
unless Array === sub
|
@@ -186,14 +193,4 @@ class Symbol
|
|
186
193
|
ds.quote_column_ref(s)
|
187
194
|
end
|
188
195
|
end
|
189
|
-
|
190
|
-
# Converts missing method calls into functions on columns, if the
|
191
|
-
# method name is made of all upper case letters.
|
192
|
-
def method_missing(sym, *args)
|
193
|
-
if ((s = sym.to_s) =~ /^([A-Z]+)$/)
|
194
|
-
Sequel::SQL::Function.new(s.downcase, self)
|
195
|
-
else
|
196
|
-
super
|
197
|
-
end
|
198
|
-
end
|
199
196
|
end
|
data/lib/sequel_core/database.rb
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
require 'uri'
|
2
2
|
|
3
3
|
module Sequel
|
4
|
+
DATABASES = []
|
4
5
|
# A Database object represents a virtual connection to a database.
|
5
6
|
# The Database class is meant to be subclassed by database adapters in order
|
6
7
|
# to provide the functionality needed for executing queries.
|
7
8
|
class Database
|
9
|
+
ADAPTERS = %w'ado db2 dbi informix jdbc mysql odbc odbc_mssql openbase oracle postgres sqlite'.collect{|x| x.to_sym}
|
8
10
|
attr_reader :opts, :pool
|
9
11
|
attr_accessor :logger
|
10
|
-
|
12
|
+
|
11
13
|
# Constructs a new instance of a database connection with the specified
|
12
14
|
# options hash.
|
13
15
|
#
|
14
16
|
# Sequel::Database is an abstract class that is not useful by itself.
|
15
17
|
def initialize(opts = {}, &block)
|
16
|
-
Model.database_opened(self)
|
17
18
|
@opts = opts
|
18
19
|
|
19
20
|
# Determine if the DB is single threaded or multi threaded
|
@@ -27,6 +28,7 @@ module Sequel
|
|
27
28
|
@pool.connection_proc = block || proc {connect}
|
28
29
|
|
29
30
|
@logger = opts[:logger]
|
31
|
+
::Sequel::DATABASES.push(self)
|
30
32
|
end
|
31
33
|
|
32
34
|
# Connects to the database. This method should be overriden by descendants.
|
@@ -167,7 +169,6 @@ module Sequel
|
|
167
169
|
true
|
168
170
|
end
|
169
171
|
|
170
|
-
# include Dataset::SQL
|
171
172
|
include Schema::SQL
|
172
173
|
|
173
174
|
# default serial primary key definition. this should be overriden for each adapter.
|
@@ -352,23 +353,26 @@ module Sequel
|
|
352
353
|
# Returns a string representation of the database object including the
|
353
354
|
# class name and the connection URI.
|
354
355
|
def inspect
|
355
|
-
|
356
|
+
"#<#{self.class}: #{(uri rescue opts).inspect}>"
|
356
357
|
end
|
357
358
|
|
358
359
|
@@adapters = Hash.new
|
359
360
|
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
@
|
371
|
-
|
361
|
+
class << self
|
362
|
+
private
|
363
|
+
# Sets the adapter scheme for the Database class. Call this method in
|
364
|
+
# descendnants of Database to allow connection using a URL. For example the
|
365
|
+
# following:
|
366
|
+
# class DB2::Database < Sequel::Database
|
367
|
+
# set_adapter_scheme :db2
|
368
|
+
# ...
|
369
|
+
# end
|
370
|
+
# would allow connection using:
|
371
|
+
# Sequel.open('db2://user:password@dbserver/mydb')
|
372
|
+
def set_adapter_scheme(scheme)
|
373
|
+
@scheme = scheme
|
374
|
+
@@adapters[scheme.to_sym] = self
|
375
|
+
end
|
372
376
|
end
|
373
377
|
|
374
378
|
# Returns the scheme for the Database class.
|
@@ -392,7 +396,6 @@ module Sequel
|
|
392
396
|
end
|
393
397
|
|
394
398
|
def self.adapter_class(scheme)
|
395
|
-
adapter_name = scheme.to_s =~ /\-/ ? scheme.to_s.gsub('-', '_').to_sym : scheme.to_sym
|
396
399
|
scheme = scheme.to_sym
|
397
400
|
|
398
401
|
if (klass = @@adapters[scheme]).nil?
|
data/lib/sequel_core/dataset.rb
CHANGED
@@ -7,6 +7,7 @@ require File.join(File.dirname(__FILE__), 'dataset/sql')
|
|
7
7
|
require File.join(File.dirname(__FILE__), 'dataset/sequelizer')
|
8
8
|
require File.join(File.dirname(__FILE__), 'dataset/convenience')
|
9
9
|
require File.join(File.dirname(__FILE__), 'dataset/callback')
|
10
|
+
require File.join(File.dirname(__FILE__), 'dataset/pagination')
|
10
11
|
|
11
12
|
module Sequel
|
12
13
|
# A Dataset represents a view of a the data in a database, constrained by
|
@@ -25,7 +26,7 @@ module Sequel
|
|
25
26
|
# use different datasets to access data:
|
26
27
|
# posts = DB[:posts]
|
27
28
|
# davids_posts = posts.filter(:author => 'david')
|
28
|
-
# old_posts = posts.filter('stamp < ?',
|
29
|
+
# old_posts = posts.filter('stamp < ?', Date.today - 7)
|
29
30
|
#
|
30
31
|
# Datasets are Enumerable objects, so they can be manipulated using any
|
31
32
|
# of the Enumerable methods, such as map, inject, etc.
|
@@ -72,8 +73,7 @@ module Sequel
|
|
72
73
|
include Convenience
|
73
74
|
include Callback
|
74
75
|
|
75
|
-
attr_accessor :db
|
76
|
-
attr_accessor :opts
|
76
|
+
attr_accessor :db, :opts, :row_proc
|
77
77
|
|
78
78
|
alias_method :size, :count
|
79
79
|
|
@@ -82,7 +82,7 @@ module Sequel
|
|
82
82
|
def all(opts = nil, &block)
|
83
83
|
a = []
|
84
84
|
each(opts) {|r| a << r}
|
85
|
-
|
85
|
+
post_load(a)
|
86
86
|
a.each(&block) if block
|
87
87
|
a
|
88
88
|
end
|
@@ -106,15 +106,10 @@ module Sequel
|
|
106
106
|
# Returns a new clone of the dataset with with the given options merged.
|
107
107
|
def clone(opts = {})
|
108
108
|
c = super()
|
109
|
-
c.
|
109
|
+
c.opts = @opts.merge(opts)
|
110
|
+
c.instance_variable_set(:@columns, nil)
|
110
111
|
c
|
111
112
|
end
|
112
|
-
alias_method :clone_merge, :clone # should be deprecated in the next major release.
|
113
|
-
|
114
|
-
def set_options(opts) #:nodoc:
|
115
|
-
@opts = opts
|
116
|
-
@columns = nil
|
117
|
-
end
|
118
113
|
|
119
114
|
NOTIMPL_MSG = "This method must be overriden in Sequel adapters".freeze
|
120
115
|
|
@@ -179,7 +174,17 @@ module Sequel
|
|
179
174
|
|
180
175
|
# Iterates over the records in the dataset
|
181
176
|
def each(opts = nil, &block)
|
182
|
-
|
177
|
+
if graph = @opts[:graph]
|
178
|
+
graph_each(opts, &block)
|
179
|
+
else
|
180
|
+
row_proc = @row_proc unless opts && opts[:naked]
|
181
|
+
transform = @transform
|
182
|
+
fetch_rows(select_sql(opts)) do |r|
|
183
|
+
r = transform_load(r) if transform
|
184
|
+
r = row_proc[r] if row_proc
|
185
|
+
yield r
|
186
|
+
end
|
187
|
+
end
|
183
188
|
self
|
184
189
|
end
|
185
190
|
|
@@ -248,17 +253,17 @@ module Sequel
|
|
248
253
|
when nil # set_model(nil) => no
|
249
254
|
# no argument provided, so the dataset is denuded
|
250
255
|
@opts.merge!(:naked => true, :models => nil, :polymorphic_key => nil)
|
251
|
-
|
256
|
+
self.row_proc = nil
|
252
257
|
# extend_with_stock_each
|
253
258
|
when Class
|
254
259
|
# isomorphic model
|
255
260
|
@opts.merge!(:naked => nil, :models => {nil => key}, :polymorphic_key => nil)
|
256
261
|
if key.respond_to?(:load)
|
257
262
|
# the class has a values setter method, so we use it
|
258
|
-
|
263
|
+
self.row_proc = proc{|h| key.load(h, *args)}
|
259
264
|
else
|
260
265
|
# otherwise we just pass the hash to the constructor
|
261
|
-
|
266
|
+
self.row_proc = proc{|h| key.new(h, *args)}
|
262
267
|
end
|
263
268
|
extend_with_destroy
|
264
269
|
when Symbol
|
@@ -267,14 +272,14 @@ module Sequel
|
|
267
272
|
@opts.merge!(:naked => true, :models => hash, :polymorphic_key => key)
|
268
273
|
if hash.values.first.respond_to?(:load)
|
269
274
|
# the class has a values setter method, so we use it
|
270
|
-
|
275
|
+
self.row_proc = proc do |h|
|
271
276
|
c = hash[h[key]] || hash[nil] || \
|
272
277
|
raise(Error, "No matching model class for record (#{polymorphic_key} => #{h[polymorphic_key].inspect})")
|
273
278
|
c.load(h, *args)
|
274
279
|
end
|
275
280
|
else
|
276
281
|
# otherwise we just pass the hash to the constructor
|
277
|
-
|
282
|
+
self.row_proc = proc do |h|
|
278
283
|
c = hash[h[key]] || hash[nil] || \
|
279
284
|
raise(Error, "No matching model class for record (#{polymorphic_key} => #{h[polymorphic_key].inspect})")
|
280
285
|
c.new(h, *args)
|
@@ -287,25 +292,6 @@ module Sequel
|
|
287
292
|
self
|
288
293
|
end
|
289
294
|
|
290
|
-
# Overrides the each method to pass the values through a filter. The filter
|
291
|
-
# receives as argument a hash containing the column values for the current
|
292
|
-
# record. The filter should return a value which is then passed to the
|
293
|
-
# iterating block. In order to elucidate, here's a contrived example:
|
294
|
-
#
|
295
|
-
# dataset.set_row_proc {|h| h.merge(:xxx => 'yyy')}
|
296
|
-
# dataset.first[:xxx] #=> "yyy" # always!
|
297
|
-
#
|
298
|
-
def set_row_proc(&filter)
|
299
|
-
@row_proc = filter
|
300
|
-
update_each_method
|
301
|
-
end
|
302
|
-
|
303
|
-
# Removes the row making proc.
|
304
|
-
def remove_row_proc
|
305
|
-
@row_proc = nil
|
306
|
-
update_each_method
|
307
|
-
end
|
308
|
-
|
309
295
|
STOCK_TRANSFORMS = {
|
310
296
|
:marshal => [
|
311
297
|
# for backwards-compatibility we support also non-base64-encoded values.
|
@@ -354,7 +340,6 @@ module Sequel
|
|
354
340
|
end
|
355
341
|
end
|
356
342
|
end
|
357
|
-
update_each_method
|
358
343
|
self
|
359
344
|
end
|
360
345
|
|
@@ -376,49 +361,6 @@ module Sequel
|
|
376
361
|
end
|
377
362
|
end
|
378
363
|
|
379
|
-
# Updates the each method according to whether @row_proc and @transform are
|
380
|
-
# set or not.
|
381
|
-
def update_each_method
|
382
|
-
# warning: ugly code generation ahead
|
383
|
-
if @row_proc && @transform
|
384
|
-
class << self
|
385
|
-
def each(opts = nil, &block)
|
386
|
-
if opts && opts[:naked]
|
387
|
-
fetch_rows(select_sql(opts)) {|r| block[transform_load(r)]}
|
388
|
-
else
|
389
|
-
fetch_rows(select_sql(opts)) {|r| block[@row_proc[transform_load(r)]]}
|
390
|
-
end
|
391
|
-
self
|
392
|
-
end
|
393
|
-
end
|
394
|
-
elsif @row_proc
|
395
|
-
class << self
|
396
|
-
def each(opts = nil, &block)
|
397
|
-
if opts && opts[:naked]
|
398
|
-
fetch_rows(select_sql(opts), &block)
|
399
|
-
else
|
400
|
-
fetch_rows(select_sql(opts)) {|r| block[@row_proc[r]]}
|
401
|
-
end
|
402
|
-
self
|
403
|
-
end
|
404
|
-
end
|
405
|
-
elsif @transform
|
406
|
-
class << self
|
407
|
-
def each(opts = nil, &block)
|
408
|
-
fetch_rows(select_sql(opts)) {|r| block[transform_load(r)]}
|
409
|
-
self
|
410
|
-
end
|
411
|
-
end
|
412
|
-
else
|
413
|
-
class << self
|
414
|
-
def each(opts = nil, &block)
|
415
|
-
fetch_rows(select_sql(opts), &block)
|
416
|
-
self
|
417
|
-
end
|
418
|
-
end
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
364
|
# Extends the dataset with a destroy method, that calls destroy for each
|
423
365
|
# record in the dataset.
|
424
366
|
def extend_with_destroy
|
@@ -449,6 +391,33 @@ module Sequel
|
|
449
391
|
def inspect
|
450
392
|
'#<%s: %s>' % [self.class.to_s, sql.inspect]
|
451
393
|
end
|
394
|
+
|
395
|
+
# Setup mutation (e.g. filter!) methods
|
396
|
+
def self.def_mutation_method(*meths)
|
397
|
+
meths.each do |meth|
|
398
|
+
class_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end")
|
399
|
+
end
|
400
|
+
end
|
401
|
+
def def_mutation_method(*meths)
|
402
|
+
meths.each do |meth|
|
403
|
+
instance_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end")
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
MUTATION_METHODS = %w'and distinct exclude exists filter from from_self full_outer_join graph
|
408
|
+
group group_and_count group_by having inner_join intersect invert_order join
|
409
|
+
left_outer_join limit naked or order order_by order_more paginate query reject
|
410
|
+
reverse reverse_order right_outer_join select select_all select_more
|
411
|
+
set_graph_aliases set_model sort sort_by union unordered where'.collect{|x| x.to_sym}
|
412
|
+
|
413
|
+
def_mutation_method(*MUTATION_METHODS)
|
414
|
+
|
415
|
+
private
|
416
|
+
def mutation_method(meth, *args, &block)
|
417
|
+
copy = send(meth, *args, &block)
|
418
|
+
@opts.merge!(copy.opts)
|
419
|
+
self
|
420
|
+
end
|
452
421
|
end
|
453
422
|
end
|
454
423
|
|