sequel 5.70.0 → 5.80.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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +134 -0
  3. data/README.rdoc +7 -5
  4. data/doc/dataset_basics.rdoc +1 -1
  5. data/doc/mass_assignment.rdoc +1 -1
  6. data/doc/migration.rdoc +15 -0
  7. data/doc/opening_databases.rdoc +6 -2
  8. data/doc/querying.rdoc +6 -1
  9. data/doc/release_notes/5.71.0.txt +21 -0
  10. data/doc/release_notes/5.72.0.txt +33 -0
  11. data/doc/release_notes/5.73.0.txt +66 -0
  12. data/doc/release_notes/5.74.0.txt +45 -0
  13. data/doc/release_notes/5.75.0.txt +35 -0
  14. data/doc/release_notes/5.76.0.txt +86 -0
  15. data/doc/release_notes/5.77.0.txt +63 -0
  16. data/doc/release_notes/5.78.0.txt +67 -0
  17. data/doc/release_notes/5.79.0.txt +28 -0
  18. data/doc/release_notes/5.80.0.txt +40 -0
  19. data/doc/schema_modification.rdoc +2 -2
  20. data/doc/testing.rdoc +4 -2
  21. data/lib/sequel/adapters/ibmdb.rb +1 -1
  22. data/lib/sequel/adapters/jdbc/h2.rb +3 -0
  23. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
  24. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
  25. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  26. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  27. data/lib/sequel/adapters/jdbc.rb +10 -6
  28. data/lib/sequel/adapters/mysql2.rb +2 -2
  29. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  30. data/lib/sequel/adapters/postgres.rb +6 -5
  31. data/lib/sequel/adapters/shared/db2.rb +12 -0
  32. data/lib/sequel/adapters/shared/mssql.rb +30 -2
  33. data/lib/sequel/adapters/shared/mysql.rb +68 -3
  34. data/lib/sequel/adapters/shared/oracle.rb +4 -6
  35. data/lib/sequel/adapters/shared/postgres.rb +116 -6
  36. data/lib/sequel/adapters/shared/sqlanywhere.rb +10 -4
  37. data/lib/sequel/adapters/shared/sqlite.rb +20 -3
  38. data/lib/sequel/adapters/sqlite.rb +42 -3
  39. data/lib/sequel/connection_pool.rb +4 -2
  40. data/lib/sequel/database/misc.rb +3 -2
  41. data/lib/sequel/database/schema_methods.rb +11 -4
  42. data/lib/sequel/database/transactions.rb +6 -0
  43. data/lib/sequel/dataset/actions.rb +8 -6
  44. data/lib/sequel/dataset/dataset_module.rb +1 -1
  45. data/lib/sequel/dataset/features.rb +10 -1
  46. data/lib/sequel/dataset/graph.rb +1 -0
  47. data/lib/sequel/dataset/query.rb +58 -9
  48. data/lib/sequel/dataset/sql.rb +47 -34
  49. data/lib/sequel/exceptions.rb +5 -0
  50. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  51. data/lib/sequel/extensions/async_thread_pool.rb +7 -0
  52. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  53. data/lib/sequel/extensions/caller_logging.rb +2 -1
  54. data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
  55. data/lib/sequel/extensions/index_caching.rb +5 -1
  56. data/lib/sequel/extensions/migration.rb +64 -14
  57. data/lib/sequel/extensions/named_timezones.rb +1 -1
  58. data/lib/sequel/extensions/pg_array.rb +10 -0
  59. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  60. data/lib/sequel/extensions/pg_extended_date_support.rb +4 -4
  61. data/lib/sequel/extensions/pg_json_ops.rb +52 -0
  62. data/lib/sequel/extensions/pg_range.rb +2 -2
  63. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  64. data/lib/sequel/extensions/provenance.rb +108 -0
  65. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  66. data/lib/sequel/extensions/schema_caching.rb +1 -1
  67. data/lib/sequel/extensions/sqlite_json_ops.rb +76 -18
  68. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  69. data/lib/sequel/model/associations.rb +9 -2
  70. data/lib/sequel/model/base.rb +26 -13
  71. data/lib/sequel/model/exceptions.rb +15 -3
  72. data/lib/sequel/plugins/column_encryption.rb +27 -6
  73. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  74. data/lib/sequel/plugins/list.rb +5 -2
  75. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  76. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  77. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  78. data/lib/sequel/plugins/paged_operations.rb +181 -0
  79. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +5 -1
  80. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  81. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  82. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  83. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  84. data/lib/sequel/version.rb +1 -1
  85. metadata +44 -3
