sequel 5.75.0 → 5.76.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 +28 -0
- data/doc/opening_databases.rdoc +4 -2
- data/doc/release_notes/5.76.0.txt +86 -0
- data/doc/testing.rdoc +1 -0
- 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 +5 -0
- data/lib/sequel/adapters/sqlite.rb +24 -3
- 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/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/plugins/rcte_tree.rb +7 -4
- data/lib/sequel/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca7f95e54ed437de173bbdfac08f4091f0e8debfb1a89bb168cd6157341afa9d
|
4
|
+
data.tar.gz: f49e07aec7ae48c509abf3965bc10ec102616d5ccc99ecb49d0116a9162760af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b8453bf85f48038160481a3b795a692a670b3e73084e6f531f353739c2549f809af5d45368db4dbcc0ac0bbd318eb4a94a02d0289d8c09117641f0ea4dc493d
|
7
|
+
data.tar.gz: a7daa11f1dc3d757f1618f4e8ee832c33e7072f375cfeea028e68b340414a159c92d05d76d75fd9bbb35cb498699ed9db32c9e7cbb158bb2b8a88e598ade34e6
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
=== 5.76.0 (2024-01-01)
|
2
|
+
|
3
|
+
* Improve performance and flexibility of regexp matching in sqlite adapter (paddor) (#2108)
|
4
|
+
|
5
|
+
* Support SQL::Identifier for Database#tables :schema option values on PostgreSQL (jeremyevans)
|
6
|
+
|
7
|
+
* Support generating rcte queries using UNION or UNION ALL in the rcte plugin (jonathanfrias) (#2107)
|
8
|
+
|
9
|
+
* Make Database#table_exists? on PostgreSQL handle lock or statement timeout errors as evidence the table exists (jeremyevans) (#2106)
|
10
|
+
|
11
|
+
* Work around DateTime.jd fractional second bug on JRuby in named_timezones extension (jeremyevans)
|
12
|
+
|
13
|
+
* Support fractional times and timestamps on SQLAnywhere (jeremyevans)
|
14
|
+
|
15
|
+
* Make round_timestamps extension use Dataset#sqltime_precision for rounding Sequel::SQLTime values (jeremyevans)
|
16
|
+
|
17
|
+
* Remove special handling of %N modifier in Dataset#default_timestamp_format (jeremyevans)
|
18
|
+
|
19
|
+
* Add Dataset#default_time_format private method, for adapters to override for time (not timestamp) formatting (jeremyevans)
|
20
|
+
|
21
|
+
* Remove Dataset#format_timestamp_offset private method (jeremyevans)
|
22
|
+
|
23
|
+
* Remove special handling of %z modifier in Dataset#default_timestamp_format (jeremyevans)
|
24
|
+
|
25
|
+
* Add Dataset#literal_date_or_time, for simpler use by bound argument code (jeremyevans)
|
26
|
+
|
27
|
+
* Add auto_cast_date_and_time extension, for casting date and time values using SQL standard functions (jeremyevans)
|
28
|
+
|
1
29
|
=== 5.75.0 (2023-12-01)
|
2
30
|
|
3
31
|
* Make any_not_empty? extension support passing pattern argument to any? (jeremyevans) (#2100)
|
data/doc/opening_databases.rdoc
CHANGED
@@ -388,8 +388,10 @@ The following additional options are supported:
|
|
388
388
|
:readonly :: open database in read-only mode
|
389
389
|
:timeout :: the busy timeout to use in milliseconds (default: 5000).
|
390
390
|
:setup_regexp_function :: Whether to setup a REGEXP function in the underlying SQLite3::Database object. Doing so
|
391
|
-
allows you to use regexp support in dataset expressions.
|
392
|
-
|
391
|
+
allows you to use regexp support in dataset expressions. If +:cached+ or <tt>"cached"</tt>+, caches each
|
392
|
+
unique regex (more efficient but risk of memory leak). If a Proc is provided, it will be called with
|
393
|
+
a string for the regexp and a string for the value to compare, and should return whether the regexp
|
394
|
+
string matches the string value to compare. The default Proc used does <tt>Regexp.new(regexp_str).match(str)</tt>.
|
393
395
|
|
394
396
|
Note that SQLite memory databases are restricted to a single connection by
|
395
397
|
default. This is because SQLite does not allow multiple connections to
|
@@ -0,0 +1,86 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* An auto_cast_date_and_time extension has been added, which will
|
4
|
+
automatically cast date and time values using SQL standard functions.
|
5
|
+
This makes sure the database will treat the value as a date, time,
|
6
|
+
or timestamp, instead of treating it as a string or unknown type:
|
7
|
+
|
8
|
+
DB.get(Date.today).class
|
9
|
+
# SELECT '2024-01-01' AS v LIMIT 1
|
10
|
+
String
|
11
|
+
|
12
|
+
DB.extension(:auto_cast_date_and_time)
|
13
|
+
DB.get(Date.today).class
|
14
|
+
# SELECT DATE '2024-01-01' AS v LIMIT 1
|
15
|
+
Date
|
16
|
+
|
17
|
+
This was already Sequel's default behavior on adapters that required
|
18
|
+
it. This extension is usable on PostgreSQL and MySQL. It is not
|
19
|
+
usable on SQLite (no date/time types) or Microsoft SQL Server (no
|
20
|
+
support for the SQL standard conversion syntax).
|
21
|
+
|
22
|
+
This extension can break code that currently works. If using it on
|
23
|
+
PostgreSQL, it will cast the values to TIMESTAMP, not TIMESTAMP
|
24
|
+
WITH TIME ZONE, which can break code that depended on an implicit
|
25
|
+
conversion to TIMESTAMP WITH TIME ZONE. The pg_timestamptz
|
26
|
+
extension integrates with the the auto_cast_date_and_time extension
|
27
|
+
and will implicitly cast Time/DateTime to TIMESTAMP WITH TIME ZONE.
|
28
|
+
|
29
|
+
* The sqlite adapter now supports a :cached value for the
|
30
|
+
:setup_regexp_function Database option, which will cache regexp
|
31
|
+
values instead of creating a new regexp per value to compare. This
|
32
|
+
is much faster when using a regexp comparison on a large dataset,
|
33
|
+
but can result in a memory leak if using dynamic regexps. You can
|
34
|
+
also provide a Proc value for the :setup_regexp_function option,
|
35
|
+
which will be passed both the regexp source string and the database
|
36
|
+
string to compare, and should return whether the database string
|
37
|
+
matches the regexp string.
|
38
|
+
|
39
|
+
* The rcte_tree plugin now supports a :union_all option, which can
|
40
|
+
be set to false to use UNION instead of UNION ALL in the recursive
|
41
|
+
common table expression.
|
42
|
+
|
43
|
+
= Other Improvements
|
44
|
+
|
45
|
+
* Time/DateTime/SQLite literalization speed has more than doubled
|
46
|
+
compared to the previous version. The internal code is also much
|
47
|
+
simpler, as the speedup resulted from removing multiple abstraction
|
48
|
+
layers that mostly existed for Ruby 1.8 support.
|
49
|
+
|
50
|
+
* Database#table_exists? on PostgreSQL now handles lock or statement
|
51
|
+
timeout errors as evidence the table exists.
|
52
|
+
|
53
|
+
* The round_timestamps extension now correctly rounds SQLTime values
|
54
|
+
on Microsoft SQL Server (the only database Sequel supports where
|
55
|
+
time precision is different than timestamp precision).
|
56
|
+
|
57
|
+
* Fractional times and timestamps are now supported on SQLAnywhere,
|
58
|
+
except for time values when using the jdbc adapter due to a
|
59
|
+
limitation in the JDBC sqlanywhere driver.
|
60
|
+
|
61
|
+
* Database#tables and #views on PostgreSQL now supports
|
62
|
+
SQL::Identifier values for the :schema option.
|
63
|
+
|
64
|
+
* The named_timezones extension now works around a bug in DateTime.jd
|
65
|
+
on JRuby.
|
66
|
+
|
67
|
+
= Backwards Compatibility
|
68
|
+
|
69
|
+
* Time/DateTime/SQLTime literalization internals have changed.
|
70
|
+
If you are using an external adapter and the external adapter
|
71
|
+
overrides or calls any of the following methods:
|
72
|
+
|
73
|
+
* requires_sql_standard_datetimes?
|
74
|
+
* supports_timestamp_usecs?
|
75
|
+
* supports_timestamp_timezones?
|
76
|
+
* timestamp_precision
|
77
|
+
* sqltime_precision
|
78
|
+
|
79
|
+
then the adapter may need to be updated to support Sequel 5.76.0.
|
80
|
+
Additionally, if the adapter uses %N or %z in
|
81
|
+
default_timestamp_format, it may need to be updated. Adapters
|
82
|
+
should now just override default_timestamp_format and/or
|
83
|
+
default_time_format methods as appropriate for the database.
|
84
|
+
|
85
|
+
* The Dataset#format_timestamp_offset private method has been
|
86
|
+
removed.
|
data/doc/testing.rdoc
CHANGED
@@ -159,6 +159,7 @@ The SEQUEL_INTEGRATION_URL environment variable specifies the Database connectio
|
|
159
159
|
|
160
160
|
=== Other
|
161
161
|
|
162
|
+
SEQUEL_AUTO_CAST_DATE_TIME :: Use the auto_cast_date_and_time extension when running the specs
|
162
163
|
SEQUEL_ASYNC_THREAD_POOL :: Use the async_thread_pool extension when running the specs
|
163
164
|
SEQUEL_ASYNC_THREAD_POOL_PREEMPT :: Use the async_thread_pool extension when running the specs, with the :preempt_async_thread option
|
164
165
|
SEQUEL_CHECK_PENDING :: Try running all specs (note, can cause lockups for some adapters), and raise errors for skipped specs that don't fail
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
3
|
Sequel::JDBC.load_driver('org.h2.Driver', :H2)
|
4
|
+
require_relative '../../extensions/auto_cast_date_and_time'
|
4
5
|
|
5
6
|
module Sequel
|
6
7
|
module JDBC
|
@@ -14,6 +15,8 @@ module Sequel
|
|
14
15
|
|
15
16
|
module H2
|
16
17
|
module DatabaseMethods
|
18
|
+
include AutoCastDateAndTime
|
19
|
+
|
17
20
|
def commit_prepared_transaction(transaction_id, opts=OPTS)
|
18
21
|
run("COMMIT TRANSACTION #{transaction_id}", opts)
|
19
22
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
Sequel::JDBC.load_driver('org.hsqldb.jdbcDriver', :HSQLDB)
|
4
4
|
require_relative 'transactions'
|
5
|
+
require_relative '../../extensions/auto_cast_date_and_time'
|
5
6
|
|
6
7
|
module Sequel
|
7
8
|
module JDBC
|
@@ -15,6 +16,7 @@ module Sequel
|
|
15
16
|
|
16
17
|
module HSQLDB
|
17
18
|
module DatabaseMethods
|
19
|
+
include AutoCastDateAndTime
|
18
20
|
include ::Sequel::JDBC::Transactions
|
19
21
|
|
20
22
|
def database_type
|
@@ -56,6 +56,17 @@ module Sequel
|
|
56
56
|
|
57
57
|
private
|
58
58
|
|
59
|
+
# JDBC SQLAnywhere driver does not appear to handle fractional
|
60
|
+
# times correctly.
|
61
|
+
def default_time_format
|
62
|
+
"'%H:%M:%S'"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Set to zero to work around JDBC SQLAnywhere driver bug.
|
66
|
+
def sqltime_precision
|
67
|
+
0
|
68
|
+
end
|
69
|
+
|
59
70
|
SMALLINT_TYPE = Java::JavaSQL::Types::SMALLINT
|
60
71
|
BOOLEAN_METHOD = Object.new
|
61
72
|
def BOOLEAN_METHOD.call(r, i)
|
@@ -43,7 +43,7 @@ module Sequel
|
|
43
43
|
# Use ODBC format, not Microsoft format, as the ODBC layer does
|
44
44
|
# some translation, but allow for millisecond precision.
|
45
45
|
def default_timestamp_format
|
46
|
-
"{ts '%Y-%m-%d %H:%M:%S
|
46
|
+
"{ts '%Y-%m-%d %H:%M:%S.%3N'}"
|
47
47
|
end
|
48
48
|
|
49
49
|
# Use ODBC format, not Microsoft format, as the ODBC layer does
|
@@ -188,8 +188,8 @@ module Sequel
|
|
188
188
|
# :nocov:
|
189
189
|
# Not covered by tests as tests use pg_extended_date_support
|
190
190
|
# extension, which has basically the same code.
|
191
|
-
when
|
192
|
-
|
191
|
+
when Time, DateTime
|
192
|
+
@default_dataset.literal_date_or_time(arg)
|
193
193
|
# :nocov:
|
194
194
|
else
|
195
195
|
arg
|
@@ -932,7 +932,7 @@ module Sequel
|
|
932
932
|
# since that is the format that is multilanguage and not
|
933
933
|
# DATEFORMAT dependent.
|
934
934
|
def default_timestamp_format
|
935
|
-
"'%Y-%m-%dT%H:%M:%S
|
935
|
+
"'%Y-%m-%dT%H:%M:%S.%3N'"
|
936
936
|
end
|
937
937
|
|
938
938
|
# Only include the primary table in the main delete clause
|
@@ -929,6 +929,16 @@ module Sequel
|
|
929
929
|
super if type == :truncate || @opts[:offset]
|
930
930
|
end
|
931
931
|
|
932
|
+
# The strftime format to use when literalizing time (Sequel::SQLTime) values.
|
933
|
+
def default_time_format
|
934
|
+
db.supports_timestamp_usecs? ? super : "'%H:%M:%S'"
|
935
|
+
end
|
936
|
+
|
937
|
+
# The strftime format to use when literalizing timestamp (Time/DateTime) values.
|
938
|
+
def default_timestamp_format
|
939
|
+
db.supports_timestamp_usecs? ? super : "'%Y-%m-%d %H:%M:%S'"
|
940
|
+
end
|
941
|
+
|
932
942
|
# Consider the first table in the joined dataset is the table to delete
|
933
943
|
# from, but include the others for the purposes of selecting rows.
|
934
944
|
def delete_from_sql(sql)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
3
|
require_relative '../utils/emulate_offset_with_row_number'
|
4
|
+
require_relative '../../extensions/auto_cast_date_and_time'
|
4
5
|
|
5
6
|
module Sequel
|
6
7
|
module Oracle
|
@@ -326,6 +327,8 @@ module Sequel
|
|
326
327
|
end
|
327
328
|
|
328
329
|
module DatasetMethods
|
330
|
+
include AutoCastDateAndTime
|
331
|
+
|
329
332
|
ROW_NUMBER_EXPRESSION = LiteralString.new('ROWNUM').freeze
|
330
333
|
BITAND_PROC = lambda{|a, b| Sequel.lit(["CAST(BITAND(", ", ", ") AS INTEGER)"], a, b)}
|
331
334
|
|
@@ -623,7 +626,7 @@ module Sequel
|
|
623
626
|
|
624
627
|
# The strftime format to use when literalizing the time.
|
625
628
|
def default_timestamp_format
|
626
|
-
"
|
629
|
+
"'%Y-%m-%d %H:%M:%S.%6N %:z'"
|
627
630
|
end
|
628
631
|
|
629
632
|
def empty_from_sql
|
@@ -660,11 +663,6 @@ module Sequel
|
|
660
663
|
super
|
661
664
|
end
|
662
665
|
|
663
|
-
# Use a colon for the timestamp offset, since Oracle appears to require it.
|
664
|
-
def format_timestamp_offset(hour, minute)
|
665
|
-
sprintf("%+03i:%02i", hour, minute)
|
666
|
-
end
|
667
|
-
|
668
666
|
# Oracle doesn't support empty values when inserting.
|
669
667
|
def insert_supports_empty_values?
|
670
668
|
false
|
@@ -1092,6 +1092,14 @@ module Sequel
|
|
1092
1092
|
sql << type
|
1093
1093
|
end
|
1094
1094
|
|
1095
|
+
# Consider lock or statement timeout errors as evidence that the table exists
|
1096
|
+
# but is locked.
|
1097
|
+
def _table_exists?(ds)
|
1098
|
+
super
|
1099
|
+
rescue DatabaseError => e
|
1100
|
+
raise e unless /canceling statement due to (?:statement|lock) timeout/ =~ e.message
|
1101
|
+
end
|
1102
|
+
|
1095
1103
|
def alter_table_add_column_sql(table, op)
|
1096
1104
|
"ADD COLUMN#{' IF NOT EXISTS' if op[:if_not_exists]} #{column_definition_sql(op)}"
|
1097
1105
|
end
|
@@ -1505,7 +1513,11 @@ module Sequel
|
|
1505
1513
|
# currently visible schemas.
|
1506
1514
|
def filter_schema(ds, opts)
|
1507
1515
|
expr = if schema = opts[:schema]
|
1508
|
-
schema.
|
1516
|
+
if schema.is_a?(SQL::Identifier)
|
1517
|
+
schema.value.to_s
|
1518
|
+
else
|
1519
|
+
schema.to_s
|
1520
|
+
end
|
1509
1521
|
else
|
1510
1522
|
Sequel.function(:any, Sequel.function(:current_schemas, false))
|
1511
1523
|
end
|
@@ -2138,10 +2150,14 @@ module Sequel
|
|
2138
2150
|
server_version >= 90500
|
2139
2151
|
end
|
2140
2152
|
|
2153
|
+
# :nocov:
|
2154
|
+
|
2141
2155
|
# PostgreSQL supports timezones in literal timestamps
|
2142
2156
|
def supports_timestamp_timezones?
|
2157
|
+
# SEQUEL6: Remove
|
2143
2158
|
true
|
2144
2159
|
end
|
2160
|
+
# :nocov:
|
2145
2161
|
|
2146
2162
|
# PostgreSQL 8.4+ supports WINDOW clause.
|
2147
2163
|
def supports_window_clause?
|
@@ -2261,6 +2277,11 @@ module Sequel
|
|
2261
2277
|
raise(InvalidOperation, "Joined datasets cannot be truncated") if opts[:join]
|
2262
2278
|
end
|
2263
2279
|
|
2280
|
+
# The strftime format to use when literalizing the time.
|
2281
|
+
def default_timestamp_format
|
2282
|
+
"'%Y-%m-%d %H:%M:%S.%6N%z'"
|
2283
|
+
end
|
2284
|
+
|
2264
2285
|
# Only include the primary table in the main delete clause
|
2265
2286
|
def delete_from_sql(sql)
|
2266
2287
|
sql << ' FROM '
|
@@ -281,10 +281,6 @@ module Sequel
|
|
281
281
|
false
|
282
282
|
end
|
283
283
|
|
284
|
-
def supports_timestamp_usecs?
|
285
|
-
false
|
286
|
-
end
|
287
|
-
|
288
284
|
def supports_window_clause?
|
289
285
|
true
|
290
286
|
end
|
@@ -378,6 +374,16 @@ module Sequel
|
|
378
374
|
|
379
375
|
private
|
380
376
|
|
377
|
+
# SQLAnywhere only supports 3 digits after the decimal point for times.
|
378
|
+
def default_time_format
|
379
|
+
"'%H:%M:%S.%3N'"
|
380
|
+
end
|
381
|
+
|
382
|
+
# SQLAnywhere only supports 3 digits after the decimal point for timestamps.
|
383
|
+
def default_timestamp_format
|
384
|
+
"'%Y-%m-%d %H:%M:%S.%3N'"
|
385
|
+
end
|
386
|
+
|
381
387
|
# Use 1 for true on Sybase
|
382
388
|
def literal_true
|
383
389
|
'1'
|
@@ -917,6 +917,11 @@ module Sequel
|
|
917
917
|
500
|
918
918
|
end
|
919
919
|
|
920
|
+
# The strftime format to use when literalizing the time.
|
921
|
+
def default_timestamp_format
|
922
|
+
db.use_timestamp_timezones? ? "'%Y-%m-%d %H:%M:%S.%6N%z'" : super
|
923
|
+
end
|
924
|
+
|
920
925
|
# SQL fragment specifying a list of identifiers
|
921
926
|
def identifier_list(columns)
|
922
927
|
columns.map{|i| quote_identifier(i)}.join(', ')
|
@@ -111,6 +111,13 @@ module Sequel
|
|
111
111
|
# static data that you do not want to modify
|
112
112
|
# :timeout :: how long to wait for the database to be available if it
|
113
113
|
# is locked, given in milliseconds (default is 5000)
|
114
|
+
# :setup_regexp_function :: enable use of Regexp objects with SQL
|
115
|
+
# 'REGEXP' operator. If the value is :cached or "cached",
|
116
|
+
# caches the generated regexps, which can result in a memory
|
117
|
+
# leak if dynamic regexps are used. If the value is a Proc,
|
118
|
+
# it will be called with a string for the regexp and a string
|
119
|
+
# for the value to compare, and should return whether the regexp
|
120
|
+
# matches.
|
114
121
|
def connect(server)
|
115
122
|
opts = server_opts(server)
|
116
123
|
opts[:database] = ':memory:' if blank_object?(opts[:database])
|
@@ -126,9 +133,7 @@ module Sequel
|
|
126
133
|
connection_pragmas.each{|s| log_connection_yield(s, db){db.execute_batch(s)}}
|
127
134
|
|
128
135
|
if typecast_value_boolean(opts[:setup_regexp_function])
|
129
|
-
db
|
130
|
-
func.result = Regexp.new(regexp_str).match(string) ? 1 : 0
|
131
|
-
end
|
136
|
+
setup_regexp_function(db, opts[:setup_regexp_function])
|
132
137
|
end
|
133
138
|
|
134
139
|
class << db
|
@@ -202,6 +207,22 @@ module Sequel
|
|
202
207
|
@conversion_procs['datetime'] = @conversion_procs['timestamp'] = method(:to_application_timestamp)
|
203
208
|
set_integer_booleans
|
204
209
|
end
|
210
|
+
|
211
|
+
def setup_regexp_function(db, how)
|
212
|
+
case how
|
213
|
+
when Proc
|
214
|
+
# nothing
|
215
|
+
when :cached, "cached"
|
216
|
+
cache = Hash.new{|h,k| h[k] = Regexp.new(k)}
|
217
|
+
how = lambda{|regexp_str, str| cache[regexp_str].match(str)}
|
218
|
+
else
|
219
|
+
how = lambda{|regexp_str, str| Regexp.new(regexp_str).match(str)}
|
220
|
+
end
|
221
|
+
|
222
|
+
db.create_function("regexp", 2) do |func, regexp_str, str|
|
223
|
+
func.result = how.call(regexp_str, str) ? 1 : 0
|
224
|
+
end
|
225
|
+
end
|
205
226
|
|
206
227
|
# Yield an available connection. Rescue
|
207
228
|
# any SQLite3::Exceptions and turn them into DatabaseErrors.
|
@@ -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
|
+
|
@@ -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
|
@@ -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 = 76
|
10
10
|
|
11
11
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
12
12
|
# releases that fix regressions from previous versions.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.76.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bigdecimal
|
@@ -222,6 +222,7 @@ extra_rdoc_files:
|
|
222
222
|
- doc/release_notes/5.73.0.txt
|
223
223
|
- doc/release_notes/5.74.0.txt
|
224
224
|
- doc/release_notes/5.75.0.txt
|
225
|
+
- doc/release_notes/5.76.0.txt
|
225
226
|
- doc/release_notes/5.8.0.txt
|
226
227
|
- doc/release_notes/5.9.0.txt
|
227
228
|
files:
|
@@ -325,6 +326,7 @@ files:
|
|
325
326
|
- doc/release_notes/5.73.0.txt
|
326
327
|
- doc/release_notes/5.74.0.txt
|
327
328
|
- doc/release_notes/5.75.0.txt
|
329
|
+
- doc/release_notes/5.76.0.txt
|
328
330
|
- doc/release_notes/5.8.0.txt
|
329
331
|
- doc/release_notes/5.9.0.txt
|
330
332
|
- doc/schema_modification.rdoc
|
@@ -426,6 +428,7 @@ files:
|
|
426
428
|
- lib/sequel/extensions/any_not_empty.rb
|
427
429
|
- lib/sequel/extensions/arbitrary_servers.rb
|
428
430
|
- lib/sequel/extensions/async_thread_pool.rb
|
431
|
+
- lib/sequel/extensions/auto_cast_date_and_time.rb
|
429
432
|
- lib/sequel/extensions/auto_literal_strings.rb
|
430
433
|
- lib/sequel/extensions/blank.rb
|
431
434
|
- lib/sequel/extensions/caller_logging.rb
|
@@ -656,7 +659,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
656
659
|
- !ruby/object:Gem::Version
|
657
660
|
version: '0'
|
658
661
|
requirements: []
|
659
|
-
rubygems_version: 3.
|
662
|
+
rubygems_version: 3.5.3
|
660
663
|
signing_key:
|
661
664
|
specification_version: 4
|
662
665
|
summary: The Database Toolkit for Ruby
|