sequel 4.8.0 → 4.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +48 -0
  3. data/doc/association_basics.rdoc +1 -1
  4. data/doc/opening_databases.rdoc +4 -0
  5. data/doc/postgresql.rdoc +27 -3
  6. data/doc/release_notes/4.9.0.txt +190 -0
  7. data/doc/security.rdoc +1 -1
  8. data/doc/testing.rdoc +2 -2
  9. data/doc/validations.rdoc +8 -0
  10. data/lib/sequel/adapters/jdbc.rb +5 -3
  11. data/lib/sequel/adapters/jdbc/derby.rb +2 -8
  12. data/lib/sequel/adapters/jdbc/h2.rb +2 -13
  13. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -16
  14. data/lib/sequel/adapters/mysql2.rb +11 -1
  15. data/lib/sequel/adapters/postgres.rb +33 -10
  16. data/lib/sequel/adapters/shared/db2.rb +2 -10
  17. data/lib/sequel/adapters/shared/mssql.rb +10 -8
  18. data/lib/sequel/adapters/shared/oracle.rb +9 -24
  19. data/lib/sequel/adapters/shared/postgres.rb +32 -9
  20. data/lib/sequel/adapters/shared/sqlanywhere.rb +2 -4
  21. data/lib/sequel/adapters/shared/sqlite.rb +4 -7
  22. data/lib/sequel/database/schema_methods.rb +15 -0
  23. data/lib/sequel/dataset.rb +1 -1
  24. data/lib/sequel/dataset/actions.rb +159 -27
  25. data/lib/sequel/dataset/graph.rb +29 -7
  26. data/lib/sequel/dataset/misc.rb +6 -0
  27. data/lib/sequel/dataset/placeholder_literalizer.rb +164 -0
  28. data/lib/sequel/dataset/query.rb +2 -0
  29. data/lib/sequel/dataset/sql.rb +103 -91
  30. data/lib/sequel/extensions/current_datetime_timestamp.rb +57 -0
  31. data/lib/sequel/extensions/pg_array.rb +68 -106
  32. data/lib/sequel/extensions/pg_hstore.rb +5 -5
  33. data/lib/sequel/extensions/schema_dumper.rb +49 -49
  34. data/lib/sequel/model.rb +4 -2
  35. data/lib/sequel/model/associations.rb +1 -1
  36. data/lib/sequel/model/base.rb +136 -3
  37. data/lib/sequel/model/errors.rb +6 -0
  38. data/lib/sequel/plugins/defaults_setter.rb +1 -1
  39. data/lib/sequel/plugins/eager_each.rb +9 -0
  40. data/lib/sequel/plugins/nested_attributes.rb +2 -2
  41. data/lib/sequel/plugins/timestamps.rb +2 -2
  42. data/lib/sequel/plugins/touch.rb +2 -2
  43. data/lib/sequel/sql.rb +20 -15
  44. data/lib/sequel/version.rb +1 -1
  45. data/spec/adapters/postgres_spec.rb +70 -8
  46. data/spec/core/dataset_spec.rb +172 -27
  47. data/spec/core/expression_filters_spec.rb +3 -3
  48. data/spec/core/object_graph_spec.rb +17 -1
  49. data/spec/core/placeholder_literalizer_spec.rb +128 -0
  50. data/spec/core/schema_spec.rb +54 -0
  51. data/spec/extensions/current_datetime_timestamp_spec.rb +27 -0
  52. data/spec/extensions/defaults_setter_spec.rb +12 -0
  53. data/spec/extensions/eager_each_spec.rb +6 -0
  54. data/spec/extensions/nested_attributes_spec.rb +14 -2
  55. data/spec/extensions/pg_array_spec.rb +15 -7
  56. data/spec/extensions/shared_caching_spec.rb +5 -5
  57. data/spec/extensions/timestamps_spec.rb +9 -0
  58. data/spec/extensions/touch_spec.rb +9 -0
  59. data/spec/integration/database_test.rb +1 -1
  60. data/spec/integration/dataset_test.rb +27 -5
  61. data/spec/model/eager_loading_spec.rb +32 -0
  62. data/spec/model/model_spec.rb +119 -9
  63. metadata +8 -2
@@ -143,11 +143,8 @@ module Sequel
143
143
  # Dataset class for H2 datasets accessed via JDBC.
144
144
  class Dataset < JDBC::Dataset
145
145
  SELECT_CLAUSE_METHODS = clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
146
- BITWISE_METHOD_MAP = {:& =>:BITAND, :| => :BITOR, :^ => :BITXOR}
147
146
  APOS = Dataset::APOS
148
147
  HSTAR = "H*".freeze