@@ -498,6 +498,25 @@ module Sequel
498
498
  :postgres
499
499
  end
500
500
 
501
+ # For constraints that are deferrable, defer constraints until
502
+ # transaction commit. Options:
503
+ #
504
+ # :constraints :: An identifier of the constraint, or an array of
505
+ # identifiers for constraints, to apply this
506
+ # change to specific constraints.
507
+ # :server :: The server/shard on which to run the query.
508
+ #
509
+ # Examples:
510
+ #
511
+ # DB.defer_constraints
512
+ # # SET CONSTRAINTS ALL DEFERRED
513
+ #
514
+ # DB.defer_constraints(constraints: [:c1, Sequel[:sc][:c2]])
515
+ # # SET CONSTRAINTS "c1", "sc"."s2" DEFERRED
516
+ def defer_constraints(opts=OPTS)
517
+ _set_constraints(' DEFERRED', opts)
518
+ end
519
+
501
520
  # Use PostgreSQL's DO syntax to execute an anonymous code block. The code should
502
521
  # be the literal code string to use in the underlying procedural language. Options:
503
522
  #
@@ -611,6 +630,24 @@ module Sequel
611
630
  super
612
631
  end
613
632
 
633
+ # Immediately apply deferrable constraints.
634
+ #
635
+ # :constraints :: An identifier of the constraint, or an array of
636
+ # identifiers for constraints, to apply this
637
+ # change to specific constraints.
638
+ # :server :: The server/shard on which to run the query.
639
+ #
640
+ # Examples:
641
+ #
642
+ # DB.immediate_constraints
643
+ # # SET CONSTRAINTS ALL IMMEDIATE
644
+ #
645
+ # DB.immediate_constraints(constraints: [:c1, Sequel[:sc][:c2]])
646
+ # # SET CONSTRAINTS "c1", "sc"."s2" IMMEDIATE
647
+ def immediate_constraints(opts=OPTS)
648
+ _set_constraints(' IMMEDIATE', opts)
649
+ end
650
+
614
651
  # Use the pg_* system tables to determine indexes on a table
615
652
  def indexes(table, opts=OPTS)
616
653
  m = output_identifier_meth
@@ -822,6 +859,41 @@ module Sequel
822
859
  pg_class_relname(relkind, opts)
823
860
  end
824
861
 
862
+ # Attempt to acquire an exclusive advisory lock with the given lock_id (which should be
863
+ # a 64-bit integer). If successful, yield to the block, then release the advisory lock
864
+ # when the block exits. If unsuccessful, raise a Sequel::AdvisoryLockError.
865
+ #
866
+ # DB.with_advisory_lock(1347){DB.get(1)}
867
+ # # SELECT pg_try_advisory_lock(1357) LIMIT 1
868
+ # # SELECT 1 AS v LIMIT 1
869
+ # # SELECT pg_advisory_unlock(1357) LIMIT 1
870
+ #
871
+ # Options:
872
+ # :wait :: Do not raise an error, instead, wait until the advisory lock can be acquired.
873
+ def with_advisory_lock(lock_id, opts=OPTS)
874
+ ds = dataset
875
+ if server = opts[:server]
876
+ ds = ds.server(server)
877
+ end
878
+
879
+ synchronize(server) do |c|
880
+ begin
881
+ if opts[:wait]
882
+ ds.get{pg_advisory_lock(lock_id)}
883
+ locked = true
884
+ else
885
+ unless locked = ds.get{pg_try_advisory_lock(lock_id)}
886
+ raise AdvisoryLockError, "unable to acquire advisory lock #{lock_id.inspect}"
887
+ end
888
+ end
889
+
890
+ yield
891
+ ensure
892
+ ds.get{pg_advisory_unlock(lock_id)} if locked
893
+ end
894
+ end
895
+ end
896
+
825
897
  private
826
898
 
827
899
  # Dataset used to retrieve CHECK constraint information
