sequel 3.10.0 → 3.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|