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
@@ -333,34 +333,69 @@ module Sequel
333
333
  COMMA_SEPARATOR = ', '.freeze
334
334
  FOR_SHARE = ' LOCK IN SHARE MODE'.freeze
335
335
  SQL_CALC_FOUND_ROWS = ' SQL_CALC_FOUND_ROWS'.freeze
336
- DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'from where order limit')
337
- INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'ignore into columns values on_duplicate_key_update')
338
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct calc_found_rows columns from join where group having compounds order limit lock')
339
- UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'table set where order limit')
336
+ DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'delete from where order limit')
337
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'insert ignore into columns values on_duplicate_key_update')
338
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct calc_found_rows columns from join where group having compounds order limit lock')
339
+ UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'update table set where order limit')
340
+ SPACE = Dataset::SPACE
341
+ PAREN_OPEN = Dataset::PAREN_OPEN
342
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
343
+ NOT_SPACE = Dataset::NOT_SPACE
344
+ FROM = Dataset::FROM
345
+ INSERT = Dataset::INSERT
346
+ COMMA = Dataset::COMMA
347
+ LIMIT = Dataset::LIMIT
348
+ REGEXP = 'REGEXP'.freeze
349
+ LIKE = 'LIKE'.freeze
350
+ BINARY = 'BINARY '.freeze
351
+ CONCAT = "CONCAT".freeze
352
+ CAST_BITCOMP_OPEN = "CAST(~".freeze
353
+ CAST_BITCOMP_CLOSE = " AS SIGNED INTEGER)".freeze
354
+ STRAIGHT_JOIN = 'STRAIGHT_JOIN'.freeze
355
+ NATURAL_LEFT_JOIN = 'NATURAL LEFT JOIN'.freeze
356
+ BACKTICK = '`'.freeze
357
+ EMPTY_COLUMNS = " ()".freeze
358
+ EMPTY_VALUES = " VALUES ()".freeze
359
+ IGNORE = " IGNORE".freeze
360
+ REPLACE = 'REPLACE'.freeze
361
+ ON_DUPLICATE_KEY_UPDATE = " ON DUPLICATE KEY UPDATE ".freeze
362
+ EQ_VALUES = '=VALUES('.freeze
363
+ EQ = '='.freeze
340
364
 
341
365
  # MySQL specific syntax for LIKE/REGEXP searches, as well as
342
366
  # string concatenation.
343
- def complex_expression_sql(op, args)
367
+ def complex_expression_sql_append(sql, op, args)
344
368
  case op
345
369
  when :IN, :"NOT IN"
346
370
  ds = args.at(1)
347
371
  if ds.is_a?(Sequel::Dataset) && ds.opts[:limit]
348
- super(op, [args.at(0), ds.from_self])
372
+ super(sql, op, [args.at(0), ds.from_self])
349
373
  else
350
374
  super
351
375
  end
352
376
  when :~, :'!~', :'~*', :'!~*', :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
353
- "(#{literal(args.at(0))} #{'NOT ' if [:'NOT LIKE', :'NOT ILIKE', :'!~', :'!~*'].include?(op)}#{[:~, :'!~', :'~*', :'!~*'].include?(op) ? 'REGEXP' : 'LIKE'} #{'BINARY ' if [:~, :'!~', :LIKE, :'NOT LIKE'].include?(op)}#{literal(args.at(1))})"
377
+ sql << PAREN_OPEN
378
+ literal_append(sql, args.at(0))
379
+ sql << SPACE
380
+ sql << 'NOT ' if [:'NOT LIKE', :'NOT ILIKE', :'!~', :'!~*'].include?(op)
381
+ sql << ([:~, :'!~', :'~*', :'!~*'].include?(op) ? REGEXP : LIKE)
382
+ sql << SPACE
383
+ sql << BINARY if [:~, :'!~', :LIKE, :'NOT LIKE'].include?(op)
384
+ literal_append(sql, args.at(1))
385
+ sql << PAREN_CLOSE
354
386
  when :'||'
355
387
  if args.length > 1
356
- "CONCAT(#{args.collect{|a| literal(a)}.join(', ')})"
388
+ sql << CONCAT
389
+ array_sql_append(sql, args)
357
390
  else
