sequel_core 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +50 -0
- data/README +1 -2
- data/Rakefile +1 -1
- data/bin/sequel +26 -11
- data/doc/dataset_filtering.rdoc +9 -2
- data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -2
- data/lib/sequel_core/adapters/mysql.rb +11 -97
- data/lib/sequel_core/adapters/odbc_mssql.rb +1 -1
- data/lib/sequel_core/adapters/oracle.rb +1 -1
- data/lib/sequel_core/adapters/postgres.rb +33 -17
- data/lib/sequel_core/adapters/sqlite.rb +1 -1
- data/lib/sequel_core/connection_pool.rb +12 -35
- data/lib/sequel_core/core_ext.rb +5 -19
- data/lib/sequel_core/core_sql.rb +17 -6
- data/lib/sequel_core/database.rb +14 -2
- data/lib/sequel_core/dataset/callback.rb +0 -3
- data/lib/sequel_core/dataset/convenience.rb +4 -3
- data/lib/sequel_core/dataset/parse_tree_sequelizer.rb +0 -21
- data/lib/sequel_core/dataset/sequelizer.rb +42 -43
- data/lib/sequel_core/dataset/sql.rb +121 -62
- data/lib/sequel_core/dataset.rb +20 -4
- data/lib/sequel_core/deprecated.rb +0 -6
- data/lib/sequel_core/migration.rb +4 -0
- data/lib/sequel_core/object_graph.rb +8 -6
- data/lib/sequel_core/pretty_table.rb +1 -1
- data/lib/sequel_core/schema/sql.rb +2 -0
- data/lib/sequel_core/sql.rb +393 -153
- data/lib/sequel_core.rb +22 -7
- data/spec/adapters/mysql_spec.rb +11 -7
- data/spec/adapters/postgres_spec.rb +32 -4
- data/spec/blockless_filters_spec.rb +33 -6
- data/spec/connection_pool_spec.rb +18 -16
- data/spec/core_ext_spec.rb +0 -13
- data/spec/core_sql_spec.rb +59 -13
- data/spec/database_spec.rb +5 -5
- data/spec/dataset_spec.rb +167 -55
- data/spec/object_graph_spec.rb +9 -4
- data/spec/sequelizer_spec.rb +8 -17
- data/spec/spec_helper.rb +4 -3
- metadata +2 -2
@@ -138,7 +138,7 @@ module Sequel
|
|
138
138
|
|
139
139
|
private
|
140
140
|
def connection_pool_default_options
|
141
|
-
o = super.merge(:
|
141
|
+
o = super.merge(:pool_convert_exceptions=>false)
|
142
142
|
# Default to only a single connection if a memory database is used,
|
143
143
|
# because otherwise each connection will get a separate database
|
144
144
|
o[:max_connections] = 1 if @opts[:database] == ':memory:' || @opts[:database].blank?
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# A ConnectionPool manages access to database connections by keeping
|
2
2
|
# multiple connections and giving threads exclusive access to each
|
3
3
|
# connection.
|
4
|
-
class ConnectionPool
|
5
|
-
#
|
4
|
+
class Sequel::ConnectionPool
|
5
|
+
# A hash of connections currently being used, key is the Thread,
|
6
|
+
# value is the connection.
|
6
7
|
attr_reader :allocated
|
7
8
|
|
8
9
|
# An array of connections opened but not currently used
|
@@ -42,13 +43,6 @@ class ConnectionPool
|
|
42
43
|
# will open (default 4)
|
43
44
|
# * :pool_convert_exceptions - Whether to convert non-StandardError based exceptions
|
44
45
|
# to RuntimeError exceptions (default true)
|
45
|
-
# * :pool_reuse_connections - Which strategy to follow in regards to reusing connections:
|
46
|
-
# * :always - Always reuse a connection that belongs to the same thread
|
47
|
-
# * :allow - Only reuse a connection that belongs to the same thread if
|
48
|
-
# another cannot be acquired immediately (default)
|
49
|
-
# * :last_resort - Only reuse a connection that belongs to the same thread if
|
50
|
-
# the pool timeout has expired
|
51
|
-
# * :never - Never reuse a connection that belongs to the same thread
|
52
46
|
# * :pool_sleep_time - The amount of time to sleep before attempting to acquire
|
53
47
|
# a connection again (default 0.001)
|
54
48
|
# * :pool_timeout - The amount of seconds to wait to acquire a connection
|
@@ -59,11 +53,10 @@ class ConnectionPool
|
|
59
53
|
@connection_proc = block
|
60
54
|
|
61
55
|
@available_connections = []
|
62
|
-
@allocated =
|
56
|
+
@allocated = {}
|
63
57
|
@created_count = 0
|
64
58
|
@timeout = opts[:pool_timeout] || 5
|
65
59
|
@sleep_time = opts[:pool_sleep_time] || 0.001
|
66
|
-
@reuse_connections = opts[:pool_reuse_connections] || :allow
|
67
60
|
@convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
|
68
61
|
end
|
69
62
|
|
@@ -72,10 +65,8 @@ class ConnectionPool
|
|
72
65
|
#
|
73
66
|
# pool.hold {|conn| conn.execute('DROP TABLE posts')}
|
74
67
|
#
|
75
|
-
# Pool#hold
|
76
|
-
# the same thread without blocking
|
77
|
-
# was set to :always or :allow. Depending on the :pool_reuse_connections
|
78
|
-
# option you may get the connection currently used by the thread or a new connection.
|
68
|
+
# Pool#hold is re-entrant, meaning it can be called recursively in
|
69
|
+
# the same thread without blocking.
|
79
70
|
#
|
80
71
|
# If no connection is immediately available and the pool is already using the maximum
|
81
72
|
# number of connections, Pool#hold will block until a connection
|
@@ -88,21 +79,11 @@ class ConnectionPool
|
|
88
79
|
time = Time.new
|
89
80
|
timeout = time + @timeout
|
90
81
|
sleep_time = @sleep_time
|
91
|
-
|
92
|
-
if (reuse == :always) && (conn = owned_connection(t))
|
82
|
+
if conn = owned_connection(t)
|
93
83
|
return yield(conn)
|
94
84
|
end
|
95
|
-
reuse = reuse == :allow ? true : false
|
96
85
|
until conn = acquire(t)
|
97
|
-
if
|
98
|
-
return yield(conn)
|
99
|
-
end
|
100
|
-
if Time.new > timeout
|
101
|
-
if (@reuse_connections == :last_resort) && (conn = owned_connection(t))
|
102
|
-
return yield(conn)
|
103
|
-
end
|
104
|
-
raise(::Sequel::Error::PoolTimeoutError)
|
105
|
-
end
|
86
|
+
raise(::Sequel::Error::PoolTimeoutError) if Time.new > timeout
|
106
87
|
sleep sleep_time
|
107
88
|
end
|
108
89
|
begin
|
@@ -131,18 +112,14 @@ class ConnectionPool
|
|
131
112
|
|
132
113
|
# Returns the connection owned by the supplied thread, if any.
|
133
114
|
def owned_connection(thread)
|
134
|
-
@mutex.synchronize
|
135
|
-
x = @allocated.assoc(thread)
|
136
|
-
x[1] if x
|
137
|
-
end
|
115
|
+
@mutex.synchronize{@allocated[thread]}
|
138
116
|
end
|
139
117
|
|
140
118
|
# Assigns a connection to the supplied thread, if one is available.
|
141
119
|
def acquire(thread)
|
142
120
|
@mutex.synchronize do
|
143
121
|
if conn = available
|
144
|
-
@allocated
|
145
|
-
conn
|
122
|
+
@allocated[thread] = conn
|
146
123
|
end
|
147
124
|
end
|
148
125
|
end
|
@@ -166,7 +143,7 @@ class ConnectionPool
|
|
166
143
|
# Releases the connection assigned to the supplied thread.
|
167
144
|
def release(thread, conn)
|
168
145
|
@mutex.synchronize do
|
169
|
-
@allocated.delete(
|
146
|
+
@allocated.delete(thread)
|
170
147
|
@available_connections << conn
|
171
148
|
end
|
172
149
|
end
|
@@ -178,7 +155,7 @@ end
|
|
178
155
|
#
|
179
156
|
# Note that using a single threaded pool with some adapters can cause
|
180
157
|
# errors in certain cases, see Sequel.single_threaded=.
|
181
|
-
class SingleThreadedPool
|
158
|
+
class Sequel::SingleThreadedPool
|
182
159
|
# The single database connection for the pool
|
183
160
|
attr_reader :conn
|
184
161
|
|
data/lib/sequel_core/core_ext.rb
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
# This file includes augmentations to the core ruby classes the Sequel uses,
|
2
|
-
# which are unrelated to the creation of SQL. It includes common
|
3
|
-
# idioms to reduce the amount of code duplication.
|
4
|
-
|
5
1
|
class Array
|
6
2
|
# True if the array is not empty and all of its elements are
|
7
3
|
# arrays of size 2. This is used to determine if the array
|
@@ -65,7 +61,7 @@ class Module
|
|
65
61
|
# alias_method to, from
|
66
62
|
# end
|
67
63
|
def metaalias(to, from)
|
68
|
-
|
64
|
+
meta_eval{alias_method to, from}
|
69
65
|
end
|
70
66
|
|
71
67
|
# Make a singleton/class attribute accessor method(s).
|
@@ -75,7 +71,7 @@ class Module
|
|
75
71
|
# attr_accessor *meths
|
76
72
|
# end
|
77
73
|
def metaattr_accessor(*meths)
|
78
|
-
|
74
|
+
meta_eval{attr_accessor(*meths)}
|
79
75
|
end
|
80
76
|
|
81
77
|
# Make a singleton/class method(s) private.
|
@@ -86,17 +82,7 @@ class Module
|
|
86
82
|
# attr_reader *meths
|
87
83
|
# end
|
88
84
|
def metaattr_reader(*meths)
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
|
-
# Make a singleton/class method(s) private.
|
93
|
-
# Replaces the construct:
|
94
|
-
#
|
95
|
-
# class << self
|
96
|
-
# private *meths
|
97
|
-
# end
|
98
|
-
def metaprivate(*meths)
|
99
|
-
metaclass.instance_eval{private(*meths)}
|
85
|
+
meta_eval{attr_reader(*meths)}
|
100
86
|
end
|
101
87
|
end
|
102
88
|
|
@@ -184,10 +170,10 @@ class String
|
|
184
170
|
end
|
185
171
|
|
186
172
|
# Converts a string into a Time or DateTime object, depending on the
|
187
|
-
# value of Sequel.
|
173
|
+
# value of Sequel.datetime_class
|
188
174
|
def to_sequel_time
|
189
175
|
begin
|
190
|
-
Sequel.
|
176
|
+
Sequel.datetime_class.parse(self)
|
191
177
|
rescue => e
|
192
178
|
raise Sequel::Error::InvalidValue, "Invalid time value '#{self}' (#{e.message})"
|
193
179
|
end
|
data/lib/sequel_core/core_sql.rb
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
# This file holds the extensions to core ruby classes that relate to the creation of SQL
|
2
|
-
# code.
|
3
|
-
|
4
1
|
class Array
|
5
2
|
# Return a Sequel::SQL::BooleanExpression created from this array, not matching any of the
|
6
3
|
# conditions.
|
@@ -8,6 +5,12 @@ class Array
|
|
8
5
|
sql_expr_if_all_two_pairs(:OR, true)
|
9
6
|
end
|
10
7
|
|
8
|
+
# Return a Sequel::SQL::CaseExpression with this array as the conditions and the given
|
9
|
+
# default value.
|
10
|
+
def case(default)
|
11
|
+
::Sequel::SQL::CaseExpression.new(self, default)
|
12
|
+
end
|
13
|
+
|
11
14
|
# Return a Sequel::SQL::BooleanExpression created from this array, matching all of the
|
12
15
|
# conditions.
|
13
16
|
def sql_expr
|
@@ -81,6 +84,13 @@ class Hash
|
|
81
84
|
::Sequel::SQL::BooleanExpression.from_value_pairs(self, :OR, true)
|
82
85
|
end
|
83
86
|
|
87
|
+
# Return a Sequel::SQL::CaseExpression with this hash as the conditions and the given
|
88
|
+
# default value. Note that the order of the conditions will be arbitrary, so all
|
89
|
+
# conditions should be orthogonal.
|
90
|
+
def case(default)
|
91
|
+
::Sequel::SQL::CaseExpression.new(to_a, default)
|
92
|
+
end
|
93
|
+
|
84
94
|
# Return a Sequel::SQL::BooleanExpression created from this hash, matching all of the
|
85
95
|
# conditions.
|
86
96
|
def sql_expr
|
@@ -101,7 +111,8 @@ class Hash
|
|
101
111
|
end
|
102
112
|
|
103
113
|
class String
|
104
|
-
include Sequel::SQL::
|
114
|
+
include Sequel::SQL::AliasMethods
|
115
|
+
include Sequel::SQL::CastMethods
|
105
116
|
|
106
117
|
# Converts a string into an LiteralString, in order to override string
|
107
118
|
# literalization, e.g.:
|
@@ -131,8 +142,8 @@ class String
|
|
131
142
|
end
|
132
143
|
|
133
144
|
class Symbol
|
134
|
-
include Sequel::SQL::
|
135
|
-
include Sequel::SQL::
|
145
|
+
include Sequel::SQL::QualifyingMethods
|
146
|
+
include Sequel::SQL::GenericExpressionMethods
|
136
147
|
|
137
148
|
# If no argument is given, returns a Sequel::SQL::ColumnAll object specifying all
|
138
149
|
# columns for this table.
|
data/lib/sequel_core/database.rb
CHANGED
@@ -172,7 +172,8 @@ module Sequel
|
|
172
172
|
@scheme = scheme
|
173
173
|
@@adapters[scheme.to_sym] = self
|
174
174
|
end
|
175
|
-
|
175
|
+
|
176
|
+
private_class_method :set_adapter_scheme
|
176
177
|
|
177
178
|
### Instance Methods ###
|
178
179
|
|
@@ -378,6 +379,17 @@ module Sequel
|
|
378
379
|
value.to_s
|
379
380
|
when :float
|
380
381
|
Float(value)
|
382
|
+
when :decimal
|
383
|
+
case value
|
384
|
+
when BigDecimal
|
385
|
+
value
|
386
|
+
when String, Float
|
387
|
+
value.to_d
|
388
|
+
when Integer
|
389
|
+
value.to_s.to_d
|
390
|
+
else
|
391
|
+
raise ArgumentError, "invalid value for BigDecimal: #{value.inspect}"
|
392
|
+
end
|
381
393
|
when :boolean
|
382
394
|
case value
|
383
395
|
when false, 0, "0", /\Af(alse)?\z/i
|
@@ -407,7 +419,7 @@ module Sequel
|
|
407
419
|
end
|
408
420
|
when :datetime
|
409
421
|
raise(ArgumentError, "invalid value for #{tc}: #{value.inspect}") unless value.is_one_of?(DateTime, Date, Time, String)
|
410
|
-
if Sequel.
|
422
|
+
if Sequel.datetime_class === value
|
411
423
|
# Already the correct class, no need to convert
|
412
424
|
value
|
413
425
|
else
|
@@ -233,10 +233,11 @@ module Sequel
|
|
233
233
|
|
234
234
|
# Returns a hash with one column used as key and another used as value.
|
235
235
|
# If rows have duplicate values for the key column, the latter row(s)
|
236
|
-
# will overwrite the value of the previous row(s).
|
237
|
-
|
236
|
+
# will overwrite the value of the previous row(s). If the value_column
|
237
|
+
# is not given or nil, uses the entire hash as the value.
|
238
|
+
def to_hash(key_column, value_column = nil)
|
238
239
|
inject({}) do |m, r|
|
239
|
-
m[r[key_column]] = r[value_column]
|
240
|
+
m[r[key_column]] = value_column ? r[value_column] : r
|
240
241
|
m
|
241
242
|
end
|
242
243
|
end
|
@@ -1,24 +1,3 @@
|
|
1
|
-
# This file includes dataset methods for translating Ruby expressions
|
2
|
-
# into SQL expressions, making it possible to specify dataset filters using
|
3
|
-
# blocks, e.g.:
|
4
|
-
#
|
5
|
-
# DB[:items].filter {:price < 100}
|
6
|
-
# DB[:items].filter {:category == 'ruby' && :date < Date.today - 7}
|
7
|
-
#
|
8
|
-
# Block filters can refer to literals, variables, constants, arguments,
|
9
|
-
# instance variables or anything else in order to create parameterized
|
10
|
-
# queries. Block filters can also refer to other dataset objects as
|
11
|
-
# sub-queries. Block filters are pretty much limitless!
|
12
|
-
#
|
13
|
-
# Block filters are based on ParseTree. If you do not have the ParseTree
|
14
|
-
# gem installed, block filters will raise an error.
|
15
|
-
#
|
16
|
-
# To enable full block filter support make sure you have both ParseTree and
|
17
|
-
# Ruby2Ruby installed:
|
18
|
-
#
|
19
|
-
# sudo gem install parsetree
|
20
|
-
# sudo gem install ruby2ruby
|
21
|
-
|
22
1
|
module Sequel
|
23
2
|
class Dataset
|
24
3
|
private
|
@@ -1,51 +1,50 @@
|
|
1
|
-
|
2
|
-
require 'parse_tree'
|
3
|
-
require 'sequel_core/dataset/parse_tree_sequelizer'
|
4
|
-
class Proc
|
5
|
-
def to_sql(dataset, opts = {})
|
6
|
-
dataset.send(:pt_expr, to_sexp[2], self.binding, opts)
|
7
|
-
end
|
8
|
-
end
|
1
|
+
unless defined?(SEQUEL_NO_PARSE_TREE)
|
9
2
|
begin
|
10
|
-
require '
|
11
|
-
|
12
|
-
# Evaluates a method call. This method is used to evaluate Ruby expressions
|
13
|
-
# referring to indirect values, e.g.:
|
14
|
-
#
|
15
|
-
# dataset.filter {:category => category.to_s}
|
16
|
-
# dataset.filter {:x > y[0..3]}
|
17
|
-
#
|
18
|
-
# This method depends on the Ruby2Ruby gem. If you do not have Ruby2Ruby
|
19
|
-
# installed, this method will raise an error.
|
20
|
-
def ext_expr(e, b, opts)
|
21
|
-
eval(::RubyToRuby.new.process(e), b)
|
22
|
-
end
|
23
|
-
end
|
3
|
+
require 'parse_tree'
|
4
|
+
require 'sequel_core/dataset/parse_tree_sequelizer'
|
24
5
|
class Proc
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
class Sequel::Dataset
|
29
|
-
def ext_expr(*args)
|
30
|
-
raise Sequel::Error, "You must have the Ruby2Ruby gem installed in order to use this block filter."
|
6
|
+
def to_sql(dataset, opts = {})
|
7
|
+
Sequel::Deprecation.deprecate("ParseTree filters are deprecated and will be removed in Sequel 2.2")
|
8
|
+
dataset.send(:pt_expr, to_sexp[2], self.binding, opts)
|
31
9
|
end
|
32
10
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
11
|
+
begin
|
12
|
+
require 'ruby2ruby'
|
13
|
+
class Sequel::Dataset
|
14
|
+
# Evaluates a method call. This method is used to evaluate Ruby expressions
|
15
|
+
# referring to indirect values, e.g.:
|
16
|
+
#
|
17
|
+
# dataset.filter {:category => category.to_s}
|
18
|
+
# dataset.filter {:x > y[0..3]}
|
19
|
+
#
|
20
|
+
# This method depends on the Ruby2Ruby gem. If you do not have Ruby2Ruby
|
21
|
+
# installed, this method will raise an error.
|
22
|
+
def ext_expr(e, b, opts)
|
23
|
+
eval(::RubyToRuby.new.process(e), b)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
class Proc
|
27
|
+
remove_method :to_sexp
|
28
|
+
end
|
29
|
+
rescue LoadError
|
30
|
+
class Sequel::Dataset
|
31
|
+
def ext_expr(*args)
|
32
|
+
raise Sequel::Error, "You must have the Ruby2Ruby gem installed in order to use this block filter."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
ensure
|
36
|
+
class Proc
|
37
|
+
# replacement for Proc#to_sexp as defined in ruby2ruby.
|
38
|
+
# see also: http://rubyforge.org/tracker/index.php?func=detail&aid=18095&group_id=1513&atid=5921
|
39
|
+
# The ruby2ruby implementation leaks memory, so we fix it.
|
40
|
+
def to_sexp
|
41
|
+
block = self
|
42
|
+
c = Class.new {define_method(:m, &block)}
|
43
|
+
ParseTree.translate(c, :m)[2]
|
44
|
+
end
|
42
45
|
end
|
43
46
|
end
|
44
|
-
|
45
|
-
|
46
|
-
class Proc
|
47
|
-
def to_sql(*args)
|
48
|
-
raise Sequel::Error, "You must have the ParseTree gem installed in order to use block filters."
|
49
|
-
end
|
47
|
+
rescue LoadError
|
48
|
+
SEQUEL_NO_PARSE_TREE = true
|
50
49
|
end
|
51
50
|
end
|
@@ -1,6 +1,3 @@
|
|
1
|
-
# This file includes all the dataset methods concerned with
|
2
|
-
# generating SQL statements for retrieving and manipulating records.
|
3
|
-
|
4
1
|
module Sequel
|
5
2
|
class Dataset
|
6
3
|
AND_SEPARATOR = " AND ".freeze
|
@@ -9,13 +6,8 @@ module Sequel
|
|
9
6
|
COLUMN_REF_RE1 = /\A([\w ]+)__([\w ]+)___([\w ]+)\z/.freeze
|
10
7
|
COLUMN_REF_RE2 = /\A([\w ]+)___([\w ]+)\z/.freeze
|
11
8
|
COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
|
9
|
+
COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql]
|
12
10
|
DATE_FORMAT = "DATE '%Y-%m-%d'".freeze
|
13
|
-
JOIN_TYPES = {
|
14
|
-
:left_outer => 'LEFT OUTER JOIN'.freeze,
|
15
|
-
:right_outer => 'RIGHT OUTER JOIN'.freeze,
|
16
|
-
:full_outer => 'FULL OUTER JOIN'.freeze,
|
17
|
-
:inner => 'INNER JOIN'.freeze
|
18
|
-
}
|
19
11
|
N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
|
20
12
|
NULL = "NULL".freeze
|
21
13
|
QUESTION_MARK = '?'.freeze
|
@@ -32,17 +24,21 @@ module Sequel
|
|
32
24
|
filter(*cond, &block)
|
33
25
|
end
|
34
26
|
|
27
|
+
# SQL fragment for the aliased expression
|
28
|
+
def aliased_expression_sql(ae)
|
29
|
+
"#{literal(ae.expression)} AS #{quote_identifier(ae.aliaz)}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# SQL fragment for specifying given CaseExpression.
|
33
|
+
def case_expression_sql(ce)
|
34
|
+
"(CASE #{ce.conditions.collect{|c,r| "WHEN #{literal(c)} THEN #{literal(r)} "}.join}ELSE #{literal(ce.default)} END)"
|
35
|
+
end
|
36
|
+
|
35
37
|
# SQL fragment for specifying all columns in a given table.
|
36
38
|
def column_all_sql(ca)
|
37
39
|
"#{quote_identifier(ca.table)}.*"
|
38
40
|
end
|
39
41
|
|
40
|
-
# SQL fragment for column expressions
|
41
|
-
def column_expr_sql(ce)
|
42
|
-
r = ce.r
|
43
|
-
"#{literal(ce.l)} #{ce.op}#{" #{literal(r)}" if r}"
|
44
|
-
end
|
45
|
-
|
46
42
|
# SQL fragment for complex expressions
|
47
43
|
def complex_expression_sql(op, args)
|
48
44
|
case op
|
@@ -54,6 +50,8 @@ module Sequel
|
|
54
50
|
"NOT #{literal(args.at(0))}"
|
55
51
|
when :NOOP
|
56
52
|
literal(args.at(0))
|
53
|
+
when :'B~'
|
54
|
+
"~#{literal(args.at(0))}"
|
57
55
|
else
|
58
56
|
raise(Sequel::Error, "invalid operator #{op}")
|
59
57
|
end
|
@@ -61,11 +59,7 @@ module Sequel
|
|
61
59
|
|
62
60
|
# Returns the number of records in the dataset.
|
63
61
|
def count
|
64
|
-
|
65
|
-
from_self.count
|
66
|
-
else
|
67
|
-
single_value(STOCK_COUNT_OPTS).to_i
|
68
|
-
end
|
62
|
+
options_overlap(COUNT_FROM_SELF_OPTS) ? from_self.count : single_value(STOCK_COUNT_OPTS).to_i
|
69
63
|
end
|
70
64
|
alias_method :size, :count
|
71
65
|
|
@@ -108,7 +102,7 @@ module Sequel
|
|
108
102
|
clause = (@opts[:having] ? :having : :where)
|
109
103
|
cond = cond.first if cond.size == 1
|
110
104
|
cond = cond.sql_or if (Hash === cond) || ((Array === cond) && (cond.all_two_pairs?))
|
111
|
-
cond = filter_expr(block
|
105
|
+
cond = filter_expr(cond, &block)
|
112
106
|
cond = SQL::BooleanExpression.invert(cond)
|
113
107
|
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
|
114
108
|
clone(clause => cond)
|
@@ -168,9 +162,8 @@ module Sequel
|
|
168
162
|
def filter(*cond, &block)
|
169
163
|
clause = (@opts[:having] ? :having : :where)
|
170
164
|
cond = cond.first if cond.size == 1
|
171
|
-
raise(Error::InvalidFilter, "Invalid filter specified. Did you mean to supply a block?") if cond === true || cond === false
|
172
165
|
cond = transform_save(cond) if @transform if cond.is_a?(Hash)
|
173
|
-
cond = filter_expr(block
|
166
|
+
cond = filter_expr(cond, &block)
|
174
167
|
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause] && !@opts[clause].blank?
|
175
168
|
clone(clause => cond)
|
176
169
|
end
|
@@ -187,6 +180,9 @@ module Sequel
|
|
187
180
|
case s = source.first
|
188
181
|
when Hash
|
189
182
|
s.values.first
|
183
|
+
when Symbol
|
184
|
+
sch, table, aliaz = split_symbol(s)
|
185
|
+
aliaz ? aliaz.to_sym : s
|
190
186
|
else
|
191
187
|
s
|
192
188
|
end
|
@@ -319,6 +315,30 @@ module Sequel
|
|
319
315
|
clone(o)
|
320
316
|
end
|
321
317
|
|
318
|
+
# SQL fragment specifying an Irregular (cast/extract) SQL function call
|
319
|
+
def irregular_function_sql(f)
|
320
|
+
"#{f.f}(#{literal(f.arg1)} #{f.joiner} #{literal(f.arg2)})"
|
321
|
+
end
|
322
|
+
|
323
|
+
# SQL fragment specifying a JOIN clause without ON or USING.
|
324
|
+
def join_clause_sql(jc)
|
325
|
+
table = jc.table
|
326
|
+
table_alias = jc.table_alias
|
327
|
+
table_alias = nil if table == table_alias
|
328
|
+
" #{join_type_sql(jc.join_type)} #{table_ref(table)}" \
|
329
|
+
"#{" AS #{quote_identifier(jc.table_alias)}" if table_alias}"
|
330
|
+
end
|
331
|
+
|
332
|
+
# SQL fragment specifying a JOIN clause with ON.
|
333
|
+
def join_on_clause_sql(jc)
|
334
|
+
"#{join_clause_sql(jc)} ON #{literal(filter_expr(jc.on))}"
|
335
|
+
end
|
336
|
+
|
337
|
+
# SQL fragment specifying a JOIN clause with USING.
|
338
|
+
def join_using_clause_sql(jc)
|
339
|
+
"#{join_clause_sql(jc)} USING (#{column_list(jc.using)})"
|
340
|
+
end
|
341
|
+
|
322
342
|
# Returns a joined dataset. Uses the following arguments:
|
323
343
|
#
|
324
344
|
# * type - The type of join to do (:inner, :left_outer, :right_outer, :full)
|
@@ -326,39 +346,59 @@ module Sequel
|
|
326
346
|
# * Dataset - a subselect is performed with an alias of tN for some value of N
|
327
347
|
# * Model (or anything responding to :table_name) - table.table_name
|
328
348
|
# * String, Symbol: table
|
329
|
-
# * expr -
|
330
|
-
# * Hash, Array - Assumes key (1st arg) is column of joined table (unless already
|
349
|
+
# * expr - specifies conditions, depends on type:
|
350
|
+
# * Hash, Array with all two pairs - Assumes key (1st arg) is column of joined table (unless already
|
331
351
|
# qualified), and value (2nd arg) is column of the last joined or primary table.
|
332
352
|
# To specify multiple conditions on a single joined table column, you must use an array.
|
333
|
-
#
|
334
|
-
#
|
353
|
+
# Uses a JOIN with an ON clause.
|
354
|
+
# * Array - If all members of the array are symbols, considers them as columns and
|
355
|
+
# uses a JOIN with a USING clause. Most databases will remove duplicate columns from
|
356
|
+
# the result set if this is used.
|
357
|
+
# * nil - If a block is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
|
358
|
+
# or CROSS join. If a block is given, uses a ON clause based on the block, see below.
|
359
|
+
# * Everything else - pretty much the same as a using the argument in a call to filter,
|
360
|
+
# so strings are considered literal, symbols specify boolean columns, and blockless
|
361
|
+
# filter expressions can be used. Uses a JOIN with an ON clause.
|
335
362
|
# * table_alias - the name of the table's alias when joining, necessary for joining
|
336
363
|
# to the same table more than once. No alias is used by default.
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
364
|
+
# * block - The block argument should only be given if a JOIN with an ON clause is used,
|
365
|
+
# in which case it yields the table alias/name for the table currently being joined,
|
366
|
+
# the table alias/name for the last joined (or first table), and an array of previous
|
367
|
+
# SQL::JoinClause.
|
368
|
+
def join_table(type, table, expr=nil, table_alias=nil, &block)
|
369
|
+
if Dataset === table
|
370
|
+
if table_alias.nil?
|
342
371
|
table_alias_num = (@opts[:num_dataset_sources] || 0) + 1
|
343
|
-
"t#{table_alias_num}"
|
372
|
+
table_alias = "t#{table_alias_num}"
|
344
373
|
end
|
345
|
-
|
374
|
+
table_name = table_alias
|
346
375
|
else
|
347
376
|
table = table.table_name if table.respond_to?(:table_name)
|
348
|
-
table_alias
|
349
|
-
table_ref(table)
|
377
|
+
table_name = table_alias || table
|
350
378
|
end
|
351
379
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
380
|
+
join = if expr.nil? and !block_given?
|
381
|
+
SQL::JoinClause.new(type, table, table_alias)
|
382
|
+
elsif Array === expr and !expr.empty? and expr.all?{|x| Symbol === x}
|
383
|
+
raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block_given?
|
384
|
+
SQL::JoinUsingClause.new(expr, type, table, table_alias)
|
385
|
+
else
|
386
|
+
last_alias = @opts[:last_joined_table] || first_source
|
387
|
+
if Hash === expr or (Array === expr and expr.all_two_pairs?)
|
388
|
+
expr = expr.collect do |k, v|
|
389
|
+
k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
|
390
|
+
v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
|
391
|
+
[k,v]
|
392
|
+
end
|
393
|
+
end
|
394
|
+
if block_given?
|
395
|
+
expr2 = yield(table_name, last_alias, @opts[:join] || [])
|
396
|
+
expr = expr ? SQL::BooleanExpression.new(:AND, expr, expr2) : expr2
|
397
|
+
end
|
398
|
+
SQL::JoinOnClause.new(expr, type, table, table_alias)
|
357
399
|
end
|
358
400
|
|
359
|
-
|
360
|
-
clause = "#{@opts[:join]} #{join_type} #{table}#{" #{quoted_table_alias}" if quoted_table_alias != table} ON #{literal(filter_expr(join_conditions))}"
|
361
|
-
opts = {:join => clause, :last_joined_table => table_alias}
|
401
|
+
opts = {:join => (@opts[:join] || []) + [join], :last_joined_table => table_name}
|
362
402
|
opts[:num_dataset_sources] = table_alias_num if table_alias_num
|
363
403
|
clone(opts)
|
364
404
|
end
|
@@ -449,7 +489,7 @@ module Sequel
|
|
449
489
|
clause = (@opts[:having] ? :having : :where)
|
450
490
|
cond = cond.first if cond.size == 1
|
451
491
|
if @opts[clause]
|
452
|
-
clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(block
|
492
|
+
clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(cond, &block)))
|
453
493
|
else
|
454
494
|
raise Error::NoExistingFilter, "No existing filter found."
|
455
495
|
end
|
@@ -478,9 +518,15 @@ module Sequel
|
|
478
518
|
order(*((@opts[:order] || []) + order))
|
479
519
|
end
|
480
520
|
|
481
|
-
# SQL fragment for the
|
482
|
-
#
|
483
|
-
def
|
521
|
+
# SQL fragment for the ordered expression, used in the ORDER BY
|
522
|
+
# clause.
|
523
|
+
def ordered_expression_sql(oe)
|
524
|
+
"#{literal(oe.expression)} #{oe.descending ? 'DESC' : 'ASC'}"
|
525
|
+
end
|
526
|
+
|
527
|
+
# SQL fragment for the qualifed identifier, specifying
|
528
|
+
# a table and a column (or schema and table).
|
529
|
+
def qualified_identifier_sql(qcr)
|
484
530
|
"#{quote_identifier(qcr.table)}.#{quote_identifier(qcr.column)}"
|
485
531
|
end
|
486
532
|
|
@@ -553,7 +599,7 @@ module Sequel
|
|
553
599
|
end
|
554
600
|
|
555
601
|
if join = opts[:join]
|
556
|
-
sql <<
|
602
|
+
join.each{|j| sql << literal(j)}
|
557
603
|
end
|
558
604
|
|
559
605
|
if where = opts[:where]
|
@@ -564,14 +610,14 @@ module Sequel
|
|
564
610
|
sql << " GROUP BY #{column_list(group)}"
|
565
611
|
end
|
566
612
|
|
567
|
-
if order = opts[:order]
|
568
|
-
sql << " ORDER BY #{column_list(order)}"
|
569
|
-
end
|
570
|
-
|
571
613
|
if having = opts[:having]
|
572
614
|
sql << " HAVING #{literal(having)}"
|
573
615
|
end
|
574
616
|
|
617
|
+
if order = opts[:order]
|
618
|
+
sql << " ORDER BY #{column_list(order)}"
|
619
|
+
end
|
620
|
+
|
575
621
|
if limit = opts[:limit]
|
576
622
|
sql << " LIMIT #{limit}"
|
577
623
|
if offset = opts[:offset]
|
@@ -683,7 +729,7 @@ module Sequel
|
|
683
729
|
end
|
684
730
|
|
685
731
|
[:inner, :full_outer, :right_outer, :left_outer].each do |jtype|
|
686
|
-
|
732
|
+
class_eval("def #{jtype}_join(*args, &block); join_table(:#{jtype}, *args, &block) end")
|
687
733
|
end
|
688
734
|
alias_method :join, :inner_join
|
689
735
|
|
@@ -711,7 +757,13 @@ module Sequel
|
|
711
757
|
end
|
712
758
|
|
713
759
|
# SQL fragment based on the expr type. See #filter.
|
714
|
-
def filter_expr(expr)
|
760
|
+
def filter_expr(expr = nil, &block)
|
761
|
+
expr = nil if expr == []
|
762
|
+
if expr && block
|
763
|
+
return SQL::BooleanExpression.new(:AND, filter_expr(expr), filter_expr(block))
|
764
|
+
elsif block
|
765
|
+
expr = block
|
766
|
+
end
|
715
767
|
case expr
|
716
768
|
when Hash
|
717
769
|
SQL::BooleanExpression.from_value_pairs(expr)
|
@@ -722,11 +774,13 @@ module Sequel
|
|
722
774
|
SQL::BooleanExpression.from_value_pairs(expr)
|
723
775
|
end
|
724
776
|
when Proc
|
725
|
-
expr.to_sql(self).lit
|
777
|
+
Sequel.use_parse_tree ? expr.to_sql(self).lit : filter_expr(expr.call)
|
726
778
|
when SQL::NumericExpression, SQL::StringExpression
|
727
779
|
raise(Error, "Invalid SQL Expression type: #{expr.inspect}")
|
728
780
|
when Symbol, SQL::Expression
|
729
781
|
expr
|
782
|
+
when TrueClass, FalseClass
|
783
|
+
SQL::BooleanExpression.new(:NOOP, expr)
|
730
784
|
when String
|
731
785
|
"(#{expr})".lit
|
732
786
|
else
|
@@ -749,16 +803,21 @@ module Sequel
|
|
749
803
|
return nil unless order
|
750
804
|
new_order = []
|
751
805
|
order.map do |f|
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
f.l.desc
|
806
|
+
case f
|
807
|
+
when SQL::OrderedExpression
|
808
|
+
SQL::OrderedExpression.new(f.expression, !f.descending)
|
756
809
|
else
|
757
|
-
f
|
810
|
+
SQL::OrderedExpression.new(f)
|
758
811
|
end
|
759
812
|
end
|
760
813
|
end
|
761
814
|
|
815
|
+
# SQL fragment specifying a JOIN type, converts underscores to
|
816
|
+
# spaces and upcases.
|
817
|
+
def join_type_sql(join_type)
|
818
|
+
"#{join_type.to_s.gsub('_', ' ').upcase} JOIN"
|
819
|
+
end
|
820
|
+
|
762
821
|
# Returns a qualified column name (including a table name) if the column
|
763
822
|
# name isn't already qualified.
|
764
823
|
def qualified_column_name(column, table)
|
@@ -766,7 +825,7 @@ module Sequel
|
|
766
825
|
c_table, column, c_alias = split_symbol(column)
|
767
826
|
schema, table, t_alias = split_symbol(table) if Symbol === table
|
768
827
|
c_table ||= t_alias || table
|
769
|
-
::Sequel::SQL::
|
828
|
+
::Sequel::SQL::QualifiedIdentifier.new(c_table, column)
|
770
829
|
else
|
771
830
|
column
|
772
831
|
end
|