sequel 3.10.0 → 3.11.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 +68 -0
- data/COPYING +1 -1
- data/README.rdoc +87 -27
- data/bin/sequel +2 -4
- data/doc/association_basics.rdoc +1383 -0
- data/doc/dataset_basics.rdoc +106 -0
- data/doc/opening_databases.rdoc +45 -16
- data/doc/querying.rdoc +210 -0
- data/doc/release_notes/3.11.0.txt +254 -0
- data/doc/virtual_rows.rdoc +217 -31
- data/lib/sequel/adapters/ado.rb +28 -12
- data/lib/sequel/adapters/ado/mssql.rb +33 -1
- data/lib/sequel/adapters/amalgalite.rb +13 -8
- data/lib/sequel/adapters/db2.rb +1 -2
- data/lib/sequel/adapters/dbi.rb +7 -4
- data/lib/sequel/adapters/do.rb +14 -15
- data/lib/sequel/adapters/do/postgres.rb +4 -5
- data/lib/sequel/adapters/do/sqlite.rb +9 -0
- data/lib/sequel/adapters/firebird.rb +5 -10
- data/lib/sequel/adapters/informix.rb +2 -4
- data/lib/sequel/adapters/jdbc.rb +111 -49
- data/lib/sequel/adapters/jdbc/mssql.rb +1 -2
- data/lib/sequel/adapters/jdbc/mysql.rb +11 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +4 -7
- data/lib/sequel/adapters/jdbc/postgresql.rb +8 -1
- data/lib/sequel/adapters/jdbc/sqlite.rb +12 -0
- data/lib/sequel/adapters/mysql.rb +14 -5
- data/lib/sequel/adapters/odbc.rb +2 -4
- data/lib/sequel/adapters/odbc/mssql.rb +2 -4
- data/lib/sequel/adapters/openbase.rb +1 -2
- data/lib/sequel/adapters/oracle.rb +4 -8
- data/lib/sequel/adapters/postgres.rb +4 -11
- data/lib/sequel/adapters/shared/mssql.rb +22 -9
- data/lib/sequel/adapters/shared/mysql.rb +33 -30
- data/lib/sequel/adapters/shared/oracle.rb +0 -5
- data/lib/sequel/adapters/shared/postgres.rb +13 -11
- data/lib/sequel/adapters/shared/sqlite.rb +56 -10
- data/lib/sequel/adapters/sqlite.rb +16 -9
- data/lib/sequel/connection_pool.rb +6 -1
- data/lib/sequel/connection_pool/single.rb +1 -0
- data/lib/sequel/core.rb +6 -1
- data/lib/sequel/database.rb +52 -23
- data/lib/sequel/database/schema_generator.rb +6 -0
- data/lib/sequel/database/schema_methods.rb +5 -5
- data/lib/sequel/database/schema_sql.rb +1 -1
- data/lib/sequel/dataset.rb +4 -190
- data/lib/sequel/dataset/actions.rb +323 -1
- data/lib/sequel/dataset/features.rb +18 -2
- data/lib/sequel/dataset/graph.rb +7 -0
- data/lib/sequel/dataset/misc.rb +119 -0
- data/lib/sequel/dataset/mutation.rb +64 -0
- data/lib/sequel/dataset/prepared_statements.rb +6 -0
- data/lib/sequel/dataset/query.rb +272 -6
- data/lib/sequel/dataset/sql.rb +186 -394
- data/lib/sequel/model.rb +4 -2
- data/lib/sequel/model/associations.rb +31 -14
- data/lib/sequel/model/base.rb +32 -13
- data/lib/sequel/model/exceptions.rb +8 -4
- data/lib/sequel/model/plugins.rb +3 -13
- data/lib/sequel/plugins/active_model.rb +26 -7
- data/lib/sequel/plugins/instance_filters.rb +98 -0
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/optimistic_locking.rb +25 -9
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +26 -0
- data/spec/adapters/mysql_spec.rb +33 -4
- data/spec/adapters/postgres_spec.rb +24 -1
- data/spec/adapters/spec_helper.rb +6 -0
- data/spec/adapters/sqlite_spec.rb +28 -0
- data/spec/core/connection_pool_spec.rb +17 -5
- data/spec/core/database_spec.rb +101 -1
- data/spec/core/dataset_spec.rb +42 -4
- data/spec/core/schema_spec.rb +13 -0
- data/spec/extensions/active_model_spec.rb +34 -11
- data/spec/extensions/caching_spec.rb +2 -0
- data/spec/extensions/instance_filters_spec.rb +55 -0
- data/spec/extensions/spec_helper.rb +2 -0
- data/spec/integration/dataset_test.rb +12 -1
- data/spec/integration/model_test.rb +12 -0
- data/spec/integration/plugin_test.rb +61 -1
- data/spec/integration/schema_test.rb +14 -3
- data/spec/model/base_spec.rb +27 -0
- data/spec/model/plugins_spec.rb +0 -22
- data/spec/model/record_spec.rb +32 -1
- data/spec/model/spec_helper.rb +2 -0
- metadata +14 -3
- data/lib/sequel/dataset/convenience.rb +0 -326
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -1,29 +1,197 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
|
+
# ---------------------
|
4
|
+
# :section: User Methods relating to SQL Creation
|
5
|
+
# These are methods you can call to see what SQL will be generated by the dataset.
|
6
|
+
# ---------------------
|
7
|
+
|
8
|
+
# Formats a DELETE statement using the given options and dataset options.
|
9
|
+
#
|
10
|
+
# dataset.filter{|o| o.price >= 100}.delete_sql #=>
|
11
|
+
# "DELETE FROM items WHERE (price >= 100)"
|
12
|
+
def delete_sql
|
13
|
+
return static_sql(opts[:sql]) if opts[:sql]
|
14
|
+
check_modification_allowed!
|
15
|
+
clause_sql(:delete)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns an EXISTS clause for the dataset as a LiteralString.
|
19
|
+
#
|
20
|
+
# DB.select(1).where(DB[:items].exists).sql
|
21
|
+
# #=> "SELECT 1 WHERE (EXISTS (SELECT * FROM items))"
|
22
|
+
def exists
|
23
|
+
LiteralString.new("EXISTS (#{select_sql})")
|
24
|
+
end
|
25
|
+
|
26
|
+
# Formats an INSERT statement using the given values. The API is a little
|
27
|
+
# complex, and best explained by example:
|
28
|
+
#
|
29
|
+
# # Default values
|
30
|
+
# DB[:items].insert_sql #=> 'INSERT INTO items DEFAULT VALUES'
|
31
|
+
# DB[:items].insert_sql({}) #=> 'INSERT INTO items DEFAULT VALUES'
|
32
|
+
# # Values without columns
|
33
|
+
# DB[:items].insert_sql(1,2,3) #=> 'INSERT INTO items VALUES (1, 2, 3)'
|
34
|
+
# DB[:items].insert_sql([1,2,3]) #=> 'INSERT INTO items VALUES (1, 2, 3)'
|
35
|
+
# # Values with columns
|
36
|
+
# DB[:items].insert_sql([:a, :b], [1,2]) #=> 'INSERT INTO items (a, b) VALUES (1, 2)'
|
37
|
+
# DB[:items].insert_sql(:a => 1, :b => 2) #=> 'INSERT INTO items (a, b) VALUES (1, 2)'
|
38
|
+
# # Using a subselect
|
39
|
+
# DB[:items].insert_sql(DB[:old_items]) #=> 'INSERT INTO items SELECT * FROM old_items
|
40
|
+
# # Using a subselect with columns
|
41
|
+
# DB[:items].insert_sql([:a, :b], DB[:old_items]) #=> 'INSERT INTO items (a, b) SELECT * FROM old_items
|
42
|
+
def insert_sql(*values)
|
43
|
+
return static_sql(@opts[:sql]) if @opts[:sql]
|
44
|
+
|
45
|
+
check_modification_allowed!
|
46
|
+
|
47
|
+
columns = []
|
48
|
+
|
49
|
+
case values.size
|
50
|
+
when 0
|
51
|
+
return insert_sql({})
|
52
|
+
when 1
|
53
|
+
case vals = values.at(0)
|
54
|
+
when Hash
|
55
|
+
vals = @opts[:defaults].merge(vals) if @opts[:defaults]
|
56
|
+
vals = vals.merge(@opts[:overrides]) if @opts[:overrides]
|
57
|
+
values = []
|
58
|
+
vals.each do |k,v|
|
59
|
+
columns << k
|
60
|
+
values << v
|
61
|
+
end
|
62
|
+
when Dataset, Array, LiteralString
|
63
|
+
values = vals
|
64
|
+
else
|
65
|
+
if vals.respond_to?(:values) && (v = vals.values).is_a?(Hash)
|
66
|
+
return insert_sql(v)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
when 2
|
70
|
+
if (v0 = values.at(0)).is_a?(Array) && ((v1 = values.at(1)).is_a?(Array) || v1.is_a?(Dataset) || v1.is_a?(LiteralString))
|
71
|
+
columns, values = v0, v1
|
72
|
+
raise(Error, "Different number of values and columns given to insert_sql") if values.is_a?(Array) and columns.length != values.length
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
columns = columns.map{|k| literal(String === k ? k.to_sym : k)}
|
77
|
+
clone(:columns=>columns, :values=>values)._insert_sql
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a literal representation of a value to be used as part
|
81
|
+
# of an SQL expression.
|
82
|
+
#
|
83
|
+
# dataset.literal("abc'def\\") #=> "'abc''def\\\\'"
|
84
|
+
# dataset.literal(:items__id) #=> "items.id"
|
85
|
+
# dataset.literal([1, 2, 3]) => "(1, 2, 3)"
|
86
|
+
# dataset.literal(DB[:items]) => "(SELECT * FROM items)"
|
87
|
+
# dataset.literal(:x + 1 > :y) => "((x + 1) > y)"
|
88
|
+
#
|
89
|
+
# If an unsupported object is given, an exception is raised.
|
90
|
+
def literal(v)
|
91
|
+
case v
|
92
|
+
when String
|
93
|
+
return v if v.is_a?(LiteralString)
|
94
|
+
v.is_a?(SQL::Blob) ? literal_blob(v) : literal_string(v)
|
95
|
+
when Symbol
|
96
|
+
literal_symbol(v)
|
97
|
+
when Integer
|
98
|
+
literal_integer(v)
|
99
|
+
when Hash
|
100
|
+
literal_hash(v)
|
101
|
+
when SQL::Expression
|
102
|
+
literal_expression(v)
|
103
|
+
when Float
|
104
|
+
literal_float(v)
|
105
|
+
when BigDecimal
|
106
|
+
literal_big_decimal(v)
|
107
|
+
when NilClass
|
108
|
+
literal_nil
|
109
|
+
when TrueClass
|
110
|
+
literal_true
|
111
|
+
when FalseClass
|
112
|
+
literal_false
|
113
|
+
when Array
|
114
|
+
literal_array(v)
|
115
|
+
when Time
|
116
|
+
literal_time(v)
|
117
|
+
when DateTime
|
118
|
+
literal_datetime(v)
|
119
|
+
when Date
|
120
|
+
literal_date(v)
|
121
|
+
when Dataset
|
122
|
+
literal_dataset(v)
|
123
|
+
else
|
124
|
+
literal_other(v)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns an array of insert statements for inserting multiple records.
|
129
|
+
# This method is used by #multi_insert to format insert statements and
|
130
|
+
# expects a keys array and and an array of value arrays.
|
131
|
+
#
|
132
|
+
# This method should be overridden by descendants if the support
|
133
|
+
# inserting multiple records in a single SQL statement.
|
134
|
+
def multi_insert_sql(columns, values)
|
135
|
+
values.map{|r| insert_sql(columns, r)}
|
136
|
+
end
|
137
|
+
|
138
|
+
# Formats a SELECT statement
|
139
|
+
#
|
140
|
+
# dataset.select_sql # => "SELECT * FROM items"
|
141
|
+
def select_sql
|
142
|
+
return static_sql(@opts[:sql]) if @opts[:sql]
|
143
|
+
clause_sql(:select)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Same as select_sql, not aliased directly to make subclassing simpler.
|
147
|
+
def sql
|
148
|
+
select_sql
|
149
|
+
end
|
150
|
+
|
151
|
+
# SQL query to truncate the table
|
152
|
+
def truncate_sql
|
153
|
+
if opts[:sql]
|
154
|
+
static_sql(opts[:sql])
|
155
|
+
else
|
156
|
+
check_modification_allowed!
|
157
|
+
raise(InvalidOperation, "Can't truncate filtered datasets") if opts[:where]
|
158
|
+
_truncate_sql(source_list(opts[:from]))
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Formats an UPDATE statement using the given values.
|
163
|
+
#
|
164
|
+
# dataset.update_sql(:price => 100, :category => 'software') #=>
|
165
|
+
# "UPDATE items SET price = 100, category = 'software'"
|
166
|
+
#
|
167
|
+
# Raises an error if the dataset is grouped or includes more
|
168
|
+
# than one table.
|
169
|
+
def update_sql(values = {})
|
170
|
+
return static_sql(opts[:sql]) if opts[:sql]
|
171
|
+
check_modification_allowed!
|
172
|
+
clone(:values=>values)._update_sql
|
173
|
+
end
|
174
|
+
|
175
|
+
# ---------------------
|
176
|
+
# :section: Internal Methods relating to SQL Creation
|
177
|
+
# These methods, while public, are not designed to be used directly by the end user.
|
178
|
+
# ---------------------
|
179
|
+
|
3
180
|
# Given a type (e.g. select) and an array of clauses,
|
4
181
|
# return an array of methods to call to build the SQL string.
|
5
182
|
def self.clause_methods(type, clauses)
|
6
183
|
clauses.map{|clause| :"#{type}_#{clause}_sql"}.freeze
|
7
184
|
end
|
8
185
|
|
9
|
-
# These symbols have _join methods created (e.g. inner_join) that
|
10
|
-
# call join_table with the symbol, passing along the arguments and
|
11
|
-
# block from the method call.
|
12
|
-
CONDITIONED_JOIN_TYPES = [:inner, :full_outer, :right_outer, :left_outer, :full, :right, :left]
|
13
|
-
|
14
|
-
# These symbols have _join methods created (e.g. natural_join) that
|
15
|
-
# call join_table with the symbol. They only accept a single table
|
16
|
-
# argument which is passed to join_table, and they raise an error
|
17
|
-
# if called with a block.
|
18
|
-
UNCONDITIONED_JOIN_TYPES = [:natural, :natural_left, :natural_right, :natural_full, :cross]
|
19
|
-
|
20
186
|
AND_SEPARATOR = " AND ".freeze
|
21
187
|
BOOL_FALSE = "'f'".freeze
|
22
188
|
BOOL_TRUE = "'t'".freeze
|
189
|
+
COMMA_SEPARATOR = ', '.freeze
|
23
190
|
COLUMN_REF_RE1 = /\A([\w ]+)__([\w ]+)___([\w ]+)\z/.freeze
|
24
191
|
COLUMN_REF_RE2 = /\A([\w ]+)___([\w ]+)\z/.freeze
|
25
192
|
COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
|
26
193
|
COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :compounds]
|
194
|
+
COUNT_OF_ALL_AS_COUNT = SQL::Function.new(:count, LiteralString.new('*'.freeze)).as(:count)
|
27
195
|
DATASET_ALIAS_BASE_NAME = 't'.freeze
|
28
196
|
FOR_UPDATE = ' FOR UPDATE'.freeze
|
29
197
|
IS_LITERALS = {nil=>'NULL'.freeze, true=>'TRUE'.freeze, false=>'FALSE'.freeze}.freeze
|
@@ -39,7 +207,7 @@ module Sequel
|
|
39
207
|
TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S%N%z'".freeze
|
40
208
|
STANDARD_TIMESTAMP_FORMAT = "TIMESTAMP #{TIMESTAMP_FORMAT}".freeze
|
41
209
|
TWO_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::TWO_ARITY_OPERATORS
|
42
|
-
WILDCARD = '*'.freeze
|
210
|
+
WILDCARD = LiteralString.new('*').freeze
|
43
211
|
SQL_WITH = "WITH ".freeze
|
44
212
|
|
45
213
|
# SQL fragment for the aliased expression
|
@@ -149,139 +317,12 @@ module Sequel
|
|
149
317
|
constant.to_s
|
150
318
|
end
|
151
319
|
|
152
|
-
# Returns the number of records in the dataset.
|
153
|
-
def count
|
154
|
-
aggregate_dataset.get{COUNT(:*){}.as(count)}.to_i
|
155
|
-
end
|
156
|
-
|
157
|
-
# Formats a DELETE statement using the given options and dataset options.
|
158
|
-
#
|
159
|
-
# dataset.filter{|o| o.price >= 100}.delete_sql #=>
|
160
|
-
# "DELETE FROM items WHERE (price >= 100)"
|
161
|
-
def delete_sql
|
162
|
-
return static_sql(opts[:sql]) if opts[:sql]
|
163
|
-
check_modification_allowed!
|
164
|
-
clause_sql(:delete)
|
165
|
-
end
|
166
|
-
|
167
|
-
# Returns an EXISTS clause for the dataset as a LiteralString.
|
168
|
-
#
|
169
|
-
# DB.select(1).where(DB[:items].exists).sql
|
170
|
-
# #=> "SELECT 1 WHERE (EXISTS (SELECT * FROM items))"
|
171
|
-
def exists
|
172
|
-
LiteralString.new("EXISTS (#{select_sql})")
|
173
|
-
end
|
174
|
-
|
175
|
-
# The first source (primary table) for this dataset. If the dataset doesn't
|
176
|
-
# have a table, raises an error. If the table is aliased, returns the aliased name.
|
177
|
-
def first_source_alias
|
178
|
-
source = @opts[:from]
|
179
|
-
if source.nil? || source.empty?
|
180
|
-
raise Error, 'No source specified for query'
|
181
|
-
end
|
182
|
-
case s = source.first
|
183
|
-
when SQL::AliasedExpression
|
184
|
-
s.aliaz
|
185
|
-
when Symbol
|
186
|
-
sch, table, aliaz = split_symbol(s)
|
187
|
-
aliaz ? aliaz.to_sym : s
|
188
|
-
else
|
189
|
-
s
|
190
|
-
end
|
191
|
-
end
|
192
|
-
alias first_source first_source_alias
|
193
|
-
|
194
|
-
# The first source (primary table) for this dataset. If the dataset doesn't
|
195
|
-
# have a table, raises an error. If the table is aliased, returns the original
|
196
|
-
# table, not the alias
|
197
|
-
def first_source_table
|
198
|
-
source = @opts[:from]
|
199
|
-
if source.nil? || source.empty?
|
200
|
-
raise Error, 'No source specified for query'
|
201
|
-
end
|
202
|
-
case s = source.first
|
203
|
-
when SQL::AliasedExpression
|
204
|
-
s.expression
|
205
|
-
when Symbol
|
206
|
-
sch, table, aliaz = split_symbol(s)
|
207
|
-
aliaz ? (sch ? SQL::QualifiedIdentifier.new(sch, table) : table.to_sym) : s
|
208
|
-
else
|
209
|
-
s
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
320
|
# SQL fragment specifying an SQL function call
|
214
321
|
def function_sql(f)
|
215
322
|
args = f.args
|
216
323
|
"#{f.f}#{args.empty? ? '()' : literal(args)}"
|
217
324
|
end
|
218
325
|
|
219
|
-
# Inserts multiple values. If a block is given it is invoked for each
|
220
|
-
# item in the given array before inserting it. See #multi_insert as
|
221
|
-
# a possible faster version that inserts multiple records in one
|
222
|
-
# SQL statement.
|
223
|
-
def insert_multiple(array, &block)
|
224
|
-
if block
|
225
|
-
array.each {|i| insert(block[i])}
|
226
|
-
else
|
227
|
-
array.each {|i| insert(i)}
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
# Formats an INSERT statement using the given values. The API is a little
|
232
|
-
# complex, and best explained by example:
|
233
|
-
#
|
234
|
-
# # Default values
|
235
|
-
# DB[:items].insert_sql #=> 'INSERT INTO items DEFAULT VALUES'
|
236
|
-
# DB[:items].insert_sql({}) #=> 'INSERT INTO items DEFAULT VALUES'
|
237
|
-
# # Values without columns
|
238
|
-
# DB[:items].insert_sql(1,2,3) #=> 'INSERT INTO items VALUES (1, 2, 3)'
|
239
|
-
# DB[:items].insert_sql([1,2,3]) #=> 'INSERT INTO items VALUES (1, 2, 3)'
|
240
|
-
# # Values with columns
|
241
|
-
# DB[:items].insert_sql([:a, :b], [1,2]) #=> 'INSERT INTO items (a, b) VALUES (1, 2)'
|
242
|
-
# DB[:items].insert_sql(:a => 1, :b => 2) #=> 'INSERT INTO items (a, b) VALUES (1, 2)'
|
243
|
-
# # Using a subselect
|
244
|
-
# DB[:items].insert_sql(DB[:old_items]) #=> 'INSERT INTO items SELECT * FROM old_items
|
245
|
-
# # Using a subselect with columns
|
246
|
-
# DB[:items].insert_sql([:a, :b], DB[:old_items]) #=> 'INSERT INTO items (a, b) SELECT * FROM old_items
|
247
|
-
def insert_sql(*values)
|
248
|
-
return static_sql(@opts[:sql]) if @opts[:sql]
|
249
|
-
|
250
|
-
check_modification_allowed!
|
251
|
-
|
252
|
-
columns = []
|
253
|
-
|
254
|
-
case values.size
|
255
|
-
when 0
|
256
|
-
return insert_sql({})
|
257
|
-
when 1
|
258
|
-
case vals = values.at(0)
|
259
|
-
when Hash
|
260
|
-
vals = @opts[:defaults].merge(vals) if @opts[:defaults]
|
261
|
-
vals = vals.merge(@opts[:overrides]) if @opts[:overrides]
|
262
|
-
values = []
|
263
|
-
vals.each do |k,v|
|
264
|
-
columns << k
|
265
|
-
values << v
|
266
|
-
end
|
267
|
-
when Dataset, Array, LiteralString
|
268
|
-
values = vals
|
269
|
-
else
|
270
|
-
if vals.respond_to?(:values) && (v = vals.values).is_a?(Hash)
|
271
|
-
return insert_sql(v)
|
272
|
-
end
|
273
|
-
end
|
274
|
-
when 2
|
275
|
-
if (v0 = values.at(0)).is_a?(Array) && ((v1 = values.at(1)).is_a?(Array) || v1.is_a?(Dataset) || v1.is_a?(LiteralString))
|
276
|
-
columns, values = v0, v1
|
277
|
-
raise(Error, "Different number of values and columns given to insert_sql") if values.is_a?(Array) and columns.length != values.length
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
columns = columns.map{|k| literal(String === k ? k.to_sym : k)}
|
282
|
-
clone(:columns=>columns, :values=>values)._insert_sql
|
283
|
-
end
|
284
|
-
|
285
326
|
# SQL fragment specifying a JOIN clause without ON or USING.
|
286
327
|
def join_clause_sql(jc)
|
287
328
|
table = jc.table
|
@@ -300,150 +341,6 @@ module Sequel
|
|
300
341
|
def join_using_clause_sql(jc)
|
301
342
|
"#{join_clause_sql(jc)} USING (#{column_list(jc.using)})"
|
302
343
|
end
|
303
|
-
|
304
|
-
# Returns a joined dataset. Uses the following arguments:
|
305
|
-
#
|
306
|
-
# * type - The type of join to do (e.g. :inner)
|
307
|
-
# * table - Depends on type:
|
308
|
-
# * Dataset - a subselect is performed with an alias of tN for some value of N
|
309
|
-
# * Model (or anything responding to :table_name) - table.table_name
|
310
|
-
# * String, Symbol: table
|
311
|
-
# * expr - specifies conditions, depends on type:
|
312
|
-
# * Hash, Array with all two pairs - Assumes key (1st arg) is column of joined table (unless already
|
313
|
-
# qualified), and value (2nd arg) is column of the last joined or primary table (or the
|
314
|
-
# :implicit_qualifier option).
|
315
|
-
# To specify multiple conditions on a single joined table column, you must use an array.
|
316
|
-
# Uses a JOIN with an ON clause.
|
317
|
-
# * Array - If all members of the array are symbols, considers them as columns and
|
318
|
-
# uses a JOIN with a USING clause. Most databases will remove duplicate columns from
|
319
|
-
# the result set if this is used.
|
320
|
-
# * nil - If a block is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
|
321
|
-
# or CROSS join. If a block is given, uses a ON clause based on the block, see below.
|
322
|
-
# * Everything else - pretty much the same as a using the argument in a call to filter,
|
323
|
-
# so strings are considered literal, symbols specify boolean columns, and blockless
|
324
|
-
# filter expressions can be used. Uses a JOIN with an ON clause.
|
325
|
-
# * options - a hash of options, with any of the following keys:
|
326
|
-
# * :table_alias - the name of the table's alias when joining, necessary for joining
|
327
|
-
# to the same table more than once. No alias is used by default.
|
328
|
-
# * :implicit_qualifier - The name to use for qualifying implicit conditions. By default,
|
329
|
-
# the last joined or primary table is used.
|
330
|
-
# * block - The block argument should only be given if a JOIN with an ON clause is used,
|
331
|
-
# in which case it yields the table alias/name for the table currently being joined,
|
332
|
-
# the table alias/name for the last joined (or first table), and an array of previous
|
333
|
-
# SQL::JoinClause.
|
334
|
-
def join_table(type, table, expr=nil, options={}, &block)
|
335
|
-
using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
|
336
|
-
if using_join && !supports_join_using?
|
337
|
-
h = {}
|
338
|
-
expr.each{|s| h[s] = s}
|
339
|
-
return join_table(type, table, h, options)
|
340
|
-
end
|
341
|
-
|
342
|
-
case options
|
343
|
-
when Hash
|
344
|
-
table_alias = options[:table_alias]
|
345
|
-
last_alias = options[:implicit_qualifier]
|
346
|
-
when Symbol, String, SQL::Identifier
|
347
|
-
table_alias = options
|
348
|
-
last_alias = nil
|
349
|
-
else
|
350
|
-
raise Error, "invalid options format for join_table: #{options.inspect}"
|
351
|
-
end
|
352
|
-
|
353
|
-
if Dataset === table
|
354
|
-
if table_alias.nil?
|
355
|
-
table_alias_num = (@opts[:num_dataset_sources] || 0) + 1
|
356
|
-
table_alias = dataset_alias(table_alias_num)
|
357
|
-
end
|
358
|
-
table_name = table_alias
|
359
|
-
else
|
360
|
-
table = table.table_name if table.respond_to?(:table_name)
|
361
|
-
table_name = table_alias || table
|
362
|
-
end
|
363
|
-
|
364
|
-
join = if expr.nil? and !block_given?
|
365
|
-
SQL::JoinClause.new(type, table, table_alias)
|
366
|
-
elsif using_join
|
367
|
-
raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block_given?
|
368
|
-
SQL::JoinUsingClause.new(expr, type, table, table_alias)
|
369
|
-
else
|
370
|
-
last_alias ||= @opts[:last_joined_table] || first_source_alias
|
371
|
-
if Sequel.condition_specifier?(expr)
|
372
|
-
expr = expr.collect do |k, v|
|
373
|
-
k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
|
374
|
-
v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
|
375
|
-
[k,v]
|
376
|
-
end
|
377
|
-
end
|
378
|
-
if block_given?
|
379
|
-
expr2 = yield(table_name, last_alias, @opts[:join] || [])
|
380
|
-
expr = expr ? SQL::BooleanExpression.new(:AND, expr, expr2) : expr2
|
381
|
-
end
|
382
|
-
SQL::JoinOnClause.new(expr, type, table, table_alias)
|
383
|
-
end
|
384
|
-
|
385
|
-
opts = {:join => (@opts[:join] || []) + [join], :last_joined_table => table_name}
|
386
|
-
opts[:num_dataset_sources] = table_alias_num if table_alias_num
|
387
|
-
clone(opts)
|
388
|
-
end
|
389
|
-
|
390
|
-
# Returns a literal representation of a value to be used as part
|
391
|
-
# of an SQL expression.
|
392
|
-
#
|
393
|
-
# dataset.literal("abc'def\\") #=> "'abc''def\\\\'"
|
394
|
-
# dataset.literal(:items__id) #=> "items.id"
|
395
|
-
# dataset.literal([1, 2, 3]) => "(1, 2, 3)"
|
396
|
-
# dataset.literal(DB[:items]) => "(SELECT * FROM items)"
|
397
|
-
# dataset.literal(:x + 1 > :y) => "((x + 1) > y)"
|
398
|
-
#
|
399
|
-
# If an unsupported object is given, an exception is raised.
|
400
|
-
def literal(v)
|
401
|
-
case v
|
402
|
-
when String
|
403
|
-
return v if v.is_a?(LiteralString)
|
404
|
-
v.is_a?(SQL::Blob) ? literal_blob(v) : literal_string(v)
|
405
|
-
when Symbol
|
406
|
-
literal_symbol(v)
|
407
|
-
when Integer
|
408
|
-
literal_integer(v)
|
409
|
-
when Hash
|
410
|
-
literal_hash(v)
|
411
|
-
when SQL::Expression
|
412
|
-
literal_expression(v)
|
413
|
-
when Float
|
414
|
-
literal_float(v)
|
415
|
-
when BigDecimal
|
416
|
-
literal_big_decimal(v)
|
417
|
-
when NilClass
|
418
|
-
literal_nil
|
419
|
-
when TrueClass
|
420
|
-
literal_true
|
421
|
-
when FalseClass
|
422
|
-
literal_false
|
423
|
-
when Array
|
424
|
-
literal_array(v)
|
425
|
-
when Time
|
426
|
-
literal_time(v)
|
427
|
-
when DateTime
|
428
|
-
literal_datetime(v)
|
429
|
-
when Date
|
430
|
-
literal_date(v)
|
431
|
-
when Dataset
|
432
|
-
literal_dataset(v)
|
433
|
-
else
|
434
|
-
literal_other(v)
|
435
|
-
end
|
436
|
-
end
|
437
|
-
|
438
|
-
# Returns an array of insert statements for inserting multiple records.
|
439
|
-
# This method is used by #multi_insert to format insert statements and
|
440
|
-
# expects a keys array and and an array of value arrays.
|
441
|
-
#
|
442
|
-
# This method should be overridden by descendants if the support
|
443
|
-
# inserting multiple records in a single SQL statement.
|
444
|
-
def multi_insert_sql(columns, values)
|
445
|
-
values.map{|r| insert_sql(columns, r)}
|
446
|
-
end
|
447
344
|
|
448
345
|
# SQL fragment for NegativeBooleanConstants
|
449
346
|
def negative_boolean_constant_sql(constant)
|
@@ -476,35 +373,6 @@ module Sequel
|
|
476
373
|
[qcr.table, qcr.column].map{|x| [SQL::QualifiedIdentifier, SQL::Identifier, Symbol].any?{|c| x.is_a?(c)} ? literal(x) : quote_identifier(x)}.join('.')
|
477
374
|
end
|
478
375
|
|
479
|
-
# Qualify to the given table, or first source if not table is given.
|
480
|
-
def qualify(table=first_source)
|
481
|
-
qualify_to(table)
|
482
|
-
end
|
483
|
-
|
484
|
-
# Return a copy of the dataset with unqualified identifiers in the
|
485
|
-
# SELECT, WHERE, GROUP, HAVING, and ORDER clauses qualified by the
|
486
|
-
# given table. If no columns are currently selected, select all
|
487
|
-
# columns of the given table.
|
488
|
-
def qualify_to(table)
|
489
|
-
o = @opts
|
490
|
-
return clone if o[:sql]
|
491
|
-
h = {}
|
492
|
-
(o.keys & QUALIFY_KEYS).each do |k|
|
493
|
-
h[k] = qualified_expression(o[k], table)
|
494
|
-
end
|
495
|
-
h[:select] = [SQL::ColumnAll.new(table)] if !o[:select] || o[:select].empty?
|
496
|
-
clone(h)
|
497
|
-
end
|
498
|
-
|
499
|
-
# Qualify the dataset to its current first source. This is useful
|
500
|
-
# if you have unqualified identifiers in the query that all refer to
|
501
|
-
# the first source, and you want to join to another table which
|
502
|
-
# has columns with the same name as columns in the current dataset.
|
503
|
-
# See qualify_to.
|
504
|
-
def qualify_to_first_source
|
505
|
-
qualify_to(first_source)
|
506
|
-
end
|
507
|
-
|
508
376
|
# Adds quoting to identifiers (columns and tables). If identifiers are not
|
509
377
|
# being quoted, returns name as a string. If identifiers are being quoted
|
510
378
|
# quote the name with quoted_identifier.
|
@@ -548,55 +416,10 @@ module Sequel
|
|
548
416
|
end
|
549
417
|
end
|
550
418
|
|
551
|
-
# Formats a SELECT statement
|
552
|
-
#
|
553
|
-
# dataset.select_sql # => "SELECT * FROM items"
|
554
|
-
def select_sql
|
555
|
-
return static_sql(@opts[:sql]) if @opts[:sql]
|
556
|
-
clause_sql(:select)
|
557
|
-
end
|
558
|
-
|
559
|
-
# Same as select_sql, not aliased directly to make subclassing simpler.
|
560
|
-
def sql
|
561
|
-
select_sql
|
562
|
-
end
|
563
|
-
|
564
419
|
# SQL fragment for specifying subscripts (SQL arrays)
|
565
420
|
def subscript_sql(s)
|
566
421
|
"#{literal(s.f)}[#{expression_list(s.sub)}]"
|
567
422
|
end
|
568
|
-
|
569
|
-
# SQL query to truncate the table
|
570
|
-
def truncate_sql
|
571
|
-
if opts[:sql]
|
572
|
-
static_sql(opts[:sql])
|
573
|
-
else
|
574
|
-
check_modification_allowed!
|
575
|
-
raise(InvalidOperation, "Can't truncate filtered datasets") if opts[:where]
|
576
|
-
_truncate_sql(source_list(opts[:from]))
|
577
|
-
end
|
578
|
-
end
|
579
|
-
|
580
|
-
# Formats an UPDATE statement using the given values.
|
581
|
-
#
|
582
|
-
# dataset.update_sql(:price => 100, :category => 'software') #=>
|
583
|
-
# "UPDATE items SET price = 100, category = 'software'"
|
584
|
-
#
|
585
|
-
# Raises an error if the dataset is grouped or includes more
|
586
|
-
# than one table.
|
587
|
-
def update_sql(values = {})
|
588
|
-
return static_sql(opts[:sql]) if opts[:sql]
|
589
|
-
check_modification_allowed!
|
590
|
-
clone(:values=>values)._update_sql
|
591
|
-
end
|
592
|
-
|
593
|
-
# Add a condition to the WHERE clause. See #filter for argument types.
|
594
|
-
#
|
595
|
-
# dataset.group(:a).having(:a).filter(:b) # SELECT * FROM items GROUP BY a HAVING a AND b
|
596
|
-
# dataset.group(:a).having(:a).where(:b) # SELECT * FROM items WHERE b GROUP BY a HAVING a
|
597
|
-
def where(*cond, &block)
|
598
|
-
_filter(:where, *cond, &block)
|
599
|
-
end
|
600
423
|
|
601
424
|
# The SQL fragment for the given window's options.
|
602
425
|
def window_sql(opts)
|
@@ -621,43 +444,6 @@ module Sequel
|
|
621
444
|
def window_function_sql(function, window)
|
622
445
|
"#{literal(function)} OVER #{literal(window)}"
|
623
446
|
end
|
624
|
-
|
625
|
-
# Add a simple common table expression (CTE) with the given name and a dataset that defines the CTE.
|
626
|
-
# A common table expression acts as an inline view for the query.
|
627
|
-
# Options:
|
628
|
-
# * :args - Specify the arguments/columns for the CTE, should be an array of symbols.
|
629
|
-
# * :recursive - Specify that this is a recursive CTE
|
630
|
-
def with(name, dataset, opts={})
|
631
|
-
raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
|
632
|
-
clone(:with=>(@opts[:with]||[]) + [opts.merge(:name=>name, :dataset=>dataset)])
|
633
|
-
end
|
634
|
-
|
635
|
-
# Add a recursive common table expression (CTE) with the given name, a dataset that
|
636
|
-
# defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
|
637
|
-
# of the CTE. Options:
|
638
|
-
# * :args - Specify the arguments/columns for the CTE, should be an array of symbols.
|
639
|
-
# * :union_all - Set to false to use UNION instead of UNION ALL combining the nonrecursive and recursive parts.
|
640
|
-
def with_recursive(name, nonrecursive, recursive, opts={})
|
641
|
-
raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
|
642
|
-
clone(:with=>(@opts[:with]||[]) + [opts.merge(:recursive=>true, :name=>name, :dataset=>nonrecursive.union(recursive, {:all=>opts[:union_all] != false, :from_self=>false}))])
|
643
|
-
end
|
644
|
-
|
645
|
-
# Returns a copy of the dataset with the static SQL used. This is useful if you want
|
646
|
-
# to keep the same row_proc/graph, but change the SQL used to custom SQL.
|
647
|
-
#
|
648
|
-
# dataset.with_sql('SELECT * FROM foo') # SELECT * FROM foo
|
649
|
-
def with_sql(sql, *args)
|
650
|
-
sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
|
651
|
-
clone(:sql=>sql)
|
652
|
-
end
|
653
|
-
|
654
|
-
CONDITIONED_JOIN_TYPES.each do |jtype|
|
655
|
-
class_eval("def #{jtype}_join(*args, &block); join_table(:#{jtype}, *args, &block) end", __FILE__, __LINE__)
|
656
|
-
end
|
657
|
-
UNCONDITIONED_JOIN_TYPES.each do |jtype|
|
658
|
-
class_eval("def #{jtype}_join(table); raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if block_given?; join_table(:#{jtype}, table) end", __FILE__, __LINE__)
|
659
|
-
end
|
660
|
-
alias join inner_join
|
661
447
|
|
662
448
|
protected
|
663
449
|
|
@@ -816,6 +602,12 @@ module Sequel
|
|
816
602
|
def identifier_list(columns)
|
817
603
|
columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
|
818
604
|
end
|
605
|
+
|
606
|
+
# Modify the identifier returned from the database based on the
|
607
|
+
# identifier_output_method.
|
608
|
+
def input_identifier(v)
|
609
|
+
(i = identifier_input_method) ? v.to_s.send(i) : v.to_s
|
610
|
+
end
|
819
611
|
|
820
612
|
# SQL fragment specifying the table to insert INTO
|
821
613
|
def insert_into_sql(sql)
|