358
- literal(args.at(0))
391
+ literal_append(sql, args.at(0))
359
392
  end
360
393
  when :'B~'
361
- "CAST(~#{literal(args.at(0))} AS SIGNED INTEGER)"
394
+ sql << CAST_BITCOMP_OPEN
395
+ literal_append(sql, args.at(0))
396
+ sql << CAST_BITCOMP_CLOSE
362
397
  else
363
- super(op, args)
398
+ super
364
399
  end
365
400
  end
366
401
 
@@ -409,9 +444,12 @@ module Sequel
409
444
  # STRAIGHT_JOIN.
410
445
  def join_type_sql(join_type)
411
446
  case join_type
412
- when :straight then 'STRAIGHT_JOIN'
413
- when :natural_inner then 'NATURAL LEFT JOIN'
414
- else super
447
+ when :straight
448
+ STRAIGHT_JOIN
449
+ when :natural_inner
450
+ NATURAL_LEFT_JOIN
451
+ else
452
+ super
415
453
  end
416
454
  end
417
455
 
@@ -452,7 +490,9 @@ module Sequel
452
490
 
453
491
  # MySQL specific syntax for inserting multiple values at once.
454
492
  def multi_insert_sql(columns, values)
455
- [insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
493
+ sql = LiteralString.new('VALUES ')
494
+ expression_list_append(sql, values.map{|r| Array(r)})
495
+ [insert_sql(columns, sql)]
456
496
  end
457
497
 
458
498
  # MySQL uses the number of rows actually modified in the update,
@@ -462,8 +502,8 @@ module Sequel
462
502
  end
463
503
 
464
504
  # MySQL uses the nonstandard ` (backtick) for quoting identifiers.
465
- def quoted_identifier(c)
466
- "`#{c}`"
505
+ def quoted_identifier_append(sql, c)
506
+ sql << BACKTICK << c.to_s << BACKTICK
467
507
  end
468
508
 
469
509
  # MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
@@ -501,13 +541,6 @@ module Sequel
501
541
  false
502
542
  end
503
543
 
504
- protected
505
-
506
- # If this is an replace instead of an insert, use replace instead
507
- def _insert_sql
508
- @opts[:replace] ? clause_sql(:replace) : super
509
- end
510
-
511
544
  private
512
545
 
513
546
  # MySQL supports the ORDER BY and LIMIT clauses for DELETE statements
@@ -519,7 +552,10 @@ module Sequel
519
552
  # from, but include the others for the purposes of selecting rows.
520
553
  def delete_from_sql(sql)
521
554
  if joined_dataset?
522
- sql << " #{source_list(@opts[:from][0..0])} FROM #{source_list(@opts[:from])}"
555
+ sql << SPACE
556
+ source_list_append(sql, @opts[:from][0..0])
557
+ sql << FROM
558
+ source_list_append(sql, @opts[:from])
523
559
  select_join_sql(sql)
524
560
  else
525
561
  super
@@ -536,7 +572,7 @@ module Sequel
536
572
  def insert_columns_sql(sql)
537
573
  values = opts[:values]
538
574
  if values.is_a?(Array) && values.empty?
539
- sql << " ()"
575
+ sql << EMPTY_COLUMNS
540
576
  else
541
577
  super
542
578
  end
@@ -544,19 +580,57 @@ module Sequel
544
580
 
545
581
  # MySQL supports INSERT IGNORE INTO
546
582
  def insert_ignore_sql(sql)
547
- sql << " IGNORE" if opts[:insert_ignore]
583
+ sql << IGNORE if opts[:insert_ignore]
584
+ end
585
+
586
+ # If this is an replace instead of an insert, use replace instead
587
+ def insert_insert_sql(sql)
588
+ sql << (@opts[:replace] ? REPLACE : INSERT)
548
589
  end
549
590
 
550
591
  # MySQL supports INSERT ... ON DUPLICATE KEY UPDATE
551
592
  def insert_on_duplicate_key_update_sql(sql)
552
- sql << on_duplicate_key_update_sql if opts[:on_duplicate_key_update]
593
+ if update_cols = opts[:on_duplicate_key_update]
594
+ update_vals = nil
595
+
596
+ if update_cols.empty?
597
+ update_cols = columns
598
+ elsif update_cols.last.is_a?(Hash)
599
+ update_vals = update_cols.last
600
+ update_cols = update_cols[0..-2]
601
+ end
602
+
603
+ sql << ON_DUPLICATE_KEY_UPDATE
604
+ c = false
605
+ co = COMMA
606
+ values = EQ_VALUES
607
+ endp = PAREN_CLOSE
608
+ update_cols.each do |col|
609
+ sql << co if c
610
+ quote_identifier_append(sql, col)
611
+ sql << values
612
+ quote_identifier_append(sql, col)
613
+ sql << endp
614
+ c ||= true
615
+ end
616
+ if update_vals
617
+ eq = EQ
618
+ update_vals.map do |col,v|
619
+ sql << co if c
620
+ quote_identifier_append(sql, col)
621
+ sql << eq
622
+ literal_append(sql, v)
623
+ c ||= true
624
+ end
625
+ end
626
+ end
553
627
  end
554
628
 
555
629
  # MySQL doesn't use the standard DEFAULT VALUES for empty values.
556
630
  def insert_values_sql(sql)
557
631
  values = opts[:values]
558
632
  if values.is_a?(Array) && values.empty?
559
- sql << " VALUES ()"
633
+ sql << EMPTY_VALUES
560
634
  else
561
635
  super
562
636
  end
@@ -564,7 +638,10 @@ module Sequel
564
638
 
565
639
  # MySQL allows a LIMIT in DELETE and UPDATE statements.
566
640
  def limit_sql(sql)
567
- sql << " LIMIT #{@opts[:limit]}" if @opts[:limit]
641
+ if l = @opts[:limit]
642
+ sql << LIMIT
643
+ literal_append(sql, @opts[:limit])
644
+ end
568
645
  end
569
646
  alias delete_limit_sql limit_sql
570
647
  alias update_limit_sql limit_sql
@@ -579,25 +656,6 @@ module Sequel
579
656
  BOOL_TRUE
580
657
  end
581
658
 
582
- # MySQL specific syntax for ON DUPLICATE KEY UPDATE
583
- def on_duplicate_key_update_sql
584
- if update_cols = opts[:on_duplicate_key_update]
585
- update_vals = nil
586
-
587
- if update_cols.empty?
588
- update_cols = columns
589
- elsif update_cols.last.is_a?(Hash)
590
- update_vals = update_cols.last
591
- update_cols = update_cols[0..-2]
592
- end
593
-
594
- updating = update_cols.map{|c| "#{quote_identifier(c)}=VALUES(#{quote_identifier(c)})" }
595
- updating += update_vals.map{|c,v| "#{quote_identifier(c)}=#{literal(v)}" } if update_vals
596
-
597
- " ON DUPLICATE KEY UPDATE #{updating.join(COMMA_SEPARATOR)}"
598
- end
599
- end
600
-
601
659
  # MySQL does not support the SQL WITH clause for SELECT statements
602
660
  def select_clause_methods
603
661
  SELECT_CLAUSE_METHODS
@@ -63,8 +63,8 @@ module Sequel
63
63
  module CallableStatementMethods
64
64
  # Extend given dataset with this module so subselects inside subselects in
65
65
  # prepared statements work.
66
- def subselect_sql(ds)
67
- ps = ds.to_prepared_statement(:select)
66
+ def subselect_sql_append(sql, ds)
67
+ ps = ds.to_prepared_statement(:select).clone(:append_sql => sql)
68
68
  ps.extend(CallableStatementMethods)
69
69
  ps = ps.bind(@opts[:bind_vars]) if @opts[:bind_vars]
70
70
  ps.prepared_args = prepared_args
@@ -177,27 +177,53 @@ module Sequel
177
177
  module DatasetMethods
178
178
  include EmulateOffsetWithRowNumber
179
179
 
180
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct columns from join where group having compounds order lock')
180
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct columns from join where group having compounds order lock')
181
181
  ROW_NUMBER_EXPRESSION = 'ROWNUM'.lit.freeze
182
+ SPACE = Dataset::SPACE
183
+ APOS = Dataset::APOS
184
+ APOS_RE = Dataset::APOS_RE
185
+ DOUBLE_APOS = Dataset::DOUBLE_APOS
186
+ FROM = Dataset::FROM
187
+ BITCOMP_OPEN = "((0 - ".freeze
188
+ BITCOMP_CLOSE = ") - 1)".freeze
189
+ ILIKE_0 = "(UPPER(".freeze
190
+ ILIKE_1 = ") ".freeze
191
+ ILIKE_2 = ' UPPER('.freeze
192
+ ILIKE_3 = "))".freeze
193
+ LIKE = 'LIKE'.freeze
194
+ NOT_LIKE = 'NOT LIKE'.freeze
195
+ TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S%N %z'".freeze
196
+ TIMESTAMP_OFFSET_FORMAT = "%+03i:%02i".freeze
197
+ BOOL_FALSE = "'N'".freeze
198
+ BOOL_TRUE = "'Y'".freeze
199
+ HSTAR = "H*".freeze
200
+ DUAL = ['DUAL'.freeze].freeze
182
201
 
183
202
  # Oracle needs to emulate bitwise operators and ILIKE/NOT ILIKE operators.
184
- def complex_expression_sql(op, args)
203
+ def complex_expression_sql_append(sql, op, args)
185
204
  case op
186
205
  when :&
187
- complex_expression_arg_pairs(args){|a, b| "CAST(BITAND(#{literal(a)}, #{literal(b)}) AS INTEGER)"}
206
+ sql << complex_expression_arg_pairs(args){|a, b| "CAST(BITAND(#{literal(a)}, #{literal(b)}) AS INTEGER)"}
188
207
  when :|
189
- complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} - #{complex_expression_sql(:&, [a, b])} + #{literal(b)})"}
208
+ sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} - #{complex_expression_sql(:&, [a, b])} + #{literal(b)})"}
190
209
  when :^
