sequel 3.29.0 → 3.30.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 (106) hide show
  1. data/CHANGELOG +35 -3
  2. data/Rakefile +2 -1
  3. data/doc/association_basics.rdoc +11 -0
  4. data/doc/opening_databases.rdoc +2 -0
  5. data/doc/release_notes/3.30.0.txt +135 -0
  6. data/doc/testing.rdoc +17 -3
  7. data/lib/sequel/adapters/amalgalite.rb +2 -2
  8. data/lib/sequel/adapters/do/mysql.rb +5 -2
  9. data/lib/sequel/adapters/ibmdb.rb +2 -2
  10. data/lib/sequel/adapters/jdbc.rb +126 -43
  11. data/lib/sequel/adapters/jdbc/as400.rb +11 -3
  12. data/lib/sequel/adapters/jdbc/db2.rb +2 -1
  13. data/lib/sequel/adapters/jdbc/derby.rb +44 -19
  14. data/lib/sequel/adapters/jdbc/h2.rb +32 -19
  15. data/lib/sequel/adapters/jdbc/hsqldb.rb +21 -17
  16. data/lib/sequel/adapters/jdbc/jtds.rb +9 -4
  17. data/lib/sequel/adapters/jdbc/mssql.rb +3 -1
  18. data/lib/sequel/adapters/jdbc/mysql.rb +2 -1
  19. data/lib/sequel/adapters/jdbc/oracle.rb +21 -7
  20. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -2
  21. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -1
  22. data/lib/sequel/adapters/jdbc/sqlserver.rb +48 -18
  23. data/lib/sequel/adapters/mock.rb +2 -1
  24. data/lib/sequel/adapters/mysql.rb +4 -2
  25. data/lib/sequel/adapters/mysql2.rb +2 -2
  26. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  27. data/lib/sequel/adapters/openbase.rb +1 -1
  28. data/lib/sequel/adapters/oracle.rb +6 -6
  29. data/lib/sequel/adapters/postgres.rb +25 -12
  30. data/lib/sequel/adapters/shared/access.rb +14 -6
  31. data/lib/sequel/adapters/shared/db2.rb +36 -13
  32. data/lib/sequel/adapters/shared/firebird.rb +12 -5
  33. data/lib/sequel/adapters/shared/informix.rb +11 -3
  34. data/lib/sequel/adapters/shared/mssql.rb +94 -47
  35. data/lib/sequel/adapters/shared/mysql.rb +107 -49
  36. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +2 -2
  37. data/lib/sequel/adapters/shared/oracle.rb +54 -27
  38. data/lib/sequel/adapters/shared/postgres.rb +65 -26
  39. data/lib/sequel/adapters/shared/progress.rb +4 -1
  40. data/lib/sequel/adapters/shared/sqlite.rb +36 -20
  41. data/lib/sequel/adapters/sqlite.rb +2 -3
  42. data/lib/sequel/adapters/swift/mysql.rb +3 -2
  43. data/lib/sequel/adapters/swift/sqlite.rb +2 -2
  44. data/lib/sequel/adapters/tinytds.rb +14 -8
  45. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +7 -4
  46. data/lib/sequel/database/misc.rb +6 -2
  47. data/lib/sequel/dataset/graph.rb +33 -7
  48. data/lib/sequel/dataset/prepared_statements.rb +19 -5
  49. data/lib/sequel/dataset/sql.rb +611 -201
  50. data/lib/sequel/model/associations.rb +12 -5
  51. data/lib/sequel/model/base.rb +20 -5
  52. data/lib/sequel/plugins/sharding.rb +9 -29
  53. data/lib/sequel/sql.rb +2 -1
  54. data/lib/sequel/timezones.rb +14 -4
  55. data/lib/sequel/version.rb +1 -1
  56. data/spec/adapters/mysql_spec.rb +10 -0
  57. data/spec/adapters/oracle_spec.rb +1 -1
  58. data/spec/core/core_sql_spec.rb +3 -1
  59. data/spec/core/database_spec.rb +42 -0
  60. data/spec/core/dataset_spec.rb +10 -3
  61. data/spec/core/mock_adapter_spec.rb +4 -0
  62. data/spec/core/object_graph_spec.rb +38 -0
  63. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  64. data/spec/extensions/association_dependencies_spec.rb +2 -12
  65. data/spec/extensions/association_pks_spec.rb +35 -39
  66. data/spec/extensions/caching_spec.rb +23 -50
  67. data/spec/extensions/class_table_inheritance_spec.rb +30 -82
  68. data/spec/extensions/composition_spec.rb +18 -13
  69. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  70. data/spec/extensions/identity_map_spec.rb +33 -103
  71. data/spec/extensions/instance_filters_spec.rb +10 -21
  72. data/spec/extensions/instance_hooks_spec.rb +6 -24
  73. data/spec/extensions/json_serializer_spec.rb +4 -5
  74. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  75. data/spec/extensions/list_spec.rb +17 -39
  76. data/spec/extensions/many_through_many_spec.rb +135 -277
  77. data/spec/extensions/migration_spec.rb +18 -15
  78. data/spec/extensions/named_timezones_spec.rb +1 -1
  79. data/spec/extensions/nested_attributes_spec.rb +97 -92
  80. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  81. data/spec/extensions/prepared_statements_associations_spec.rb +22 -37
  82. data/spec/extensions/prepared_statements_safe_spec.rb +9 -27
  83. data/spec/extensions/prepared_statements_spec.rb +11 -30
  84. data/spec/extensions/prepared_statements_with_pk_spec.rb +6 -13
  85. data/spec/extensions/pretty_table_spec.rb +1 -6
  86. data/spec/extensions/rcte_tree_spec.rb +41 -43
  87. data/spec/extensions/schema_dumper_spec.rb +3 -6
  88. data/spec/extensions/serialization_spec.rb +20 -32
  89. data/spec/extensions/sharding_spec.rb +66 -140
  90. data/spec/extensions/single_table_inheritance_spec.rb +14 -36
  91. data/spec/extensions/spec_helper.rb +10 -64
  92. data/spec/extensions/sql_expr_spec.rb +20 -60
  93. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  94. data/spec/extensions/timestamps_spec.rb +6 -6
  95. data/spec/extensions/to_dot_spec.rb +1 -2
  96. data/spec/extensions/touch_spec.rb +13 -14
  97. data/spec/extensions/tree_spec.rb +11 -26
  98. data/spec/extensions/update_primary_key_spec.rb +30 -24
  99. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  100. data/spec/extensions/validation_helpers_spec.rb +16 -35
  101. data/spec/integration/dataset_test.rb +16 -4
  102. data/spec/integration/prepared_statement_test.rb +4 -2
  103. data/spec/model/eager_loading_spec.rb +16 -0
  104. data/spec/model/model_spec.rb +15 -1
  105. data/spec/model/record_spec.rb +60 -0
  106. metadata +23 -40
@@ -31,7 +31,10 @@ module Sequel
31
31
  # The Progress adapter targets Progress 9, so it silently ignores the option.
32
32
  def select_limit_sql(sql)
33
33
  raise(Error, "OFFSET not supported") if @opts[:offset]
34
- #sql << " TOP #{@opts[:limit]}" if @opts[:limit]
34
+ # if l = @opts[:limit]
35
+ # sql << " TOP "
36
+ # literal_append(sql, l)
37
+ # end
35
38
  end
36
39
  end
37
40
  end
@@ -396,24 +396,36 @@ module Sequel
396
396
 
397
397
  # Instance methods for datasets that connect to an SQLite database
398
398
  module DatasetMethods
399
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
400
- COMMA_SEPARATOR = ', '.freeze
399
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
401
400
  CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
402
401
  EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}
402
+ NOT_SPACE = Dataset::NOT_SPACE
403
+ COMMA = Dataset::COMMA
404
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
405
+ AS = Dataset::AS
406
+ APOS = Dataset::APOS
407
+ EXTRACT_OPEN = "CAST(strftime(".freeze
408
+ EXRACT_CLOSE = ') AS '.freeze
409
+ NUMERIC = 'NUMERIC'.freeze
410
+ INTEGER = 'INTEGER'.freeze
411
+ BACKTICK = '`'.freeze
412
+ BLOB_START = "X'".freeze
413
+ HSTAR = "H*".freeze
403
414
 
404
415
  # SQLite does not support pattern matching via regular expressions.
405
416
  # SQLite is case insensitive (depending on pragma), so use LIKE for
406
417
  # ILIKE.
407
- def complex_expression_sql(op, args)
418
+ def complex_expression_sql_append(sql, op, args)
408
419
  case op
409
420
  when :~, :'!~', :'~*', :'!~*'
410
421
  raise Error, "SQLite does not support pattern matching via regular expressions"
411
422
  when :ILIKE
412
- super(:LIKE, args.map{|a| SQL::Function.new(:upper, a)})
423
+ super(sql, :LIKE, args.map{|a| SQL::Function.new(:upper, a)})
413
424
  when :"NOT LIKE", :"NOT ILIKE"
414
- "NOT #{complex_expression_sql((op == :"NOT ILIKE" ? :ILIKE : :LIKE), args)}"
425
+ sql << NOT_SPACE
426
+ complex_expression_sql_append(sql, (op == :"NOT ILIKE" ? :ILIKE : :LIKE), args)
415
427
  when :^
416
- complex_expression_arg_pairs(args) do |a, b|
428
+ sql << complex_expression_arg_pairs(args) do |a, b|
417
429
  a = literal(a)
418
430
  b = literal(b)
419
431
  "((~(#{a} & #{b})) & (#{a} | #{b}))"
@@ -421,16 +433,21 @@ module Sequel
421
433
  when :extract
422
434
  part = args.at(0)
423
435
  raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
424
- expr = args.at(1)
425
- "CAST(strftime(#{format}, #{literal(expr)}) AS #{part == :second ? 'NUMERIC' : 'INTEGER'})"
436
+ sql << EXTRACT_OPEN << format << COMMA
437
+ literal_append(sql, args.at(1))
438
+ sql << EXRACT_CLOSE << (part == :second ? NUMERIC : INTEGER) << PAREN_CLOSE
426
439
  else
427
- super(op, args)
440
+ super
428
441
  end
429
442
  end
430
443
 