149
- BITCOMP_OPEN = "((0 - ".freeze
150
- BITCOMP_CLOSE = ") - 1)".freeze
151
148
  ILIKE_PLACEHOLDER = ["CAST(".freeze, " AS VARCHAR_IGNORECASE)".freeze].freeze
152
149
  TIME_FORMAT = "'%H:%M:%S'".freeze
153
150
 
@@ -156,16 +153,8 @@ module Sequel
156
153
  case op
157
154
  when :ILIKE, :"NOT ILIKE"
158
155
  super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), [SQL::PlaceholderLiteralString.new(ILIKE_PLACEHOLDER, [args.at(0)]), args.at(1)])
159
- when :&, :|, :^
160
- sql << complex_expression_arg_pairs(args){|a, b| literal(SQL::Function.new(BITWISE_METHOD_MAP[op], a, b))}
161
- when :<<
162
- sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
163
- when :>>
164
- sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
165
- when :'B~'
166
- sql << BITCOMP_OPEN
167
- literal_append(sql, args.at(0))
168
- sql << BITCOMP_CLOSE
156
+ when :&, :|, :^, :<<, :>>, :'B~'
157
+ complex_expression_emulate_append(sql, op, args)
169
158
  else
170
159
  super
171
160
  end
@@ -111,7 +111,6 @@ module Sequel
111
111
 
112
112
  # Dataset class for HSQLDB datasets accessed via JDBC.
113
113
  class Dataset < JDBC::Dataset
114
- BITWISE_METHOD_MAP = {:& =>:BITAND, :| => :BITOR, :^ => :BITXOR}
115
114
  BOOL_TRUE = 'TRUE'.freeze
116
115
  BOOL_FALSE = 'FALSE'.freeze
117
116
  # HSQLDB does support common table expressions, but the support is broken.
@@ -124,8 +123,6 @@ module Sequel
124
123
  APOS = Dataset::APOS
125
124
  HSTAR = "H*".freeze
126
125
  BLOB_OPEN = "X'".freeze
127
- BITCOMP_OPEN = "((0 - ".freeze
128
- BITCOMP_CLOSE = ") - 1)".freeze
129
126
  DEFAULT_FROM = " FROM (VALUES (0))".freeze
130
127
  TIME_FORMAT = "'%H:%M:%S'".freeze
131
128
 
@@ -134,19 +131,8 @@ module Sequel
134
131
  case op
135
132
  when :ILIKE, :"NOT ILIKE"
136
133
  super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args.map{|v| SQL::Function.new(:ucase, v)})
137
- when :&, :|, :^
138
- op = BITWISE_METHOD_MAP[op]
139
- sql << complex_expression_arg_pairs(args){|a, b| literal(SQL::Function.new(op, a, b))}
140
- when :<<
141
- sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
142
- when :>>
143
- sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
144
- when :%
145
- sql << complex_expression_arg_pairs(args){|a, b| "MOD(#{literal(a)}, #{literal(b)})"}
146
- when :'B~'
147
- sql << BITCOMP_OPEN
148
- literal_append(sql, args.at(0))
149
- sql << BITCOMP_CLOSE
134
+ when :&, :|, :^, :%, :<<, :>>, :'B~'
135
+ complex_expression_emulate_append(sql, op, args)
150
136
  else
151
137
  super
152
138
  end
@@ -144,6 +144,7 @@ module Sequel
144
144
  class Dataset < Sequel::Dataset
145
145
  include Sequel::MySQL::DatasetMethods
146
146
  include Sequel::MySQL::PreparedStatements::DatasetMethods
147
+ STREAMING_SUPPORTED = ::Mysql2::VERSION >= '0.3.12'
147
148
 
148
149
  Database::DatasetClass = self
149
150
 
@@ -160,9 +161,18 @@ module Sequel
160
161
  self
161
162
  end
162
163
 
164
+ # Use streaming to implement paging if Mysql2 supports it.
165
+ def paged_each(opts=OPTS, &block)
166
+ if STREAMING_SUPPORTED
167
+ stream.each(&block)
168
+ else
169
+ super
170
+ end
171
+ end
172
+
163
173
  # Return a clone of the dataset that will stream rows when iterating
164
174
  # over the result set, so it can handle large datasets that
165
- # won't fit in memory (Requires mysql 0.3.12 to have an effect).
175
+ # won't fit in memory (Requires mysql 0.3.12+ to have an effect).
166
176
  def stream
167
177
  clone(:stream=>true)
168
178
  end
@@ -618,6 +618,7 @@ module Sequel
618
618
 
619
619
  Database::DatasetClass = self
620
620
  APOS = Sequel::Dataset::APOS
