sequel_core 2.0.1 → 2.1.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 +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
|