191
- complex_expression_arg_pairs(args){|*x| "(#{complex_expression_sql(:|, x)} - #{complex_expression_sql(:&, x)})"}
210
+ sql << complex_expression_arg_pairs(args){|*x| "(#{complex_expression_sql(:|, x)} - #{complex_expression_sql(:&, x)})"}
192
211
  when :'B~'
193
- "((0 - #{literal(args.at(0))}) - 1)"
212
+ sql << BITCOMP_OPEN
213
+ literal_append(sql, args.at(0))
214
+ sql << BITCOMP_CLOSE
194
215
  when :<<
195
- complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * power(2, #{literal b}))"}
216
+ sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * power(2, #{literal b}))"}
196
217
  when :>>
197
- complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / power(2, #{literal b}))"}
218
+ sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / power(2, #{literal b}))"}
198
219
  when :ILIKE, :'NOT ILIKE'
199
- a, b = args
200
- "(UPPER(#{literal(a)}) #{op == :ILIKE ? :LIKE : :'NOT LIKE'} UPPER(#{literal(b)}))"
220
+ sql << ILIKE_0
221
+ literal_append(sql, args.at(0))
222
+ sql << ILIKE_1
223
+ sql << (op == :ILIKE ? LIKE : NOT_LIKE)
224
+ sql<< ILIKE_2
225
+ literal_append(sql, args.at(1))
226
+ sql << ILIKE_3
201
227
  else
202
228
  super
203
229
  end
@@ -206,9 +232,9 @@ module Sequel
206
232
  # Oracle doesn't support CURRENT_TIME, as it doesn't have
207
233
  # a type for storing just time values without a date, so
208
234
  # use CURRENT_TIMESTAMP in its place.
209
- def constant_sql(c)
235
+ def constant_sql_append(sql, c)
210
236
  if c == :CURRENT_TIME
211
- super(:CURRENT_TIMESTAMP)
237
+ super(sql, :CURRENT_TIMESTAMP)
212
238
  else
213
239
  super
214
240
  end
@@ -244,7 +270,9 @@ module Sequel
244
270
  # Lock doesn't work in subselects, so don't use a subselect when locking.
245
271
  # Don't use a subselect if custom SQL is used, as it breaks somethings.
246
272
  ds = ds.from_self unless @opts[:lock]
247
- subselect_sql(ds.where(SQL::ComplexExpression.new(:<=, ROW_NUMBER_EXPRESSION, limit)))
273
+ sql = @opts[:append_sql] || ''
274
+ subselect_sql_append(sql, ds.where(SQL::ComplexExpression.new(:<=, ROW_NUMBER_EXPRESSION, limit)))
275
+ sql
248
276
  else
249
277
  super
250
278
  end
@@ -289,13 +317,14 @@ module Sequel
289
317
 
290
318
  # Oracle doesn't support the use of AS when aliasing a dataset. It doesn't require
291
319
  # the use of AS anywhere, so this disables it in all cases.
292
- def as_sql(expression, aliaz)
293
- "#{expression} #{quote_identifier(aliaz)}"
320
+ def as_sql_append(sql, aliaz)
321
+ sql << SPACE
322
+ quote_identifier_append(sql, aliaz)
294
323
  end
295
324
 
296
325
  # The strftime format to use when literalizing the time.
297
326
  def default_timestamp_format
298
- "TIMESTAMP '%Y-%m-%d %H:%M:%S%N %z'".freeze
327
+ TIMESTAMP_FORMAT
299
328
  end
300
329
 
301
330
  # If this dataset is associated with a sequence, return the most recently
@@ -307,7 +336,7 @@ module Sequel
307
336
 
308
337
  # Use a colon for the timestamp offset, since Oracle appears to require it.
309
338
  def format_timestamp_offset(hour, minute)
310
- sprintf("%+03i:%02i", hour, minute)
339
+ sprintf(TIMESTAMP_OFFSET_FORMAT, hour, minute)
311
340
  end
312
341
 
313
342
  # Oracle doesn't support empty values when inserting.
@@ -316,26 +345,23 @@ module Sequel
316
345
  end
317
346
 
318
347
  # Use string in hex format for blob data.
319
- def literal_blob(v)
320
- blob = "'"
321
- v.each_byte{|x| blob << sprintf('%02x', x)}
322
- blob << "'"
323
- blob
348
+ def literal_blob_append(sql, v)
349
+ sql << APOS << v.unpack(HSTAR).first << APOS
324
350
  end
325
351
 
326
352
  # Oracle uses 'N' for false values.
327
353
  def literal_false
328
- "'N'"
354
+ BOOL_FALSE
329
355
  end
330
356
 
331
357
  # Oracle uses the SQL standard of only doubling ' inside strings.
332
- def literal_string(v)
333
- "'#{v.gsub("'", "''")}'"
358
+ def literal_string_append(sql, v)
359
+ sql << APOS << v.gsub(APOS_RE, DOUBLE_APOS) << APOS
334
360
  end
335
361
 
336
362
  # Oracle uses 'Y' for true values.
337
363
  def literal_true
338
- "'Y'"
364
+ BOOL_TRUE
339
365
  end
340
366
 
341
367
  # Use the Oracle-specific SQL clauses (no limit, since it is emulated).
@@ -348,7 +374,8 @@ module Sequel
348
374
  # so add the dummy DUAL table if the dataset doesn't select
349
375
  # from a table.
350
376
  def select_from_sql(sql)
351
- sql << " FROM #{source_list(@opts[:from] || ['DUAL'])}"
377
+ sql << FROM
378
+ source_list_append(sql, @opts[:from] || DUAL)
352
379
  end
353
380
  end
354
381
  end
@@ -638,29 +638,43 @@ module Sequel
638
638
  BOOL_FALSE = 'false'.freeze
639
639
  BOOL_TRUE = 'true'.freeze
640
640
  COMMA_SEPARATOR = ', '.freeze
641
- DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'from using where')
642
- DELETE_CLAUSE_METHODS_91 = Dataset.clause_methods(:delete, %w'with from using where returning')
641
+ DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'delete from using where')
642
+ DELETE_CLAUSE_METHODS_91 = Dataset.clause_methods(:delete, %w'with delete from using where returning')
643
643
  EXCLUSIVE = 'EXCLUSIVE'.freeze
644
644
  EXPLAIN = 'EXPLAIN '.freeze
645
645
  EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
646
646
  FOR_SHARE = ' FOR SHARE'.freeze
647
- INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'into columns values')
648
- INSERT_CLAUSE_METHODS_82 = Dataset.clause_methods(:insert, %w'into columns values returning')
649
- INSERT_CLAUSE_METHODS_91 = Dataset.clause_methods(:insert, %w'with into columns values returning')
647
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'insert into columns values')
648
+ INSERT_CLAUSE_METHODS_82 = Dataset.clause_methods(:insert, %w'insert into columns values returning')
649
+ INSERT_CLAUSE_METHODS_91 = Dataset.clause_methods(:insert, %w'with insert into columns values returning')
650
650
  LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