621
+ DEFAULT_CURSOR_NAME = 'sequel_cursor'.freeze
621
622
 
622
623
  # Yield all rows returned by executing the given SQL and converting
623
624
  # the types.
@@ -626,15 +627,22 @@ module Sequel
626
627
  execute(sql){|res| yield_hash_rows(res, fetch_rows_set_cols(res)){|h| yield h}}
627
628
  end
628
629
 
630
+ # Use a cursor for paging.
631
+ def paged_each(opts=OPTS, &block)
632
+ use_cursor(opts).each(&block)
633
+ end
634
+
629
635
  # Uses a cursor for fetching records, instead of fetching the entire result
630
636
  # set at once. Can be used to process large datasets without holding
631
- # all rows in memory (which is what the underlying drivers do
637
+ # all rows in memory (which is what the underlying drivers may do
632
638
  # by default). Options:
633
639
  #
634
- # * :rows_per_fetch - the number of rows per fetch (default 1000). Higher
635
- # numbers result in fewer queries but greater memory use.
636
- # * :cursor_name - the name assigned to the cursor (default 'sequel_cursor').
637
- # Nested cursors require different names.
640
+ # :cursor_name :: The name assigned to the cursor (default 'sequel_cursor').
641
+ # Nested cursors require different names.
642
+ # :hold :: Declare the cursor WITH HOLD and don't use transaction around the
643
+ # cursor usage.
644
+ # :rows_per_fetch :: The number of rows per fetch (default 1000). Higher
645
+ # numbers result in fewer queries but greater memory use.
638
646
  #
639
647
  # Usage:
640
648
  #
@@ -645,7 +653,19 @@ module Sequel
645
653
  # This is untested with the prepared statement/bound variable support,
646
654
  # and unlikely to work with either.
647
655
  def use_cursor(opts=OPTS)
648
- clone(:cursor=>{:rows_per_fetch=>1000, :cursor_name => 'sequel_cursor'}.merge(opts))
656
+ clone(:cursor=>{:rows_per_fetch=>1000}.merge(opts))
657
+ end
658
+
659
+ # Replace the WHERE clause with one that uses CURRENT OF with the given
660
+ # cursor name (or the default cursor name). This allows you to update a
661
+ # large dataset by updating individual rows while processing the dataset
662
+ # via a cursor:
663
+ #
664
+ # DB[:huge_table].use_cursor(:rows_per_fetch=>1).each do |row|
665
+ # DB[:huge_table].where_current_of.update(:column=>ruby_method(row))
666
+ # end
667
+ def where_current_of(cursor_name=DEFAULT_CURSOR_NAME)
668
+ clone(:where=>Sequel.lit(['CURRENT OF '], Sequel.identifier(cursor_name)))
649
669
  end
650
670
 
651
671
  if SEQUEL_POSTGRES_USES_PG
@@ -760,11 +780,14 @@ module Sequel
760
780
  # Use a cursor to fetch groups of records at a time, yielding them to the block.
761
781
  def cursor_fetch_rows(sql)
762
782
  server_opts = {:server=>@opts[:server] || :read_only}
763
- cursor_name = quote_identifier(@opts[:cursor][:cursor_name])
764
- db.transaction(server_opts) do
783
+ cursor = @opts[:cursor]
784
+ hold = cursor[:hold]
785
+ cursor_name = quote_identifier(cursor[:cursor_name] || DEFAULT_CURSOR_NAME)
786
+ rows_per_fetch = cursor[:rows_per_fetch].to_i
787
+
788
+ db.send(*(hold ? [:synchronize, server_opts[:server]] : [:transaction, server_opts])) do
765
789
  begin
766
- execute_ddl("DECLARE #{cursor_name} NO SCROLL CURSOR WITHOUT HOLD FOR #{sql}", server_opts)
767
- rows_per_fetch = @opts[:cursor][:rows_per_fetch].to_i
790
+ execute_ddl("DECLARE #{cursor_name} NO SCROLL CURSOR WITH#{'OUT' unless hold} HOLD FOR #{sql}", server_opts)
768
791
  rows_per_fetch = 1000 if rows_per_fetch <= 0
769
792
  fetch_sql = "FETCH FORWARD #{rows_per_fetch} FROM #{cursor_name}"
770
793
  cols = nil
@@ -256,16 +256,8 @@ module Sequel
256
256
 
257
257
  def complex_expression_sql_append(sql, op, args)
258
258
  case op
