sequel 4.8.0 → 4.9.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 (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)