431
444
  # MSSQL doesn't support the SQL standard CURRENT_DATE or CURRENT_TIME
432
- def constant_sql(constant)
433
- CONSTANT_MAP[constant] || super
445
+ def constant_sql_append(sql, constant)
446
+ if c = CONSTANT_MAP[constant]
447
+ sql << c
448
+ else
449
+ super
450
+ end
434
451
  end
435
452
 
436
453
  # SQLite performs a TRUNCATE style DELETE if no filter is specified.
@@ -454,8 +471,8 @@ module Sequel
454
471
  end
455
472
 
456
473
  # SQLite uses the nonstandard ` (backtick) for quoting identifiers.
457
- def quoted_identifier(c)
458
- "`#{c}`"
474
+ def quoted_identifier_append(sql, c)
475
+ sql << BACKTICK << c.to_s << BACKTICK
459
476
  end
460
477
 
461
478
  # When a qualified column is selected on SQLite and the qualifier
@@ -500,9 +517,10 @@ module Sequel
500
517
  private
501
518
 
502
519
  # SQLite uses string literals instead of identifiers in AS clauses.
503
- def as_sql(expression, aliaz)
520
+ def as_sql_append(sql, aliaz)
504
521
  aliaz = aliaz.value if aliaz.is_a?(SQL::Identifier)
505
- "#{expression} AS #{literal(aliaz.to_s)}"
522
+ sql << AS
523
+ literal_append(sql, aliaz.to_s)
506
524
  end
507
525
 
508
526
  # If col is a qualified column, alias it to the same as the column name
@@ -524,14 +542,12 @@ module Sequel
524
542
 
525
543
  # SQL fragment specifying a list of identifiers
526
544
  def identifier_list(columns)
527
- columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
545
+ columns.map{|i| quote_identifier(i)}.join(COMMA)
528
546
  end
529
547
 
530
548
  # SQLite uses a preceding X for hex escaping strings
531
- def literal_blob(v)
532
- blob = ''
533
- v.each_byte{|x| blob << sprintf('%02x', x)}
534
- "X'#{blob}'"
549
+ def literal_blob_append(sql, v)
550
+ sql << BLOB_START << v.unpack(HSTAR).first << APOS
535
551
  end
536
552
 
537
553
  # SQLite does not support the SQL WITH clause
@@ -323,7 +323,6 @@ module Sequel
323
323
  ps.prepared_statement_name = name
324
324
  db.prepared_statements[name] = ps
325
325
  end
326
- ps.prepared_sql
327
326
  ps
328
327
  end
329
328
 
@@ -335,8 +334,8 @@ module Sequel
335
334
  end
336
335
 
337
336
  # Quote the string using the adapter class method.
338
- def literal_string(v)
339
- "'#{::SQLite3::Database.quote(v)}'"
337
+ def literal_string_append(sql, v)
338
+ sql << "'" << ::SQLite3::Database.quote(v) << "'"
340
339
  end
341
340
 
342
341
  # SQLite uses a : before the name of the argument as a placeholder.
@@ -34,6 +34,7 @@ module Sequel
34
34
  # Dataset class for MySQL datasets accessed via Swift.
35
35
  class Dataset < Swift::Dataset
36
36
  include Sequel::MySQL::DatasetMethods
37
+ APOS = Dataset::APOS
37
38
 
38
39
  # Use execute_insert to execute the replace_sql.
39
40
  def replace(*args)
@@ -43,8 +44,8 @@ module Sequel
43
44
  private
44
45
 
45
46
  # Use Swift's escape method for quoting.
46
- def literal_string(s)
47
- db.synchronize{|c| "'#{c.escape(s)}'"}
47
+ def literal_string_append(sql, s)
48
+ sql << APOS << db.synchronize{|c| c.escape(s)} << APOS
48
49
  end
49
50
  end
50
51
  end
@@ -24,8 +24,8 @@ module Sequel
24
24
  private
25
25
 
26
26
  # Use Swift's escape method for quoting.
27
- def literal_string(s)
28
- db.synchronize{|c| "#{c.escape(s)}"}
27
+ def literal_string_append(sql, s)
28
+ sql << db.synchronize{|c| c.escape(s)}
29
29
  end
30
30
  end
31
31
  end
@@ -186,17 +186,17 @@ module Sequel
186
186
  # Run execute_select on the database with the given SQL and the stored
187
187
  # bind arguments.
188
188
  def execute(sql, opts={}, &block)
189
- super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
189
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
190
190
  end
191
191
 
192
192
  # Same as execute, explicit due to intricacies of alias and super.
193
193
  def execute_dui(sql, opts={}, &block)
194
- super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
194
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
195
195
  end
196
196
 
197
197
  # Same as execute, explicit due to intricacies of alias and super.
198
198
  def execute_insert(sql, opts={}, &block)
199
- super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
199
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
200
200
  end
201
201
  end
202
202
 
@@ -206,15 +206,15 @@ module Sequel
206
206
  execute(sql) do |result|
207
207
  each_opts = {:cache_rows=>false}
208
208
  each_opts[:timezone] = :utc if db.timezone == :utc
209
- rn = row_number_column if @opts[:offset]
209
+ rn = row_number_column if offset = @opts[:offset]
210
210
  columns = cols = result.fields.map{|c| output_identifier(c)}
211
- if opts[:offset]
211
+ if offset
212
212
  rn = row_number_column
213
213
  columns = columns.dup
214
214
  columns.delete(rn)
215
215
  end
216
216
  @columns = columns
217
- if identifier_output_method
217
+ #if identifier_output_method
218
218
  each_opts[:as] = :array
219
219
  result.each(each_opts) do |r|
220
220
  h = {}
@@ -222,6 +222,10 @@ module Sequel
222
222
  h.delete(rn) if rn
223
223
  yield h
224
224
  end
225
+ =begin
226
+ # Temporarily disable this optimization, as tiny_tds uses string keys
227
+ # if result.fields is called before result.each(:symbolize_keys=>true).
228
+ # See https://github.com/rails-sqlserver/tiny_tds/issues/57
225
229
  else
226
230
  each_opts[:symbolize_keys] = true
227
231
  if offset
@@ -233,6 +237,7 @@ module Sequel
233
237
  result.each(each_opts, &Proc.new)
234
238
  end
235
239
  end
240
+ =end
236
241
  end
237
242
  self
238
243
  end
@@ -252,8 +257,9 @@ module Sequel
252
257
  private
253
258
 
254
259
  # Properly escape the given string +v+.
255
- def literal_string(v)
256
- s = db.synchronize{|c| "#{'N' if mssql_unicode_strings}'#{c.escape(v)}'"}
260
+ def literal_string_append(sql, v)
261
+ sql << 'N' if mssql_unicode_strings
262
+ sql << "'" << db.synchronize{|c| c.escape(v)} << "'"
257
263
  end
258
264
  end
259
265
  end
@@ -3,7 +3,7 @@ module Sequel
3
3
  # When a subselect that uses :offset is used in IN or NOT IN,
4
4
  # use a nested subselect that only includes the first column
5
5
  # instead of the ROW_NUMBER column added by the emulated offset support.
6
- def complex_expression_sql(op, args)
6
+ def complex_expression_sql_append(sql, op, args)
7
7
  case op
8
8
  when :IN, :"NOT IN"
9
9
  ds = args.at(1)
@@ -22,7 +22,7 @@ module Sequel
22
22
  when SQL::QualifiedIdentifier
23
23
  c = SQL::Identifier.new(c.column)
24
24
  end
25
- super(op, [args.at(0), ds.from_self.select(c)])
25
+ super(sql, op, [args.at(0), ds.from_self.select(c)])
26
26
  else
27
27
  super
28
28
  end
@@ -44,12 +44,15 @@ module Sequel
44
44
  raise(Error, "#{db.database_type} requires an order be provided if using an offset") unless order = @opts[:order]
45
45
  dsa1 = dataset_alias(1)
46
46
  rn = row_number_column
47
- subselect_sql(unlimited.
47
+ sql = @opts[:append_sql] || ''
48
+ subselect_sql_append(sql, unlimited.
48
49
  unordered.
49
50
  select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
50
51
  from_self(:alias=>dsa1).
51
52
  limit(@opts[:limit]).
52
- where(SQL::Identifier.new(rn) > o))
53
+ where(SQL::Identifier.new(rn) > o).
54
+ order(rn))
55
+ sql
53
56
  end
54
57
 
55
58
  private
@@ -69,9 +69,11 @@ module Sequel
69
69
  # If a transaction is not currently in process, yield to the block immediately.
70
70
  # Otherwise, add the block to the list of blocks to call after the currently
71
71
  # in progress transaction commits (and only if it commits).
72
+ # Options:
73
+ # :server :: The server/shard to use.
72
74
  def after_commit(opts={}, &block)
73
75
  raise Error, "must provide block to after_commit" unless block
74
- synchronize(opts) do |conn|
76
+ synchronize(opts[:server]) do |conn|
75
77
  if h = @transactions[conn]
76
78
  raise Error, "cannot call after_commit in a prepared transaction" if h[:prepare]
77
79
  (h[:after_commit] ||= []) << block
@@ -84,9 +86,11 @@ module Sequel
84
86
  # If a transaction is not currently in progress, ignore the block.
85
87
  # Otherwise, add the block to the list of the blocks to call after the currently
86
88
  # in progress transaction rolls back (and only if it rolls back).
89
+ # Options:
90
+ # :server :: The server/shard to use.
87
91
  def after_rollback(opts={}, &block)
88
92
  raise Error, "must provide block to after_rollback" unless block
89
- synchronize(opts) do |conn|
93
+ synchronize(opts[:server]) do |conn|
90
94
  if h = @transactions[conn]
91
95
  raise Error, "cannot call after_rollback in a prepared transaction" if h[:prepare]
92
96
  (h[:after_rollback] ||= []) << block
@@ -113,11 +113,13 @@ module Sequel
113
113
  add_table = options[:select] == false ? false : true
114
114
  # Whether to add the columns to the list of column aliases
115
115
  add_columns = !ds.opts.include?(:graph_aliases)
116
- # columns to select
117
- select = (opts[:select] || []).dup
118
116
 
119
117
  # Setup the initial graph data structure if it doesn't exist