651
651
  NULL = LiteralString.new('NULL').freeze
652
652
  PG_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
653
653
  QUERY_PLAN = 'QUERY PLAN'.to_sym
654
654
  ROW_EXCLUSIVE = 'ROW EXCLUSIVE'.freeze
655
655
  ROW_SHARE = 'ROW SHARE'.freeze
656
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit lock')
657
- SELECT_CLAUSE_METHODS_84 = Dataset.clause_methods(:select, %w'with distinct columns from join where group having window compounds order limit lock')
656
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct columns from join where group having compounds order limit lock')
657
+ SELECT_CLAUSE_METHODS_84 = Dataset.clause_methods(:select, %w'with select distinct columns from join where group having window compounds order limit lock')
658
658
  SHARE = 'SHARE'.freeze
659
659
  SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
660
660
  SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
661
661
  SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
662
- UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'table set from where')
663
- UPDATE_CLAUSE_METHODS_91 = Dataset.clause_methods(:update, %w'with table set from where returning')
662
+ UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'update table set from where')
663
+ UPDATE_CLAUSE_METHODS_91 = Dataset.clause_methods(:update, %w'with update table set from where returning')
664
+ SPACE = Dataset::SPACE
665
+ FROM = Dataset::FROM
666
+ APOS = Dataset::APOS
667
+ APOS_RE = Dataset::APOS_RE
668
+ DOUBLE_APOS = Dataset::DOUBLE_APOS
669
+ PAREN_OPEN = Dataset::PAREN_OPEN
670
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
671
+ COMMA = Dataset::COMMA
672
+ AS = Dataset::AS
673
+ XOR_OP = ' # '.freeze
674
+ CRLF = "\r\n".freeze
675
+ BLOB_RE = /[\000-\037\047\134\177-\377]/n.freeze
676
+ WINDOW = " WINDOW ".freeze
677
+ EMPTY_STRING = ''.freeze
664
678
 
