sequel 3.33.0 → 3.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/CHANGELOG +140 -0
  2. data/Rakefile +7 -0
  3. data/bin/sequel +22 -2
  4. data/doc/dataset_basics.rdoc +1 -1
  5. data/doc/mass_assignment.rdoc +3 -1
  6. data/doc/querying.rdoc +28 -4
  7. data/doc/reflection.rdoc +23 -3
  8. data/doc/release_notes/3.34.0.txt +671 -0
  9. data/doc/schema_modification.rdoc +18 -2
  10. data/doc/virtual_rows.rdoc +49 -0
  11. data/lib/sequel/adapters/do/mysql.rb +0 -5
  12. data/lib/sequel/adapters/ibmdb.rb +9 -4
  13. data/lib/sequel/adapters/jdbc.rb +9 -4
  14. data/lib/sequel/adapters/jdbc/h2.rb +8 -2
  15. data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
  16. data/lib/sequel/adapters/jdbc/postgresql.rb +43 -0
  17. data/lib/sequel/adapters/jdbc/sqlite.rb +19 -0
  18. data/lib/sequel/adapters/mock.rb +24 -3
  19. data/lib/sequel/adapters/mysql.rb +29 -50
  20. data/lib/sequel/adapters/mysql2.rb +13 -28
  21. data/lib/sequel/adapters/oracle.rb +8 -2
  22. data/lib/sequel/adapters/postgres.rb +115 -20
  23. data/lib/sequel/adapters/shared/db2.rb +1 -1
  24. data/lib/sequel/adapters/shared/mssql.rb +14 -3
  25. data/lib/sequel/adapters/shared/mysql.rb +59 -11
  26. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +1 -1
  28. data/lib/sequel/adapters/shared/postgres.rb +127 -30
  29. data/lib/sequel/adapters/shared/sqlite.rb +55 -38
  30. data/lib/sequel/adapters/sqlite.rb +9 -3
  31. data/lib/sequel/adapters/swift.rb +2 -2
  32. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  33. data/lib/sequel/adapters/swift/postgres.rb +10 -0
  34. data/lib/sequel/ast_transformer.rb +4 -0
  35. data/lib/sequel/connection_pool.rb +8 -0
  36. data/lib/sequel/connection_pool/sharded_single.rb +5 -0
  37. data/lib/sequel/connection_pool/sharded_threaded.rb +17 -0
  38. data/lib/sequel/connection_pool/single.rb +5 -0
  39. data/lib/sequel/connection_pool/threaded.rb +14 -0
  40. data/lib/sequel/core.rb +24 -3
  41. data/lib/sequel/database/connecting.rb +24 -14
  42. data/lib/sequel/database/dataset_defaults.rb +1 -0
  43. data/lib/sequel/database/misc.rb +16 -25
  44. data/lib/sequel/database/query.rb +20 -2
  45. data/lib/sequel/database/schema_generator.rb +2 -2
  46. data/lib/sequel/database/schema_methods.rb +120 -23
  47. data/lib/sequel/dataset/actions.rb +91 -18
  48. data/lib/sequel/dataset/features.rb +5 -0
  49. data/lib/sequel/dataset/prepared_statements.rb +6 -2
  50. data/lib/sequel/dataset/sql.rb +68 -51
  51. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  52. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +18 -13
  53. data/lib/sequel/extensions/migration.rb +4 -0
  54. data/lib/sequel/extensions/null_dataset.rb +90 -0
  55. data/lib/sequel/extensions/pg_array.rb +460 -0
  56. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  57. data/lib/sequel/extensions/pg_auto_parameterize.rb +174 -0
  58. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  59. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  60. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  61. data/lib/sequel/extensions/pretty_table.rb +5 -71
  62. data/lib/sequel/extensions/query_literals.rb +79 -0
  63. data/lib/sequel/extensions/schema_caching.rb +76 -0
  64. data/lib/sequel/extensions/schema_dumper.rb +227 -31
  65. data/lib/sequel/extensions/select_remove.rb +35 -0
  66. data/lib/sequel/extensions/sql_expr.rb +4 -110
  67. data/lib/sequel/extensions/to_dot.rb +1 -1
  68. data/lib/sequel/model.rb +11 -2
  69. data/lib/sequel/model/associations.rb +35 -7
  70. data/lib/sequel/model/base.rb +159 -36
  71. data/lib/sequel/no_core_ext.rb +2 -0
  72. data/lib/sequel/plugins/caching.rb +25 -18
  73. data/lib/sequel/plugins/composition.rb +1 -1
  74. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  75. data/lib/sequel/plugins/identity_map.rb +11 -3
  76. data/lib/sequel/plugins/instance_filters.rb +10 -0
  77. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  78. data/lib/sequel/plugins/nested_attributes.rb +4 -3
  79. data/lib/sequel/plugins/prepared_statements.rb +3 -1
  80. data/lib/sequel/plugins/prepared_statements_associations.rb +5 -1
  81. data/lib/sequel/plugins/schema.rb +7 -2
  82. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  83. data/lib/sequel/plugins/static_cache.rb +99 -0
  84. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  85. data/lib/sequel/sql.rb +417 -7
  86. data/lib/sequel/version.rb +1 -1
  87. data/spec/adapters/firebird_spec.rb +1 -1
  88. data/spec/adapters/mssql_spec.rb +12 -15
  89. data/spec/adapters/mysql_spec.rb +81 -23
  90. data/spec/adapters/postgres_spec.rb +444 -77
  91. data/spec/adapters/spec_helper.rb +2 -0
  92. data/spec/adapters/sqlite_spec.rb +8 -8
  93. data/spec/core/connection_pool_spec.rb +85 -0
  94. data/spec/core/database_spec.rb +29 -5
  95. data/spec/core/dataset_spec.rb +171 -3
  96. data/spec/core/expression_filters_spec.rb +364 -0
  97. data/spec/core/mock_adapter_spec.rb +17 -3
  98. data/spec/core/schema_spec.rb +133 -0
  99. data/spec/extensions/association_dependencies_spec.rb +13 -13
  100. data/spec/extensions/caching_spec.rb +26 -3
  101. data/spec/extensions/class_table_inheritance_spec.rb +2 -2
  102. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +23 -94
  103. data/spec/extensions/force_encoding_spec.rb +4 -2
  104. data/spec/extensions/hook_class_methods_spec.rb +5 -2
  105. data/spec/extensions/identity_map_spec.rb +17 -0
  106. data/spec/extensions/instance_filters_spec.rb +1 -1
  107. data/spec/extensions/lazy_attributes_spec.rb +2 -2
  108. data/spec/extensions/list_spec.rb +4 -4
  109. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  110. data/spec/extensions/migration_spec.rb +6 -2
  111. data/spec/extensions/nested_attributes_spec.rb +20 -0
  112. data/spec/extensions/null_dataset_spec.rb +85 -0
  113. data/spec/extensions/optimistic_locking_spec.rb +2 -2
  114. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  115. data/spec/extensions/pg_array_spec.rb +196 -0
  116. data/spec/extensions/pg_auto_parameterize_spec.rb +64 -0
  117. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  118. data/spec/extensions/pg_hstore_spec.rb +195 -0
  119. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  120. data/spec/extensions/prepared_statements_spec.rb +4 -0
  121. data/spec/extensions/pretty_table_spec.rb +6 -0
  122. data/spec/extensions/query_literals_spec.rb +168 -0
  123. data/spec/extensions/schema_caching_spec.rb +41 -0
  124. data/spec/extensions/schema_dumper_spec.rb +231 -11
  125. data/spec/extensions/schema_spec.rb +14 -2
  126. data/spec/extensions/select_remove_spec.rb +38 -0
  127. data/spec/extensions/sharding_spec.rb +6 -6
  128. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  129. data/spec/extensions/spec_helper.rb +2 -1
  130. data/spec/extensions/sql_expr_spec.rb +28 -19
  131. data/spec/extensions/static_cache_spec.rb +145 -0
  132. data/spec/extensions/touch_spec.rb +1 -1
  133. data/spec/extensions/typecast_on_load_spec.rb +9 -1
  134. data/spec/integration/associations_test.rb +6 -6
  135. data/spec/integration/database_test.rb +1 -1
  136. data/spec/integration/dataset_test.rb +89 -26
  137. data/spec/integration/migrator_test.rb +2 -3
  138. data/spec/integration/model_test.rb +3 -3
  139. data/spec/integration/plugin_test.rb +85 -22
  140. data/spec/integration/prepared_statement_test.rb +28 -8
  141. data/spec/integration/schema_test.rb +78 -7
  142. data/spec/integration/spec_helper.rb +1 -0
  143. data/spec/integration/timezone_test.rb +1 -1
  144. data/spec/integration/transaction_test.rb +4 -6
  145. data/spec/integration/type_test.rb +2 -2
  146. data/spec/model/associations_spec.rb +94 -8
  147. data/spec/model/base_spec.rb +4 -4
  148. data/spec/model/hooks_spec.rb +2 -2
  149. data/spec/model/model_spec.rb +19 -7
  150. data/spec/model/record_spec.rb +135 -58
  151. data/spec/model/spec_helper.rb +1 -0
  152. metadata +35 -7