120
- unless graph = opts[:graph]
118
+ if graph = opts[:graph]
119
+ opts[:graph] = graph = graph.dup
120
+ select = opts[:select].dup
121
+ [:column_aliases, :table_aliases, :column_alias_num].each{|k| graph[k] = graph[k].dup}
122
+ else
121
123
  master = alias_symbol(ds.first_source_alias)
122
124
  raise_alias_error.call if master == table_alias
123
125
  # Master hash storing all .graph related information
@@ -132,9 +134,33 @@ module Sequel
132
134
  # aliased, but are not included if set_graph_aliases
133
135
  # has been used.
134
136
  if add_columns
135
- columns.each do |column|
136
- column_aliases[column] = [master, column]
137
- select.push(SQL::QualifiedIdentifier.new(master, column))
137
+ if (select = @opts[:select]) && !select.empty? && !(select.length == 1 && (select.first.is_a?(SQL::ColumnAll)))
138
+ select = select.each do |sel|
139
+ column = case sel
140
+ when Symbol
141
+ _, c, a = split_symbol(sel)
142
+ (a || c).to_sym
143
+ when SQL::Identifier
144
+ sel.value.to_sym
145
+ when SQL::QualifiedIdentifier
146
+ column = sel.column
147
+ column = column.value if column.is_a?(SQL::Identifier)
148
+ column.to_sym
149
+ when SQL::AliasedExpression
150
+ column = sel.aliaz
151
+ column = column.value if column.is_a?(SQL::Identifier)
152
+ column.to_sym
153
+ else
154
+ raise Error, "can't figure out alias to use for graphing for #{sel.inspect}"
155
+ end
156
+ column_aliases[column] = [master, column]
157
+ end
158
+ select = qualified_expression(select, master)
159
+ else
160
+ select = columns.map do |column|
161
+ column_aliases[column] = [master, column]
162
+ SQL::QualifiedIdentifier.new(master, column)
163
+ end
138
164
  end
139
165
  end
140
166
  end
@@ -174,7 +200,7 @@ module Sequel
174
200
  select.push(identifier)
175
201
  end
176
202
  end
177
- ds.select(*select)
203
+ add_columns ? ds.select(*select) : ds
178
204
  end
179
205
 
180
206
  # This allows you to manually specify the graph aliases to use
@@ -63,6 +63,9 @@ module Sequel
63
63
  # The array/hash of bound variable placeholder names.
64
64
  attr_accessor :prepared_args
65
65
 
66
+ # The dataset that created this prepared statement.
67
+ attr_accessor :orig_dataset
68
+
66
69
  # The argument to supply to insert and update, which may use
67
70
  # placeholders specified by prepared_args
68
71
  attr_accessor :prepared_modify_values
@@ -72,6 +75,12 @@ module Sequel
72
75
  def call(bind_vars={}, &block)
73
76
  bind(bind_vars).run(&block)
74
77
  end
78
+
79
+ # Send the columns to the original dataset, as calling it
80
+ # on the prepared statement can cause problems.
81
+ def columns
82
+ orig_dataset.columns
83
+ end
75
84
 
76
85
  # Returns the SQL for the prepared statement, depending on
77
86
  # the type of the statement and the prepared_modify_values.
@@ -80,7 +89,7 @@ module Sequel
80
89
  when :select, :all
81
90
  select_sql
82
91
  when :first
83
- limit(1).select_sql
92
+ clone(:limit=>1).select_sql
84
93
  when :insert_select
85
94
  returning.insert_sql(*@prepared_modify_values)
86
95
  when :insert
@@ -95,10 +104,14 @@ module Sequel
95
104
  # Changes the values of symbols if they start with $ and
96
105
  # prepared_args is present. If so, they are considered placeholders,
97
106
  # and they are substituted using prepared_arg.
98
- def literal_symbol(v)
107
+ def literal_symbol_append(sql, v)
99
108
  if @opts[:bind_vars] and match = PLACEHOLDER_RE.match(v.to_s)
100
109
  s = match[1].to_sym
101
- prepared_arg?(s) ? literal(prepared_arg(s)) : v
110
+ if prepared_arg?(s)
111
+ literal_append(sql, prepared_arg(s))
112
+ else
113
+ sql << v.to_s
114
+ end
102
115
  else
103
116
  super
104
117
  end
@@ -148,8 +161,8 @@ module Sequel
148
161
  # Use a clone of the dataset extended with prepared statement
149
162
  # support and using the same argument hash so that you can use
150
163
  # bind variables/prepared arguments in subselects.
151
- def subselect_sql(ds)
152
- ps = ds.prepare(:select)
164
+ def subselect_sql_append(sql, ds)
165
+ ps = ds.clone(:append_sql=>sql).prepare(:select)
153
166
  ps = ps.bind(@opts[:bind_vars]) if @opts[:bind_vars]
154
167
  ps.prepared_args = prepared_args
155
168
  ps.prepared_sql
@@ -240,6 +253,7 @@ module Sequel
240
253
  def to_prepared_statement(type, values=nil)
241
254
  ps = bind
242
255
  ps.extend(PreparedStatementMethods)
256
+ ps.orig_dataset = self
243
257
  ps.prepared_type = type
244
258
  ps.prepared_modify_values = values
245
259
  ps
@@ -61,9 +61,8 @@ module Sequel
61
61
  end
62
62
  end
63
63
 
64
- columns = columns.map{|k| literal(String === k ? k.to_sym : k)}
65
64
  if values.is_a?(Array) && values.empty? && !insert_supports_empty_values?
66
- columns = [literal(columns().last)]
65
+ columns = [columns().last]
67
66
  values = ['DEFAULT'.lit]
68
67
  end
69
68
  clone(:columns=>columns, :values=>values)._insert_sql
@@ -79,41 +78,47 @@ module Sequel
79
78
  # DB[:items].literal(:x + 1 > :y) => "((x + 1) > y)"
80
79
  #
81
80
  # If an unsupported object is given, an +Error+ is raised.
82
- def literal(v)
81
+ def literal_append(sql, v)
83
82
  case v
84
- when String
85
- return v if v.is_a?(LiteralString)
86
- v.is_a?(SQL::Blob) ? literal_blob(v) : literal_string(v)
87
83
  when Symbol
88
- literal_symbol(v)
84
+ literal_symbol_append(sql, v)
85
+ when String
86
+ case v
87
+ when LiteralString
88
+ sql << v
89
+ when SQL::Blob
90
+ literal_blob_append(sql, v)
91
+ else
92
+ literal_string_append(sql, v)
93
+ end
89
94
  when Integer
90
- literal_integer(v)
95
+ sql << literal_integer(v)
91
96
  when Hash
92
- literal_hash(v)
97
+ literal_hash_append(sql, v)
93
98
  when SQL::Expression
94
- literal_expression(v)
99
+ literal_expression_append(sql, v)
95
100
  when Float
96
- literal_float(v)
101
+ sql << literal_float(v)
97
102
  when BigDecimal
98
- literal_big_decimal(v)
103
+ sql << literal_big_decimal(v)
99
104
  when NilClass
100
- literal_nil
105
+ sql << literal_nil
101
106
  when TrueClass
102
- literal_true
107
+ sql << literal_true
103
108
  when FalseClass
104
- literal_false
109
+ sql << literal_false
105
110
  when Array
106
- literal_array(v)
111
+ literal_array_append(sql, v)
107
112
  when Time
108
- v.is_a?(SQLTime) ? literal_sqltime(v) : literal_time(v)
113
+ sql << (v.is_a?(SQLTime) ? literal_sqltime(v) : literal_time(v))
109
114
  when DateTime
110
- literal_datetime(v)
115
+ sql << literal_datetime(v)
111
116
  when Date
112
- literal_date(v)
117
+ sql << literal_date(v)
113
118
  when Dataset
114
- literal_dataset(v)
119
+ literal_dataset_append(sql, v)
115
120
  else
116
- literal_other(v)
121
+ literal_other_append(sql, v)
117
122
  end
118
123
  end
119
124
 
@@ -177,84 +182,234 @@ module Sequel
177
182
  clauses.map{|clause| :"#{type}_#{clause}_sql"}.freeze
178
183
  end
179
184
 
185
+ ALL = ' ALL'.freeze
180
186
  AND_SEPARATOR = " AND ".freeze
187
+ APOS = "'".freeze
188
+ APOS_RE = /'/.freeze
189
+ ARRAY_EMPTY = '(NULL)'.freeze
190
+ AS = ' AS '.freeze
191
+ ASC = ' ASC'.freeze
192
+ BACKSLASH_RE = /\\/.freeze
181
193
  BOOL_FALSE = "'f'".freeze
182
194
  BOOL_TRUE = "'t'".freeze
183
- COMMA_SEPARATOR = ', '.freeze
184
- COLUMN_REF_RE1 = /\A(((?!__).)+)__(((?!___).)+)___(.+)\z/.freeze
185
- COLUMN_REF_RE2 = /\A(((?!___).)+)___(.+)\z/.freeze
186
- COLUMN_REF_RE3 = /\A(((?!__).)+)__(.+)\z/.freeze
195
+ BRACKET_CLOSE = ']'.freeze
196
+ BRACKET_OPEN = '['.freeze
197
+ CASE_ELSE = " ELSE ".freeze
198
+ CASE_END = " END)".freeze
199
+ CASE_OPEN = '(CASE'.freeze
200
+ CASE_THEN = " THEN ".freeze
201
+ CASE_WHEN = " WHEN ".freeze
202
+ CAST_OPEN = 'CAST('.freeze
203
+ COLUMN_ALL = '.*'.freeze
204
+ COLUMN_REF_RE1 = /\A((?:(?!__).)+)__((?:(?!___).)+)___(.+)\z/.freeze
205
+ COLUMN_REF_RE2 = /\A((?:(?!___).)+)___(.+)\z/.freeze
206
+ COLUMN_REF_RE3 = /\A((?:(?!__).)+)__(.+)\z/.freeze
207
+ COMMA = ', '.freeze
208
+ COMMA_SEPARATOR = COMMA
209
+ CONDITION_FALSE = '(1 = 0)'.freeze
210
+ CONDITION_TRUE = '(1 = 1)'.freeze
187
211
  COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :compounds]
