sequel 5.75.0 → 5.77.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.
- 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.
|