sequel 3.33.0 → 3.34.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 (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