259
- when :&, :|, :^
260
- # works with db2 v9.5 and after
261
- op = BITWISE_METHOD_MAP[op]
262
- sql << complex_expression_arg_pairs(args){|a, b| literal(SQL::Function.new(op, a, b))}
263
- when :<<
264
- sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
265
- when :>>
266
- sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
267
- when :%
268
- sql << complex_expression_arg_pairs(args){|a, b| "MOD(#{literal(a)}, #{literal(b)})"}
259
+ when :&, :|, :^, :%, :<<, :>>
260
+ complex_expression_emulate_append(sql, op, args)
269
261
  when :'B~'
270
262
  literal_append(sql, SQL::Function.new(:BITNOT, *args))
271
263
  when :extract
@@ -523,19 +523,21 @@ module Sequel
523
523
  when :'||'
524
524
  super(sql, :+, args)
525
525
  when :LIKE, :"NOT LIKE"
526
- super(sql, op, args.map{|a| LiteralString.new("(#{literal(a)} COLLATE #{CASE_SENSITIVE_COLLATION})")})
526
+ super(sql, op, args.map{|a| Sequel.lit(["(", " COLLATE #{CASE_SENSITIVE_COLLATION})"], a)})
527
527
  when :ILIKE, :"NOT ILIKE"
528
- super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args.map{|a| LiteralString.new("(#{literal(a)} COLLATE #{CASE_INSENSITIVE_COLLATION})")})
529
- when :<<
530
- sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
531
- when :>>
532
- sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
528
+ super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args.map{|a| Sequel.lit(["(", " COLLATE #{CASE_INSENSITIVE_COLLATION})"], a)})
529
+ when :<<, :>>
530
+ complex_expression_emulate_append(sql, op, args)
533
531
  when :extract
534
532
  part = args.at(0)
535
533
  raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
536
534
  if part == :second
537
- expr = literal(args.at(1))
538
- sql << DATEPART_SECOND_OPEN << format.to_s << COMMA << expr << DATEPART_SECOND_MIDDLE << expr << DATEPART_SECOND_CLOSE
535
+ expr = args.at(1)
536
+ sql << DATEPART_SECOND_OPEN << format.to_s << COMMA
537
+ literal_append(sql, expr)
538
+ sql << DATEPART_SECOND_MIDDLE
539
+ literal_append(sql, expr)
540
+ sql << DATEPART_SECOND_CLOSE
539
541
  else
540
542
  sql << DATEPART_OPEN << format.to_s << COMMA
541
543
  literal_append(sql, args.at(1))
@@ -254,43 +254,28 @@ module Sequel
254
254
  APOS_RE = Dataset::APOS_RE
255
255
  DOUBLE_APOS = Dataset::DOUBLE_APOS
256
256
  FROM = Dataset::FROM
257
- BITCOMP_OPEN = "((0 - ".freeze
258
- BITCOMP_CLOSE = ") - 1)".freeze
259
257
  TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S%N %z'".freeze
260
258
  TIMESTAMP_OFFSET_FORMAT = "%+03i:%02i".freeze
261
259
  BOOL_FALSE = "'N'".freeze
262
260
  BOOL_TRUE = "'Y'".freeze
263
261
  HSTAR = "H*".freeze
264
262
  DUAL = ['DUAL'.freeze].freeze
263
+ BITAND_PROC = lambda{|a, b| Sequel.lit(["CAST(BITAND(", ", ", ") AS INTEGER)"], a, b)}
265
264
 
266
265
  def complex_expression_sql_append(sql, op, args)
267
266
  case op
268
267
  when :&
269
- sql << complex_expression_arg_pairs(args){|a, b| "CAST(BITAND(#{literal(a)}, #{literal(b)}) AS INTEGER)"}
268
+ complex_expression_arg_pairs_append(sql, args, &BITAND_PROC)
270
269
  when :|
271
- sql << complex_expression_arg_pairs(args) do |a, b|
272
- s1 = ''
273
- complex_expression_sql_append(s1, :&, [a, b])
274
- "(#{literal(a)} - #{s1} + #{literal(b)})"
275
- end
270
+ complex_expression_arg_pairs_append(sql, args){|a, b| Sequel.lit(["(", " - ", " + ", ")"], a, complex_expression_arg_pairs([a, b], &BITAND_PROC), b)}
276
271
  when :^
277
- sql << complex_expression_arg_pairs(args) do |*x|
278
- s1 = ''
279
- s2 = ''
280
- complex_expression_sql_append(s1, :|, x)
281
- complex_expression_sql_append(s2, :&, x)
282
- "(#{s1} - #{s2})"
272
+ complex_expression_arg_pairs_append(sql, args) do |*x|
273
+ s1 = complex_expression_arg_pairs(x){|a, b| Sequel.lit(["(", " - ", " + ", ")"], a, complex_expression_arg_pairs([a, b], &BITAND_PROC), b)}
274
+ s2 = complex_expression_arg_pairs(x, &BITAND_PROC)
275
+ Sequel.lit(["(", " - ", ")"], s1, s2)
283
276
  end
