sequel 5.75.0 → 5.77.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +48 -0
  3. data/doc/opening_databases.rdoc +4 -2
  4. data/doc/release_notes/5.76.0.txt +86 -0
  5. data/doc/release_notes/5.77.0.txt +63 -0
  6. data/doc/testing.rdoc +3 -1
  7. data/lib/sequel/adapters/jdbc/h2.rb +3 -0
  8. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
  9. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +11 -0
  10. data/lib/sequel/adapters/mysql2.rb +2 -2
  11. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  12. data/lib/sequel/adapters/postgres.rb +2 -2
  13. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  14. data/lib/sequel/adapters/shared/mysql.rb +10 -0
  15. data/lib/sequel/adapters/shared/oracle.rb +4 -6
  16. data/lib/sequel/adapters/shared/postgres.rb +22 -1
  17. data/lib/sequel/adapters/shared/sqlanywhere.rb +10 -4
  18. data/lib/sequel/adapters/shared/sqlite.rb +20 -2
  19. data/lib/sequel/adapters/sqlite.rb +42 -3
  20. data/lib/sequel/connection_pool.rb +4 -2
  21. data/lib/sequel/database/misc.rb +2 -2
  22. data/lib/sequel/database/schema_methods.rb +6 -0
  23. data/lib/sequel/dataset/features.rb +10 -1
  24. data/lib/sequel/dataset/sql.rb +47 -34
  25. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  26. data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
  27. data/lib/sequel/extensions/named_timezones.rb +1 -1
  28. data/lib/sequel/extensions/pg_array.rb +2 -0
  29. data/lib/sequel/extensions/pg_extended_date_support.rb +4 -4
  30. data/lib/sequel/extensions/pg_range.rb +2 -2
  31. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  32. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  33. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  34. data/lib/sequel/model/associations.rb +9 -2
  35. data/lib/sequel/model/base.rb +5 -2
  36. data/lib/sequel/plugins/list.rb +5 -2
  37. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  38. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  39. data/lib/sequel/version.rb +1 -1
  40. metadata +9 -3
@@ -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?
@@ -82,7 +82,7 @@ module Sequel
82
82
  when DateTime
83
83
  literal_datetime_append(sql, v)
84
84
  when Date
85
- sql << literal_date(v)
85
+ literal_date_append(sql, v)
86
86
  when Dataset
87
87
  literal_dataset_append(sql, v)
88
88
  else
@@ -115,6 +115,33 @@ module Sequel
115
115
  sql
116
116
  end
117
117
 