@@ -1038,6 +1110,31 @@ module Sequel
1038
1110
  end
1039
1111
  end
1040
1112
 
1113
+ # Internals of defer_constraints/immediate_constraints
1114
+ def _set_constraints(type, opts)
1115
+ execute_ddl(_set_constraints_sql(type, opts), opts)
1116
+ end
1117
+
1118
+ # SQL to use for SET CONSTRAINTS
1119
+ def _set_constraints_sql(type, opts)
1120
+ sql = String.new
1121
+ sql << "SET CONSTRAINTS "
1122
+ if constraints = opts[:constraints]
1123
+ dataset.send(:source_list_append, sql, Array(constraints))
1124
+ else
1125
+ sql << "ALL"
1126
+ end
1127
+ sql << type
1128
+ end
1129
+
1130
+ # Consider lock or statement timeout errors as evidence that the table exists
1131
+ # but is locked.
1132
+ def _table_exists?(ds)
1133
+ super
1134
+ rescue DatabaseError => e
1135
+ raise e unless /canceling statement due to (?:statement|lock) timeout/ =~ e.message
1136
+ end
1137
+
1041
1138
  def alter_table_add_column_sql(table, op)
1042
1139
  "ADD COLUMN#{' IF NOT EXISTS' if op[:if_not_exists]} #{column_definition_sql(op)}"
1043
1140
  end
@@ -1328,7 +1425,7 @@ module Sequel
1328
1425
  elsif options[:foreign]
1329
1426
  raise(Error, "can't provide both :foreign and :unlogged to create_table") if options[:unlogged]
1330
1427
  'FOREIGN '
1331
- elsif options[:unlogged]
1428
+ elsif options.fetch(:unlogged){typecast_value_boolean(@opts[:unlogged_tables_default])}
1332
1429
  'UNLOGGED '
1333
1430
  end
1334
1431
 
@@ -1451,7 +1548,11 @@ module Sequel
1451
1548
  # currently visible schemas.
1452
1549
  def filter_schema(ds, opts)
1453
1550
  expr = if schema = opts[:schema]
1454
- schema.to_s
1551
+ if schema.is_a?(SQL::Identifier)
1552
+ schema.value.to_s
1553
+ else
1554
+ schema.to_s
1555
+ end
1455
1556
  else
1456
1557
  Sequel.function(:any, Sequel.function(:current_schemas, false))
1457
1558
  end
@@ -1719,7 +1820,7 @@ module Sequel
1719
1820
 
1720
1821
  Dataset.def_sql_method(self, :delete, [['if server_version >= 90100', %w'with delete from using where returning'], ['else', %w'delete from using where returning']])
1721
1822
  Dataset.def_sql_method(self, :insert, [['if server_version >= 90500', %w'with insert into columns override values conflict returning'], ['elsif server_version >= 90100', %w'with insert into columns values returning'], ['else', %w'insert into columns values returning']])
1722
- Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'values order limit'], ['elsif server_version >= 80400', %w'with select distinct columns from join where group having window compounds order limit lock'], ['else', %w'select distinct columns from join where group having compounds order limit lock']])
1823
+ Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'values compounds order limit'], ['elsif server_version >= 80400', %w'with select distinct columns from join where group having window compounds order limit lock'], ['else', %w'select distinct columns from join where group having compounds order limit lock']])
1723
1824
  Dataset.def_sql_method(self, :update, [['if server_version >= 90100', %w'with update table set from where returning'], ['else', %w'update table set from where returning']])
1724
1825
 
1725
1826
  # Return the results of an EXPLAIN ANALYZE query as a string
@@ -1745,8 +1846,6 @@ module Sequel
1745
1846
  literal_append(sql, args[0])
1746
1847
  sql << ' ' << op.to_s << ' '
1747
1848
  literal_append(sql, args[1])
1748
- sql << " ESCAPE "
1749
- literal_append(sql, "\\")
1750
1849
  sql << ')'
1751
1850
  else
1752
1851
  super
@@ -1800,7 +1899,7 @@ module Sequel
1800
1899
  # :phrase :: Similar to :plain, but also adding an ILIKE filter to ensure that
1801
1900
  # returned rows also include the exact phrase used.