665
679
  # Shared methods for prepared statements when used with PostgreSQL databases.
666
680
  module PreparedStatementMethods
@@ -698,10 +712,16 @@ module Sequel
698
712
 
699
713
  # Handle converting the ruby xor operator (^) into the
700
714
  # PostgreSQL xor operator (#).
701
- def complex_expression_sql(op, args)
715
+ def complex_expression_sql_append(sql, op, args)
702
716
  case op
703
717
  when :^
704
- "(#{args.collect{|a| literal(a)}.join(" # ")})"
718
+ j = XOR_OP
719
+ c = false
720
+ args.each do |a|
721
+ sql << j if c
722
+ literal_append(sql, a)
723
+ c ||= true
724
+ end
705
725
  else
706
726
  super
707
727
  end
@@ -714,7 +734,7 @@ module Sequel
714
734
 
715
735
  # Return the results of an EXPLAIN query as a string
716
736
  def explain(opts={})
717
- with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join("\r\n")
737
+ with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join(CRLF)
718
738
  end
719
739
 
720
740
  # Return a cloned dataset which will use FOR SHARE to lock returned rows.
@@ -767,7 +787,9 @@ module Sequel
767
787
  return super if server_version < 80200
768
788
 
769
789
  # postgresql 8.2 introduces support for multi-row insert