118
+ # Literalize a date or time value, as a SQL string value with no
119
+ # typecasting. If +raw+ is true, remove the surrounding single
120
+ # quotes. This is designed for usage by bound argument code that
121
+ # can work even if the auto_cast_date_and_time extension is
122
+ # used (either manually or implicitly in the related adapter).
123
+ def literal_date_or_time(dt, raw=false)
124
+ value = case dt
125
+ when SQLTime
126
+ literal_sqltime(dt)
127
+ when Time
128
+ literal_time(dt)
129
+ when DateTime
130
+ literal_datetime(dt)
131
+ when Date
132
+ literal_date(dt)
133
+ else
134
+ raise TypeError, "unsupported type: #{dt.inspect}"
135
+ end
136
+
137
+ if raw
138
+ value.sub!(/\A'/, '')
139
+ value.sub!(/'\z/, '')
140
+ end
141
+
142
+ value
143
+ end
144
+
118
145
  # Returns an array of insert statements for inserting multiple records.
119
146
  # This method is used by +multi_insert+ to format insert statements and
120
147
  # expects a keys array and and an array of value arrays.
@@ -1104,9 +1131,14 @@ module Sequel
1104
1131
  :"t#{number}"
1105
1132
  end
1106
1133
 
1107
- # The strftime format to use when literalizing the time.
1134
+ # The strftime format to use when literalizing time (Sequel::SQLTime) values.
1135
+ def default_time_format
1136
+ "'%H:%M:%S.%6N'"
1137
+ end
1138
+
1139
+ # The strftime format to use when literalizing timestamp (Time/DateTime) values.
1108
1140
  def default_timestamp_format
1109
- requires_sql_standard_datetimes? ? "TIMESTAMP '%Y-%m-%d %H:%M:%S%N%z'" : "'%Y-%m-%d %H:%M:%S%N%z'"
1141
+ "'%Y-%m-%d %H:%M:%S.%6N'"
1110
1142
  end
1111
1143
 
1112
1144
  def delete_delete_sql(sql)
@@ -1169,43 +1201,23 @@ module Sequel
1169
1201
  {1 => ((op == :IN) ? 0 : 1)}
1170
1202
  end
1171
1203
 
1172
- # Format the timestamp based on the default_timestamp_format, with a couple
1173
- # of modifiers. First, allow %N to be used for fractions seconds (if the
1174
- # database supports them), and override %z to always use a numeric offset
1175
- # of hours and minutes.
1204
+ # Format the timestamp based on the default_timestamp_format.
1176
1205
  def format_timestamp(v)
1177
- v2 = db.from_application_timestamp(v)
1178
- fmt = default_timestamp_format.gsub(/%[Nz]/) do |m|
1179
- if m == '%N'
1180
- # Ruby 1.9 supports %N in timestamp formats, but Sequel has supported %N
1181
- # for longer in a different way, where the . is already appended and only 6
1182
- # decimal places are used by default.
1183
- format_timestamp_usec(v.is_a?(DateTime) ? v.sec_fraction*(1000000) : v.usec) if supports_timestamp_usecs?
1184
- else
1185
- if supports_timestamp_timezones?
1186
- # Would like to just use %z format, but it doesn't appear to work on Windows
1187
- # Instead, the offset fragment is constructed manually
1188
- minutes = (v2.is_a?(DateTime) ? v2.offset * 1440 : v2.utc_offset/60).to_i
1189
- format_timestamp_offset(*minutes.divmod(60))
1190
- end
1191
- end
1192
- end
1193
- v2.strftime(fmt)
1206
+ db.from_application_timestamp(v).strftime(default_timestamp_format)
1194
1207
  end
1195
1208
 
1196
- # Return the SQL timestamp fragment to use for the timezone offset.
1197
- def format_timestamp_offset(hour, minute)
1198
- sprintf("%+03i%02i", hour, minute)
1199
- end
1209
+ # :nocov:
1200
1210
 
1201
1211
  # Return the SQL timestamp fragment to use for the fractional time part.
1202
1212
  # Should start with the decimal point. Uses 6 decimal places by default.
1203
1213
  def format_timestamp_usec(usec, ts=timestamp_precision)
1214
+ # SEQUEL6: Remove
1204
1215
  unless ts == 6
1205
1216
  usec = usec/(10 ** (6 - ts))
1206
1217
  end
1207
1218
  sprintf(".%0#{ts}d", usec)
1208
1219
  end
1220
+ # :nocov:
1209
1221
 
1210
1222
  # Append literalization of identifier to SQL string, considering regular strings
1211
1223
  # as SQL identifiers instead of SQL strings.
@@ -1347,11 +1359,12 @@ module Sequel
1347
1359
 
1348
1360
  # SQL fragment for Date, using the ISO8601 format.
1349
1361
  def literal_date(v)
1350
- if requires_sql_standard_datetimes?
1351
- v.strftime("DATE '%Y-%m-%d'")
1352
- else
1353
- v.strftime("'%Y-%m-%d'")
1354
- end
1362
+ v.strftime("'%Y-%m-%d'")
1363
+ end
1364
+
1365
+ # Append literalization of date to SQL string.
1366
+ def literal_date_append(sql, v)
1367
+ sql << literal_date(v)
1355
1368
  end
1356
1369
 
1357
1370
  # SQL fragment for DateTime
@@ -1414,7 +1427,7 @@ module Sequel
1414
1427
 
1415
1428
  # SQL fragment for Sequel::SQLTime, containing just the time part
1416
1429
  def literal_sqltime(v)
1417
- v.strftime("'%H:%M:%S#{format_timestamp_usec(v.usec, sqltime_precision) if supports_timestamp_usecs?}'")
1430
+ v.strftime(default_time_format)
1418
1431
  end
1419
1432
 
1420
1433
  # Append literalization of Sequel::SQLTime to SQL string.
@@ -0,0 +1,94 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The auto_cast_date_and_time extension uses SQL standard type casting
4
+ # when literalizing date, time, and timestamp values:
5
+ #
6
+ # DB.literal(Time.now)
7
+ # # => "TIMESTAMP '...'"
8
+ #
9
+ # DB.literal(Date.today)
10
+ # # => "DATE '...'"
11
+ #
12
+ # DB.literal(Sequel::SQLTime.create(10, 20, 30))
13
+ # # => "TIME '10:20:30.000000'"
14
+ #
15
+ # The default behavior of Sequel on adapters that do not require the
16
+ # SQL standard behavior is to format the date or time value without:
17
+ # casting
18
+ #
19
+ # DB.literal(Sequel::SQLTime.create(10, 20, 30))
20
+ # # => "'10:20:30.000000'"
21
+ #
22
+ # However, then the database cannot determine the type of the string,
23
+ # and must perform some implicit casting. If implicit casting cannot
24
+ # be used, it will probably treat the value as a string:
25
+ #
26
+ # DB.get(Time.now).class
27
+ # # Without auto_cast_date_and_time: String
28
+ # # With auto_cast_date_and_time: Time
29
+ #
30
+ # Note that not all databases support this extension. PostgreSQL and
31
+ # MySQL support it, but SQLite and Microsoft SQL Server do not.
32
+ #
33
+ # You can load this extension into specific datasets:
34
+ #
35
+ # ds = DB[:table]
36
+ # ds = ds.extension(:auto_cast_date_and_time)
37
+ #
38
+ # Or you can load it into all of a database's datasets, which
39
+ # is probably the desired behavior if you are using this extension:
40
+ #
41
+ # DB.extension(:auto_cast_date_and_time)
42
+ #
43
+ # Related module: Sequel::AutoCastDateAndTime
44
+
45
+ #
46
+ module Sequel
47
+ module AutoCastDateAndTime
48
+ # :nocov:
49
+
50
+ # Mark the datasets as requiring sql standard date times. This is only needed
51
+ # for backwards compatibility.
52
+ def requires_sql_standard_datetimes?
53
+ # SEQUEL6: Remove
54
+ true
55
+ end
56
+ # :nocov:
57
+
58
+ private
59
+
60
+ # Explicitly cast SQLTime objects to TIME.
61
+ def literal_sqltime_append(sql, v)
62
+ sql << "TIME "
63
+ super
64
+ end
65
+
66
+ # Explicitly cast Time objects to TIMESTAMP.
67
+ def literal_time_append(sql, v)
68
+ sql << literal_datetime_timestamp_cast
69
+ super
70
+ end
71
+
72
+ # Explicitly cast DateTime objects to TIMESTAMP.
73
+ def literal_datetime_append(sql, v)
74
+ sql << literal_datetime_timestamp_cast
75
+ super
76
+ end
77
+
78
+ # Explicitly cast Date objects to DATE.
79
+ def literal_date_append(sql, v)
80
+ sql << "DATE "
81
+ super
82
+ end
83
+
84
+ # The default cast string to use for Time/DateTime objects.
85
+ # Respects existing method if already defined.
86
+ def literal_datetime_timestamp_cast
87
+ return super if defined?(super)
88
+ 'TIMESTAMP '
89
+ end
90
+ end
91
+
92
+ Dataset.register_extension(:auto_cast_date_and_time, AutoCastDateAndTime)
93
+ end
94
+
@@ -14,12 +14,12 @@
14
14
  #
15
15
  # ds = DB[:items].extension(:duplicate_columns_handler)
16
16
  #
17
- # A database option is introduced: :on_duplicate_columns. It accepts a Symbol
18
- # or any object that responds to :call.
17
+ # If the Database option :on_duplicate_columns is set, it configures how this
18
+ # extension works. The value should be # or any object that responds to :call.
19
19
  #
20
- # on_duplicate_columns: :raise
21
- # on_duplicate_columns: :warn
22
- # on_duplicate_columns: :ignore
20
+ # on_duplicate_columns: :raise # or 'raise'
21
+ # on_duplicate_columns: :warn # or 'warn'
22
+ # on_duplicate_columns: :ignore # or anything unrecognized
23
23
  # on_duplicate_columns: lambda{|columns| arbitrary_condition? ? :raise : :warn}
24
24
  #
25
25
  # You may also configure duplicate columns handling for a specific dataset:
@@ -30,9 +30,10 @@
30
30
  # ds.on_duplicate_columns{|columns| arbitrary_condition? ? :raise : :warn}
31
31
  # ds.on_duplicate_columns(lambda{|columns| arbitrary_condition? ? :raise : :warn})
32
32
  #
33
- # If :raise is specified, a Sequel::DuplicateColumnError is raised.
34
- # If :warn is specified, you will receive a warning via +warn+.
33
+ # If :raise or 'raise' is specified, a Sequel::DuplicateColumnError is raised.
34
+ # If :warn or 'warn' is specified, you will receive a warning via +warn+.
35
35
  # If a callable is specified, it will be called.
36
+ # For other values, duplicate columns are ignored (Sequel's default behavior)
36
37
  # If no on_duplicate_columns is specified, the default is :warn.
37
38
  #
38
39
  # Related module: Sequel::DuplicateColumnsHandler
@@ -64,9 +65,9 @@ module Sequel
64
65
  message = "#{caller(*CALLER_ARGS).first}: One or more duplicate columns present in #{cols.inspect}"
65
66
 
66
67
  case duplicate_columns_handler_type(cols)
67
- when :raise
68
+ when :raise, 'raise'
68
69
  raise DuplicateColumnError, message
69
- when :warn
70
+ when :warn, 'warn'
70
71
  warn message
71
72
  end
72
73
  end
@@ -136,7 +136,7 @@ module Sequel
136
136
  v = output_timezone.utc_to_local(v.new_offset(0))
137
137
 
138
138
  # Force DateTime output instead of TZInfo::DateTimeWithOffset
139
- DateTime.jd(v.jd, v.hour, v.minute, v.second + v.sec_fraction, v.offset, v.start)
139
+ DateTime.civil(v.year, v.month, v.day, v.hour, v.minute, v.second + v.sec_fraction, v.offset, v.start)
140
140
  end
141
141
  # :nodoc:
142
142
  # :nocov:
@@ -241,6 +241,8 @@ module Sequel
241
241
  else
242
242
  literal(a)
243
243
  end
244
+ when Time, Date
245
+ @default_dataset.literal_date_or_time(a)
244
246
  else
245
247
  if (s = bound_variable_arg(a, nil)).is_a?(String)
246
248
  bound_variable_array_string(s)
@@ -53,8 +53,8 @@ module Sequel
53
53
  # on jdbc.
54
54
  def bound_variable_arg(arg, conn)
55
55
  case arg
56
- when Date, Time
57
- literal(arg)
56
+ when Time, Date
57
+ @default_dataset.literal_date_or_time(arg)
58
58
  else
59
59
  super
60
60
  end
@@ -203,7 +203,7 @@ module Sequel
203
203
  date <<= ((date.year) * 24 - 12)
204
204
  date = db.from_application_timestamp(date)
205
205
  minutes = (date.offset * 1440).to_i
206
- date.strftime("'%Y-%m-%d %H:%M:%S.%N#{format_timestamp_offset(*minutes.divmod(60))} BC'")
206
+ date.strftime("'%Y-%m-%d %H:%M:%S.%6N#{sprintf("%+03i%02i", *minutes.divmod(60))} BC'")
207
207
  else
208
208
  super
209
209
  end
@@ -247,7 +247,7 @@ module Sequel
247
247
  def literal_time(time)
248
248
  if time < TIME_YEAR_1
249
249
  time = db.from_application_timestamp(time)
250
- time.strftime("'#{sprintf('%04i', time.year.abs+1)}-%m-%d %H:%M:%S.%N#{format_timestamp_offset(*(time.utc_offset/RATIONAL_60).divmod(60))} BC'")
250
+ time.strftime("'#{sprintf('%04i', time.year.abs+1)}-%m-%d %H:%M:%S.%6N#{sprintf("%+03i%02i", *(time.utc_offset/RATIONAL_60).divmod(60))} BC'")
251
251
  else
252
252
  super
253
253
  end
@@ -494,8 +494,8 @@ module Sequel
494
494
  case k
495
495
  when nil
496
496
  ''
497
- when Date, Time
498
- ds.literal(k)[1...-1]
497
+ when Time, Date
498
+ ds.literal_date_or_time(k, true)
499
499
  when Integer, Float
500
500
  k.to_s
501
501
  when BigDecimal
@@ -1,20 +1,35 @@
1
1
  # frozen-string-literal: true
2
2
  #
3
3
  # The pg_timestamptz extension changes the default timestamp
4
- # type for the database to be +timestamptz+ (+timestamp with time zone+)
5
- # instead of +timestamp+ (+timestamp without time zone+). This is
4
+ # type for the database to be +timestamptz+ (<tt>timestamp with time zone</tt>)
5
+ # instead of +timestamp+ (<tt>timestamp without time zone</tt>). This is
6
6
  # recommended if you are dealing with multiple timezones in your application.
7
+ #
8
+ # If you are using the auto_cast_date_and_time extension, the pg_timestamptz
9
+ # extension will automatically cast Time and DateTime values to
10
+ # <tt>TIMESTAMP WITH TIME ZONE</tt> instead of +TIMESTAMP+.
7
11
  #
8
12
  # To load the extension into the database:
9
13
  #
10
14
  # DB.extension :pg_timestamptz
11
15
  #
12
- # Related module: Sequel::Postgres::Timestamptz
16
+ # To load the extension into individual datasets:
17
+ #
18
+ # ds = ds.extension(:pg_timestamptz)
19
+ #
20
+ # Note that the loading into individual datasets only affects the integration
21
+ # with the auto_cast_date_and_time extension.
22
+ #
23
+ # Related modules: Sequel::Postgres::Timestamptz, Sequel::Postgres::TimestamptzDatasetMethods
13
24
 
14
25
  #
15
26
  module Sequel
16
27
  module Postgres
17
28
  module Timestamptz
29
+ def self.extended(db)
30
+ db.extend_datasets(TimestamptzDatasetMethods)
31
+ end
32
+
18
33
  private
19
34
 
20
35
  # Use timestamptz by default for generic timestamp value.
@@ -22,7 +37,16 @@ module Sequel
22
37
  :timestamptz
23
38
  end
24
39
  end
40
+
41
+ module TimestamptzDatasetMethods
42
+ private
43
+
44
+ def literal_datetime_timestamp_cast
45
+ 'TIMESTAMP WITH TIME ZONE '
46
+ end
47
+ end
25
48
  end
26
49
 
50
+ Dataset.register_extension(:pg_timestamptz, Postgres::TimestamptzDatasetMethods)
27
51
  Database.register_extension(:pg_timestamptz, Postgres::Timestamptz)
28
52
  end
@@ -35,7 +35,7 @@ module Sequel
35
35
 
36
36
  # Round Sequel::SQLTime values before literalizing
37
37
  def literal_sqltime(v)
38
- super(v.round(timestamp_precision))
38
+ super(v.round(sqltime_precision))
39
39
  end
40
40
 
41
41
  # Round Time values before literalizing
@@ -0,0 +1,78 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The transaction_connection_validator extension automatically
4
+ # retries a transaction on a connection if an disconnect error
5
+ # is raised when sending the statement to begin a new
6
+ # transaction, as long as the user has not already checked out
7
+ # a connection. This is safe to do because no other queries
8
+ # have been issued on the connection, and no user-level code
9
+ # is run before retrying.
10
+ #
11
+ # This approach to connection validation can be significantly
12
+ # lower overhead than the connection_validator extension,
13
+ # though it does not handle all cases handled by the
14
+ # connection_validator extension. However, it performs the
15
+ # validation checks on every new transaction, so it will
16
+ # automatically handle disconnected connections in some cases
17
+ # where the connection_validator extension will not by default
18
+ # (as the connection_validator extension only checks
19
+ # connections if they have not been used in the last hour by
20
+ # default).
21
+ #
22
+ # Related module: Sequel::TransactionConnectionValidator
23
+
24
+ #
25
+ module Sequel
26
+ module TransactionConnectionValidator
27
+ class DisconnectRetry < DatabaseDisconnectError
28
+ # The connection that raised the disconnect error
29
+ attr_accessor :connection
30
+
31
+ # The underlying disconnect error, in case it needs to be reraised.
32
+ attr_accessor :database_error
33
+ end
34
+
35
+ # Rescue disconnect errors raised when beginning a new transaction. If there
36
+ # is a disconnnect error, it should be safe to retry the transaction using a
37
+ # new connection, as we haven't yielded control to the user yet.
38
+ def transaction(opts=OPTS)
39
+ super
40
+ rescue DisconnectRetry => e
41
+ if synchronize(opts[:server]){|conn| conn.equal?(e.connection)}
42
+ # If retrying would use the same connection, that means the
43
+ # connection was not removed from the pool, which means the caller has
44
+ # already checked out the connection, and retrying will not be successful.
45
+ # In this case, we can only reraise the exception.
46
+ raise e.database_error
47
+ end
48
+
49
+ num_retries ||= 0
50
+ num_retries += 1
51
+ retry if num_retries < 5
52
+
53
+ raise e.database_error
54
+ end
55
+
56
+ private
57
+
58
+ # Reraise disconnect errors as DisconnectRetry so they can be retried.
59
+ def begin_new_transaction(conn, opts)
60
+ super
61
+ rescue Sequel::DatabaseDisconnectError, *database_error_classes => e
62
+ if e.is_a?(Sequel::DatabaseDisconnectError) || disconnect_error?(e, OPTS)
63
+ exception = DisconnectRetry.new(e.message)
64
+ exception.set_backtrace([])
65
+ exception.connection = conn
66
+ unless e.is_a?(Sequel::DatabaseError)
67
+ e = Sequel.convert_exception_class(e, database_error_class(e, OPTS))
68
+ end
69
+ exception.database_error = e
70
+ raise exception
71
+ end
72
+
73
+ raise
74
+ end
75
+ end
76
+
77
+ Database.register_extension(:transaction_connection_validator, TransactionConnectionValidator)
78
+ end
@@ -3387,8 +3387,15 @@ module Sequel
3387
3387
  local_opts = ds.opts[:eager_graph][:local]
3388
3388
  limit_strategy = r.eager_graph_limit_strategy(local_opts[:limit_strategy])
3389
3389
 
3390
- if r[:conditions] && !Sequel.condition_specifier?(r[:conditions]) && !r[:orig_opts].has_key?(:graph_conditions) && !r[:orig_opts].has_key?(:graph_only_conditions) && !r.has_key?(:graph_block)
3391
- raise Error, "Cannot eager_graph association when :conditions specified and not a hash or an array of pairs. Specify :graph_conditions, :graph_only_conditions, or :graph_block for the association. Model: #{r[:model]}, association: #{r[:name]}"
3390
+ # SEQUEL6: remove and integrate the auto_restrict_eager_graph plugin
3391
+ if !r[:orig_opts].has_key?(:graph_conditions) && !r[:orig_opts].has_key?(:graph_only_conditions) && !r.has_key?(:graph_block) && !r[:allow_eager_graph]
3392
+ if r[:conditions] && !Sequel.condition_specifier?(r[:conditions])
3393
+ raise Error, "Cannot eager_graph association when :conditions specified and not a hash or an array of pairs. Specify :graph_conditions, :graph_only_conditions, or :graph_block for the association. Model: #{r[:model]}, association: #{r[:name]}"
3394
+ end
3395
+
3396
+ if r[:block] && !r[:graph_use_association_block]
3397
+ warn "eager_graph used for association when association given a block without graph options. The block is ignored in this case. This will result in an exception starting in Sequel 6. Model: #{r[:model]}, association: #{r[:name]}"
3398
+ end
3392
3399
  end
3393
3400
 
3394
3401
  ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>(ta == ds.opts[:eager_graph][:master]) ? first_source : qualifier_from_alias_symbol(ta, first_source), :callback=>callback, :join_type=>join_type || local_opts[:join_type], :join_only=>local_opts[:join_only], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
@@ -1244,18 +1244,21 @@ module Sequel
1244
1244
  @errors ||= errors_class.new
1245
1245
  end
1246
1246
 
1247
+ EXISTS_SELECT_ = SQL::AliasedExpression.new(1, :one)
1248
+ private_constant :EXISTS_SELECT_
1249
+
1247
1250
  # Returns true when current instance exists, false otherwise.
1248
1251
  # Generally an object that isn't new will exist unless it has
1249
1252
  # been deleted. Uses a database query to check for existence,
1250
1253
  # unless the model object is new, in which case this is always
1251
1254
  # false.
1252
1255
  #
1253
- # Artist[1].exists? # SELECT 1 FROM artists WHERE (id = 1)
1256
+ # Artist[1].exists? # SELECT 1 AS one FROM artists WHERE (id = 1)
1254
1257
  # # => true
1255
1258
  # Artist.new.exists?
1256
1259
  # # => false
1257
1260
  def exists?
1258
- new? ? false : !this.get(SQL::AliasedExpression.new(1, :one)).nil?
1261
+ new? ? false : !this.get(EXISTS_SELECT_).nil?
1259
1262
  end
1260
1263
 
1261
1264
  # Ignore the model's setter method cache when this instances extends a module, as the
@@ -185,10 +185,13 @@ module Sequel
185
185
  end
186
186
 
187
187
  # Set the value of the position_field to the maximum value plus 1 unless the
188
- # position field already has a value.
188
+ # position field already has a value. If the list is empty, the position will
189
+ # be set to the model's +top_of_list+ value.
189
190
  def before_validation
190
191
  unless get_column_value(position_field)
191
- set_column_value("#{position_field}=", list_dataset.max(position_field).to_i+1)
192
+ current_max = list_dataset.max(position_field)
193
+ value = current_max.nil? ? model.top_of_list : current_max.to_i + 1
194
+ set_column_value("#{position_field}=", value)
192
195
  end
193
196
  super
194
197
  end
@@ -71,6 +71,8 @@ module Sequel
71
71
  # (default: :t)
72
72
  # :level_alias :: The symbol identifier to use when eagerly loading descendants
73
73
  # up to a given level (default: :x_level_x)
74
+ # :union_all :: Whether to use UNION ALL or UNION with the recursive
75
+ # common table expression (default: true)
74
76
  module RcteTree
75
77
  # Create the appropriate parent, children, ancestors, and descendants
76
78
  # associations for the model.
@@ -80,6 +82,7 @@ module Sequel
80
82
  opts = opts.dup
81
83
  opts[:class] = model
82
84
  opts[:methods_module] = Module.new
85
+ opts[:union_all] = opts[:union_all].nil? ? true : opts[:union_all]
83
86
  model.send(:include, opts[:methods_module])
84
87
 
85
88
  key = opts[:key] ||= :parent_id
@@ -142,7 +145,7 @@ module Sequel
142
145
  model.from(SQL::AliasedExpression.new(t, table_alias)).
143
146
  with_recursive(t, col_aliases ? base_ds.select(*col_aliases) : base_ds.select_all,
144
147
  recursive_ds.select(*c_all),
145
- :args=>col_aliases)
148
+ :args=>col_aliases, union_all: opts[:union_all])
146
149
  end
147
150
  aal = Array(a[:after_load])
148
151
  aal << proc do |m, ancs|
@@ -191,7 +194,7 @@ module Sequel
191
194
  table_alias = model.dataset.schema_and_table(model.table_name)[1].to_sym
192
195
  ds = model.from(SQL::AliasedExpression.new(t, table_alias)).
193
196
  with_recursive(t, base_case, recursive_case,
194
- :args=>((key_aliases + col_aliases) if col_aliases))
197
+ :args=>((key_aliases + col_aliases) if col_aliases), union_all: opts[:union_all])
195
198
  ds = r.apply_eager_dataset_changes(ds)