1802
1901
  # :rank :: Set to true to order by the rank, so that closer matches are returned first.
1803
- # :to_tsquery :: Can be set to :plain or :phrase to specify the function to use to
1902
+ # :to_tsquery :: Can be set to :plain, :phrase, or :websearch to specify the function to use to
1804
1903
  # convert the terms to a ts_query.
1805
1904
  # :tsquery :: Specifies the terms argument is already a valid SQL expression returning a
1806
1905
  # tsquery, and can be used directly in the query.
@@ -1820,6 +1919,8 @@ module Sequel
1820
1919
  query_func = case to_tsquery = opts[:to_tsquery]
1821
1920
  when :phrase, :plain
1822
1921
  :"#{to_tsquery}to_tsquery"
1922
+ when :websearch
1923
+ :"websearch_to_tsquery"
1823
1924
  else
1824
1925
  (opts[:phrase] || opts[:plain]) ? :plainto_tsquery : :to_tsquery
1825
1926
  end
@@ -2084,10 +2185,14 @@ module Sequel
2084
2185
  server_version >= 90500
2085
2186
  end
2086
2187
 
2188
+ # :nocov:
2189
+
2087
2190
  # PostgreSQL supports timezones in literal timestamps
2088
2191
  def supports_timestamp_timezones?
2192
+ # SEQUEL6: Remove
2089
2193
  true
2090
2194
  end
2195
+ # :nocov:
2091
2196
 
2092
2197
  # PostgreSQL 8.4+ supports WINDOW clause.
2093
2198
  def supports_window_clause?
@@ -2207,6 +2312,11 @@ module Sequel
2207
2312
  raise(InvalidOperation, "Joined datasets cannot be truncated") if opts[:join]
2208
2313
  end
2209
2314
 
2315
+ # The strftime format to use when literalizing the time.
2316
+ def default_timestamp_format
2317
+ "'%Y-%m-%d %H:%M:%S.%6N%z'"
2318
+ end
2319
+
2210
2320
  # Only include the primary table in the main delete clause
2211
2321
  def delete_from_sql(sql)
2212
2322
  sql << ' FROM '
@@ -281,10 +281,6 @@ module Sequel
281
281
  false
282
282
  end
283
283
 
284
- def supports_timestamp_usecs?
285
- false
286
- end
287
-
288
284
  def supports_window_clause?
289
285
  true
290
286
  end
@@ -378,6 +374,16 @@ module Sequel
378
374
 
379
375
  private
380
376
 
377
+ # SQLAnywhere only supports 3 digits after the decimal point for times.
378
+ def default_time_format
379
+ "'%H:%M:%S.%3N'"
380
+ end
381
+
382
+ # SQLAnywhere only supports 3 digits after the decimal point for timestamps.
383
+ def default_timestamp_format
384
+ "'%Y-%m-%d %H:%M:%S.%3N'"
385
+ end
386
+
381
387
  # Use 1 for true on Sybase
382
388
  def literal_true
383
389
  '1'
@@ -145,6 +145,11 @@ module Sequel
145
145
  sqlite_version >= 30608
146
146
  end
147
147
 
148
+ # SQLite 3.8.2+ supports the without rowid table constraint
149
+ def support_without_rowid?
150
+ sqlite_version >= 30802
151
+ end
152
+
148
153
  # Override the default setting for whether to use timezones in timestamps.
149
154
  # It is set to +false+ by default, as SQLite's date/time methods do not
150
155
  # support timezones in timestamps.
@@ -344,9 +349,17 @@ module Sequel
344
349
  ps
345
350
  end
346
351
 
347
- # Support creating STRICT tables via :strict option
352
+ # Support creating STRICT AND/OR WITHOUT ROWID tables via :strict and :without_rowid options
348
353
  def create_table_sql(name, generator, options)
349
- "#{super}#{' STRICT' if options[:strict]}"
354
+ if options[:strict] && options[:without_rowid]
355
+ "#{super} STRICT, WITHOUT ROWID"
356
+ elsif options[:strict]
357
+ "#{super} STRICT"
358
+ elsif options[:without_rowid]
359
+ "#{super} WITHOUT ROWID"
360
+ else
361
+ super
362
+ end
350
363
  end
351
364
 
352
365
  # SQLite support creating temporary views.