188
212
  COUNT_OF_ALL_AS_COUNT = SQL::Function.new(:count, LiteralString.new('*'.freeze)).as(:count)
189
213
  DATASET_ALIAS_BASE_NAME = 't'.freeze
214
+ DEFAULT_VALUES = " DEFAULT VALUES".freeze
215
+ DELETE = 'DELETE'.freeze
216
+ DELETE_CLAUSE_METHODS = clause_methods(:delete, %w'delete from where')
217
+ DESC = ' DESC'.freeze
218
+ DISTINCT = " DISTINCT".freeze
219
+ DOT = '.'.freeze
220
+ DOUBLE_APOS = "''".freeze
221
+ DOUBLE_QUOTE = '""'.freeze
222
+ EQUAL = ' = '.freeze
223
+ EXTRACT = 'extract('.freeze
190
224
  FOR_UPDATE = ' FOR UPDATE'.freeze
225
+ FORMAT_DATE = "'%Y-%m-%d'".freeze
226
+ FORMAT_DATE_STANDARD = "DATE '%Y-%m-%d'".freeze
227
+ FORMAT_OFFSET = "%+03i%02i".freeze
228
+ FORMAT_TIMESTAMP_RE = /%[Nz]/.freeze
229
+ FORMAT_TIMESTAMP_USEC = ".%06d".freeze
230
+ FORMAT_USEC = '%N'.freeze
231
+ FRAME_ALL = "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING".freeze
232
+ FRAME_ROWS = "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW".freeze
233
+ FROM = ' FROM '.freeze
234
+ FUNCTION_EMPTY = '()'.freeze
235
+ GROUP_BY = " GROUP BY ".freeze
236
+ HAVING = " HAVING ".freeze
237
+ INSERT = "INSERT".freeze
238
+ INSERT_CLAUSE_METHODS = clause_methods(:insert, %w'insert into columns values')
239
+ INTO = " INTO ".freeze
191
240
  IS_LITERALS = {nil=>'NULL'.freeze, true=>'TRUE'.freeze, false=>'FALSE'.freeze}.freeze
192
241
  IS_OPERATORS = ::Sequel::SQL::ComplexExpression::IS_OPERATORS
242
+ LIMIT = " LIMIT ".freeze
193
243
  N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
244
+ NOT_SPACE = 'NOT '.freeze
194
245
  NULL = "NULL".freeze
246
+ NULLS_FIRST = " NULLS FIRST".freeze
247
+ NULLS_LAST = " NULLS LAST".freeze
248
+ OFFSET = " OFFSET ".freeze
249
+ ON = ' ON '.freeze
250
+ ON_PAREN = " ON (".freeze
251
+ ORDER_BY = " ORDER BY ".freeze
252
+ ORDER_BY_NS = "ORDER BY ".freeze
253
+ OVER = ' OVER '.freeze
254
+ PAREN_CLOSE = ')'.freeze
255
+ PAREN_OPEN = '('.freeze
256
+ PAREN_SPACE_OPEN = ' ('.freeze
257
+ PARTITION_BY = "PARTITION BY ".freeze
258
+ QUAD_BACKSLASH = "\\\\\\\\".freeze
195
259
  QUALIFY_KEYS = [:select, :where, :having, :order, :group]
196
260
  QUESTION_MARK = '?'.freeze
197
- DELETE_CLAUSE_METHODS = clause_methods(:delete, %w'from where')
198
- INSERT_CLAUSE_METHODS = clause_methods(:insert, %w'into columns values')
199
- SELECT_CLAUSE_METHODS = clause_methods(:select, %w'with distinct columns from join where group having compounds order limit lock')
200
- UPDATE_CLAUSE_METHODS = clause_methods(:update, %w'table set where')
261
+ QUESTION_MARK_RE = /\?/.freeze
262
+ QUOTE = '"'.freeze
263
+ QUOTE_RE = /"/.freeze
264
+ RETURNING = " RETURNING ".freeze
265
+ SELECT = 'SELECT'.freeze
266
+ SELECT_CLAUSE_METHODS = clause_methods(:select, %w'with select distinct columns from join where group having compounds order limit lock')
267
+ SET = ' SET '.freeze
268
+ SPACE = ' '.freeze
269
+ SQL_WITH = "WITH ".freeze
270
+ TILDE = '~'.freeze
201
271
  TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S%N%z'".freeze
202
272
  STANDARD_TIMESTAMP_FORMAT = "TIMESTAMP #{TIMESTAMP_FORMAT}".freeze
203
273
  TWO_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::TWO_ARITY_OPERATORS
274
+ UNDERSCORE = '_'.freeze
275
+ UPDATE = 'UPDATE'.freeze
276
+ UPDATE_CLAUSE_METHODS = clause_methods(:update, %w'update table set where')
277
+ USING = ' USING ('.freeze
278
+ VALUES = " VALUES ".freeze
279
+ WHERE = " WHERE ".freeze
204
280
  WILDCARD = LiteralString.new('*').freeze
205
- SQL_WITH = "WITH ".freeze
281
+
282
+ PUBLIC_APPEND_METHODS = (<<-END).split.map{|x| x.to_sym}
283
+ literal
284
+ aliased_expression_sql
285
+ array_sql
286
+ boolean_constant_sql
287
+ case_expression_sql
288
+ cast_sql
289
+ column_all_sql
290
+ complex_expression_sql
291
+ constant_sql
292
+ function_sql
293
+ join_clause_sql
294
+ join_on_clause_sql
295
+ join_using_clause_sql
296
+ negative_boolean_constant_sql
297
+ ordered_expression_sql
298
+ placeholder_literal_string_sql
299
+ qualified_identifier_sql
300
+ quote_identifier
301
+ quote_schema_table
302
+ quoted_identifier
303
+ subscript_sql
304
+ window_sql
305
+ window_function_sql
306
+ END
307
+ PRIVATE_APPEND_METHODS = (<<-END).split.map{|x| x.to_sym}
308
+ argument_list
309
+ as_sql
310
+ column_list
311
+ compound_dataset_sql
312
+ expression_list
313
+ literal_array
314
+ literal_blob
315
+ literal_dataset
316
+ literal_expression
317
+ literal_hash
318
+ literal_other
319
+ literal_string
320
+ literal_symbol
321
+ source_list
322
+ subselect_sql
323
+ table_ref
324
+ END
325
+ def self.def_append_methods(meths)
326
+ meths.each do |meth|
327
+ class_eval(<<-END, __FILE__, __LINE__ + 1)
328
+ def #{meth}(*args, &block)
329
+ s = ''
330
+ #{meth}_append(s, *args, &block)
331
+ s
332
+ end
333
+ END
334
+ end
335
+ end
336
+ def_append_methods(PUBLIC_APPEND_METHODS + PRIVATE_APPEND_METHODS)
337
+ private *PRIVATE_APPEND_METHODS
206
338
 
207
339
  # SQL fragment for AliasedExpression
208
- def aliased_expression_sql(ae)
209
- as_sql(literal(ae.expression), ae.aliaz)
340
+ def aliased_expression_sql_append(sql, ae)
341
+ literal_append(sql, ae.expression)
342
+ as_sql_append(sql, ae.aliaz)
210
343
  end
211
344
 
212
345
  # SQL fragment for Array
213
- def array_sql(a)
214
- a.empty? ? '(NULL)' : "(#{expression_list(a)})"
346
+ def array_sql_append(sql, a)
347
+ if a.empty?
348
+ sql << ARRAY_EMPTY
349
+ else
350
+ sql << PAREN_OPEN
351
+ expression_list_append(sql, a)
352
+ sql << PAREN_CLOSE
353
+ end
215
354
  end
216
355
 
217
356
  # SQL fragment for BooleanConstants
218
- def boolean_constant_sql(constant)
357
+ def boolean_constant_sql_append(sql, constant)
219
358
  if (constant == true || constant == false) && !supports_where_true?
220
- constant == true ? '(1 = 1)' : '(1 = 0)'
359
+ sql << (constant == true ? CONDITION_TRUE : CONDITION_FALSE)
221
360
  else
222
- literal(constant)
361
+ literal_append(sql, constant)
223
362
  end
224
363
  end
225
364
 
226
365
  # SQL fragment for CaseExpression
227
- def case_expression_sql(ce)
228
- sql = '(CASE '
229
- sql << "#{literal(ce.expression)} " if ce.expression?
230
- ce.conditions.collect{ |c,r|
231
- sql << "WHEN #{literal(c)} THEN #{literal(r)} "
232
- }
233
- sql << "ELSE #{literal(ce.default)} END)"
366
+ def case_expression_sql_append(sql, ce)
367
+ sql << CASE_OPEN
368
+ if ce.expression?
369
+ sql << SPACE
370
+ literal_append(sql, ce.expression)
371
+ end
372
+ w = CASE_WHEN
373
+ t = CASE_THEN
374
+ ce.conditions.each do |c,r|
375
+ sql << w
376
+ literal_append(sql, c)
377
+ sql << t
378
+ literal_append(sql, r)
379
+ end
380
+ sql << CASE_ELSE
381
+ literal_append(sql, ce.default)
382
+ sql << CASE_END
234
383
  end
235
384
 
236
385
  # SQL fragment for the SQL CAST expression
237
- def cast_sql(expr, type)
238
- "CAST(#{literal(expr)} AS #{db.cast_type_literal(type)})"
386
+ def cast_sql_append(sql, expr, type)
387
+ sql << CAST_OPEN
388
+ literal_append(sql, expr)
389
+ sql << AS << db.cast_type_literal(type).to_s
390
+ sql << PAREN_CLOSE
239
391
  end
240
392
 
241
393
  # SQL fragment for specifying all columns in a given table
242
- def column_all_sql(ca)
243
- "#{quote_schema_table(ca.table)}.*"
394
+ def column_all_sql_append(sql, ca)
395
+ quote_schema_table_append(sql, ca.table)
396
+ sql << COLUMN_ALL
244
397
  end
245
398
 
246
- # SQL fragment for complex expressions
247
- def complex_expression_sql(op, args)
399
+ def complex_expression_sql_append(sql, op, args)
248
400
  case op
249
401
  when *IS_OPERATORS
250
402
  r = args.at(1)
251
403
  if r.nil? || supports_is_true?