196
199
  ds = ds.select_append(ka) unless ds.opts[:select] == nil
197
200
  model.eager_load_results(r, eo.merge(:loader=>false, :initialize_rows=>false, :dataset=>ds, :id_map=>nil)) do |obj|
@@ -240,7 +243,7 @@ module Sequel
240
243
  model.from(SQL::AliasedExpression.new(t, table_alias)).
241
244
  with_recursive(t, col_aliases ? base_ds.select(*col_aliases) : base_ds.select_all,
242
245
  recursive_ds.select(*c_all),
243
- :args=>col_aliases)
246
+ :args=>col_aliases, union_all: opts[:union_all])
244
247
  end
245
248
  dal = Array(d[:after_load])
246
249
  dal << proc do |m, descs|
@@ -299,7 +302,7 @@ module Sequel
299
302
  table_alias = model.dataset.schema_and_table(model.table_name)[1].to_sym
300
303
  ds = model.from(SQL::AliasedExpression.new(t, table_alias)).
301
304
  with_recursive(t, base_case, recursive_case,
302
- :args=>((key_aliases + col_aliases + (level ? [la] : [])) if col_aliases))
305
+ :args=>((key_aliases + col_aliases + (level ? [la] : [])) if col_aliases), union_all: opts[:union_all])
303
306
  ds = r.apply_eager_dataset_changes(ds)
304
307
  ds = ds.select_append(ka) unless ds.opts[:select] == nil
305
308
  model.eager_load_results(r, eo.merge(:loader=>false, :initialize_rows=>false, :dataset=>ds, :id_map=>nil, :associations=>OPTS)) do |obj|
@@ -295,7 +295,7 @@ module Sequel
295
295
  h = ds.joined_dataset? ? qualified_pk_hash : pk_hash
296
296
  ds = ds.exclude(h)
297
297
  end
298
- errors.add(a, message) unless ds.count == 0
298
+ errors.add(a, message) unless ds.empty?
299
299
  end
300
300
  end
301
301
 
@@ -6,7 +6,7 @@ module Sequel
6
6
 
7
7
  # The minor version of Sequel. Bumped for every non-patch level
8
8
  # release, generally around once a month.
9
- MINOR = 75
9
+ MINOR = 77
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.