770
- [insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
790
+ sql = LiteralString.new('VALUES ')
791
+ expression_list_append(sql, values.map{|r| Array(r)})
792
+ [insert_sql(columns, sql)]
771
793
  end
772
794
 
773
795
  # PostgreSQL supports using the WITH clause in subqueries if it
@@ -818,7 +840,8 @@ module Sequel
818
840
 
819
841
  # Only include the primary table in the main delete clause
820
842
  def delete_from_sql(sql)
821
- sql << " FROM #{source_list(@opts[:from][0..0])}"
843
+ sql << FROM
844
+ source_list_append(sql, @opts[:from][0..0])
822
845
  end
823
846
 
824
847
  # Use USING to specify additional tables in a delete query
@@ -849,14 +872,15 @@ module Sequel
849
872
  if(from = @opts[:from][1..-1]).empty?
850
873
  raise(Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs') if @opts[:join]
851
874
  else
852
- sql << " #{type} #{source_list(from)}"
875
+ sql << SPACE << type.to_s << SPACE
876
+ source_list_append(sql, from)
853
877
  select_join_sql(sql)
854
878
  end
855
879
  end
856
880
 
857
881
  # Use a generic blob quoting method, hopefully overridden in one of the subadapter methods
858
- def literal_blob(v)
859
- "'#{v.gsub(/[\000-\037\047\134\177-\377]/n){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"}}'"
882
+ def literal_blob_append(sql, v)
883
+ sql << APOS << v.gsub(BLOB_RE){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"} << APOS
860
884
  end
861
885
 
862
886
  # PostgreSQL uses FALSE for false values
@@ -865,8 +889,8 @@ module Sequel
865
889
  end
866
890
 
867
891
  # Assume that SQL standard quoting is on, per Sequel's defaults
868
- def literal_string(v)
869
- "'#{v.gsub("'", "''")}'"
892
+ def literal_string_append(sql, v)
893
+ sql << APOS << v.gsub(APOS_RE, DOUBLE_APOS) << APOS
870
894
  end
871
895
 
872
896
  # PostgreSQL uses FALSE for false values
@@ -881,8 +905,10 @@ module Sequel
881
905
 
882
906
  # PostgreSQL requires parentheses around compound datasets if they use
883
907
  # CTEs, and using them in other places doesn't hurt.
884
- def compound_dataset_sql(ds)
885
- "(#{super})"
908
+ def compound_dataset_sql_append(sql, ds)
909
+ sql << PAREN_OPEN
910
+ super
911
+ sql << PAREN_CLOSE
886
912
  end
887
913
 
888
914
  # Support FOR SHARE locking when using the :share lock style.
@@ -892,14 +918,26 @@ module Sequel
892
918
 
893
919
  # SQL fragment for named window specifications
894
920
  def select_window_sql(sql)
895
- sql << " WINDOW #{@opts[:window].map{|name, window| "#{literal(name)} AS #{literal(window)}"}.join(', ')}" if @opts[:window]
921
+ if ws = @opts[:window]
922
+ sql << WINDOW
923
+ c = false
924
+ co = COMMA
925
+ as = AS
926
+ ws.map do |name, window|
927
+ sql << co if c
928
+ literal_append(sql, name)
929
+ sql << as
930
+ literal_append(sql, window)
931
+ c ||= true
932
+ end
933
+ end
896
934
  end
897
935
 
898
936
  # Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
899
937
  def select_with_sql_base
900
938
  opts[:with].any?{|w| w[:recursive]} ? SQL_WITH_RECURSIVE : super
901
939
  end
902
-
940
+
903
941
  # The version of the database server
904
942
  def server_version
905
943
  db.server_version(@opts[:server])
@@ -907,8 +945,8 @@ module Sequel
907
945
 
908
946
  # Concatenate the expressions with a space in between
909
947
  def full_text_string_join(cols)
910
- cols = Array(cols).map{|x| SQL::Function.new(:COALESCE, x, '')}
911
- cols = cols.zip([' '] * cols.length).flatten
948
+ cols = Array(cols).map{|x| SQL::Function.new(:COALESCE, x, EMPTY_STRING)}
949
+ cols = cols.zip([SPACE] * cols.length).flatten
912
950
  cols.pop
913
951
  literal(SQL::StringExpression.new(:'||', *cols))
914
952
  end
@@ -925,7 +963,8 @@ module Sequel
925
963
 
926
964
  # Only include the primary table in the main update clause
927
965
  def update_table_sql(sql)
928
- sql << " #{source_list(@opts[:from][0..0])}"
966
+ sql << SPACE
967
+ source_list_append(sql, @opts[:from][0..0])
929
968
  end
930
969
  end
931
970
  end