@@ -70,6 +70,16 @@ module Sequel
70
70
  conn
71
71
  end
72
72
 
73
+ # Return the number of matched rows when executing a delete/update statement.
74
+ def execute_dui(sql, opts={})
75
+ execute(sql, opts){|c| return c.affected_rows}
76
+ end
77
+
78
+ # Return the last inserted id when executing an insert statement.
79
+ def execute_insert(sql, opts={})
80
+ execute(sql, opts){|c| return c.last_id}
81
+ end
82
+
73
83
  # Return the version of the MySQL server two which we are connecting.
74
84
  def server_version(server=nil)
75
85
  @server_version ||= (synchronize(server){|conn| conn.server_info[:id]} || super)
@@ -82,7 +92,7 @@ module Sequel
82
92
  # yield the connection if a block is given.
83
93
  def _execute(conn, sql, opts)
84
94
  begin
85
- r = log_yield(sql){conn.query(sql, :symbolize_keys => true, :database_timezone => timezone, :application_timezone => Sequel.application_timezone)}
95
+ r = log_yield((log_sql = opts[:log_sql]) ? sql + log_sql : sql){conn.query(sql, :symbolize_keys => true, :database_timezone => timezone, :application_timezone => Sequel.application_timezone)}
86
96
  if opts[:type] == :select
