sequel 5.75.0 → 5.77.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +48 -0
- data/doc/opening_databases.rdoc +4 -2
- data/doc/release_notes/5.76.0.txt +86 -0
- data/doc/release_notes/5.77.0.txt +63 -0
- data/doc/testing.rdoc +3 -1
- data/lib/sequel/adapters/jdbc/h2.rb +3 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +11 -0
- data/lib/sequel/adapters/mysql2.rb +2 -2
- data/lib/sequel/adapters/odbc/mssql.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +2 -2
- data/lib/sequel/adapters/shared/mssql.rb +1 -1
- data/lib/sequel/adapters/shared/mysql.rb +10 -0
- data/lib/sequel/adapters/shared/oracle.rb +4 -6
- data/lib/sequel/adapters/shared/postgres.rb +22 -1
- data/lib/sequel/adapters/shared/sqlanywhere.rb +10 -4
- data/lib/sequel/adapters/shared/sqlite.rb +20 -2
- data/lib/sequel/adapters/sqlite.rb +42 -3
- data/lib/sequel/connection_pool.rb +4 -2
- data/lib/sequel/database/misc.rb +2 -2
- data/lib/sequel/database/schema_methods.rb +6 -0
- data/lib/sequel/dataset/features.rb +10 -1
- data/lib/sequel/dataset/sql.rb +47 -34
- data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
- data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
- data/lib/sequel/extensions/named_timezones.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +2 -0
- data/lib/sequel/extensions/pg_extended_date_support.rb +4 -4
- data/lib/sequel/extensions/pg_range.rb +2 -2
- data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
- data/lib/sequel/extensions/round_timestamps.rb +1 -1
- data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
- data/lib/sequel/model/associations.rb +9 -2
- data/lib/sequel/model/base.rb +5 -2
- data/lib/sequel/plugins/list.rb +5 -2
- data/lib/sequel/plugins/rcte_tree.rb +7 -4
- data/lib/sequel/plugins/validation_helpers.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- 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?
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -82,7 +82,7 @@ module Sequel
|
|
82
82
|
when DateTime
|
83
83
|
literal_datetime_append(sql, v)
|
84
84
|
when Date
|
85
|
-
sql
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
#
|
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
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
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(
|
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
|
-
#
|
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.
|
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:
|
@@ -53,8 +53,8 @@ module Sequel
|
|
53
53
|
# on jdbc.
|
54
54
|
def bound_variable_arg(arg, conn)
|
55
55
|
case arg
|
56
|
-
when
|
57
|
-
|
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.%
|
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.%
|
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
|
@@ -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+ (
|
5
|
-
# instead of +timestamp+ (
|
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
|
-
#
|
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
|
@@ -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
|
-
|
3391
|
-
|
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])
|
data/lib/sequel/model/base.rb
CHANGED
@@ -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(
|
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
|
data/lib/sequel/plugins/list.rb
CHANGED
@@ -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
|
-
|
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|
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
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.
|