sequel 2.7.1 → 2.8.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 +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
|