87
97
  yield r if r
88
98
  elsif block_given?
@@ -131,13 +141,8 @@ module Sequel
131
141
 
132
142
  Database::DatasetClass = self
133
143
 
134
- # Delete rows matching this dataset
135
- def delete
136
- execute_dui(delete_sql){|c| return c.affected_rows}
137
- end
138
-
139
144
  # Yield all rows matching this dataset.
140
- def fetch_rows(sql, &block)
145
+ def fetch_rows(sql)
141
146
  execute(sql) do |r|
142
147
  if identifier_output_method
143
148
  cols = r.fields
@@ -152,27 +157,12 @@ module Sequel
152
157
  end
153
158
  else
154
159
  @columns = r.fields
155
- r.each(:cast_booleans => db.convert_tinyint_to_bool, &block)
160
+ r.each(:cast_booleans => db.convert_tinyint_to_bool){|h| yield h}
156
161
  end
157
162
  end
158
163
  self
159
164
  end
160
165
 
161
- # Insert a new value into this dataset
162
- def insert(*values)
163
- execute_dui(insert_sql(*values)){|c| return c.last_id}
164
- end
165
-
166
- # Replace (update or insert) the matching row.
167
- def replace(*args)
168
- execute_dui(replace_sql(*args)){|c| return c.last_id}
169
- end
170
-
171
- # Update the matching rows.
172
- def update(values={})
173
- execute_dui(update_sql(values)){|c| return c.affected_rows}
174
- end
175
-
176
166
  private
177
167
 
178
168
  # Set the :type option to :select if it hasn't been set.
@@ -180,11 +170,6 @@ module Sequel
180
170
  super(sql, {:type=>:select}.merge(opts), &block)
181
171
  end
182
172
 
183
- # Set the :type option to :dui if it hasn't been set.
184
- def execute_dui(sql, opts={}, &block)
185
- super(sql, {:type=>:dui}.merge(opts), &block)
186
- end
187
-
188
173
  # Handle correct quoting of strings using ::Mysql2::Client#escape.
189
174
  def literal_string_append(sql, v)
190
175
  sql << "'" << db.synchronize{|c| c.escape(v)} << "'"
@@ -136,11 +136,17 @@ module Sequel
136
136
  end