252
404
  raise(InvalidOperation, 'Invalid argument used for IS operator') unless v = IS_LITERALS[r]
253
- "(#{literal(args.at(0))} #{op} #{v})"
405
+ sql << PAREN_OPEN
406
+ literal_append(sql, args.at(0))
407
+ sql << SPACE << op.to_s << SPACE
408
+ sql << v << PAREN_CLOSE
254
409
  elsif op == :IS
255
- complex_expression_sql(:"=", args)
410
+ complex_expression_sql_append(sql, :"=", args)
256
411
  else
257
- complex_expression_sql(:OR, [SQL::BooleanExpression.new(:"!=", *args), SQL::BooleanExpression.new(:IS, args.at(0), nil)])
412
+ complex_expression_sql_append(sql, :OR, [SQL::BooleanExpression.new(:"!=", *args), SQL::BooleanExpression.new(:IS, args.at(0), nil)])
258
413
  end
259
414
  when :IN, :"NOT IN"
260
415
  cols = args.at(0)
@@ -267,147 +422,228 @@ module Sequel
267
422
  if col_array
268
423
  if empty_val_array
269
424
  if op == :IN
270
- literal(SQL::BooleanExpression.from_value_pairs(cols.to_a.map{|x| [x, x]}, :AND, true))
425
+ literal_append(sql, SQL::BooleanExpression.from_value_pairs(cols.to_a.map{|x| [x, x]}, :AND, true))
271
426
  else
272
- literal(1=>1)
427
+ literal_append(sql, 1=>1)
273
428
  end
274
429
  elsif !supports_multiple_column_in?
275
430
  if val_array
276
431
  expr = SQL::BooleanExpression.new(:OR, *vals.to_a.map{|vs| SQL::BooleanExpression.from_value_pairs(cols.to_a.zip(vs).map{|c, v| [c, v]})})
277
- literal(op == :IN ? expr : ~expr)
432
+ literal_append(sql, op == :IN ? expr : ~expr)
278
433
  else
279
434
  old_vals = vals
280
435
  vals = vals.naked if vals.is_a?(Sequel::Dataset)
281
436
  vals = vals.to_a
282
437
  val_cols = old_vals.columns
283
- complex_expression_sql(op, [cols, vals.map!{|x| x.values_at(*val_cols)}])
438
+ complex_expression_sql_append(sql, op, [cols, vals.map!{|x| x.values_at(*val_cols)}])
284
439
  end
285
440
  else
286
441
  # If the columns and values are both arrays, use array_sql instead of
287
442
  # literal so that if values is an array of two element arrays, it
288
443
  # will be treated as a value list instead of a condition specifier.
289
- "(#{literal(cols)} #{op} #{val_array ? array_sql(vals) : literal(vals)})"
444
+ sql << PAREN_OPEN
445
+ literal_append(sql, cols)
446
+ sql << SPACE << op.to_s << SPACE
447
+ if val_array
448
+ array_sql_append(sql, vals)
449
+ else
450
+ literal_append(sql, vals)
451
+ end
452
+ sql << PAREN_CLOSE
290
453
  end
291
454
  else
292
455
  if empty_val_array
293
456
  if op == :IN
294
- literal(SQL::BooleanExpression.from_value_pairs([[cols, cols]], :AND, true))
457
+ literal_append(sql, SQL::BooleanExpression.from_value_pairs([[cols, cols]], :AND, true))
295
458
  else
296
- literal(1=>1)
459
+ literal_append(sql, 1=>1)
297
460
  end
298
461
  else
299
- "(#{literal(cols)} #{op} #{literal(vals)})"
462
+ sql << PAREN_OPEN
463
+ literal_append(sql, cols)
464
+ sql << SPACE << op.to_s << SPACE
465
+ literal_append(sql, vals)
466
+ sql << PAREN_CLOSE
300
467
  end
301
468
  end
302
469
  when *TWO_ARITY_OPERATORS
303
- "(#{literal(args.at(0))} #{op} #{literal(args.at(1))})"
470
+ sql << PAREN_OPEN
471
+ literal_append(sql, args.at(0))
472
+ sql << SPACE << op.to_s << SPACE
473
+ literal_append(sql, args.at(1))
474
+ sql << PAREN_CLOSE
304
475
  when *N_ARITY_OPERATORS
305
- "(#{args.collect{|a| literal(a)}.join(" #{op} ")})"
476
+ sql << PAREN_OPEN
477
+ c = false
478
+ op_str = " #{op} "
479
+ args.each do |a|
480
+ sql << op_str if c
481
+ literal_append(sql, a)
482
+ c ||= true
483
+ end
484
+ sql << PAREN_CLOSE
306
485
  when :NOT
307
- "NOT #{literal(args.at(0))}"
486
+ sql << NOT_SPACE
487
+ literal_append(sql, args.at(0))
308
488
  when :NOOP
309
- literal(args.at(0))
489
+ literal_append(sql, args.at(0))
310
490
  when :'B~'
311
- "~#{literal(args.at(0))}"
491
+ sql << TILDE
492
+ literal_append(sql, args.at(0))
312
493
  when :extract
313
- "extract(#{args.at(0)} FROM #{literal(args.at(1))})"
494
+ sql << EXTRACT << args.at(0).to_s << FROM
495
+ literal_append(sql, args.at(1))
496
+ sql << PAREN_CLOSE
314
497
  else
315
498
  raise(InvalidOperation, "invalid operator #{op}")
316
499
  end
317
500
  end
318
501
 
319
502
  # SQL fragment for constants
320
- def constant_sql(constant)
321
- constant.to_s
503
+ def constant_sql_append(sql, constant)
504
+ sql << constant.to_s
322
505
  end
323
506
 
324
507
  # SQL fragment specifying an SQL function call
325
- def function_sql(f)
508
+ def function_sql_append(sql, f)
509
+ sql << f.f.to_s
326
510
  args = f.args
327
- "#{f.f}#{args.empty? ? '()' : literal(args)}"
511
+ if args.empty?
512
+ sql << FUNCTION_EMPTY
513
+ else
514
+ literal_append(sql, args)
515
+ end
328
516
  end
329
517
 
330
518
  # SQL fragment specifying a JOIN clause without ON or USING.
331
- def join_clause_sql(jc)
519
+ def join_clause_sql_append(sql, jc)
332
520
  table = jc.table
333
521
  table_alias = jc.table_alias
334
522
  table_alias = nil if table == table_alias
335
- tref = table_ref(table)
336
- " #{join_type_sql(jc.join_type)} #{table_alias ? as_sql(tref, table_alias) : tref}"
523
+ sql << SPACE << join_type_sql(jc.join_type) << SPACE
524
+ table_ref_append(sql, table)
525
+ as_sql_append(sql, table_alias) if table_alias
337
526
  end
338
527
 
339
528
  # SQL fragment specifying a JOIN clause with ON.
340
- def join_on_clause_sql(jc)
341
- "#{join_clause_sql(jc)} ON #{literal(filter_expr(jc.on))}"
529
+ def join_on_clause_sql_append(sql, jc)
530
+ join_clause_sql_append(sql, jc)
531
+ sql << ON
532
+ literal_append(sql, filter_expr(jc.on))
342
533
  end
343
534
 
344
535
  # SQL fragment specifying a JOIN clause with USING.
345
- def join_using_clause_sql(jc)
346
- "#{join_clause_sql(jc)} USING (#{column_list(jc.using)})"
536
+ def join_using_clause_sql_append(sql, jc)
537
+ join_clause_sql_append(sql, jc)
538
+ sql << USING
539
+ column_list_append(sql, jc.using)
540
+ sql << PAREN_CLOSE
347
541
  end
348
542
 
349
543
  # SQL fragment for NegativeBooleanConstants
350
- def negative_boolean_constant_sql(constant)
351
- "NOT #{boolean_constant_sql(constant)}"
544
+ def negative_boolean_constant_sql_append(sql, constant)
545
+ sql << NOT_SPACE
546
+ boolean_constant_sql_append(sql, constant)
352
547
  end
353
548
 
354
549
  # SQL fragment for the ordered expression, used in the ORDER BY
355
550
  # clause.
356
- def ordered_expression_sql(oe)
357
- s = "#{literal(oe.expression)} #{oe.descending ? 'DESC' : 'ASC'}"
551
+ def ordered_expression_sql_append(sql, oe)
552
+ literal_append(sql, oe.expression)
553
+ sql << (oe.descending ? DESC : ASC)
358
554
  case oe.nulls
359
555
  when :first
360
- "#{s} NULLS FIRST"
556
+ sql << NULLS_FIRST
361
557
  when :last
362
- "#{s} NULLS LAST"
363
- else
364
- s
558
+ sql << NULLS_LAST
365
559
  end
366
560
  end
367
561
 
368
562
  # SQL fragment for a literal string with placeholders
369
- def placeholder_literal_string_sql(pls)
563
+ def placeholder_literal_string_sql_append(sql, pls)
370
564
  args = pls.args