@@ -504,7 +517,6 @@ module Sequel
504
517
  # table_xinfo PRAGMA used, remove hidden columns
505
518
  # that are not generated columns
506
519
  if row[:generated] = (row.delete(:hidden) != 0)
507
- next unless row[:type].end_with?(' GENERATED ALWAYS')
508
520
  row[:type] = row[:type].sub(' GENERATED ALWAYS', '')
509
521
  end
510
522
  end
@@ -918,6 +930,11 @@ module Sequel
918
930
  500
919
931
  end
920
932
 
933
+ # The strftime format to use when literalizing the time.
934
+ def default_timestamp_format
935
+ db.use_timestamp_timezones? ? "'%Y-%m-%d %H:%M:%S.%6N%z'" : super
936
+ end
937
+
921
938
  # SQL fragment specifying a list of identifiers
922
939
  def identifier_list(columns)
923
940
  columns.map{|i| quote_identifier(i)}.join(', ')
@@ -111,6 +111,18 @@ module Sequel
111
111
  # static data that you do not want to modify
112
112
  # :timeout :: how long to wait for the database to be available if it
113
113
  # is locked, given in milliseconds (default is 5000)
114
+ # :setup_regexp_function :: enable use of Regexp objects with SQL
115
+ # 'REGEXP' operator. If the value is :cached or "cached",
116
+ # caches the generated regexps, which can result in a memory
117
+ # leak if dynamic regexps are used. If the value is a Proc,
118
+ # it will be called with a string for the regexp and a string
119
+ # for the value to compare, and should return whether the regexp
120
+ # matches.
121
+ # :regexp_function_cache :: If setting +setup_regexp_function+ to +cached+, this
122
+ # determines the cache to use. It should either be a proc or a class, and it
123
+ # defaults to +Hash+. You can use +ObjectSpace::WeakKeyMap+ on Ruby 3.3+ to
124
+ # have the VM automatically remove regexps from the cache after they
125
+ # are no longer used.
114
126
  def connect(server)
115
127
  opts = server_opts(server)
116
128
  opts[:database] = ':memory:' if blank_object?(opts[:database])
@@ -126,9 +138,7 @@ module Sequel
126
138
  connection_pragmas.each{|s| log_connection_yield(s, db){db.execute_batch(s)}}
127
139
 
128
140
  if typecast_value_boolean(opts[:setup_regexp_function])
129
- db.create_function("regexp", 2) do |func, regexp_str, string|
130
- func.result = Regexp.new(regexp_str).match(string) ? 1 : 0
131
- end
141
+ setup_regexp_function(db, opts[:setup_regexp_function])
132
142
  end
133
143
 
134
144
  class << db
@@ -202,6 +212,35 @@ module Sequel
202
212
  @conversion_procs['datetime'] = @conversion_procs['timestamp'] = method(:to_application_timestamp)
203
213
  set_integer_booleans
204
214
  end
215
+
216
+ def setup_regexp_function(db, how)
217
+ case how
218
+ when Proc
219
+ # nothing
220
+ when :cached, "cached"
221
+ cache = @opts[:regexp_function_cache] || Hash
222
+ cache = cache.is_a?(Proc) ? cache.call : cache.new
223
+ how = if RUBY_VERSION >= '2.4'
224
+ lambda do |regexp_str, str|
225
+ (cache[regexp_str] ||= Regexp.new(regexp_str)).match?(str)
226
+ end
227
+ else
228
+ lambda do |regexp_str, str|
229
+ (cache[regexp_str] ||= Regexp.new(regexp_str)).match(str)
230
+ end
231
+ end
232
+ else
233
+ how = if RUBY_VERSION >= '2.4'
234
+ lambda{|regexp_str, str| Regexp.new(regexp_str).match?(str)}
235
+ else
236
+ lambda{|regexp_str, str| Regexp.new(regexp_str).match(str)}
237
+ end
238
+ end
239
+
240
+ db.create_function("regexp", 2) do |func, regexp_str, str|
241
+ func.result = how.call(regexp_str, str) ? 1 : 0
242
+ end
243
+ end
205
244
 
206
245
  # Yield an available connection. Rescue
207
246
  # any SQLite3::Exceptions and turn them into DatabaseErrors.