137
137
  end
138
138
  unless cursor
139
- cursor = log_yield("Preparing #{name}: #{sql}"){conn.parse(sql)}
139
+ cursor = log_yield("PREPARE #{name}: #{sql}"){conn.parse(sql)}
140
140
  conn.prepared_statements[name] = [cursor, sql]
141
141
  end
142
142
  args = cursor_bind_params(conn, cursor, opts[:arguments])
143
- r = log_yield("Executing #{name}", args){cursor.exec}
143
+ log_sql = "EXECUTE #{name}"
144
+ if ps.log_sql
145
+ log_sql << " ("
146
+ log_sql << sql
147
+ log_sql << ")"
148
+ end
149
+ r = log_yield(log_sql, args){cursor.exec}
144
150
  if block_given?
145
151
  yield(cursor)
146
152
  elsif type == :insert
@@ -111,13 +111,14 @@ module Sequel
111
111
  def date(s) ::Date.new(*s.split("-").map{|x| x.to_i}) end
112
112
  end.new
113
113
 
114
- # Hash with type name symbols and callable values for converting PostgreSQL types.
114
+ # Hash with type name strings/symbols and callable values for converting PostgreSQL types.
115
115
  # Non-builtin types that don't have fixed numbers should use this to register
116
116
  # conversion procs.
117
- PG_NAMED_TYPES = {}
117
+ PG_NAMED_TYPES = {} unless defined?(PG_NAMED_TYPES)
118
118
 
119
119
  # Hash with integer keys and callable values for converting PostgreSQL types.
