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