371
- s = if args.is_a?(Hash)
565
+ sql << PAREN_OPEN if pls.parens
566
+ if args.is_a?(Hash)
372
567
  re = /:(#{args.keys.map{|k| Regexp.escape(k.to_s)}.join('|')})\b/
373
- pls.str.gsub(re){literal(args[$1.to_sym])}
568
+ if RUBY_VERSION >= '1.8.7'
569
+ str = pls.str
570
+ loop do
571
+ previous, q, str = str.partition(re)
572
+ sql << previous
573
+ literal_append(sql, args[($1||q[1..-1].to_s).to_sym]) unless q.empty?
574
+ break if str.empty?
575
+ end
576
+ else
577
+ sql << pls.str.gsub(re){literal(args[$1.to_sym])}
578
+ end
374
579
  else
375
580
  i = -1
376
- pls.str.gsub(QUESTION_MARK){literal(args.at(i+=1))}
581
+ if RUBY_VERSION >= '1.8.7'
582
+ str = pls.str
583
+ loop do
584
+ previous, q, str = str.partition(QUESTION_MARK)
585
+ sql << previous
586
+ literal_append(sql, args.at(i+=1)) unless q.empty?
587
+ break if str.empty?
588
+ end
589
+ else
590
+ sql << pls.str.gsub(QUESTION_MARK_RE){literal(args.at(i+=1))}
591
+ end
377
592
  end
378
- s = "(#{s})" if pls.parens
379
- s
593
+ sql << PAREN_CLOSE if pls.parens
380
594
  end
381
595
 
382
596
  # SQL fragment for the qualifed identifier, specifying
383
597
  # a table and a column (or schema and table).
384
- def qualified_identifier_sql(qcr)
385
- [qcr.table, qcr.column].map{|x| [SQL::QualifiedIdentifier, SQL::Identifier, Symbol].any?{|c| x.is_a?(c)} ? literal(x) : quote_identifier(x)}.join('.')
598
+ def qualified_identifier_sql_append(sql, qcr)
599
+ case t = qcr.table
600
+ when Symbol, SQL::QualifiedIdentifier, SQL::Identifier
601
+ literal_append(sql, t)
602
+ else
603
+ quote_identifier_append(sql, t)
604
+ end
605
+ sql << DOT
606
+ case c = qcr.column
607
+ when Symbol, SQL::QualifiedIdentifier, SQL::Identifier
608
+ literal_append(sql, c)
609
+ else
610
+ quote_identifier_append(sql, c)
611
+ end
386
612
  end
387
613
 
388
614
  # Adds quoting to identifiers (columns and tables). If identifiers are not
389
615
  # being quoted, returns name as a string. If identifiers are being quoted
390
616
  # quote the name with quoted_identifier.
391
- def quote_identifier(name)
392
- return name if name.is_a?(LiteralString)
393
- name = name.value if name.is_a?(SQL::Identifier)
394
- name = input_identifier(name)
395
- name = quoted_identifier(name) if quote_identifiers?
396
- name
617
+ def quote_identifier_append(sql, name)
618
+ if name.is_a?(LiteralString)
619
+ sql << name
620
+ else
621
+ name = name.value if name.is_a?(SQL::Identifier)
622
+ name = input_identifier(name)
623
+ if quote_identifiers?
624
+ quoted_identifier_append(sql, name)
625
+ else
626
+ sql << name
627
+ end
628
+ end
397
629
  end
398
630
 
399
631
  # Separates the schema from the table and returns a string with them
400
632
  # quoted (if quoting identifiers)
401
- def quote_schema_table(table)
633
+ def quote_schema_table_append(sql, table)
402
634
  schema, table = schema_and_table(table)
403
- "#{"#{quote_identifier(schema)}." if schema}#{quote_identifier(table)}"
635
+ if schema
636
+ quote_identifier_append(sql, schema)
637
+ sql << DOT
638
+ end
639
+ quote_identifier_append(sql, table)
404
640
  end
405
641
 
406
642
  # This method quotes the given name with the SQL standard double quote.
407
643
  # should be overridden by subclasses to provide quoting not matching the
408
644
  # SQL standard, such as backtick (used by MySQL and SQLite).
409
- def quoted_identifier(name)
410
- "\"#{name.to_s.gsub('"', '""')}\""
645
+ def quoted_identifier_append(sql, name)
646
+ sql << QUOTE << name.to_s.gsub(QUOTE_RE, DOUBLE_QUOTE) << QUOTE
411
647
  end
412
648
 
413
649
  # Split the schema information from the table
@@ -429,34 +665,59 @@ module Sequel
429
665
  end
430
666
 
431
667
  # SQL fragment for specifying subscripts (SQL array accesses)
432
- def subscript_sql(s)
433
- "#{literal(s.f)}[#{expression_list(s.sub)}]"
668
+ def subscript_sql_append(sql, s)
669
+ literal_append(sql, s.f)
670
+ sql << BRACKET_OPEN
671
+ expression_list_append(sql, s.sub)
672
+ sql << BRACKET_CLOSE
434
673
  end
435
674
 
436
675
  # The SQL fragment for the given window's options.
437
- def window_sql(opts)
676
+ def window_sql_append(sql, opts)
438
677
  raise(Error, 'This dataset does not support window functions') unless supports_window_functions?
439
- window = literal(opts[:window]) if opts[:window]
440
- partition = "PARTITION BY #{expression_list(Array(opts[:partition]))}" if opts[:partition]
441
- order = "ORDER BY #{expression_list(Array(opts[:order]))}" if opts[:order]
442
- frame = case opts[:frame]
678
+ sql << PAREN_OPEN
679
+ window, part, order, frame = opts.values_at(:window, :partition, :order, :frame)
680
+ space = false
681
+ space_s = SPACE
682
+ if window
683
+ literal_append(sql, window)
684
+ space = true
685
+ end
686
+ if part
687
+ sql << space_s if space
688
+ sql << PARTITION_BY
689
+ expression_list_append(sql, Array(part))
690
+ space = true
691
+ end
692
+ if order
693
+ sql << space_s if space
694
+ sql << ORDER_BY_NS
695
+ expression_list_append(sql, Array(order))
696
+ space = true
697
+ end
698
+ case frame
443
699
  when nil
444
- nil
700
+ # nothing
445
701
  when :all
446
- "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING"
702
+ sql << space_s if space
703
+ sql << FRAME_ALL
447
704
  when :rows
448
- "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
705
+ sql << space_s if space
706
+ sql << FRAME_ROWS
449
707
  when String
450
- opts[:frame]
708
+ sql << space_s if space
709
+ sql << frame
451
710
  else
452
711
  raise Error, "invalid window frame clause, should be :all, :rows, a string, or nil"
453
712
  end
454
- "(#{[window, partition, order, frame].compact.join(' ')})"
713
+ sql << PAREN_CLOSE
455
714
  end
456
715
 
457
716
  # The SQL fragment for the given window function's function and window.
458
- def window_function_sql(function, window)
459
- "#{literal(function)} OVER #{literal(window)}"
717
+ def window_function_sql_append(sql, function, window)
718
+ literal_append(sql, function)
719
+ sql << OVER
720
+ literal_append(sql, window)
460
721
  end
461
722
 
462
723
  protected
@@ -528,14 +789,20 @@ module Sequel
528
789
  options_overlap(COUNT_FROM_SELF_OPTS) ? from_self : unordered
529
790
  end
530
791
 
531
- # Do a simple join of the arguments (which should be strings or symbols) separated by commas
532
- def argument_list(args)
533
- args.join(COMMA_SEPARATOR)
792
+ def argument_list_append(sql, args)
793
+ c = false
794
+ comma = COMMA
795
+ args.each do |a|
796
+ sql << comma if c
797
+ sql << a.to_s
798
+ c ||= true
799
+ end
534
800
  end
535
801
 
536
802
  # SQL fragment for specifying an alias. expression should already be literalized.
537
- def as_sql(expression, aliaz)
538
- "#{expression} AS #{quote_identifier(aliaz)}"
803
+ def as_sql_append(sql, aliaz)
804
+ sql << AS
805
+ quote_identifier_append(sql, aliaz)
539
806
  end
540
807
 
541
808
  # Raise an InvalidOperation exception if deletion is not allowed
@@ -547,17 +814,21 @@ module Sequel
547
814
 
548
815
  # Prepare an SQL statement by calling all clause methods for the given statement type.
549
816
  def clause_sql(type)
550
- sql = type.to_s.upcase
817
+ sql = @opts[:append_sql] || ''
551
818
  send("#{type}_clause_methods").each{|x| send(x, sql)}
552
819
  sql
553
820
  end
554
821
 
555
822
  # Converts an array of column names into a comma seperated string of
556
823
  # column names. If the array is empty, a wildcard (*) is returned.
557
- def column_list(columns)
558
- (columns.nil? || columns.empty?) ? WILDCARD : expression_list(columns)
824
+ def column_list_append(sql, columns)
825
+ if (columns.nil? || columns.empty?)
826
+ sql << WILDCARD
827
+ else
828
+ expression_list_append(sql, columns)
829
+ end
559
830
  end
560
-
831
+
561
832
  # Yield each two pair of arguments to the block, which should
562
833
  # return a string representing the SQL code for those
563
834
  # two arguments. If more than 2 arguments are provided, all
@@ -576,8 +847,8 @@ module Sequel
576
847
  end
577
848
 
578
849
  # The SQL to use for the dataset used in a UNION/INTERSECT/EXCEPT clause.
579
- def compound_dataset_sql(ds)
580
- subselect_sql(ds)
850
+ def compound_dataset_sql_append(sql, ds)
851
+ subselect_sql_append(sql, ds)
581
852
  end
582
853
 
583
854
  # The alias to use for datasets, takes a number to make sure the name is unique.
@@ -590,10 +861,20 @@ module Sequel
590
861
  DELETE_CLAUSE_METHODS
591
862
  end
592
863
 
864
+ def delete_delete_sql(sql)
865
+ sql << DELETE
866
+ end
867
+
593
868
  # Converts an array of expressions into a comma separated string of
594
869
  # expressions.
595
- def expression_list(columns)
596
- columns.map{|i| literal(i)}.join(COMMA_SEPARATOR)
870
+ def expression_list_append(sql, columns)
871
+ c = false
872
+ co = COMMA
873
+ columns.each do |col|
874
+ sql << co if c
875
+ literal_append(sql, col)
876
+ c ||= true
877
+ end
597
878
  end
598
879
 
599
880
  # The strftime format to use when literalizing the time.
@@ -607,8 +888,8 @@ module Sequel
607
888
  # of hours and minutes.
608
889
  def format_timestamp(v)
609
890
  v2 = db.from_application_timestamp(v)
610
- fmt = default_timestamp_format.gsub(/%[Nz]/) do |m|
611
- if m == '%N'
891
+ fmt = default_timestamp_format.gsub(FORMAT_TIMESTAMP_RE) do |m|
892
+ if m == FORMAT_USEC
612
893
  format_timestamp_usec(v.is_a?(DateTime) ? v.sec_fraction*(RUBY_VERSION < '1.9.0' ? 86400000000 : 1000000) : v.usec) if supports_timestamp_usecs?
613
894
  else
614
895
  if supports_timestamp_timezones?
@@ -624,13 +905,13 @@ module Sequel
624
905
 
625
906
  # Return the SQL timestamp fragment to use for the timezone offset.
626
907
  def format_timestamp_offset(hour, minute)
627
- sprintf("%+03i%02i", hour, minute)
908
+ sprintf(FORMAT_OFFSET, hour, minute)
628
909
  end
629
910
 
630
911
  # Return the SQL timestamp fragment to use for the fractional time part.
631
912
  # Should start with the decimal point. Uses 6 decimal places by default.
632
913
  def format_timestamp_usec(usec)
633
- sprintf(".%06d", usec)
914
+ sprintf(FORMAT_TIMESTAMP_USEC, usec)
634
915
  end
635
916
 
636
917
  # Modify the identifier returned from the database based on the
@@ -641,7 +922,8 @@ module Sequel
641
922
 
642
923
  # SQL fragment specifying the table to insert INTO
643
924
  def insert_into_sql(sql)
644
- sql << " INTO #{source_list(@opts[:from])}"
925
+ sql << INTO
926
+ source_list_append(sql, @opts[:from])
645
927
  end
646
928
 
647
929
  # The order of methods to call to build the INSERT SQL statement
@@ -652,18 +934,42 @@ module Sequel
652
934
  # SQL fragment specifying the columns to insert into
653
935
  def insert_columns_sql(sql)
654
936
  columns = opts[:columns]
655
- sql << " (#{columns.join(COMMA_SEPARATOR)})" if columns && !columns.empty?
937
+ if columns && !columns.empty?
938
+ sql << PAREN_SPACE_OPEN
939
+ c = false
940
+ co = COMMA
941
+ columns.each do |col|
942
+ sql << co if c
943
+ if col.is_a?(String) && !col.is_a?(LiteralString)
944
+ quote_identifier_append(sql, col)
945
+ else
946
+ literal_append(sql, col)
947
+ end
948
+ c ||= true
949
+ end
950
+ sql << PAREN_CLOSE
951
+ end
952
+ end
953
+
954
+ def insert_insert_sql(sql)
955
+ sql << INSERT
656
956
  end
657
957
 
658
958
  # SQL fragment specifying the values to insert.
659
959
  def insert_values_sql(sql)
660
960
  case values = opts[:values]
661
961
  when Array
662
- sql << (values.empty? ? " DEFAULT VALUES" : " VALUES #{literal(values)}")
962
+ if values.empty?
963
+ sql << DEFAULT_VALUES
964
+ else
965
+ sql << VALUES
966
+ literal_append(sql, values)
967
+ end
663
968
  when Dataset
664
- sql << " #{subselect_sql(values)}"
969
+ sql << SPACE
970
+ subselect_sql_append(sql, values)
665
971
  when LiteralString
666
- sql << " #{values}"
972
+ sql << SPACE << values
667
973
  else
668
974
  raise Error, "Unsupported INSERT values type, should be an Array or Dataset: #{values.inspect}"
669
975
  end
@@ -672,7 +978,8 @@ module Sequel
672
978
  # SQL fragment specifying the values to return.
673
979
  def insert_returning_sql(sql)
674
980
  if opts.has_key?(:returning)
675
- sql << " RETURNING #{column_list(Array(opts[:returning]))}"
981
+ sql << RETURNING
982
+ column_list_append(sql, Array(opts[:returning]))
676
983
  end
677
984
  end
678
985
  alias delete_returning_sql insert_returning_sql
@@ -681,7 +988,7 @@ module Sequel
681
988
  # SQL fragment specifying a JOIN type, converts underscores to
682
989
  # spaces and upcases.
683
990
  def join_type_sql(join_type)
684
- "#{join_type.to_s.gsub('_', ' ').upcase} JOIN"
991
+ "#{join_type.to_s.gsub(UNDERSCORE, SPACE).upcase} JOIN"
685
992
  end
686
993
 
687
994
  # Whether this dataset is a joined dataset
@@ -690,8 +997,12 @@ module Sequel
690
997
  end
691
998
 
692
999
  # SQL fragment for Array. Treats as an expression if an array of all two pairs, or as a SQL array otherwise.
693
- def literal_array(v)
694
- Sequel.condition_specifier?(v) ? literal_expression(SQL::BooleanExpression.from_value_pairs(v)) : array_sql(v)
1000
+ def literal_array_append(sql, v)
1001
+ if Sequel.condition_specifier?(v)
1002
+ literal_expression_append(sql, SQL::BooleanExpression.from_value_pairs(v))
1003
+ else
1004
+ array_sql_append(sql, v)
1005
+ end
695
1006
  end
696
1007
 
697
1008
  # SQL fragment for BigDecimal
@@ -701,18 +1012,24 @@ module Sequel
701
1012
  end
702
1013
 
703
1014
  # SQL fragment for SQL::Blob
704
- def literal_blob(v)
705
- literal_string(v)
1015
+ def literal_blob_append(sql, v)
1016
+ literal_string_append(sql, v)
706
1017
  end
707
1018
 
708
1019
  # SQL fragment for Dataset. Does a subselect inside parantheses.
709
- def literal_dataset(v)
710
- "(#{subselect_sql(v)})"
1020
+ def literal_dataset_append(sql, v)
1021
+ sql << PAREN_OPEN
1022
+ subselect_sql_append(sql, v)
1023
+ sql << PAREN_CLOSE
711
1024
  end
712
1025
 
713
1026
  # SQL fragment for Date, using the ISO8601 format.
714
1027
  def literal_date(v)
715
- v.strftime("#{'DATE ' if requires_sql_standard_datetimes?}'%Y-%m-%d'")
1028
+ if requires_sql_standard_datetimes?
1029
+ v.strftime(FORMAT_DATE_STANDARD)
1030
+ else
1031
+ v.strftime(FORMAT_DATE)
1032
+ end
716
1033
  end
717
1034
 
718
1035
  # SQL fragment for DateTime
@@ -721,8 +1038,8 @@ module Sequel
721
1038
  end
722
1039
 
723
1040
  # SQL fragment for SQL::Expression, result depends on the specific type of expression.
724
- def literal_expression(v)
725
- v.to_s(self)
1041
+ def literal_expression_append(sql, v)
1042
+ v.to_s_append(self, sql)
726
1043
  end
727
1044
 
728
1045
  # SQL fragment for false
@@ -736,8 +1053,8 @@ module Sequel
736
1053
  end
737
1054
 
738
1055
  # SQL fragment for Hash, treated as an expression
739
- def literal_hash(v)
740
- literal_expression(SQL::BooleanExpression.from_value_pairs(v))
1056
+ def literal_hash_append(sql, v)
1057
+ literal_expression_append(sql, SQL::BooleanExpression.from_value_pairs(v))
741
1058
  end
742
1059
 
743
1060
  # SQL fragment for Integer
@@ -756,9 +1073,11 @@ module Sequel
756
1073
  # provided and should add that method to Sequel::Dataset, allowing for adapters
757
1074
  # to provide customized literalizations.
758
1075
  # If a database specific type is allowed, this should be overriden in a subclass.
759
- def literal_other(v)
760
- if v.respond_to?(:sql_literal)
761
- v.sql_literal(self)
1076
+ def literal_other_append(sql, v)
1077
+ if v.respond_to?(:sql_literal_append)
1078
+ v.sql_literal_append(self, sql)
1079
+ elsif v.respond_to?(:sql_literal)
1080
+ sql << v.sql_literal(self)
762
1081
  else
763
1082
  raise Error, "can't express #{v.inspect} as a SQL literal"
764
1083
  end
@@ -770,8 +1089,8 @@ module Sequel
770
1089
  end
771
1090
 
772
1091
  # SQL fragment for String. Doubles \ and ' by default.
773
- def literal_string(v)
774
- "'#{v.gsub(/\\/, "\\\\\\\\").gsub(/'/, "''")}'"
1092
+ def literal_string_append(sql, v)
1093
+ sql << APOS << v.gsub(BACKSLASH_RE, QUAD_BACKSLASH).gsub(APOS_RE, DOUBLE_APOS) << APOS
775
1094
  end
776
1095
 
777
1096
  # Converts a symbol into a column name. This method supports underscore
@@ -782,10 +1101,14 @@ module Sequel
782
1101
  # dataset.literal(:abc___a) #=> "abc AS a"
783
1102
  # dataset.literal(:items__abc) #=> "items.abc"
784
1103
  # dataset.literal(:items__abc___a) #=> "items.abc AS a"
785
- def literal_symbol(v)
1104
+ def literal_symbol_append(sql, v)
786
1105
  c_table, column, c_alias = split_symbol(v)
787
- qc = "#{"#{quote_identifier(c_table)}." if c_table}#{quote_identifier(column)}"
788
- c_alias ? as_sql(qc, c_alias) : qc
1106
+ if c_table
1107
+ quote_identifier_append(sql, c_table)
1108
+ sql << DOT
1109
+ end
1110
+ quote_identifier_append(sql, column)
1111
+ as_sql_append(sql, c_alias) if c_alias
789
1112
  end
790
1113
 
791
1114
  # SQL fragment for Time
@@ -831,13 +1154,19 @@ module Sequel
831
1154
 
832
1155
  # Modify the sql to add the columns selected
833
1156
  def select_columns_sql(sql)
834
- sql << " #{column_list(@opts[:select])}"
1157
+ sql << SPACE
1158
+ column_list_append(sql, @opts[:select])
835
1159
  end
836
1160
 
837
1161
  # Modify the sql to add the DISTINCT modifier
838
1162
  def select_distinct_sql(sql)
839
1163
  if distinct = @opts[:distinct]
840
- sql << " DISTINCT#{" ON (#{expression_list(distinct)})" unless distinct.empty?}"
1164
+ sql << DISTINCT
1165
+ unless distinct.empty?
1166
+ sql << ON_PAREN
1167
+ expression_list_append(sql, distinct)
1168
+ sql << PAREN_CLOSE
1169
+ end
841
1170
  end
842
1171
  end
843
1172
 
@@ -845,59 +1174,89 @@ module Sequel
845
1174
  # This uses a subselect for the compound datasets used, because using parantheses doesn't
846
1175
  # work on all databases. I consider this an ugly hack, but can't I think of a better default.
847
1176
  def select_compounds_sql(sql)
848
- return unless @opts[:compounds]
849
- @opts[:compounds].each do |type, dataset, all|
850
- sql << " #{type.to_s.upcase}#{' ALL' if all} #{compound_dataset_sql(dataset)}"
1177
+ return unless c = @opts[:compounds]
1178
+ c.each do |type, dataset, all|
1179
+ sql << SPACE << type.to_s.upcase
1180
+ sql << ALL if all
1181
+ sql << SPACE
1182
+ compound_dataset_sql_append(sql, dataset)
851
1183
  end
852
1184
  end
853
1185
 
854
1186
  # Modify the sql to add the list of tables to select FROM
855
1187
  def select_from_sql(sql)
856
- sql << " FROM #{source_list(@opts[:from])}" if @opts[:from]
1188
+ if f = @opts[:from]
1189
+ sql << FROM
1190
+ source_list_append(sql, f)
1191
+ end
857
1192
  end
858
1193
  alias delete_from_sql select_from_sql
859
1194
 
860
1195
  # Modify the sql to add the expressions to GROUP BY
861
1196
  def select_group_sql(sql)
862
- sql << " GROUP BY #{expression_list(@opts[:group])}" if @opts[:group]
1197
+ if group = @opts[:group]
1198
+ sql << GROUP_BY
1199
+ expression_list_append(sql, group)
1200
+ end
863
1201
  end
864
1202
 
865
1203
  # Modify the sql to add the filter criteria in the HAVING clause
866
1204
  def select_having_sql(sql)
867
- sql << " HAVING #{literal(@opts[:having])}" if @opts[:having]
1205
+ if having = @opts[:having]
1206
+ sql << HAVING
1207
+ literal_append(sql, having)
1208
+ end
868
1209
  end
869
1210
 
870
1211
  # Modify the sql to add the list of tables to JOIN to
871
1212
  def select_join_sql(sql)
872
- @opts[:join].each{|j| sql << literal(j)} if @opts[:join]
1213
+ if js = @opts[:join]
1214
+ js.each{|j| literal_append(sql, j)}
1215
+ end
873
1216
  end
874
1217
 
875
1218
  # Modify the sql to limit the number of rows returned and offset
876
1219
  def select_limit_sql(sql)
877
- sql << " LIMIT #{literal(@opts[:limit])}" if @opts[:limit]
878
- sql << " OFFSET #{literal(@opts[:offset])}" if @opts[:offset]
1220
+ if l = @opts[:limit]
1221
+ sql << LIMIT
1222
+ literal_append(sql, l)
1223
+ end
1224
+ if o = @opts[:offset]
1225
+ sql << OFFSET
1226
+ literal_append(sql, o)
1227
+ end
879
1228
  end
880
1229
 
881
1230
  # Modify the sql to support the different types of locking modes.
882
1231
  def select_lock_sql(sql)
883
- case @opts[:lock]
1232
+ case l = @opts[:lock]
884
1233
  when :update
885
1234
  sql << FOR_UPDATE
886
1235
  when String
887
- sql << " #{@opts[:lock]}"
1236
+ sql << SPACE << l
888
1237
  end
889
1238
  end
890
1239
 
891
1240
  # Modify the sql to add the expressions to ORDER BY
892
1241
  def select_order_sql(sql)
893
- sql << " ORDER BY #{expression_list(@opts[:order])}" if @opts[:order]
1242
+ if o = @opts[:order]
1243
+ sql << ORDER_BY
1244
+ expression_list_append(sql, o)
1245
+ end
894
1246
  end
895
1247
  alias delete_order_sql select_order_sql
896
1248
  alias update_order_sql select_order_sql
897
1249
 
1250
+ def select_select_sql(sql)
1251
+ sql << SELECT
1252
+ end
1253
+
898
1254
  # Modify the sql to add the filter criteria in the WHERE clause
899
1255
  def select_where_sql(sql)
900
- sql << " WHERE #{literal(@opts[:where])}" if @opts[:where]
1256
+ if w = @opts[:where]
1257
+ sql << WHERE
1258
+ literal_append(sql, w)
1259
+ end
901
1260
  end
902
1261
  alias delete_where_sql select_where_sql
903
1262
  alias update_where_sql select_where_sql
@@ -906,7 +1265,22 @@ module Sequel
906
1265
  def select_with_sql(sql)
907
1266
  ws = opts[:with]
908
1267
  return if !ws || ws.empty?
909
- sql.replace("#{select_with_sql_base}#{ws.map{|w| "#{quote_identifier(w[:name])}#{"(#{argument_list(w[:args])})" if w[:args]} AS #{literal_dataset(w[:dataset])}"}.join(COMMA_SEPARATOR)} #{sql}")
1268
+ sql << select_with_sql_base
1269
+ c = false
1270
+ comma = COMMA
1271
+ ws.each do |w|
1272
+ sql << comma if c
1273
+ quote_identifier_append(sql, w[:name])
1274
+ if args = w[:args]
1275
+ sql << PAREN_OPEN
1276
+ argument_list_append(sql, args)
1277
+ sql << PAREN_CLOSE
1278
+ end
1279
+ sql << AS
1280
+ literal_dataset_append(sql, w[:dataset])
1281
+ c ||= true
1282
+ end
1283
+ sql << SPACE
910
1284
  end
911
1285
  alias delete_with_sql select_with_sql
912
1286
  alias insert_with_sql select_with_sql
@@ -918,9 +1292,15 @@ module Sequel
918
1292
  end
919
1293
 
920
1294
  # Converts an array of source names into into a comma separated list.
921
- def source_list(source)
922
- raise(Error, 'No source specified for query') if source.nil? || source.empty?
923
- source.map{|s| table_ref(s)}.join(COMMA_SEPARATOR)
1295
+ def source_list_append(sql, sources)
1296
+ raise(Error, 'No source specified for query') if sources.nil? || sources == []
1297
+ c = false
1298
+ co = COMMA
1299
+ sources.each do |s|
1300
+ sql << co if c
1301
+ table_ref_append(sql, s)
1302
+ c ||= true
1303
+ end
924
1304
  end
925
1305
 
926
1306
  # Splits the symbol into three parts. Each part will
@@ -929,13 +1309,13 @@ module Sequel
929
1309
  # For columns, these parts are the table, column, and alias.
930
1310
  # For tables, these parts are the schema, table, and alias.
931
1311
  def split_symbol(sym)
932
- s = sym.to_s
933
- if m = COLUMN_REF_RE1.match(s)
934
- [m[1], m[3], m[5]]
935
- elsif m = COLUMN_REF_RE2.match(s)
936
- [nil, m[1], m[3]]
937
- elsif m = COLUMN_REF_RE3.match(s)
938
- [m[1], m[3], nil]
1312
+ case s = sym.to_s
1313
+ when COLUMN_REF_RE1
1314
+ [$1, $2, $3]
1315
+ when COLUMN_REF_RE2
1316
+ [nil, $1, $2]
1317
+ when COLUMN_REF_RE3
1318
+ [$1, $2, nil]
939
1319
  else
940
1320
  [nil, s, nil]
941
1321
  end
@@ -945,18 +1325,35 @@ module Sequel
945
1325
  # can be a PlaceholderLiteralString in addition to a String,
946
1326
  # we literalize nonstrings.
947
1327
  def static_sql(sql)
948
- sql.is_a?(String) ? sql : literal(sql)
1328
+ if append_sql = @opts[:append_sql]
1329
+ if sql.is_a?(String)
1330
+ append_sql << sql
1331
+ else
1332
+ literal_append(append_sql, sql)
1333
+ end
1334
+ else
1335
+ if sql.is_a?(String)
1336
+ sql
1337
+ else
1338
+ literal(sql)
1339
+ end
1340
+ end
949
1341
  end
950
1342
 
951
1343
  # SQL fragment for a subselect using the given database's SQL.
952
- def subselect_sql(ds)
953
- ds.sql
1344
+ def subselect_sql_append(sql, ds)
1345
+ ds.clone(:append_sql=>sql).sql
954
1346
  end
955
1347
 
956
1348
  # SQL fragment specifying a table name.
957
- def table_ref(t)
958
- t.is_a?(String) ? quote_identifier(t) : literal(t)
1349
+ def table_ref_append(sql, t)
1350
+ if t.is_a?(String)
1351
+ quote_identifier_append(sql, t)
1352
+ else
1353
+ literal_append(sql, t)
1354
+ end
959
1355
  end
1356
+ alias identifier_append table_ref_append
960
1357
 
961
1358
  # The order of methods to call to build the UPDATE SQL statement
962
1359
  def update_clause_methods
@@ -966,25 +1363,38 @@ module Sequel
966
1363
  # SQL fragment specifying the tables from with to delete.
967
1364
  # Includes join table if modifying joins is allowed.
968
1365
  def update_table_sql(sql)
969
- sql << " #{source_list(@opts[:from])}"
1366
+ sql << SPACE
1367
+ source_list_append(sql, @opts[:from])
970
1368
  select_join_sql(sql) if supports_modifying_joins?
971
1369
  end
972
1370
 
973
1371
  # The SQL fragment specifying the columns and values to SET.
974
1372
  def update_set_sql(sql)
975
1373
  values = opts[:values]
976
- set = if values.is_a?(Hash)
1374
+ sql << SET
1375
+ if values.is_a?(Hash)
977
1376
  values = opts[:defaults].merge(values) if opts[:defaults]
978
1377
  values = values.merge(opts[:overrides]) if opts[:overrides]
979
- # get values from hash
980
- values.map do |k, v|
981
- "#{k.is_a?(String) && !k.is_a?(LiteralString) ? quote_identifier(k) : literal(k)} = #{literal(v)}"
982
- end.join(COMMA_SEPARATOR)
1378
+ c = false
1379
+ eq = EQUAL
1380
+ values.each do |k, v|
1381
+ sql << COMMA if c
1382
+ if k.is_a?(String) && !k.is_a?(LiteralString)
1383
+ quote_identifier_append(sql, k)
1384
+ else
1385
+ literal_append(sql, k)
1386
+ end
1387
+ sql << eq
1388
+ literal_append(sql, v)
1389
+ c ||= true
1390
+ end
983
1391
  else
984
- # copy values verbatim
985
- values
1392
+ sql << values
986
1393
  end
987
- sql << " SET #{set}"
1394
+ end
1395
+
1396
+ def update_update_sql(sql)
1397
+ sql << UPDATE
988
1398
  end
989
1399
  end
990
1400
  end