284
- when :'B~'
285
- sql << BITCOMP_OPEN
286
- literal_append(sql, args.at(0))
287
- sql << BITCOMP_CLOSE
288
- when :<<
289
- sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * power(2, #{literal(b)}))"}
290
- when :>>
291
- sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / power(2, #{literal(b)}))"}
292
- when :%
293
- sql << complex_expression_arg_pairs(args){|a, b| "MOD(#{literal(a)}, #{literal(b)})"}
277
+ when :%, :<<, :>>, :'B~'
278
+ complex_expression_emulate_append(sql, op, args)
294
279
  else
295
280
  super
296
281
  end
@@ -1183,6 +1183,24 @@ module Sequel
1183
1183
  end
1184
1184
  end
1185
1185
 
1186
+ # Disables automatic use of INSERT ... RETURNING. You can still use
1187
+ # returning manually to force the use of RETURNING when inserting.
1188
+ #
1189
+ # This is designed for cases where INSERT RETURNING cannot be used,
1190
+ # such as when you are using partitioning with trigger functions
1191
+ # or conditional rules, or when you are using a PostgreSQL version
1192
+ # less than 8.2, or a PostgreSQL derivative that does not support
1193
+ # returning.
1194
+ #
1195
+ # Note that when this method is used, insert will not return the
1196
+ # primary key of the inserted row, you will have to get the primary
1197
+ # key of the inserted row before inserting via nextval, or after
1198
+ # inserting via currval or lastval (making sure to use the same
1199
+ # database connection for currval or lastval).
1200
+ def disable_insert_returning
1201
+ clone(:disable_insert_returning=>true)
1202
+ end
1203
+
1186
1204
  # Return the results of an EXPLAIN query as a string
1187
1205
  def explain(opts=OPTS)
1188
1206
  with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join(CRLF)
@@ -1220,24 +1238,24 @@ module Sequel
1220
1238
  # Insert given values into the database.
1221
1239
  def insert(*values)
1222
1240
  if @opts[:returning]
1223
- # already know which columns to return, let the standard code
1224
- # handle it
1241
+ # Already know which columns to return, let the standard code handle it
1225
1242
  super
1226
- elsif @opts[:sql]
1227
- # raw SQL used, so don't know which table is being inserted
1228
- # into, and therefore can't determine primary key. Run the
1229
- # insert statement and return nil.
1243
+ elsif @opts[:sql] || @opts[:disable_insert_returning]
1244
+ # Raw SQL used or RETURNING disabled, just use the default behavior
1245
+ # and return nil since sequence is not known.
1230
1246
  super
1231
1247
  nil
1232
1248
  else
1233
- # Force the use of RETURNING with the primary key value.
1249
+ # Force the use of RETURNING with the primary key value,
1250
+ # unless it has been disabled.
1234
1251
  returning(insert_pk).insert(*values){|r| return r.values.first}
1235
1252
  end
1236
1253
  end
1237
1254
 
1238
- # Insert a record returning the record inserted
1255
+ # Insert a record returning the record inserted. Always returns nil without
1256
+ # inserting a query if disable_insert_returning is used.
1239
1257
  def insert_select(*values)
1240
- returning.insert(*values){|r| return r}
1258
+ returning.insert(*values){|r| return r} unless @opts[:disable_insert_returning]
1241
1259
  end
1242
1260
 
1243
1261
  # Locks all tables in the dataset's FROM clause (but not in JOINs) with
@@ -1279,6 +1297,11 @@ module Sequel
1279
1297
  true
1280
1298
  end
1281
1299
 
1300
+ # True unless insert returning has been disabled for this dataset.
1301
+ def supports_insert_select?
1302
+ !@opts[:disable_insert_returning]
1303
+ end
1304
+
1282
1305
  # PostgreSQL 9.3rc1+ supports lateral subqueries
1283
1306
  def supports_lateral_subqueries?
1284
1307
  server_version >= 90300
@@ -306,10 +306,8 @@ module Sequel
306
306
  case op
307
307
  when :'||'
308
308
  super(sql, :+, args)
309
- when :<<
310
- sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
311
- when :>>
312
- sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
309
+ when :<<, :>>
310
+ complex_expression_emulate_append(sql, op, args)
313
311
  when :LIKE, :"NOT LIKE"
314
312
  sql << Sequel::Dataset::PAREN_OPEN
315
313
  literal_append(sql, args.at(0))
@@ -332,8 +332,9 @@ module Sequel
332
332
  end
333
333
 
334
334
  DATABASE_ERROR_REGEXPS = {
335
- /(is|are) not unique\z|UNIQUE constraint failed: .+\z/ => UniqueConstraintViolation,
336
- /foreign key constraint failed\z|FOREIGN KEY constraint failed\z/ => ForeignKeyConstraintViolation,
335
+ /(is|are) not unique\z|PRIMARY KEY must be unique\z|UNIQUE constraint failed: .+\z/ => UniqueConstraintViolation,
336
+ /foreign key constraint failed\z/i => ForeignKeyConstraintViolation,
337
+ /\ACHECK constraint failed/ => CheckConstraintViolation,
337
338
  /\A(SQLITE ERROR 19 \(CONSTRAINT\) : )?constraint failed\z/ => ConstraintViolation,
338
339
  /may not be NULL\z|NOT NULL constraint failed: .+\z/ => NotNullConstraintViolation,
339
340
  }.freeze
@@ -539,11 +540,7 @@ module Sequel
539
540
  sql << NOT_SPACE
540
541
  complex_expression_sql_append(sql, (op == :"NOT ILIKE" ? :ILIKE : :LIKE), args)
541
542
  when :^
542
- sql << complex_expression_arg_pairs(args) do |a, b|
543
- a = literal(a)
544
- b = literal(b)
545
- "((~(#{a} & #{b})) & (#{a} | #{b}))"
546
- end
543
+ complex_expression_arg_pairs_append(sql, args){|a, b| Sequel.lit(["((~(", " & ", ")) & (", " | ", "))"], a, b, a, b)}
547
544
  when :extract
548
545
  part = args.at(0)
549
546
  raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
@@ -134,6 +134,21 @@ module Sequel
134
134
  end
135
135
  end
136
136
 
137
+ # Forcibly create a join table, attempting to drop it if it already exists, then creating it.
138
+ def create_join_table!(hash, options=OPTS)
139
+ drop_table?(join_table_name(hash, options))
140
+ create_join_table(hash, options)
141
+ end
142
+
143
+ # Creates the join table unless it already exists.
144
+ def create_join_table?(hash, options=OPTS)
145
+ if supports_create_table_if_not_exists?
146
+ create_join_table(hash, options.merge(:if_not_exists=>true))
147
+ elsif !table_exists?(join_table_name(hash, options))
148
+ create_join_table(hash, options)
149
+ end
150
+ end
151
+
137
152
  # Creates a table with the columns given in the provided block:
138
153
  #
139
154
  # DB.create_table :posts do
@@ -36,5 +36,5 @@ module Sequel
36
36
  include SQL::StringMethods
37
37
  end
38
38
 
39
- require(%w"query actions features graph prepared_statements misc mutation sql", 'dataset')
39
+ require(%w"query actions features graph prepared_statements misc mutation sql placeholder_literalizer", 'dataset')
40
40
  end
@@ -42,11 +42,7 @@ module Sequel
42
42
  # # Iterate over all rows in the table
43
43
  # DB[:table].all{|row| p row}
44
44
  def all(&block)
45
- a = []
46
- each{|r| a << r}
47
- post_load(a)
48
- a.each(&block) if block
49
- a
45
+ _all(block){|a| each{|r| a << r}}
50
46
  end
51
47
 
52
48
  # Returns the average value for the given column/expression.
@@ -434,49 +430,107 @@ module Sequel
434
430
  import(columns, hashes.map{|h| columns.map{|c| h[c]}}, opts)
435
431
  end
436
432
 
437
- # Yields each row in the dataset, but interally uses multiple queries as needed with
438
- # limit and offset to process the entire result set without keeping all
439
- # rows in the dataset in memory, even if the underlying driver buffers all
440
- # query results in memory.
433
+ # Yields each row in the dataset, but interally uses multiple queries as needed to
434
+ # process the entire result set without keeping all rows in the dataset in memory,
435
+ # even if the underlying driver buffers all query results in memory.
441
436
  #
442
437
  # Because this uses multiple queries internally, in order to remain consistent,
443
- # it also uses a transaction internally. Additionally, to make sure that all rows
444
- # in the dataset are yielded and none are yielded twice, the dataset must have an
445
- # unambiguous order. Sequel requires that datasets using this method have an
446
- # order, but it cannot ensure that the order is unambiguous.
438
+ # it also uses a transaction internally. Additionally, to work correctly, the dataset
439
+ # must have unambiguous order. Using an ambiguous order can result in an infinite loop,
440
+ # as well as subtler bugs such as yielding duplicate rows or rows being skipped.
441
+ #
442
+ # Sequel checks that the datasets using this method have an order, but it cannot
443
+ # ensure that the order is unambiguous.
447
444
  #
448
445
  # Options:
449
446
  # :rows_per_fetch :: The number of rows to fetch per query. Defaults to 1000.
447
+ # :strategy :: The strategy to use for paging of results. By default this is :offset,
448
+ # for using an approach with a limit and offset for every page. This can
449
+ # be set to :filter, which uses a limit and a filter that excludes
450
+ # rows from previous pages. In order for this strategy to work, you must be
451
+ # selecting the columns you are ordering by, and non of the columns can contain
452
+ # NULLs. Note that some Sequel adapters have optimized implementations that will
453
+ # use cursors or streaming regardless of the :strategy option used.
454
+ # :filter_values :: If the :strategy=>:filter option is used, this option should be a proc
455
+ # that accepts the last retreived row for the previous page and an array of
456
+ # ORDER BY expressions, and returns an array of values relating to those
457
+ # expressions for the last retrieved row. You will need to use this option
458
+ # if your ORDER BY expressions are not simple columns, if they contain
459
+ # qualified identifiers that would be ambiguous unqualified, if they contain
460
+ # any identifiers that are aliased in SELECT, and potentially other cases.
461
+ #
462
+ # Examples:
463
+ #
464
+ # DB[:table].order(:id).paged_each{|row| ...}
465
+ # # SELECT * FROM table ORDER BY id LIMIT 1000
466
+ # # SELECT * FROM table ORDER BY id LIMIT 1000 OFFSET 1000
467
+ # # ...
468
+ #
469
+ # DB[:table].order(:id).paged_each(:rows_per_fetch=>100){|row| ...}
470
+ # # SELECT * FROM table ORDER BY id LIMIT 100
471
+ # # SELECT * FROM table ORDER BY id LIMIT 100 OFFSET 100
472
+ # # ...
473
+ #
474
+ # DB[:table].order(:id).paged_each(:strategy=>:filter){|row| ...}
475
+ # # SELECT * FROM table ORDER BY id LIMIT 1000
476
+ # # SELECT * FROM table WHERE id > 1001 ORDER BY id LIMIT 1000
477
+ # # ...
478
+ #
479
+ # DB[:table].order(:table__id).paged_each(:strategy=>:filter,
480
+ # :filter_values=>proc{|row, exprs| [row[:id]]}){|row| ...}
481
+ # # SELECT * FROM table ORDER BY id LIMIT 1000
482
+ # # SELECT * FROM table WHERE id > 1001 ORDER BY id LIMIT 1000
483
+ # # ...
450
484
  def paged_each(opts=OPTS)
451
485
  unless @opts[:order]
452
486
  raise Sequel::Error, "Dataset#paged_each requires the dataset be ordered"
453
487
  end
454
488
 
455
489
  total_limit = @opts[:limit]
456
- offset = @opts[:offset] || 0
457
-
490
+ offset = @opts[:offset]
458
491
  if server = @opts[:server]
459
492
  opts = opts.merge(:server=>server)
460
493
  end
461
494
 
462
495
  rows_per_fetch = opts[:rows_per_fetch] || 1000
463
- num_rows_yielded = rows_per_fetch
464
- total_rows = 0
496
+ strategy = if offset || total_limit
497
+ :offset
498
+ else
499
+ opts[:strategy] || :offset
500
+ end
465
501
 
466
502
  db.transaction(opts) do
467
- while num_rows_yielded == rows_per_fetch && (total_limit.nil? || total_rows < total_limit)
468
- if total_limit && total_rows + rows_per_fetch > total_limit
469
- rows_per_fetch = total_limit - total_rows
503
+ case strategy
504
+ when :filter
505
+ filter_values = opts[:filter_values] || proc{|row, exprs| exprs.map{|e| row[hash_key_symbol(e)]}}
506
+ base_ds = ds = limit(rows_per_fetch)
507
+ while ds
508
+ last_row = nil
509
+ ds.each do |row|
510
+ last_row = row
511
+ yield row
512
+ end
513
+ ds = (base_ds.where(ignore_values_preceding(last_row, &filter_values)) if last_row)
470
514
  end
515
+ else
516
+ offset ||= 0
517
+ num_rows_yielded = rows_per_fetch
518
+ total_rows = 0
471
519
 
472
- num_rows_yielded = 0
473
- limit(rows_per_fetch, offset).each do |row|
474
- num_rows_yielded += 1
475
- total_rows += 1 if total_limit
476
- yield row
477
- end
520
+ while num_rows_yielded == rows_per_fetch && (total_limit.nil? || total_rows < total_limit)
521
+ if total_limit && total_rows + rows_per_fetch > total_limit
522
+ rows_per_fetch = total_limit - total_rows
523
+ end
524
+
525
+ num_rows_yielded = 0
526
+ limit(rows_per_fetch, offset).each do |row|
527
+ num_rows_yielded += 1
528
+ total_rows += 1 if total_limit
529
+ yield row
530
+ end
478
531
 
479
- offset += rows_per_fetch
532
+ offset += rows_per_fetch
533
+ end
480
534
  end
481
535
  end
482
536
 
@@ -720,12 +774,54 @@ module Sequel
720
774
  end
721
775
  end
722
776
 
777
+ # Run the given SQL and return an array of all rows. If a block is given,
778
+ # each row is yielded to the block after all rows are loaded. See with_sql_each.
779
+ def with_sql_all(sql, &block)
780
+ _all(block){|a| with_sql_each(sql){|r| a << r}}
781
+ end
782
+
723
783
  # Execute the given SQL and return the number of rows deleted. This exists
724
784
  # solely as an optimization, replacing with_sql(sql).delete. It's significantly
725
785
  # faster as it does not require cloning the current dataset.
726
786
  def with_sql_delete(sql)
727
787
  execute_dui(sql)
728
788
  end
789
+ alias with_sql_update with_sql_delete
790
+
791
+ # Run the given SQL and yield each returned row to the block.
792
+ #
793
+ # This method should not be called on a shared dataset if the columns selected
794
+ # in the given SQL do not match the columns in the receiver.
795
+ def with_sql_each(sql)
796
+ if row_proc = @row_proc
797
+ fetch_rows(sql){|r| yield row_proc.call(r)}
798
+ else
799
+ fetch_rows(sql){|r| yield r}
800
+ end
801
+ self
802
+ end
803
+
804
+ # Run the given SQL and return the first row, or nil if no rows were returned.
805
+ # See with_sql_each.
806
+ def with_sql_first(sql)
807
+ with_sql_each(sql){|r| return r}
808
+ nil
809
+ end
810
+
811
+ # Run the given SQL and return the first value in the first row, or nil if no
812
+ # rows were returned. For this to make sense, the SQL given should select
813
+ # only a single value. See with_sql_each.
814
+ def with_sql_single_value(sql)
815
+ if r = with_sql_first(sql)
816
+ r.values.first
817
+ end
818
+ end
819
+
820
+ # Execute the given SQL and (on most databases) return the primary key of the
821
+ # inserted row.
822
+ def with_sql_insert(sql)
823
+ execute_insert(sql)
824
+ end
729
825
 
730
826
  protected
731
827
 
@@ -754,6 +850,15 @@ module Sequel
754
850
 
755
851
  private
756
852
 
853
+ # Internals of all and with_sql_all
854
+ def _all(block)
855
+ a = []
856
+ yield a
857
+ post_load(a)
858
+ a.each(&block) if block
859
+ a
860
+ end
861
+
757
862
  # Internals of +select_hash+ and +select_hash_groups+
758
863
  def _select_hash(meth, key_column, value_column)
759
864
  select(*(key_column.is_a?(Array) ? key_column : [key_column]) + (value_column.is_a?(Array) ? value_column : [value_column])).
@@ -850,6 +955,33 @@ module Sequel
850
955
  s.is_a?(Array) ? s.map{|c| hash_key_symbol(c)} : hash_key_symbol(s)
851
956
  end
852
957
 
958
+ # Returns an expression that will ignore values preceding the given row, using the
959
+ # receiver's current order. This yields the row and the array of order expressions
960
+ # to the block, which should return an array of values to use.
961
+ def ignore_values_preceding(row)
962
+ @opts[:order].map{|v| v.is_a?(SQL::OrderedExpression) ? v.expression : v}
963
+
964
+ order_exprs = @opts[:order].map do |v|
965
+ if v.is_a?(SQL::OrderedExpression)
966
+ descending = v.descending
967
+ v = v.expression
968
+ else
969
+ descending = false
970
+ end
971
+ [v, descending]
972
+ end
973
+
974
+ row_values = yield(row, order_exprs.map{|e| e.first})
975
+
976
+ last_expr = []
977
+ cond = order_exprs.zip(row_values).map do |(v, descending), value|
978
+ expr = last_expr + [SQL::BooleanExpression.new(descending ? :< : :>, v, value)]
979
+ last_expr += [SQL::BooleanExpression.new(:'=', v, value)]
980
+ Sequel.&(*expr)
981
+ end
982
+ Sequel.|(*cond)
983
+ end
984
+
853
985
  # Modify the identifier returned from the database based on the
854
986
  # identifier_output_method.
855
987
  def output_identifier(v)