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.
Files changed (50) hide show
  1. data/CHANGELOG +56 -0
  2. data/README +1 -0
  3. data/Rakefile +1 -1
  4. data/lib/sequel_core.rb +9 -16
  5. data/lib/sequel_core/adapters/ado.rb +6 -15
  6. data/lib/sequel_core/adapters/db2.rb +8 -10
  7. data/lib/sequel_core/adapters/dbi.rb +6 -4
  8. data/lib/sequel_core/adapters/informix.rb +21 -22
  9. data/lib/sequel_core/adapters/jdbc.rb +69 -10
  10. data/lib/sequel_core/adapters/jdbc/postgresql.rb +1 -0
  11. data/lib/sequel_core/adapters/mysql.rb +81 -13
  12. data/lib/sequel_core/adapters/odbc.rb +32 -4
  13. data/lib/sequel_core/adapters/openbase.rb +6 -5
  14. data/lib/sequel_core/adapters/oracle.rb +23 -7
  15. data/lib/sequel_core/adapters/postgres.rb +42 -32
  16. data/lib/sequel_core/adapters/shared/mssql.rb +37 -62
  17. data/lib/sequel_core/adapters/shared/mysql.rb +22 -7
  18. data/lib/sequel_core/adapters/shared/oracle.rb +27 -48
  19. data/lib/sequel_core/adapters/shared/postgres.rb +64 -43
  20. data/lib/sequel_core/adapters/shared/progress.rb +31 -0
  21. data/lib/sequel_core/adapters/shared/sqlite.rb +15 -4
  22. data/lib/sequel_core/adapters/sqlite.rb +6 -14
  23. data/lib/sequel_core/connection_pool.rb +47 -13
  24. data/lib/sequel_core/database.rb +60 -35
  25. data/lib/sequel_core/database/schema.rb +4 -4
  26. data/lib/sequel_core/dataset.rb +12 -3
  27. data/lib/sequel_core/dataset/convenience.rb +4 -13
  28. data/lib/sequel_core/dataset/prepared_statements.rb +30 -28
  29. data/lib/sequel_core/dataset/sql.rb +144 -85
  30. data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
  31. data/lib/sequel_core/dataset/unsupported.rb +31 -0
  32. data/lib/sequel_core/exceptions.rb +6 -0
  33. data/lib/sequel_core/schema/generator.rb +4 -3
  34. data/lib/sequel_core/schema/sql.rb +41 -23
  35. data/lib/sequel_core/sql.rb +29 -1
  36. data/lib/sequel_model/associations.rb +1 -1
  37. data/lib/sequel_model/record.rb +31 -28
  38. data/spec/adapters/mysql_spec.rb +37 -4
  39. data/spec/adapters/oracle_spec.rb +26 -4
  40. data/spec/adapters/sqlite_spec.rb +7 -0
  41. data/spec/integration/prepared_statement_test.rb +24 -0
  42. data/spec/integration/schema_test.rb +1 -1
  43. data/spec/sequel_core/connection_pool_spec.rb +49 -2
  44. data/spec/sequel_core/core_sql_spec.rb +9 -2
  45. data/spec/sequel_core/database_spec.rb +64 -14
  46. data/spec/sequel_core/dataset_spec.rb +105 -7
  47. data/spec/sequel_core/schema_spec.rb +40 -12
  48. data/spec/sequel_core/spec_helper.rb +1 -0
  49. data/spec/sequel_model/spec_helper.rb +1 -0
  50. metadata +6 -3
@@ -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 odbc_mssql openbase oracle postgres sqlite'.collect{|x| x.to_sym}
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 from the database. This method should be overridden by
216
- # descendants.
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
- raise NotImplementedError, "#disconnect should be overridden by adapters"
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 = sql.gsub('?') {|m| ds.literal(args.shift)}
254
- ds.opts[:sql] = sql
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
- begin
341
- if respond_to?(:tables)
342
- tables.include?(name.to_sym)
343
- else
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
- case table_name
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
- @schemas.delete(name.to_sym) if @schemas
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
- @schemas.delete(name.to_sym) if @schemas
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
- @schemas.delete(n.is_a?(String) ? n.to_sym : n) if @schemas
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
- @schemas.delete(n.to_sym) if @schemas
122
+ remove_cached_schema(n)
123
123
  execute_ddl("DROP VIEW #{quote_identifier(n)}")
124
124
  end
125
125
  end
@@ -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 quote identifiers for this dataset
84
- attr_writer :quote_identifiers
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
- raise Sequel::Error, "this dataset has fixed SQL"
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
- raise Sequel::Error, "this dataset selects from a sub query"
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. This requires that the object
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 = prepared_args_hash
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
- array = []
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
- @max_prepared_arg ||= 0
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
- to_prepared_statement(type, values).call(bind_variables)
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
- db.prepared_statements[name] = to_prepared_statement(type, values)
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
- private
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
- "#{quote_identifier(ca.table)}.*"
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
- if sql = opts[:sql]
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
- if sql = @opts[:sql]
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.sql})"
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
- quote_identifiers? ? quoted_identifier(name) : name.to_s
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), or where
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.to_s.upcase}\""
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
- if sql = opts[:sql]
615
- return sql
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
- if sql = opts[:sql]
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.empty?
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
- filter_expr(expr.shift.gsub(QUESTION_MARK){literal(expr.shift)}.lit)
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
- schema, table, t_alias = split_symbol(table) if Symbol === table
880
- c_table ||= t_alias || table
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 fragement specifying a table name.
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