sequel 2.7.1 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +56 -0
- data/README +1 -0
- data/Rakefile +1 -1
- data/lib/sequel_core.rb +9 -16
- data/lib/sequel_core/adapters/ado.rb +6 -15
- data/lib/sequel_core/adapters/db2.rb +8 -10
- data/lib/sequel_core/adapters/dbi.rb +6 -4
- data/lib/sequel_core/adapters/informix.rb +21 -22
- data/lib/sequel_core/adapters/jdbc.rb +69 -10
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +1 -0
- data/lib/sequel_core/adapters/mysql.rb +81 -13
- data/lib/sequel_core/adapters/odbc.rb +32 -4
- data/lib/sequel_core/adapters/openbase.rb +6 -5
- data/lib/sequel_core/adapters/oracle.rb +23 -7
- data/lib/sequel_core/adapters/postgres.rb +42 -32
- data/lib/sequel_core/adapters/shared/mssql.rb +37 -62
- data/lib/sequel_core/adapters/shared/mysql.rb +22 -7
- data/lib/sequel_core/adapters/shared/oracle.rb +27 -48
- data/lib/sequel_core/adapters/shared/postgres.rb +64 -43
- data/lib/sequel_core/adapters/shared/progress.rb +31 -0
- data/lib/sequel_core/adapters/shared/sqlite.rb +15 -4
- data/lib/sequel_core/adapters/sqlite.rb +6 -14
- data/lib/sequel_core/connection_pool.rb +47 -13
- data/lib/sequel_core/database.rb +60 -35
- data/lib/sequel_core/database/schema.rb +4 -4
- data/lib/sequel_core/dataset.rb +12 -3
- data/lib/sequel_core/dataset/convenience.rb +4 -13
- data/lib/sequel_core/dataset/prepared_statements.rb +30 -28
- data/lib/sequel_core/dataset/sql.rb +144 -85
- data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
- data/lib/sequel_core/dataset/unsupported.rb +31 -0
- data/lib/sequel_core/exceptions.rb +6 -0
- data/lib/sequel_core/schema/generator.rb +4 -3
- data/lib/sequel_core/schema/sql.rb +41 -23
- data/lib/sequel_core/sql.rb +29 -1
- data/lib/sequel_model/associations.rb +1 -1
- data/lib/sequel_model/record.rb +31 -28
- data/spec/adapters/mysql_spec.rb +37 -4
- data/spec/adapters/oracle_spec.rb +26 -4
- data/spec/adapters/sqlite_spec.rb +7 -0
- data/spec/integration/prepared_statement_test.rb +24 -0
- data/spec/integration/schema_test.rb +1 -1
- data/spec/sequel_core/connection_pool_spec.rb +49 -2
- data/spec/sequel_core/core_sql_spec.rb +9 -2
- data/spec/sequel_core/database_spec.rb +64 -14
- data/spec/sequel_core/dataset_spec.rb +105 -7
- data/spec/sequel_core/schema_spec.rb +40 -12
- data/spec/sequel_core/spec_helper.rb +1 -0
- data/spec/sequel_model/spec_helper.rb +1 -0
- metadata +6 -3
data/lib/sequel_core/database.rb
CHANGED
@@ -14,7 +14,7 @@ module Sequel
|
|
14
14
|
include Schema::SQL
|
15
15
|
|
16
16
|
# Array of supported database adapters
|
17
|
-
ADAPTERS = %w'ado db2 dbi informix jdbc mysql odbc
|
17
|
+
ADAPTERS = %w'ado db2 dbi informix jdbc mysql odbc openbase oracle postgres sqlite'.collect{|x| x.to_sym}
|
18
18
|
|
19
19
|
SQL_BEGIN = 'BEGIN'.freeze
|
20
20
|
SQL_COMMIT = 'COMMIT'.freeze
|
@@ -29,6 +29,12 @@ module Sequel
|
|
29
29
|
# Whether to quote identifiers (columns and tables) by default
|
30
30
|
@@quote_identifiers = true
|
31
31
|
|
32
|
+
# Whether to upcase identifiers (columns and tables) by default
|
33
|
+
@@upcase_identifiers = nil
|
34
|
+
|
35
|
+
# The default schema to use
|
36
|
+
attr_accessor :default_schema
|
37
|
+
|
32
38
|
# Array of SQL loggers to use for this database
|
33
39
|
attr_accessor :loggers
|
34
40
|
|
@@ -38,26 +44,41 @@ module Sequel
|
|
38
44
|
# The connection pool for this database
|
39
45
|
attr_reader :pool
|
40
46
|
|
41
|
-
# Whether to quote identifiers (columns and tables) for this database
|
42
|
-
attr_writer :quote_identifiers
|
43
|
-
|
44
47
|
# The prepared statement objects for this database, keyed by name
|
45
48
|
attr_reader :prepared_statements
|
46
49
|
|
50
|
+
# Whether to quote identifiers (columns and tables) for this database
|
51
|
+
attr_writer :quote_identifiers
|
52
|
+
|
53
|
+
# Whether to upcase identifiers (columns and tables) for this database
|
54
|
+
attr_writer :upcase_identifiers
|
55
|
+
|
47
56
|
# Constructs a new instance of a database connection with the specified
|
48
57
|
# options hash.
|
49
58
|
#
|
50
59
|
# Sequel::Database is an abstract class that is not useful by itself.
|
60
|
+
#
|
61
|
+
# Takes the following options:
|
62
|
+
# * :default_schema : The default schema to use, should generally be nil
|
63
|
+
# * :disconnection_proc: A proc used to disconnect the connection.
|
64
|
+
# * :loggers : An array of loggers to use.
|
65
|
+
# * :quote_identifiers : Whether to quote identifiers
|
66
|
+
# * :single_threaded : Whether to use a single-threaded connection pool
|
67
|
+
#
|
68
|
+
# All options given are also passed to the ConnectionPool. If a block
|
69
|
+
# is given, it is used as the connection_proc for the ConnectionPool.
|
51
70
|
def initialize(opts = {}, &block)
|
52
71
|
@opts = opts
|
53
72
|
|
54
73
|
@quote_identifiers = opts.include?(:quote_identifiers) ? opts[:quote_identifiers] : @@quote_identifiers
|
55
74
|
@single_threaded = opts.include?(:single_threaded) ? opts[:single_threaded] : @@single_threaded
|
56
75
|
@schemas = nil
|
76
|
+
@default_schema = opts[:default_schema]
|
57
77
|
@prepared_statements = {}
|
58
78
|
@transactions = []
|
59
79
|
@pool = (@single_threaded ? SingleThreadedPool : ConnectionPool).new(connection_pool_default_options.merge(opts), &block)
|
60
80
|
@pool.connection_proc = proc{|server| connect(server)} unless block
|
81
|
+
@pool.disconnection_proc = proc{|conn| disconnect_connection(conn)} unless opts[:disconnection_proc]
|
61
82
|
|
62
83
|
@loggers = Array(opts[:logger]) + Array(opts[:loggers])
|
63
84
|
::Sequel::DATABASES.push(self)
|
@@ -142,6 +163,12 @@ module Sequel
|
|
142
163
|
@@single_threaded = value
|
143
164
|
end
|
144
165
|
|
166
|
+
# Sets the default quote_identifiers mode for new databases.
|
167
|
+
# See Sequel.quote_identifiers=.
|
168
|
+
def self.upcase_identifiers=(value)
|
169
|
+
@@upcase_identifiers = value
|
170
|
+
end
|
171
|
+
|
145
172
|
### Private Class Methods ###
|
146
173
|
|
147
174
|
# Sets the adapter scheme for the Database class. Call this method in
|
@@ -212,10 +239,10 @@ module Sequel
|
|
212
239
|
ds = Sequel::Dataset.new(self)
|
213
240
|
end
|
214
241
|
|
215
|
-
# Disconnects
|
216
|
-
#
|
242
|
+
# Disconnects all available connections from the connection pool. If any
|
243
|
+
# connections are currently in use, they will not be disconnected.
|
217
244
|
def disconnect
|
218
|
-
|
245
|
+
pool.disconnect
|
219
246
|
end
|
220
247
|
|
221
248
|
# Executes the given SQL. This method should be overridden in descendants.
|
@@ -250,9 +277,8 @@ module Sequel
|
|
250
277
|
# DB.fetch('SELECT * FROM items WHERE name = ?', my_name).print
|
251
278
|
def fetch(sql, *args, &block)
|
252
279
|
ds = dataset
|
253
|
-
sql =
|
254
|
-
ds.
|
255
|
-
ds.fetch_rows(sql, &block) if block
|
280
|
+
ds.opts[:sql] = Sequel::SQL::PlaceholderLiteralString.new(sql, args)
|
281
|
+
ds.each(&block) if block
|
256
282
|
ds
|
257
283
|
end
|
258
284
|
alias_method :>>, :fetch
|
@@ -335,17 +361,19 @@ module Sequel
|
|
335
361
|
@pool.hold(server || :default, &block)
|
336
362
|
end
|
337
363
|
|
338
|
-
# Returns true if a table with the given name exists.
|
364
|
+
# Returns true if a table with the given name exists. This requires a query
|
365
|
+
# to the database unless this database object already has the schema for
|
366
|
+
# the given table name.
|
339
367
|
def table_exists?(name)
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
368
|
+
if @schemas && @schemas[name]
|
369
|
+
true
|
370
|
+
else
|
371
|
+
begin
|
344
372
|
from(name).first
|
345
373
|
true
|
374
|
+
rescue
|
375
|
+
false
|
346
376
|
end
|
347
|
-
rescue
|
348
|
-
false
|
349
377
|
end
|
350
378
|
end
|
351
379
|
|
@@ -458,6 +486,12 @@ module Sequel
|
|
458
486
|
end
|
459
487
|
end
|
460
488
|
|
489
|
+
# Returns true if the database upcases identifiers.
|
490
|
+
def upcase_identifiers?
|
491
|
+
return @upcase_identifiers unless @upcase_identifiers.nil?
|
492
|
+
@upcase_identifiers = @opts.include?(:upcase_identifiers) ? @opts[:upcase_identifiers] : (@@upcase_identifiers.nil? ? upcase_identifiers_default : @@upcase_identifiers)
|
493
|
+
end
|
494
|
+
|
461
495
|
# Returns the URI identifying the database.
|
462
496
|
# This method can raise an error if the database used options
|
463
497
|
# instead of a connection string.
|
@@ -496,11 +530,6 @@ module Sequel
|
|
496
530
|
{}
|
497
531
|
end
|
498
532
|
|
499
|
-
# Sequel doesn't use database schema's by default.
|
500
|
-
def default_schema
|
501
|
-
nil
|
502
|
-
end
|
503
|
-
|
504
533
|
# SQL to ROLLBACK a transaction.
|
505
534
|
def rollback_transaction_sql
|
506
535
|
SQL_ROLLBACK
|
@@ -520,19 +549,7 @@ module Sequel
|
|
520
549
|
|
521
550
|
# Split the schema information from the table
|
522
551
|
def schema_and_table(table_name)
|
523
|
-
|
524
|
-
when Symbol
|
525
|
-
s, t, a = dataset.send(:split_symbol, table_name)
|
526
|
-
[s||default_schema, t]
|
527
|
-
when SQL::QualifiedIdentifier
|
528
|
-
[table_name.table, table_name.column]
|
529
|
-
when SQL::Identifier
|
530
|
-
[default_schema, table_name.value]
|
531
|
-
when String
|
532
|
-
[default_schema, table_name]
|
533
|
-
else
|
534
|
-
raise Error, 'table_name should be a Symbol, SQL::QualifiedIdentifier, SQL::Identifier, or String'
|
535
|
-
end
|
552
|
+
schema_utility_dataset.schema_and_table(table_name)
|
536
553
|
end
|
537
554
|
|
538
555
|
# Return the options for the given server by merging the generic
|
@@ -559,6 +576,14 @@ module Sequel
|
|
559
576
|
def transaction_error(e, *classes)
|
560
577
|
raise_error(e, :classes=>classes) unless Error::Rollback === e
|
561
578
|
end
|
579
|
+
|
580
|
+
# Sets whether to upcase identifiers by default. Should be
|
581
|
+
# overridden in subclasses for databases that fold unquoted
|
582
|
+
# identifiers to lower case instead of uppercase, such as
|
583
|
+
# MySQL, PostgreSQL, and SQLite.
|
584
|
+
def upcase_identifiers_default
|
585
|
+
true
|
586
|
+
end
|
562
587
|
end
|
563
588
|
end
|
564
589
|
|
@@ -40,7 +40,7 @@ module Sequel
|
|
40
40
|
#
|
41
41
|
# See Schema::AlterTableGenerator.
|
42
42
|
def alter_table(name, generator=nil, &block)
|
43
|
-
|
43
|
+
remove_cached_schema(name)
|
44
44
|
generator ||= Schema::AlterTableGenerator.new(self, &block)
|
45
45
|
alter_table_sql_list(name, generator.operations).flatten.each {|sql| execute_ddl(sql)}
|
46
46
|
end
|
@@ -71,7 +71,7 @@ module Sequel
|
|
71
71
|
# DB.create_or_replace_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
|
72
72
|
# DB.create_or_replace_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
|
73
73
|
def create_or_replace_view(name, source)
|
74
|
-
|
74
|
+
remove_cached_schema(name)
|
75
75
|
source = source.sql if source.is_a?(Dataset)
|
76
76
|
execute_ddl("CREATE OR REPLACE VIEW #{quote_identifier(name)} AS #{source}")
|
77
77
|
end
|
@@ -109,7 +109,7 @@ module Sequel
|
|
109
109
|
# DB.drop_table(:posts, :comments)
|
110
110
|
def drop_table(*names)
|
111
111
|
names.each do |n|
|
112
|
-
|
112
|
+
remove_cached_schema(n)
|
113
113
|
execute_ddl(drop_table_sql(n))
|
114
114
|
end
|
115
115
|
end
|
@@ -119,7 +119,7 @@ module Sequel
|
|
119
119
|
# DB.drop_view(:cheap_items)
|
120
120
|
def drop_view(*names)
|
121
121
|
names.each do |n|
|
122
|
-
|
122
|
+
remove_cached_schema(n)
|
123
123
|
execute_ddl("DROP VIEW #{quote_identifier(n)}")
|
124
124
|
end
|
125
125
|
end
|
data/lib/sequel_core/dataset.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
%w'callback convenience pagination prepared_statements query schema sql'.each do |f|
|
1
|
+
%w'callback convenience pagination prepared_statements query schema sql unsupported'.each do |f|
|
2
2
|
require "sequel_core/dataset/#{f}"
|
3
3
|
end
|
4
4
|
|
@@ -75,13 +75,16 @@ module Sequel
|
|
75
75
|
# The hash of options for this dataset, keys are symbols.
|
76
76
|
attr_accessor :opts
|
77
77
|
|
78
|
+
# Whether to quote identifiers for this dataset
|
79
|
+
attr_writer :quote_identifiers
|
80
|
+
|
78
81
|
# The row_proc for this database, should be a Proc that takes
|
79
82
|
# a single hash argument and returns the object you want to
|
80
83
|
# fetch_rows to return.
|
81
84
|
attr_accessor :row_proc
|
82
85
|
|
83
|
-
# Whether to
|
84
|
-
attr_writer :
|
86
|
+
# Whether to upcase identifiers for this dataset
|
87
|
+
attr_writer :upcase_identifiers
|
85
88
|
|
86
89
|
# Constructs a new instance of a dataset with an associated database and
|
87
90
|
# options. Datasets are usually constructed by invoking Database methods:
|
@@ -97,6 +100,7 @@ module Sequel
|
|
97
100
|
def initialize(db, opts = nil)
|
98
101
|
@db = db
|
99
102
|
@quote_identifiers = db.quote_identifiers? if db.respond_to?(:quote_identifiers?)
|
103
|
+
@upcase_identifiers = db.upcase_identifiers? if db.respond_to?(:upcase_identifiers?)
|
100
104
|
@opts = opts || {}
|
101
105
|
@row_proc = nil
|
102
106
|
@transform = nil
|
@@ -415,6 +419,11 @@ module Sequel
|
|
415
419
|
end
|
416
420
|
end
|
417
421
|
|
422
|
+
# Whether this dataset upcases identifiers.
|
423
|
+
def upcase_identifiers?
|
424
|
+
@upcase_identifiers
|
425
|
+
end
|
426
|
+
|
418
427
|
# Updates values for the dataset. The returned value is generally the
|
419
428
|
# number of rows updated, but that is adapter dependent.
|
420
429
|
def update(*args)
|
@@ -198,20 +198,11 @@ module Sequel
|
|
198
198
|
# if the dataset has fixed SQL or selects from another dataset
|
199
199
|
# or more than one table.
|
200
200
|
def table_exists?
|
201
|
-
if @opts[:sql]
|
202
|
-
|
203
|
-
end
|
204
|
-
|
205
|
-
if @opts[:from].size != 1
|
206
|
-
raise Sequel::Error, "this dataset selects from multiple sources"
|
207
|
-
end
|
208
|
-
|
201
|
+
raise(Sequel::Error, "this dataset has fixed SQL") if @opts[:sql]
|
202
|
+
raise(Sequel::Error, "this dataset selects from multiple sources") if @opts[:from].size != 1
|
209
203
|
t = @opts[:from].first
|
210
|
-
if t.is_a?(Dataset)
|
211
|
-
|
212
|
-
end
|
213
|
-
|
214
|
-
@db.table_exists?(t.to_sym)
|
204
|
+
raise(Sequel::Error, "this dataset selects from a sub query") if t.is_a?(Dataset)
|
205
|
+
@db.table_exists?(t)
|
215
206
|
end
|
216
207
|
|
217
208
|
# Returns a string in CSV format containing the dataset records. By
|
@@ -25,11 +25,10 @@ module Sequel
|
|
25
25
|
end
|
26
26
|
|
27
27
|
# Override the given *_sql method based on the type, and
|
28
|
-
# cache the result of the sql.
|
29
|
-
# that includes this module implement prepared_args_hash.
|
28
|
+
# cache the result of the sql.
|
30
29
|
def prepared_sql
|
31
30
|
return @prepared_sql if @prepared_sql
|
32
|
-
@prepared_args
|
31
|
+
@prepared_args ||= []
|
33
32
|
@prepared_sql = super
|
34
33
|
meta_def("#{sql_query_type}_sql"){|*args| prepared_sql}
|
35
34
|
@prepared_sql
|
@@ -37,6 +36,7 @@ module Sequel
|
|
37
36
|
|
38
37
|
private
|
39
38
|
|
39
|
+
# The type of query (:select, :insert, :delete, :update).
|
40
40
|
def sql_query_type
|
41
41
|
SQL_QUERY_TYPE[@prepared_type]
|
42
42
|
end
|
@@ -51,7 +51,7 @@ module Sequel
|
|
51
51
|
module PreparedStatementMethods
|
52
52
|
PLACEHOLDER_RE = /\A\$(.*)\z/
|
53
53
|
|
54
|
-
# The type of prepared statement, should be one of :select,
|
54
|
+
# The type of prepared statement, should be one of :select, :first,
|
55
55
|
# :insert, :update, or :delete
|
56
56
|
attr_accessor :prepared_type
|
57
57
|
|
@@ -136,6 +136,15 @@ module Sequel
|
|
136
136
|
def prepared_arg(k)
|
137
137
|
@prepared_args[k]
|
138
138
|
end
|
139
|
+
|
140
|
+
# Use a clone of the dataset extended with prepared statement
|
141
|
+
# support and using the same argument hash so that you can use
|
142
|
+
# bind variables/prepared arguments in subselects.
|
143
|
+
def subselect_sql(ds)
|
144
|
+
ps = ds.prepare(:select)
|
145
|
+
ps.prepared_args = prepared_args
|
146
|
+
ps.prepared_sql
|
147
|
+
end
|
139
148
|
end
|
140
149
|
|
141
150
|
# Default implementation for an argument mapper that uses
|
@@ -151,26 +160,15 @@ module Sequel
|
|
151
160
|
# Keys in the input hash that are used more than once in the query
|
152
161
|
# have multiple entries in the output array.
|
153
162
|
def map_to_prepared_args(hash)
|
154
|
-
|
155
|
-
@prepared_args.each{|k,vs| vs.each{|v| array[v] = hash[k]}}
|
156
|
-
array
|
163
|
+
@prepared_args.map{|v| hash[v]}
|
157
164
|
end
|
158
165
|
|
159
166
|
private
|
160
167
|
|
161
|
-
# Uses a separate array of each key, holding the positions
|
162
|
-
# in the output array (necessary to support arguments
|
163
|
-
# that are used more than once).
|
164
|
-
def prepared_args_hash
|
165
|
-
Hash.new{|h,k| h[k] = Array.new}
|
166
|
-
end
|
167
|
-
|
168
168
|
# Associates the argument with name k with the next position in
|
169
169
|
# the output array.
|
170
170
|
def prepared_arg(k)
|
171
|
-
@
|
172
|
-
@prepared_args[k] << @max_prepared_arg
|
173
|
-
@max_prepared_arg += 1
|
171
|
+
@prepared_args << k
|
174
172
|
prepared_arg_placeholder
|
175
173
|
end
|
176
174
|
end
|
@@ -181,7 +179,7 @@ module Sequel
|
|
181
179
|
# insert or update (if one of those types is used),
|
182
180
|
# which may contain placeholders.
|
183
181
|
def call(type, bind_variables={}, values=nil)
|
184
|
-
|
182
|
+
prepare(type, nil, values).call(bind_variables)
|
185
183
|
end
|
186
184
|
|
187
185
|
# Prepare an SQL statement for later execution. This returns
|
@@ -193,17 +191,13 @@ module Sequel
|
|
193
191
|
# ps = prepare(:select, :select_by_name)
|
194
192
|
# ps.call(:name=>'Blah')
|
195
193
|
# db.call(:select_by_name, :name=>'Blah')
|
196
|
-
def prepare(type, name, values=nil)
|
197
|
-
|
194
|
+
def prepare(type, name=nil, values=nil)
|
195
|
+
ps = to_prepared_statement(type, values)
|
196
|
+
db.prepared_statements[name] = ps if name
|
197
|
+
ps
|
198
198
|
end
|
199
199
|
|
200
|
-
|
201
|
-
|
202
|
-
# The argument placeholder. Most databases used unnumbered
|
203
|
-
# arguments with question marks, so that is the default.
|
204
|
-
def prepared_arg_placeholder
|
205
|
-
PREPARED_ARG_PLACEHOLDER
|
206
|
-
end
|
200
|
+
protected
|
207
201
|
|
208
202
|
# Return a cloned copy of the current dataset extended with
|
209
203
|
# PreparedStatementMethods, setting the type and modify values.
|
@@ -214,5 +208,13 @@ module Sequel
|
|
214
208
|
ps.prepared_modify_values = values
|
215
209
|
ps
|
216
210
|
end
|
211
|
+
|
212
|
+
private
|
213
|
+
|
214
|
+
# The argument placeholder. Most databases used unnumbered
|
215
|
+
# arguments with question marks, so that is the default.
|
216
|
+
def prepared_arg_placeholder
|
217
|
+
PREPARED_ARG_PLACEHOLDER
|
218
|
+
end
|
217
219
|
end
|
218
|
-
end
|
220
|
+
end
|
@@ -12,6 +12,7 @@ module Sequel
|
|
12
12
|
NULL = "NULL".freeze
|
13
13
|
QUESTION_MARK = '?'.freeze
|
14
14
|
STOCK_COUNT_OPTS = {:select => ["COUNT(*)".lit], :order => nil}.freeze
|
15
|
+
SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having intersect union except order limit'.freeze
|
15
16
|
TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S'".freeze
|
16
17
|
TWO_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::TWO_ARITY_OPERATORS
|
17
18
|
WILDCARD = '*'.freeze
|
@@ -46,7 +47,7 @@ module Sequel
|
|
46
47
|
|
47
48
|
# SQL fragment for specifying all columns in a given table.
|
48
49
|
def column_all_sql(ca)
|
49
|
-
"#{
|
50
|
+
"#{quote_schema_table(ca.table)}.*"
|
50
51
|
end
|
51
52
|
|
52
53
|
# SQL fragment for complex expressions
|
@@ -80,9 +81,7 @@ module Sequel
|
|
80
81
|
def delete_sql(opts = nil)
|
81
82
|
opts = opts ? @opts.merge(opts) : @opts
|
82
83
|
|
83
|
-
|
84
|
-
return sql
|
85
|
-
end
|
84
|
+
return static_sql(opts[:sql]) if opts[:sql]
|
86
85
|
|
87
86
|
if opts[:group]
|
88
87
|
raise Error::InvalidOperation, "Grouped datasets cannot be deleted from"
|
@@ -122,12 +121,12 @@ module Sequel
|
|
122
121
|
clone(clause => cond)
|
123
122
|
end
|
124
123
|
|
125
|
-
# Returns an EXISTS clause for the dataset.
|
124
|
+
# Returns an EXISTS clause for the dataset as a LiteralString.
|
126
125
|
#
|
127
126
|
# DB.select(1).where(DB[:items].exists).sql
|
128
127
|
# #=> "SELECT 1 WHERE EXISTS (SELECT * FROM items)"
|
129
128
|
def exists(opts = nil)
|
130
|
-
"EXISTS (#{select_sql(opts)})"
|
129
|
+
"EXISTS (#{select_sql(opts)})".lit
|
131
130
|
end
|
132
131
|
|
133
132
|
# Returns a copy of the dataset with the given conditions imposed upon it.
|
@@ -270,9 +269,7 @@ module Sequel
|
|
270
269
|
# dataset.insert_sql(:a => 1, :b => 2) #=>
|
271
270
|
# 'INSERT INTO items (a, b) VALUES (1, 2)'
|
272
271
|
def insert_sql(*values)
|
273
|
-
|
274
|
-
return sql
|
275
|
-
end
|
272
|
+
return static_sql(@opts[:sql]) if @opts[:sql]
|
276
273
|
|
277
274
|
from = source_list(@opts[:from])
|
278
275
|
case values.size
|
@@ -494,7 +491,7 @@ module Sequel
|
|
494
491
|
when Date
|
495
492
|
v.strftime(DATE_FORMAT)
|
496
493
|
when Dataset
|
497
|
-
"(#{v
|
494
|
+
"(#{subselect_sql(v)})"
|
498
495
|
else
|
499
496
|
raise Error, "can't express #{v.inspect} as a SQL literal"
|
500
497
|
end
|
@@ -555,31 +552,43 @@ module Sequel
|
|
555
552
|
"#{literal(oe.expression)} #{oe.descending ? 'DESC' : 'ASC'}"
|
556
553
|
end
|
557
554
|
|
555
|
+
# SQL fragment for a literal string with placeholders
|
556
|
+
def placeholder_literal_string_sql(pls)
|
557
|
+
args = pls.args.dup
|
558
|
+
s = pls.str.gsub(QUESTION_MARK){literal(args.shift)}
|
559
|
+
s = "(#{s})" if pls.parens
|
560
|
+
s
|
561
|
+
end
|
562
|
+
|
558
563
|
# SQL fragment for the qualifed identifier, specifying
|
559
564
|
# a table and a column (or schema and table).
|
560
565
|
def qualified_identifier_sql(qcr)
|
561
|
-
[qcr.table, qcr.column].map{|x| x.is_one_of?(SQL::QualifiedIdentifier, SQL::Identifier) ? literal(x) : quote_identifier(x)}.join('.')
|
566
|
+
[qcr.table, qcr.column].map{|x| x.is_one_of?(SQL::QualifiedIdentifier, SQL::Identifier, Symbol) ? literal(x) : quote_identifier(x)}.join('.')
|
562
567
|
end
|
563
568
|
|
564
569
|
# Adds quoting to identifiers (columns and tables). If identifiers are not
|
565
570
|
# being quoted, returns name as a string. If identifiers are being quoted
|
566
571
|
# quote the name with quoted_identifier.
|
567
572
|
def quote_identifier(name)
|
568
|
-
|
573
|
+
name = name.to_s
|
574
|
+
name = name.upcase if upcase_identifiers?
|
575
|
+
name = quoted_identifier(name) if quote_identifiers?
|
576
|
+
name
|
569
577
|
end
|
570
578
|
alias_method :quote_column_ref, :quote_identifier
|
571
579
|
|
580
|
+
# Separates the schema from the table and returns a string with them
|
581
|
+
# quoted (if quoting identifiers)
|
582
|
+
def quote_schema_table(table)
|
583
|
+
schema, table = schema_and_table(table)
|
584
|
+
"#{"#{quote_identifier(schema)}." if schema}#{quote_identifier(table)}"
|
585
|
+
end
|
586
|
+
|
572
587
|
# This method quotes the given name with the SQL standard double quote.
|
573
|
-
# It uppercases the name given to conform with the SQL standard. This
|
574
588
|
# should be overridden by subclasses to provide quoting not matching the
|
575
|
-
# SQL standard, such as backtick (used by MySQL and SQLite)
|
576
|
-
# lowercase is the default for unquoted identifiers (PostgreSQL).
|
577
|
-
#
|
578
|
-
# If you are using a database such as Oracle that defaults to uppercase
|
579
|
-
# but you are using lower case identifiers, you should override this
|
580
|
-
# method to not upcase the name for those identifiers.
|
589
|
+
# SQL standard, such as backtick (used by MySQL and SQLite).
|
581
590
|
def quoted_identifier(name)
|
582
|
-
"\"#{name
|
591
|
+
"\"#{name}\""
|
583
592
|
end
|
584
593
|
|
585
594
|
# Returns a copy of the dataset with the order reversed. If no order is
|
@@ -589,6 +598,24 @@ module Sequel
|
|
589
598
|
end
|
590
599
|
alias_method :reverse, :reverse_order
|
591
600
|
|
601
|
+
# Split the schema information from the table
|
602
|
+
def schema_and_table(table_name)
|
603
|
+
sch = db.default_schema if db
|
604
|
+
case table_name
|
605
|
+
when Symbol
|
606
|
+
s, t, a = split_symbol(table_name)
|
607
|
+
[s||sch, t]
|
608
|
+
when SQL::QualifiedIdentifier
|
609
|
+
[table_name.table, table_name.column]
|
610
|
+
when SQL::Identifier
|
611
|
+
[sch, table_name.value]
|
612
|
+
when String
|
613
|
+
[sch, table_name]
|
614
|
+
else
|
615
|
+
raise Error, 'table_name should be a Symbol, SQL::QualifiedIdentifier, SQL::Identifier, or String'
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
592
619
|
# Returns a copy of the dataset with the columns selected changed
|
593
620
|
# to the given columns.
|
594
621
|
def select(*columns)
|
@@ -610,63 +637,9 @@ module Sequel
|
|
610
637
|
# options.
|
611
638
|
def select_sql(opts = nil)
|
612
639
|
opts = opts ? @opts.merge(opts) : @opts
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
end
|
617
|
-
|
618
|
-
columns = opts[:select]
|
619
|
-
select_columns = columns ? column_list(columns) : WILDCARD
|
620
|
-
|
621
|
-
if distinct = opts[:distinct]
|
622
|
-
distinct_clause = distinct.empty? ? "DISTINCT" : "DISTINCT ON (#{expression_list(distinct)})"
|
623
|
-
sql = "SELECT #{distinct_clause} #{select_columns}"
|
624
|
-
else
|
625
|
-
sql = "SELECT #{select_columns}"
|
626
|
-
end
|
627
|
-
|
628
|
-
if opts[:from]
|
629
|
-
sql << " FROM #{source_list(opts[:from])}"
|
630
|
-
end
|
631
|
-
|
632
|
-
if join = opts[:join]
|
633
|
-
join.each{|j| sql << literal(j)}
|
634
|
-
end
|
635
|
-
|
636
|
-
if where = opts[:where]
|
637
|
-
sql << " WHERE #{literal(where)}"
|
638
|
-
end
|
639
|
-
|
640
|
-
if group = opts[:group]
|
641
|
-
sql << " GROUP BY #{expression_list(group)}"
|
642
|
-
end
|
643
|
-
|
644
|
-
if having = opts[:having]
|
645
|
-
sql << " HAVING #{literal(having)}"
|
646
|
-
end
|
647
|
-
|
648
|
-
if order = opts[:order]
|
649
|
-
sql << " ORDER BY #{expression_list(order)}"
|
650
|
-
end
|
651
|
-
|
652
|
-
if limit = opts[:limit]
|
653
|
-
sql << " LIMIT #{limit}"
|
654
|
-
if offset = opts[:offset]
|
655
|
-
sql << " OFFSET #{offset}"
|
656
|
-
end
|
657
|
-
end
|
658
|
-
|
659
|
-
if union = opts[:union]
|
660
|
-
sql << (opts[:union_all] ? \
|
661
|
-
" UNION ALL #{union.sql}" : " UNION #{union.sql}")
|
662
|
-
elsif intersect = opts[:intersect]
|
663
|
-
sql << (opts[:intersect_all] ? \
|
664
|
-
" INTERSECT ALL #{intersect.sql}" : " INTERSECT #{intersect.sql}")
|
665
|
-
elsif except = opts[:except]
|
666
|
-
sql << (opts[:except_all] ? \
|
667
|
-
" EXCEPT ALL #{except.sql}" : " EXCEPT #{except.sql}")
|
668
|
-
end
|
669
|
-
|
640
|
+
return static_sql(opts[:sql]) if opts[:sql]
|
641
|
+
sql = 'SELECT'
|
642
|
+
select_clause_order.each{|x| send("select_#{x}_sql", sql, opts)}
|
670
643
|
sql
|
671
644
|
end
|
672
645
|
|
@@ -733,9 +706,7 @@ module Sequel
|
|
733
706
|
def update_sql(values = {}, opts = nil)
|
734
707
|
opts = opts ? @opts.merge(opts) : @opts
|
735
708
|
|
736
|
-
|
737
|
-
return sql
|
738
|
-
end
|
709
|
+
return static_sql(opts[:sql]) if opts[:sql]
|
739
710
|
|
740
711
|
if opts[:group]
|
741
712
|
raise Error::InvalidOperation, "A grouped dataset cannot be updated"
|
@@ -788,7 +759,7 @@ module Sequel
|
|
788
759
|
# Converts an array of column names into a comma seperated string of
|
789
760
|
# column names. If the array is empty, a wildcard (*) is returned.
|
790
761
|
def column_list(columns)
|
791
|
-
if columns.
|
762
|
+
if columns.blank?
|
792
763
|
WILDCARD
|
793
764
|
else
|
794
765
|
m = columns.map do |i|
|
@@ -817,7 +788,7 @@ module Sequel
|
|
817
788
|
SQL::BooleanExpression.from_value_pairs(expr)
|
818
789
|
when Array
|
819
790
|
if String === expr[0]
|
820
|
-
|
791
|
+
SQL::PlaceholderLiteralString.new(expr.shift, expr, true)
|
821
792
|
else
|
822
793
|
SQL::BooleanExpression.from_value_pairs(expr)
|
823
794
|
end
|
@@ -876,14 +847,90 @@ module Sequel
|
|
876
847
|
def qualified_column_name(column, table)
|
877
848
|
if Symbol === column
|
878
849
|
c_table, column, c_alias = split_symbol(column)
|
879
|
-
|
880
|
-
|
850
|
+
unless c_table
|
851
|
+
case table
|
852
|
+
when Symbol
|
853
|
+
schema, table, t_alias = split_symbol(table)
|
854
|
+
t_alias ||= Sequel::SQL::QualifiedIdentifier.new(schema, table) if schema
|
855
|
+
when Sequel::SQL::AliasedExpression
|
856
|
+
t_alias = table.aliaz
|
857
|
+
end
|
858
|
+
c_table = t_alias || table
|
859
|
+
end
|
881
860
|
::Sequel::SQL::QualifiedIdentifier.new(c_table, column)
|
882
861
|
else
|
883
862
|
column
|
884
863
|
end
|
885
864
|
end
|
886
865
|
|
866
|
+
# The order of methods to call to build the SELECT SQL statement
|
867
|
+
def select_clause_order
|
868
|
+
SELECT_CLAUSE_ORDER
|
869
|
+
end
|
870
|
+
|
871
|
+
# Modify the sql to add the columns selected
|
872
|
+
def select_columns_sql(sql, opts)
|
873
|
+
sql << " #{column_list(opts[:select])}"
|
874
|
+
end
|
875
|
+
|
876
|
+
# Modify the sql to add the DISTINCT modifier
|
877
|
+
def select_distinct_sql(sql, opts)
|
878
|
+
if distinct = opts[:distinct]
|
879
|
+
sql << " DISTINCT#{" ON (#{expression_list(distinct)})" unless distinct.empty?}"
|
880
|
+
end
|
881
|
+
end
|
882
|
+
|
883
|
+
# Modify the sql to add a dataset to the EXCEPT clause
|
884
|
+
def select_except_sql(sql, opts)
|
885
|
+
sql << " EXCEPT#{' ALL' if opts[:except_all]} #{opts[:except].sql}" if opts[:except]
|
886
|
+
end
|
887
|
+
|
888
|
+
# Modify the sql to add the list of tables to select FROM
|
889
|
+
def select_from_sql(sql, opts)
|
890
|
+
sql << " FROM #{source_list(opts[:from])}" if opts[:from]
|
891
|
+
end
|
892
|
+
|
893
|
+
# Modify the sql to add the expressions to GROUP BY
|
894
|
+
def select_group_sql(sql, opts)
|
895
|
+
sql << " GROUP BY #{expression_list(opts[:group])}" if opts[:group]
|
896
|
+
end
|
897
|
+
|
898
|
+
# Modify the sql to add the filter criteria in the HAVING clause
|
899
|
+
def select_having_sql(sql, opts)
|
900
|
+
sql << " HAVING #{literal(opts[:having])}" if opts[:having]
|
901
|
+
end
|
902
|
+
|
903
|
+
# Modify the sql to add a dataset to the INTERSECT clause
|
904
|
+
def select_intersect_sql(sql, opts)
|
905
|
+
sql << " INTERSECT#{' ALL' if opts[:intersect_all]} #{opts[:intersect].sql}" if opts[:intersect]
|
906
|
+
end
|
907
|
+
|
908
|
+
# Modify the sql to add the list of tables to JOIN to
|
909
|
+
def select_join_sql(sql, opts)
|
910
|
+
opts[:join].each{|j| sql << literal(j)} if opts[:join]
|
911
|
+
end
|
912
|
+
|
913
|
+
# Modify the sql to limit the number of rows returned and offset
|
914
|
+
def select_limit_sql(sql, opts)
|
915
|
+
sql << " LIMIT #{opts[:limit]}" if opts[:limit]
|
916
|
+
sql << " OFFSET #{opts[:offset]}" if opts[:offset]
|
917
|
+
end
|
918
|
+
|
919
|
+
# Modify the sql to add the expressions to ORDER BY
|
920
|
+
def select_order_sql(sql, opts)
|
921
|
+
sql << " ORDER BY #{expression_list(opts[:order])}" if opts[:order]
|
922
|
+
end
|
923
|
+
|
924
|
+
# Modify the sql to add a dataset to the UNION clause
|
925
|
+
def select_union_sql(sql, opts)
|
926
|
+
sql << " UNION#{' ALL' if opts[:union_all]} #{opts[:union].sql}" if opts[:union]
|
927
|
+
end
|
928
|
+
|
929
|
+
# Modify the sql to add the filter criteria in the WHERE clause
|
930
|
+
def select_where_sql(sql, opts)
|
931
|
+
sql << " WHERE #{literal(opts[:where])}" if opts[:where]
|
932
|
+
end
|
933
|
+
|
887
934
|
# Converts an array of source names into into a comma separated list.
|
888
935
|
def source_list(source)
|
889
936
|
if source.nil? || source.empty?
|
@@ -920,7 +967,19 @@ module Sequel
|
|
920
967
|
end
|
921
968
|
end
|
922
969
|
|
923
|
-
# SQL
|
970
|
+
# SQL to use if this dataset uses static SQL. Since static SQL
|
971
|
+
# can be a PlaceholderLiteralString in addition to a String,
|
972
|
+
# we literalize nonstrings.
|
973
|
+
def static_sql(sql)
|
974
|
+
sql.is_a?(String) ? sql : literal(sql)
|
975
|
+
end
|
976
|
+
|
977
|
+
# SQL fragment for a subselect using the given database's SQL.
|
978
|
+
def subselect_sql(ds)
|
979
|
+
ds.sql
|
980
|
+
end
|
981
|
+
|
982
|
+
# SQL fragment specifying a table name.
|
924
983
|
def table_ref(t)
|
925
984
|
case t
|
926
985
|
when Dataset
|