120
- PG_TYPES = {}
120
+ PG_TYPES = {} unless defined?(PG_TYPES)
121
+
121
122
  {
122
123
  [16] => tt.method(:boolean),
123
124
  [17] => tt.method(:bytea),
@@ -193,7 +194,8 @@ module Sequel
193
194
  # Execute the given SQL with this connection. If a block is given,
194
195
  # yield the results, otherwise, return the number of changed rows.
195
196
  def execute(sql, args=nil)
196
- q = check_disconnect_errors{@db.log_yield(sql, args){args ? async_exec(sql, args) : async_exec(sql)}}
197
+ args = args.map{|v| @db.bound_variable_arg(v, self)} if args
198
+ q = check_disconnect_errors{execute_query(sql, args)}
197
199
  begin
198
200
  block_given? ? yield(q) : q.cmd_tuples
199
201
  ensure
@@ -202,6 +204,12 @@ module Sequel
202
204
  end
203
205
 
204
206
  private
207
+
208
+ # Return the PGResult object that is returned by executing the given
209
+ # sql and args.
210
+ def execute_query(sql, args)
211
+ @db.log_yield(sql, args){args ? async_exec(sql, args) : async_exec(sql)}
212
+ end
205
213
 
206
214
  # Return the requested values for the given row.
207
215
  def single_value(r)
@@ -213,21 +221,48 @@ module Sequel
213
221
  # pg, postgres, or postgres-pr driver.
214
222
  class Database < Sequel::Database
215
223
  include Sequel::Postgres::DatabaseMethods
224
+
225
+ INFINITE_TIMESTAMP_STRINGS = ['infinity'.freeze, '-infinity'.freeze].freeze
226
+ INFINITE_DATETIME_VALUES = ([1.0/0.0, -1.0/0.0] + INFINITE_TIMESTAMP_STRINGS).freeze
216
227
 
217
228
  set_adapter_scheme :postgres
218
229
 
219
230
  # A hash of conversion procs, keyed by type integer (oid) and
220
231
  # having callable values for the conversion proc for that type.
221
232
  attr_reader :conversion_procs
233
+
234
+ # Whether infinite timestamps should be converted on retrieval. By default, no
235
+ # conversion is done, so an error is raised if you attempt to retrieve an infinite
236
+ # timestamp. You can set this to :nil to convert to nil, :string to leave
237
+ # as a string, or :float to convert to an infinite float.
238
+ attr_accessor :convert_infinite_timestamps
222
239
 
223
240
  # Add the primary_keys and primary_key_sequences instance variables,
224
241
  # so we can get the correct return values for inserted rows.
225
242
  def initialize(*args)
226
243
  super
244
+ @convert_infinite_timestamps = false
227
245
  @primary_keys = {}
228
246
  @primary_key_sequences = {}
229
247
  end
230
248
 
249
+ # Convert given argument so that it can be used directly by pg. Currently, pg doesn't
250
+ # handle fractional seconds in Time/DateTime or blobs with "\0", and it won't ever
251
+ # handle Sequel::SQLTime values correctly. Only public for use by the adapter, shouldn't
252
+ # be used by external code.
253
+ def bound_variable_arg(arg, conn)
254
+ case arg
255
+ when Sequel::SQL::Blob
256
+ conn.escape_bytea(arg)
257
+ when Sequel::SQLTime
258
+ literal(arg)
259
+ when DateTime, Time
260
+ literal(arg)
261
+ else
262
+ arg
263
+ end
264
+ end
265
+
231
266
  # Connects to the database. In addition to the standard database
232
267
  # options, using the :encoding or :charset option changes the
233
268
  # client encoding for the connection, :connect_timeout is a
@@ -402,9 +437,30 @@ module Sequel
402
437
  end
403
438
  end
404
439
  end
440
+
441
+ # Reset the database's conversion procs, requires a server query if there
442
+ # any named types.
443
+ def reset_conversion_procs
444
+ synchronize{|conn| @conversion_procs = get_conversion_procs(conn)}
445
+ end
446
+
447
+ # If convert_infinite_timestamps is true and the value is infinite, return an appropriate
448
+ # value based on the convert_infinite_timestamps setting.
449
+ def to_application_timestamp(value)
450
+ if c = convert_infinite_timestamps
451
+ case value
452
+ when *INFINITE_TIMESTAMP_STRINGS
453
+ infinite_timestamp_value(value)
454
+ else
455
+ super
456
+ end
457
+ else
458
+ super
459
+ end
460
+ end
405
461
 
406
462
  private
407
-
463
+
408
464
  # Convert exceptions raised from the block into DatabaseErrors.
409
465
  def check_database_errors
410
466
  begin
@@ -434,8 +490,11 @@ module Sequel
434
490
  ps = prepared_statements[name]
435
491
  sql = ps.prepared_sql
436
492
  ps_name = name.to_s
437
- args = opts[:arguments]
438
493
  synchronize(opts[:server]) do |conn|
494
+ if args = opts[:arguments]
495
+ args = args.map{|arg| bound_variable_arg(arg, conn)}
496
+ end
497
+
439
498
  unless conn.prepared_statements[ps_name] == sql
440
499
  if conn.prepared_statements.include?(ps_name)
441
500
  conn.execute("DEALLOCATE #{ps_name}") unless conn.prepared_statements[ps_name] == sql
@@ -443,7 +502,15 @@ module Sequel
443
502
  conn.prepared_statements[ps_name] = sql
444
503
  conn.check_disconnect_errors{log_yield("PREPARE #{ps_name} AS #{sql}"){conn.prepare(ps_name, sql)}}
445
504
  end
446
- q = conn.check_disconnect_errors{log_yield("EXECUTE #{ps_name}", args){conn.exec_prepared(ps_name, args)}}
505
+
506
+ log_sql = "EXECUTE #{ps_name}"
507
+ if ps.log_sql
508
+ log_sql << " ("
509
+ log_sql << sql
510
+ log_sql << ")"
511
+ end
512
+
513
+ q = conn.check_disconnect_errors{log_yield(log_sql, args){conn.exec_prepared(ps_name, args)}}
447
514
  if opts[:table] && opts[:values]
448
515
  insert_result(conn, opts[:table], opts[:values])
449
516
  else
@@ -456,20 +523,48 @@ module Sequel
456
523
  end
457
524
  end
458
525
 
459
- # Return the conversion procs hash to use for this database
526
+ # Return the conversion procs hash to use for this database.
460
527
  def get_conversion_procs(conn)
461
528
  procs = PG_TYPES.dup
462
- procs[1114] = method(:to_application_timestamp)
463
- procs[1184] = method(:to_application_timestamp)
464
- conn.execute("SELECT oid, typname FROM pg_type where typtype = 'b'") do |res|
465
- res.ntuples.times do |recnum|
466
- if pr = PG_NAMED_TYPES[res.getvalue(recnum, 1).untaint.to_sym]
467
- procs[res.getvalue(recnum, 0).to_i] ||= pr
529
+ procs[1184] = procs[1114] = method(:to_application_timestamp)
530
+ unless (pgnt = PG_NAMED_TYPES).empty?
531
+ conn.execute("SELECT oid, typname FROM pg_type where typtype = 'b' AND typname IN ('#{pgnt.keys.map{|type| conn.escape_string(type.to_s)}.join("', '")}')") do |res|
532
+ res.ntuples.times do |i|
533
+ procs[res.getvalue(i, 0).to_i] ||= pgnt[res.getvalue(i, 1).untaint.to_sym]
468
534
  end
469
535
  end
470
536
  end
471
537
  procs
472
538
  end
539
+
540
+ # Return an appropriate value for the given infinite timestamp string.
541
+ def infinite_timestamp_value(value)
542
+ case convert_infinite_timestamps
543
+ when :nil
544
+ nil
545
+ when :string
546
+ value
547
+ else
548
+ value == 'infinity' ? PLUS_INFINITY : MINUS_INFINITY
549
+ end
550
+ end
551
+
552
+
553
+ # If the value is an infinite value (either an infinite float or a string returned by
554
+ # by PostgreSQL for an infinite timestamp), return it without converting it if
555
+ # convert_infinite_timestamps is set.
556
+ def typecast_value_datetime(value)
557
+ if convert_infinite_timestamps
558
+ case value
559
+ when *INFINITE_DATETIME_VALUES
560
+ value
561
+ else
562
+ super
563
+ end
564
+ else
565
+ super
566
+ end
567
+ end
473
568
  end
474
569
 
475
570
  # Dataset class for PostgreSQL datasets that use the pg, postgres, or
@@ -481,9 +576,9 @@ module Sequel
481
576
 
482
577
  # Yield all rows returned by executing the given SQL and converting
483
578
  # the types.
484
- def fetch_rows(sql, &block)
485
- return cursor_fetch_rows(sql, &block) if @opts[:cursor]
486
- execute(sql){|res| yield_hash_rows(res, fetch_rows_set_cols(res), &block)}
579
+ def fetch_rows(sql)
580
+ return cursor_fetch_rows(sql){|h| yield h} if @opts[:cursor]
581
+ execute(sql){|res| yield_hash_rows(res, fetch_rows_set_cols(res)){|h| yield h}}
487
582
  end
488
583
 
489
584
  # Uses a cursor for fetching records, instead of fetching the entire result
@@ -630,7 +725,7 @@ module Sequel
630
725
  private
631
726
 
632
727
  # Use a cursor to fetch groups of records at a time, yielding them to the block.
633
- def cursor_fetch_rows(sql, &block)
728
+ def cursor_fetch_rows(sql)
634
729
  server_opts = {:server=>@opts[:server] || :read_only}
635
730
  db.transaction(server_opts) do
636
731
  begin
@@ -642,12 +737,12 @@ module Sequel
642
737
  # Load columns only in the first fetch, so subsequent fetches are faster
643
738
  execute(fetch_sql) do |res|
644
739
  cols = fetch_rows_set_cols(res)
645
- yield_hash_rows(res, cols, &block)
740
+ yield_hash_rows(res, cols){|h| yield h}
646
741
  return if res.ntuples < rows_per_fetch
647
742
  end
648
743
  loop do
649
744
  execute(fetch_sql) do |res|
650
- yield_hash_rows(res, cols, &block)
745
+ yield_hash_rows(res, cols){|h| yield h}
651
746
  return if res.ntuples < rows_per_fetch
652
747
  end
653
748
  end
@@ -310,7 +310,7 @@ module Sequel
310
310
  if l = @opts[:limit]
311
311
  if l == 1
312
312
  sql << FETCH_FIRST_ROW_ONLY
313
- elsif l > 1
313
+ else
314
314
  sql << FETCH_FIRST
315
315
  literal_append(sql, l)
316
316
  sql << ROWS_ONLY
@@ -455,7 +455,8 @@ module Sequel
455
455
  mutation_method(:output, into, values)
456
456
  end
457
457
 
458
- # MSSQL uses [] to quote identifiers
458
+ # MSSQL uses [] to quote identifiers. MSSQL does not support
459
+ # escaping of ], so you cannot use that character in an identifier.
459
460
  def quoted_identifier_append(sql, name)
460
461
  sql << BRACKET_OPEN << name.to_s << BRACKET_CLOSE
461
462
  end
@@ -546,10 +547,16 @@ module Sequel
546
547
 
547
548
  private
548
549
 
550
+ # Whether we are using SQL Server 2005 or later.
549
551
  def is_2005_or_later?
550
552
  server_version >= 9000000
551
553
  end
552
554
 
555
+ # Whether we are using SQL Server 2008 or later.
556
+ def is_2008_or_later?
557
+ server_version >= 10000000
558
+ end
559
+
553
560
  # MSSQL supports the OUTPUT clause for DELETE statements.
554
561
  # It also allows prepending a WITH clause.
555
562
  def delete_clause_methods
@@ -630,7 +637,7 @@ module Sequel
630
637
  def select_into_sql(sql)
631
638
  if i = @opts[:into]
632
639
  sql << INTO
633
- table_ref_append(sql, i)
640
+ identifier_append(sql, i)
634
641
  end
635
642
  end
636
643
 
@@ -669,7 +676,7 @@ module Sequel
669
676
  column_list_append(sql, output[:select_list])
670
677
  if into = output[:into]
671
678
  sql << INTO
672
- table_ref_append(sql, into)
679
+ identifier_append(sql, into)
673
680
  if column_list = output[:column_list]
674
681
  sql << PAREN_SPACE_OPEN
675
682
  source_list_append(sql, column_list)
@@ -691,6 +698,10 @@ module Sequel
691
698
  sql << SPACE
692
699
  source_list_append(sql, @opts[:from][0..0])
693
700
  end
701
+
702
+ def uses_with_rollup?
703
+ !is_2008_or_later?
704
+ end
694
705
  end
695
706
  end
696
707
  end
@@ -51,6 +51,31 @@ module Sequel
51
51
  :mysql
52
52
  end
53
53
 
54
+ # Use the Information Schema's KEY_COLUMN_USAGE table to get
55
+ # basic information on foreign key columns, but include the
56
+ # constraint name.
57
+ def foreign_key_list(table, opts={})
58
+ m = output_identifier_meth
59
+ im = input_identifier_meth
60
+ ds = metadata_dataset.
61
+ from(:INFORMATION_SCHEMA__KEY_COLUMN_USAGE).
62
+ where(:TABLE_NAME=>im.call(table)).
63
+ exclude(:CONSTRAINT_NAME=>'PRIMARY').
64
+ exclude(:REFERENCED_TABLE_NAME=>nil).
65
+ select(:CONSTRAINT_NAME___name, :COLUMN_NAME___column, :REFERENCED_TABLE_NAME___table, :REFERENCED_COLUMN_NAME___key)
66
+
67
+ h = {}
68
+ ds.each do |row|
69
+ if r = h[row[:name]]
70
+ r[:columns] << m.call(row[:column])
71
+ r[:key] << m.call(row[:key])
72
+ else
73
+ h[row[:name]] = {:name=>m.call(row[:name]), :columns=>[m.call(row[:column])], :table=>m.call(row[:table]), :key=>[m.call(row[:key])]}
74
+ end
75
+ end
76
+ h.values
77
+ end
78
+
54
79
  # Use SHOW INDEX FROM to get the index information for the
55
80
  # table.
56
81
  #
@@ -407,6 +432,8 @@ module Sequel
407
432
  STRAIGHT_JOIN = 'STRAIGHT_JOIN'.freeze
408
433
  NATURAL_LEFT_JOIN = 'NATURAL LEFT JOIN'.freeze
409
434
  BACKTICK = '`'.freeze
435
+ BACKTICK_RE = /`/.freeze
436
+ DOUBLE_BACKTICK = '``'.freeze
410
437
  EMPTY_COLUMNS = " ()".freeze
411
438
  EMPTY_VALUES = " VALUES ()".freeze
412
439
  IGNORE = " IGNORE".freeze
@@ -415,6 +442,10 @@ module Sequel
415
442
  EQ_VALUES = '=VALUES('.freeze
416
443
  EQ = '='.freeze
417
444
  WITH_ROLLUP = ' WITH ROLLUP'.freeze
445
+ MATCH_AGAINST = ["(MATCH ".freeze, " AGAINST (".freeze, "))".freeze].freeze
446
+ MATCH_AGAINST_BOOLEAN = ["(MATCH ".freeze, " AGAINST (".freeze, " IN BOOLEAN MODE))".freeze].freeze
447
+ EXPLAIN = 'EXPLAIN '.freeze
448
+ EXPLAIN_EXTENDED = 'EXPLAIN EXTENDED '.freeze
418
449
 
419
450
  # MySQL specific syntax for LIKE/REGEXP searches, as well as
420
451
  # string concatenation.
@@ -466,6 +497,17 @@ module Sequel
466
497
  clone(:calc_found_rows => true)
467
498
  end
468
499
 
500
+ # Return the results of an EXPLAIN query as a string. Options:
501
+ # :extended :: Use EXPLAIN EXPTENDED instead of EXPLAIN if true.
502
+ def explain(opts={})
503
+ # Load the PrettyTable class, needed for explain output
504
+ Sequel.extension(:_pretty_table) unless defined?(Sequel::PrettyTable)
505
+
506
+ ds = db.send(:metadata_dataset).with_sql((opts[:extended] ? EXPLAIN_EXTENDED : EXPLAIN) + select_sql).naked
507
+ rows = ds.all
508
+ Sequel::PrettyTable.string(rows, ds.columns)
509
+ end
510
+
469
511
  # Return a cloned dataset which will use LOCK IN SHARE MODE to lock returned rows.
470
512
  def for_share
471
513
  lock_style(:share)
@@ -479,7 +521,7 @@ module Sequel
479
521
  # MySQL specific full text search syntax.
480
522
  def full_text_sql(cols, terms, opts = {})
481
523
  terms = terms.join(' ') if terms.is_a?(Array)
482
- SQL::PlaceholderLiteralString.new("MATCH ? AGAINST (?#{" IN BOOLEAN MODE" if opts[:boolean]})", [Array(cols), terms], true)
524
+ SQL::PlaceholderLiteralString.new((opts[:boolean] ? MATCH_AGAINST_BOOLEAN : MATCH_AGAINST), [Array(cols), terms])
483
525
  end
484
526
 
485
527
  # MySQL allows HAVING clause on ungrouped datasets.
@@ -552,14 +594,24 @@ module Sequel
552
594
 
553
595
  # MySQL uses the nonstandard ` (backtick) for quoting identifiers.
554
596
  def quoted_identifier_append(sql, c)
555
- sql << BACKTICK << c.to_s << BACKTICK
597
+ sql << BACKTICK << c.to_s.gsub(BACKTICK_RE, DOUBLE_BACKTICK) << BACKTICK
556
598
  end
557
599
 
600
+ # Execute a REPLACE statement on the database.
601
+ def replace(*values)
602
+ execute_insert(replace_sql(*values))
603
+ end
604
+
558
605
  # MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
559
606
  # insert if it doesn't).
560
607
  def replace_sql(*values)
561
608
  clone(:replace=>true).insert_sql(*values)
562
609
  end
610
+
611
+ # Replace multiple rows in a single query.
612
+ def multi_replace(*values)
613
+ clone(:replace=>true).multi_insert(*values)
614
+ end
563
615
 
564
616
  # MySQL can emulate DISTINCT ON with its non-standard GROUP BY implementation,
565
617
  # though the rows returned cannot be made deterministic through ordering.
@@ -730,15 +782,6 @@ module Sequel
730
782
  SELECT_CLAUSE_METHODS
731
783
  end
732
784
 
733
- # MySQL supports ROLLUP via nonstandard SQL syntax
734
- def select_group_sql(sql)
735
- if group = @opts[:group]
736
- sql << GROUP_BY
737
- expression_list_append(sql, group)
738
- sql << WITH_ROLLUP if @opts[:group_options] == :rollup
739
- end
740
- end
741
-
742
785
  # Support FOR SHARE locking when using the :share lock style.
743
786
  def select_lock_sql(sql)
744
787
  @opts[:lock] == :share ? (sql << FOR_SHARE) : super
@@ -753,6 +796,11 @@ module Sequel
753
796
  def update_clause_methods
754
797
  UPDATE_CLAUSE_METHODS
755
798
  end
799
+
800
+ # MySQL uses WITH ROLLUP syntax.
801
+ def uses_with_rollup?
802
+ true
803
+ end
756
804
  end
757
805
  end
758
806
  end