@@ -70,8 +70,10 @@ class Sequel::ConnectionPool
70
70
  else
71
71
  pc = if opts[:single_threaded]
72
72
  opts[:servers] ? :sharded_single : :single
73
- #elsif RUBY_VERSION >= '3.2' # SEQUEL6 or maybe earlier
74
- # opts[:servers] ? :sharded_timed_queue : :timed_queue
73
+ # :nocov:
74
+ elsif RUBY_VERSION >= '3.4' # SEQUEL6 or maybe earlier switch to 3.2
75
+ opts[:servers] ? :sharded_timed_queue : :timed_queue
76
+ # :nocov:
75
77
  else
76
78
  opts[:servers] ? :sharded_threaded : :threaded
77
79
  end
@@ -149,6 +149,7 @@ module Sequel
149
149
  @schemas = {}
150
150
  @prepared_statements = {}
151
151
  @transactions = {}
152
+ @transactions.compare_by_identity
152
153
  @symbol_literal_cache = {}
153
154
 
154
155
  @timezone = nil
@@ -263,8 +264,8 @@ module Sequel
263
264
  # Proxy the literal call to the dataset.
264
265
  #
265
266
  # DB.literal(1) # 1
266
- # DB.literal(:a) # a
267
- # DB.literal('a') # 'a'
267
+ # DB.literal(:a) # "a" # or `a`, [a], or a, depending on identifier quoting
268
+ # DB.literal("a") # 'a'
268
269
  def literal(v)
269
270
  schema_utility_dataset.literal(v)
270
271
  end
@@ -191,6 +191,12 @@ module Sequel
191
191
  # The +any+ type is treated like a SQLite column in a non-strict table,
192
192
  # allowing any type of data to be stored. This option is supported on
193
193
  # SQLite 3.37.0+.
194
+ # :without_rowid :: Create a WITHOUT ROWID table. Every row in SQLite has a special
195
+ # 'rowid' column, that uniquely identifies that row within the table.
196
+ # If this option is used, the 'rowid' column is omitted, which can
197
+ # sometimes provide some space and speed advantages. Note that you
198
+ # must then provide an explicit primary key when you create the table.
199
+ # This option is supported on SQLite 3.8.2+.
194
200
  #
195
201
  # See <tt>Schema::CreateTableGenerator</tt> and the {"Schema Modification" guide}[rdoc-ref:doc/schema_modification.rdoc].
196
202
  def create_table(name, options=OPTS, &block)
@@ -246,10 +252,10 @@ module Sequel
246
252
  # For databases where replacing a view is not natively supported, support
247
253
  # is emulated by dropping a view with the same name before creating the view.
248
254
  def create_or_replace_view(name, source, options = OPTS)
249
- if supports_create_or_replace_view?
255
+ if supports_create_or_replace_view? && !options[:materialized]
250
256
  options = options.merge(:replace=>true)
251
257
  else
252
- swallow_database_error{drop_view(name)}
258
+ swallow_database_error{drop_view(name, options)}
253
259
  end
254
260
 
255
261
  create_view(name, source, options)
@@ -712,8 +718,9 @@ module Sequel
712
718
  e = options[:ignore_index_errors] || options[:if_not_exists]
713
719
  generator.indexes.each do |index|
714
720
  begin
715
- pr = proc{index_sql_list(name, [index]).each{|sql| execute_ddl(sql)}}
716
- supports_transactional_ddl? ? transaction(:savepoint=>:only, &pr) : pr.call
721
+ transaction(:savepoint=>:only, :skip_transaction=>supports_transactional_ddl? == false) do
722
+ index_sql_list(name, [index]).each{|sql| execute_ddl(sql)}
723
+ end
717
724
  rescue Error
718
725
  raise unless e
719
726
  end
@@ -166,6 +166,8 @@ module Sequel
166
166
  # uses :auto_savepoint, you can set this to false to not use a savepoint.
167
167
  # If the value given for this option is :only, it will only create a
168
168
  # savepoint if it is inside a transaction.
169
+ # :skip_transaction :: If set, do not actually open a transaction or savepoint,
170
+ # just checkout a connection and yield it.
169
171
  #
170
172
  # PostgreSQL specific options:
171
173
  #
@@ -193,6 +195,10 @@ module Sequel
193
195
  end
194
196
  else
195
197
  synchronize(opts[:server]) do |conn|
198
+ if opts[:skip_transaction]
199
+ return yield(conn)
200
+ end
201
+
196
202
  if opts[:savepoint] == :only
197
203
  if supports_savepoints?
198
204
  if _trans(conn)
@@ -356,9 +356,11 @@ module Sequel
356
356
  # This does not have an effect if +values+ is a Dataset.
357
357
  # :server :: Set the server/shard to use for the transaction and insert
358
358
  # queries.
359
+ # :skip_transaction :: Do not use a transaction even when using multiple
360
+ # INSERT queries.
359
361
  # :slice :: Same as :commit_every, :commit_every takes precedence.
360
362
  def import(columns, values, opts=OPTS)
361
- return @db.transaction{insert(columns, values)} if values.is_a?(Dataset)
363
+ return insert(columns, values) if values.is_a?(Dataset)
362
364
 
363
365
  return if values.empty?
364
366
  raise(Error, 'Using Sequel::Dataset#import with an empty column array is not allowed') if columns.empty?
@@ -588,6 +590,8 @@ module Sequel
588
590
  # if your ORDER BY expressions are not simple columns, if they contain
589
591
  # qualified identifiers that would be ambiguous unqualified, if they contain
590
592
  # any identifiers that are aliased in SELECT, and potentially other cases.
593
+ # :skip_transaction :: Do not use a transaction. This can be useful if you want to prevent
594
+ # a lock on the database table, at the expense of consistency.
591
595
  #
592
596
  # Examples:
593
597
  #
@@ -1111,11 +1115,9 @@ module Sequel
1111
1115
  # are provided. When only a single value or statement is provided, then yield
1112
1116
  # without using a transaction.
1113
1117
  def _import_transaction(values, trans_opts, &block)
1114
- if values.length > 1
1115
- @db.transaction(trans_opts, &block)
1116
- else
1117
- yield
1118
- end
1118
+ # OK to mutate trans_opts as it is generated by _import
1119
+ trans_opts[:skip_transaction] = true if values.length <= 1
1120
+ @db.transaction(trans_opts, &block)
1119
1121
  end
1120
1122
 
1121
1123
  # Internals of +select_hash+ and +select_hash_groups+
@@ -21,7 +21,7 @@ module Sequel
21
21
  where exclude exclude_having having
22
22
  distinct grep group group_and_count group_append
23
23
  limit offset order order_append order_prepend reverse
24
- select select_all select_append select_group server
24
+ select select_all select_append select_group select_prepend server
25
25
  METHS
26
26
 
27
27
  # Define a method in the module
@@ -25,11 +25,16 @@ module Sequel
25
25
  false
26
26
  end
27
27
 
28
+ # :nocov:
29
+
28
30
  # Whether the dataset requires SQL standard datetimes. False by default,
29
- # as most allow strings with ISO 8601 format.
31
+ # as most allow strings with ISO 8601 format. Only for backwards compatibility,
32
+ # no longer used internally, do not use in new code.
30
33
  def requires_sql_standard_datetimes?
34
+ # SEQUEL6: Remove
31
35
  false
32
36
  end
37
+ # :nocov:
33
38
 
34
39
  # Whether type specifiers are required for prepared statement/bound
35
40
  # variable argument placeholders (i.e. :bv__integer), false by default.
@@ -183,10 +188,14 @@ module Sequel
183
188
  true
184
189
  end
185
190
 
191
+ # :nocov:
192
+
186
193
  # Whether the dataset supports timezones in literal timestamps, false by default.
187
194
  def supports_timestamp_timezones?
195
+ # SEQUEL6: Remove
188
196
  false
189
197
  end
198
+ # :nocov:
190
199
 
191
200
  # Whether the dataset supports fractional seconds in literal timestamps, true by default.
192
201
  def supports_timestamp_usecs?
@@ -253,6 +253,7 @@ module Sequel
253
253
  # Remove the splitting of results into subhashes, and all metadata
254
254
  # related to the current graph (if any).
255
255
  def ungraphed
256
+ return self unless opts[:graph]
256
257
  clone(:graph=>nil)
257
258
  end
258
259