sequel 5.46.0 → 5.47.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 +14 -0
- data/doc/association_basics.rdoc +48 -8
- data/doc/migration.rdoc +11 -5
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/sql.rdoc +12 -0
- data/doc/testing.rdoc +4 -0
- data/lib/sequel/adapters/shared/mysql.rb +17 -0
- data/lib/sequel/adapters/shared/sqlite.rb +47 -5
- data/lib/sequel/database/schema_methods.rb +1 -1
- data/lib/sequel/dataset/query.rb +1 -1
- data/lib/sequel/extensions/schema_dumper.rb +11 -0
- data/lib/sequel/model/associations.rb +130 -15
- data/lib/sequel/plugins/many_through_many.rb +108 -9
- data/lib/sequel/plugins/prepared_statements.rb +10 -1
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/unused_associations.rb +29 -9
- data/lib/sequel/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 191a57b6bd41c00e023891afaddbebffeb1f54017c663d2a9b32faf83584bf1f
|
4
|
+
data.tar.gz: d158aa702dfe28dbe624b551ed455615017942caf990c599f077a4fb53f6b533
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c91ce33e0191d342f34dbeb4a402153bedc6d46a1df56d99b1393cf559bb9edd837e3eca8f259e1f7856990015e40098cbb269cbc9e915fa4dac9fc254c8e0c
|
7
|
+
data.tar.gz: 9d789484e942ac7380e6dbae64b3e0fb27aeaf9d3fc6764f2e6d6e86c17296cdab116cd6c8d53c429044604a429fdaf18536b6f68b728f5031a81aba81f4107b
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
=== 5.47.0 (2021-08-01)
|
2
|
+
|
3
|
+
* Make the unused_associations plugin track access to association reflections to determine whether associations are used (jeremyevans)
|
4
|
+
|
5
|
+
* Support :db option for join tables in {many,one}_through_many to use a separate query for each join table (jeremyevans)
|
6
|
+
|
7
|
+
* Support :join_table_db option for many_to_many/one_through_one associations, to use a separate query for the join table (jeremyevans)
|
8
|
+
|
9
|
+
* Support :allow_eager_graph and :allow_filtering_by association options (jeremyevans)
|
10
|
+
|
11
|
+
* Add Database#rename_tables on MySQL, for renaming multiple tables in a single call (nick96) (#1774)
|
12
|
+
|
13
|
+
* Support Dataset#returning on SQLite 3.35+ (jeremyevans)
|
14
|
+
|
1
15
|
=== 5.46.0 (2021-07-01)
|
2
16
|
|
3
17
|
* Add unused_associations plugin, for determining which associations and association methods are not used (jeremyevans)
|
data/doc/association_basics.rdoc
CHANGED
@@ -41,7 +41,7 @@ As is the code to add a related album to an artist:
|
|
41
41
|
|
42
42
|
@artist.add_album(name: 'RF')
|
43
43
|
|
44
|
-
It also makes it easier to
|
44
|
+
It also makes it easier to create queries that use joins based on the association:
|
45
45
|
|
46
46
|
Artist.association_join(:albums)
|
47
47
|
# SELECT * FROM artists
|
@@ -63,8 +63,8 @@ It ships with additional association types via plugins.
|
|
63
63
|
|
64
64
|
The many_to_one association is used when the table for the current class
|
65
65
|
contains a foreign key that references the primary key in the table for the
|
66
|
-
associated class. It is named because there can be many rows
|
67
|
-
table for each row in the associated table.
|
66
|
+
associated class. It is named 'many_to_one' because there can be many rows
|
67
|
+
in the current table for each row in the associated table.
|
68
68
|
|
69
69
|
# Database schema:
|
70
70
|
# albums artists
|
@@ -81,8 +81,8 @@ table for each row in the associated table.
|
|
81
81
|
|
82
82
|
The one_to_many association is used when the table for the associated class
|
83
83
|
contains a foreign key that references the primary key in the table for the
|
84
|
-
current class. It is named because for each row in the
|
85
|
-
can be many rows in the associated table:
|
84
|
+
current class. It is named 'one_to_many' because for each row in the
|
85
|
+
current table there can be many rows in the associated table:
|
86
86
|
|
87
87
|
The one_to_one association can be thought of as a subset of the one_to_many association,
|
88
88
|
but where there can only be either 0 or 1 records in the associated table. This is
|
@@ -344,7 +344,7 @@ instance method:
|
|
344
344
|
|
345
345
|
== Dataset Method
|
346
346
|
|
347
|
-
In addition to the above methods, associations also add
|
347
|
+
In addition to the above methods, associations also add an instance method
|
348
348
|
ending in +_dataset+ that returns a dataset representing the objects in the associated table:
|
349
349
|
|
350
350
|
@album.artist_id
|
@@ -1107,7 +1107,7 @@ already applied, and the proc should return a modified copy of this dataset.
|
|
1107
1107
|
Here's an example of an association of songs to artists through lyrics, where
|
1108
1108
|
the artist can perform any one of four tasks for the lyric:
|
1109
1109
|
|
1110
|
-
|
1110
|
+
Artist.one_to_many :songs, dataset: (lambda do |r|
|
1111
1111
|
r.associated_dataset.select_all(:songs).
|
1112
1112
|
join(:lyrics, id: :lyricid, id=>[:composer_id, :arranger_id, :vocalist_id, :lyricist_id])
|
1113
1113
|
end)
|
@@ -1166,6 +1166,23 @@ when deleting.
|
|
1166
1166
|
ds.where(instrument_id: 5)
|
1167
1167
|
end)
|
1168
1168
|
|
1169
|
+
==== :join_table_db [+many_to_many+, +one_through_one+]
|
1170
|
+
|
1171
|
+
A Sequel::Database to use for the join table. Specifying this option switches the
|
1172
|
+
loading to use a separate query for the join table. This is useful if the
|
1173
|
+
join table is not located in the same database as the associated table, or
|
1174
|
+
if the database account with access to the associated table doesn't have
|
1175
|
+
access to the join table.
|
1176
|
+
|
1177
|
+
For example, if the Album class uses a different Sequel::Database than the Artist
|
1178
|
+
class, and the join table is in the database that the Artist class uses:
|
1179
|
+
|
1180
|
+
Artist.many_to_many :lead_guitar_albums, class: :Album, :join_table_db=>Artist.db
|
1181
|
+
|
1182
|
+
This option also affects the add/remove/remove_all methods, by changing
|
1183
|
+
which database is used for inserts/deletes from the join table (add/remove/remove_all
|
1184
|
+
defaults to use the current model's database instead of the associated model's database).
|
1185
|
+
|
1169
1186
|
=== Callback Options
|
1170
1187
|
|
1171
1188
|
All callbacks can be specified as a Symbol, Proc, or array of both/either
|
@@ -1686,12 +1703,35 @@ If set to false, you cannot load the association eagerly via eager or
|
|
1686
1703
|
eager_graph.
|
1687
1704
|
|
1688
1705
|
Artist.one_to_many :albums, allow_eager: false
|
1689
|
-
Artist.eager(:albums)
|
1706
|
+
Artist.eager(:albums) # Raises Sequel::Error
|
1707
|
+
Artist.eager_graph(:albums) # Raises Sequel::Error
|
1690
1708
|
|
1691
1709
|
This is usually used if the association dataset depends on specific values in
|
1692
1710
|
model instance that would not be valid when eager loading for multiple
|
1693
1711
|
instances.
|
1694
1712
|
|
1713
|
+
==== :allow_eager_graph
|
1714
|
+
|
1715
|
+
If set to false, you cannot load the association eagerly via eager_graph.
|
1716
|
+
|
1717
|
+
Artist.one_to_many :albums, allow_eager_graph: false
|
1718
|
+
Artist.eager(:albums) # Allowed
|
1719
|
+
Artist.eager_graph(:albums) # Raises Sequel::Error
|
1720
|
+
|
1721
|
+
This is useful if you still want to allow loading via eager, but do not want
|
1722
|
+
to allow loading via eager graph, possibly because the association does not
|
1723
|
+
support joins.
|
1724
|
+
|
1725
|
+
==== :allow_filtering_by
|
1726
|
+
|
1727
|
+
If set to false, you cannot use the association when filtering.
|
1728
|
+
|
1729
|
+
Artist.one_to_many :albums, allow_filtering_by: false
|
1730
|
+
Artist.where(:albums=>Album.where(:name=>'A')).all # Raises Sequel::Error
|
1731
|
+
|
1732
|
+
This is useful if such filtering cannot work, such as when a subquery cannot
|
1733
|
+
be used because the necessary tables are not in the same database.
|
1734
|
+
|
1695
1735
|
==== :instance_specific
|
1696
1736
|
|
1697
1737
|
This allows you to override the setting of whether the dataset contains instance
|
data/doc/migration.rdoc
CHANGED
@@ -543,16 +543,22 @@ The main difference between the two is that <tt>-d</tt> will use the type method
|
|
543
543
|
with the database independent ruby class types, while <tt>-D</tt> will use
|
544
544
|
the +column+ method with string types.
|
545
545
|
|
546
|
-
Note that Sequel cannot dump constraints other than primary key and possibly
|
547
|
-
foreign key constraints. If you are using database features such
|
548
|
-
as constraints or triggers, you should use your database's dump and restore
|
549
|
-
programs instead of Sequel's schema dumper.
|
550
|
-
|
551
546
|
You can take the migration created by the schema dumper to another computer
|
552
547
|
with an empty database, and attempt to recreate the schema using:
|
553
548
|
|
554
549
|
sequel -m db/migrations postgres://host/database
|
555
550
|
|
551
|
+
The schema_dumper extension is quite limited in what types of
|
552
|
+
database objects it supports. In general, it only supports
|
553
|
+
dumping tables, columns, primary key and foreign key constraints,
|
554
|
+
and some indexes. It does not support most table options, CHECK
|
555
|
+
constraints, partial indexes, database functions, triggers,
|
556
|
+
security grants/revokes, and a wide variety of other useful
|
557
|
+
database properties. Be aware of the limitations when using the
|
558
|
+
schema_dumper extension. If you are dumping the schema to restore
|
559
|
+
to the same database type, it is recommended to use your database's
|
560
|
+
dump and restore programs instead of the schema_dumper extension.
|
561
|
+
|
556
562
|
== Checking for Current Migrations
|
557
563
|
|
558
564
|
In your application code, you may want to check that you are up to date in
|
@@ -0,0 +1,59 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* Sequel now supports using separate queries for each table for both
|
4
|
+
lazy and eager loading of the following associations:
|
5
|
+
|
6
|
+
* many_to_many
|
7
|
+
* one_through_one
|
8
|
+
* many_through_many # many_through_many plugin
|
9
|
+
* one_through_many # many_through_many plugin
|
10
|
+
|
11
|
+
For many_to_many/one_through_one, you specify the :join_table_db
|
12
|
+
association option, which should be a Sequel::Database instance
|
13
|
+
containing the join table. It is possible for the current table,
|
14
|
+
join table, and associated table all to be in separate databases:
|
15
|
+
|
16
|
+
JOIN_TABLE_DB = Sequel.connect('...')
|
17
|
+
Album.many_to_many :artists, join_table_db: JOIN_TABLE_DB
|
18
|
+
|
19
|
+
For many_through_many/one_through_many, you can use the :db option
|
20
|
+
in each join table specification. All join tables can be in
|
21
|
+
separate databases:
|
22
|
+
|
23
|
+
JTDB1 = Sequel.connect('...')
|
24
|
+
JTDB2 = Sequel.connect('...')
|
25
|
+
# Tracks on all albums this artist appears on
|
26
|
+
Artist.many_through_many :album_tracks, [
|
27
|
+
{table: :albums_artists, left: :artist_id, right: :album_id, db: JTDB1},
|
28
|
+
{table: :artists, left: :id, right: :id, db: JTDB2}
|
29
|
+
],
|
30
|
+
class: :Track, right_primary_key: :album_id
|
31
|
+
|
32
|
+
* The :allow_eager_graph association option has been added. Setting
|
33
|
+
this option to false will disallow eager loading via #eager_graph.
|
34
|
+
This is useful if you can eager load the association via #eager,
|
35
|
+
but not with #eager_graph.
|
36
|
+
|
37
|
+
* The :allow_filtering_by association option has been added. Setting
|
38
|
+
this option to false will disallow the use of filtering by
|
39
|
+
associations for the association.
|
40
|
+
|
41
|
+
* Dataset#returning is now supported on SQLite 3.35.0+. To work around
|
42
|
+
bugs in the SQLite implementation, identifiers used in the RETURNING
|
43
|
+
clause are automatically aliased. Additionally, prepared statements
|
44
|
+
that use the RETURNING clause on SQLite seem to have issues, so the
|
45
|
+
prepared_statements plugin does not automatically use prepared
|
46
|
+
statements on SQLite for queries that use the RETURNING clause.
|
47
|
+
|
48
|
+
* Database#rename_tables has been added on MySQL to support renaming
|
49
|
+
multiple tables in the same query.
|
50
|
+
|
51
|
+
= Other Improvements
|
52
|
+
|
53
|
+
* The unused_associations plugin now tracks access to the association
|
54
|
+
reflection for associations, so it will no longer show an
|
55
|
+
association as completely unused if something is accessing the
|
56
|
+
association reflection for it. This eliminates most of the false
|
57
|
+
positives, where the plugin would show an association as unused
|
58
|
+
even though something was using it without calling the association
|
59
|
+
methods.
|
data/doc/sql.rdoc
CHANGED
@@ -428,6 +428,18 @@ As you can see, these literalize with ANDs by default. You can use the <tt>Sequ
|
|
428
428
|
|
429
429
|
Sequel.or(column1: 1, column2: 2) # (("column1" = 1) OR ("column2" = 2))
|
430
430
|
|
431
|
+
As you can see in the above examples, <tt>Sequel.|</tt> and <tt>Sequel.or</tt> work differently.
|
432
|
+
<tt>Sequel.|</tt> is for combining an arbitrary number of expressions using OR. If you pass a single
|
433
|
+
argument, <tt>Sequel.|</tt> will just convert it to a Sequel expression, similar to <tt>Sequel.expr</tt>.
|
434
|
+
<tt>Sequel.or</tt> is for taking a single hash or array of two element arrays and combining the
|
435
|
+
elements of that single argument using OR instead of AND:
|
436
|
+
|
437
|
+
Sequel.|(column1: 1, column2: 2) # (("column1" = 1) AND ("column2" = 2))
|
438
|
+
Sequel.or(column1: 1, column2: 2) # (("column1" = 1) OR ("column2" = 2))
|
439
|
+
|
440
|
+
Sequel.|({column1: 1}, {column2: 2}) # (("column1" = 1) OR ("column2" = 2))
|
441
|
+
Sequel.or({column1: 1}, {column2: 2}) # ArgumentError
|
442
|
+
|
431
443
|
You've already seen the <tt>Sequel.negate</tt> method, which will use ANDs if multiple entries are used:
|
432
444
|
|
433
445
|
Sequel.negate(column1: 1, column2: 2) # (("column1" != 1) AND ("column2" != 2))
|
data/doc/testing.rdoc
CHANGED
@@ -175,6 +175,10 @@ SEQUEL_NO_CACHE_ASSOCIATIONS :: Don't cache association metadata when running th
|
|
175
175
|
SEQUEL_CHECK_PENDING :: Try running all specs (note, can cause lockups for some adapters), and raise errors for skipped specs that don't fail
|
176
176
|
SEQUEL_NO_PENDING :: Don't skip any specs, try running all specs (note, can cause lockups for some adapters)
|
177
177
|
SEQUEL_PG_TIMESTAMPTZ :: Use the pg_timestamptz extension when running the postgres specs
|
178
|
+
SEQUEL_QUERY_PER_ASSOCIATION_DB_0_URL :: Run query-per-association integration tests with multiple databases (all 4 must be set to run)
|
179
|
+
SEQUEL_QUERY_PER_ASSOCIATION_DB_1_URL :: Run query-per-association integration tests with multiple databases (all 4 must be set to run)
|
180
|
+
SEQUEL_QUERY_PER_ASSOCIATION_DB_2_URL :: Run query-per-association integration tests with multiple databases (all 4 must be set to run)
|
181
|
+
SEQUEL_QUERY_PER_ASSOCIATION_DB_3_URL :: Run query-per-association integration tests with multiple databases (all 4 must be set to run)
|
178
182
|
SEQUEL_SPLIT_SYMBOLS :: Turn on symbol splitting when running the adapter and integration specs
|
179
183
|
SEQUEL_SYNCHRONIZE_SQL :: Use the synchronize_sql extension when running the specs
|
180
184
|
SEQUEL_TZINFO_VERSION :: Force the given tzinfo version when running the specs (e.g. '>=2')
|
@@ -187,6 +187,15 @@ module Sequel
|
|
187
187
|
def views(opts=OPTS)
|
188
188
|
full_tables('VIEW', opts)
|
189
189
|
end
|
190
|
+
|
191
|
+
# Renames multiple tables in a single call.
|
192
|
+
#
|
193
|
+
# DB.rename_tables [:items, :old_items], [:other_items, :old_other_items]
|
194
|
+
# # RENAME TABLE items TO old_items, other_items TO old_other_items
|
195
|
+
def rename_tables(*renames)
|
196
|
+
execute_ddl(rename_tables_sql(renames))
|
197
|
+
renames.each{|from,| remove_cached_schema(from)}
|
198
|
+
end
|
190
199
|
|
191
200
|
private
|
192
201
|
|
@@ -473,6 +482,14 @@ module Sequel
|
|
473
482
|
schema(table).select{|a| a[1][:primary_key]}.map{|a| a[0]}
|
474
483
|
end
|
475
484
|
|
485
|
+
# SQL statement for renaming multiple tables.
|
486
|
+
def rename_tables_sql(renames)
|
487
|
+
rename_tos = renames.map do |from, to|
|
488
|
+
"#{quote_schema_table(from)} TO #{quote_schema_table(to)}"
|
489
|
+
end.join(', ')
|
490
|
+
"RENAME TABLE #{rename_tos}"
|
491
|
+
end
|
492
|
+
|
476
493
|
# Rollback the currently open XA transaction
|
477
494
|
def rollback_transaction(conn, opts=OPTS)
|
478
495
|
if (s = opts[:prepare]) && savepoint_level(conn) <= 1
|
@@ -562,10 +562,10 @@ module Sequel
|
|
562
562
|
EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}.freeze
|
563
563
|
EXTRACT_MAP.each_value(&:freeze)
|
564
564
|
|
565
|
-
Dataset.def_sql_method(self, :delete, [['if db.sqlite_version >= 30803', %w'with delete from where'], ["else", %w'delete from where']])
|
566
|
-
Dataset.def_sql_method(self, :insert, [['if db.sqlite_version >= 30803', %w'with insert conflict into columns values on_conflict'], ["else", %w'insert conflict into columns values']])
|
565
|
+
Dataset.def_sql_method(self, :delete, [['if db.sqlite_version >= 33500', %w'with delete from where returning'], ['elsif db.sqlite_version >= 30803', %w'with delete from where'], ["else", %w'delete from where']])
|
566
|
+
Dataset.def_sql_method(self, :insert, [['if db.sqlite_version >= 33500', %w'with insert conflict into columns values on_conflict returning'], ['elsif db.sqlite_version >= 30803', %w'with insert conflict into columns values on_conflict'], ["else", %w'insert conflict into columns values']])
|
567
567
|
Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'with values compounds'], ['else', %w'with select distinct columns from join where group having window compounds order limit lock']])
|
568
|
-
Dataset.def_sql_method(self, :update, [['if db.sqlite_version >= 33300', %w'with update table set from where'], ['elsif db.sqlite_version >= 30803', %w'with update table set where'], ["else", %w'update table set where']])
|
568
|
+
Dataset.def_sql_method(self, :update, [['if db.sqlite_version >= 33500', %w'with update table set from where returning'], ['elsif db.sqlite_version >= 33300', %w'with update table set from where'], ['elsif db.sqlite_version >= 30803', %w'with update table set where'], ["else", %w'update table set where']])
|
569
569
|
|
570
570
|
def cast_sql_append(sql, expr, type)
|
571
571
|
if type == Time or type == DateTime
|
@@ -639,8 +639,8 @@ module Sequel
|
|
639
639
|
# SQLite performs a TRUNCATE style DELETE if no filter is specified.
|
640
640
|
# Since we want to always return the count of records, add a condition
|
641
641
|
# that is always true and then delete.
|
642
|
-
def delete
|
643
|
-
@opts[:where] ? super : where(1=>1).delete
|
642
|
+
def delete(&block)
|
643
|
+
@opts[:where] ? super : where(1=>1).delete(&block)
|
644
644
|
end
|
645
645
|
|
646
646
|
# Return an array of strings specifying a query explanation for a SELECT of the
|
@@ -661,6 +661,21 @@ module Sequel
|
|
661
661
|
super
|
662
662
|
end
|
663
663
|
|
664
|
+
# Support insert select for associations, so that the model code can use
|
665
|
+
# returning instead of a separate query.
|
666
|
+
def insert_select(*values)
|
667
|
+
return unless supports_insert_select?
|
668
|
+
# Handle case where query does not return a row
|
669
|
+
server?(:default).with_sql_first(insert_select_sql(*values)) || false
|
670
|
+
end
|
671
|
+
|
672
|
+
# The SQL to use for an insert_select, adds a RETURNING clause to the insert
|
673
|
+
# unless the RETURNING clause is already present.
|
674
|
+
def insert_select_sql(*values)
|
675
|
+
ds = opts[:returning] ? self : returning
|
676
|
+
ds.insert_sql(*values)
|
677
|
+
end
|
678
|
+
|
664
679
|
# SQLite uses the nonstandard ` (backtick) for quoting identifiers.
|
665
680
|
def quoted_identifier_append(sql, c)
|
666
681
|
sql << '`' << c.to_s.gsub('`', '``') << '`'
|
@@ -742,6 +757,13 @@ module Sequel
|
|
742
757
|
insert_conflict(:ignore)
|
743
758
|
end
|
744
759
|
|
760
|
+
# Automatically add aliases to RETURNING values to work around SQLite bug.
|
761
|
+
def returning(*values)
|
762
|
+
return super if values.empty?
|
763
|
+
raise Error, "RETURNING is not supported on #{db.database_type}" unless supports_returning?(:insert)
|
764
|
+
clone(:returning=>_returning_values(values).freeze)
|
765
|
+
end
|
766
|
+
|
745
767
|
# SQLite 3.8.3+ supports common table expressions.
|
746
768
|
def supports_cte?(type=:select)
|
747
769
|
db.sqlite_version >= 30803
|
@@ -782,6 +804,11 @@ module Sequel
|
|
782
804
|
false
|
783
805
|
end
|
784
806
|
|
807
|
+
# SQLite 3.35.0 supports RETURNING on INSERT/UPDATE/DELETE.
|
808
|
+
def supports_returning?(_)
|
809
|
+
db.sqlite_version >= 33500
|
810
|
+
end
|
811
|
+
|
785
812
|
# SQLite supports timezones in literal timestamps, since it stores them
|
786
813
|
# as text. But using timezones in timestamps breaks SQLite datetime
|
787
814
|
# functions, so we allow the user to override the default per database.
|
@@ -814,6 +841,21 @@ module Sequel
|
|
814
841
|
|
815
842
|
private
|
816
843
|
|
844
|
+
# Add aliases to symbols and identifiers to work around SQLite bug.
|
845
|
+
def _returning_values(values)
|
846
|
+
values.map do |v|
|
847
|
+
case v
|
848
|
+
when Symbol
|
849
|
+
_, c, a = split_symbol(v)
|
850
|
+
a ? v : Sequel.as(v, c)
|
851
|
+
when SQL::Identifier, SQL::QualifiedIdentifier
|
852
|
+
Sequel.as(v, unqualified_column_for(v))
|
853
|
+
else
|
854
|
+
v
|
855
|
+
end
|
856
|
+
end
|
857
|
+
end
|
858
|
+
|
817
859
|
# SQLite uses string literals instead of identifiers in AS clauses.
|
818
860
|
def as_sql_append(sql, aliaz, column_aliases=nil)
|
819
861
|
raise Error, "sqlite does not support derived column lists" if column_aliases
|
@@ -63,7 +63,7 @@ module Sequel
|
|
63
63
|
# definitions using <tt>create_table</tt>, and +add_index+ accepts all the options
|
64
64
|
# available for index definition.
|
65
65
|
#
|
66
|
-
# See <tt>Schema::AlterTableGenerator</tt> and the {
|
66
|
+
# See <tt>Schema::AlterTableGenerator</tt> and the {Migrations guide}[rdoc-ref:doc/migration.rdoc].
|
67
67
|
def alter_table(name, &block)
|
68
68
|
generator = alter_table_generator(&block)
|
69
69
|
remove_cached_schema(name)
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -699,7 +699,7 @@ module Sequel
|
|
699
699
|
end
|
700
700
|
|
701
701
|
# Returns a copy of the dataset with a specified order. Can be safely combined with limit.
|
702
|
-
# If you call limit with an offset, it will override
|
702
|
+
# If you call limit with an offset, it will override the offset if you've called
|
703
703
|
# offset first.
|
704
704
|
#
|
705
705
|
# DB[:items].offset(10) # SELECT * FROM items OFFSET 10
|
@@ -6,6 +6,17 @@
|
|
6
6
|
# the current database). The main interface is through
|
7
7
|
# Sequel::Database#dump_schema_migration.
|
8
8
|
#
|
9
|
+
# The schema_dumper extension is quite limited in what types of
|
10
|
+
# database objects it supports. In general, it only supports
|
11
|
+
# dumping tables, columns, primary key and foreign key constraints,
|
12
|
+
# and some indexes. It does not support most table options, CHECK
|
13
|
+
# constraints, partial indexes, database functions, triggers,
|
14
|
+
# security grants/revokes, and a wide variety of other useful
|
15
|
+
# database properties. Be aware of the limitations when using the
|
16
|
+
# schema_dumper extension. If you are dumping the schema to restore
|
17
|
+
# to the same database type, it is recommended to use your database's
|
18
|
+
# dump and restore programs instead of the schema_dumper extension.
|
19
|
+
#
|
9
20
|
# To load the extension:
|
10
21
|
#
|
11
22
|
# DB.extension :schema_dumper
|
@@ -274,7 +274,9 @@ module Sequel
|
|
274
274
|
cascade = eo[:associations]
|
275
275
|
eager_limit = nil
|
276
276
|
|
277
|
-
if eo[:
|
277
|
+
if eo[:no_results]
|
278
|
+
no_results = true
|
279
|
+
elsif eo[:eager_block] || eo[:loader] == false
|
278
280
|
ds = eager_loading_dataset(eo)
|
279
281
|
|
280
282
|
strategy = ds.opts[:eager_limit_strategy] || strategy
|
@@ -313,7 +315,7 @@ module Sequel
|
|
313
315
|
objects = loader.all(ids)
|
314
316
|
end
|
315
317
|
|
316
|
-
Sequel.synchronize_with(eo[:mutex]){objects.each(&block)}
|
318
|
+
Sequel.synchronize_with(eo[:mutex]){objects.each(&block)} unless no_results
|
317
319
|
|
318
320
|
if strategy == :ruby
|
319
321
|
apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
|
@@ -638,9 +640,7 @@ module Sequel
|
|
638
640
|
# given the hash passed to the eager loader.
|
639
641
|
def eager_loading_dataset(eo=OPTS)
|
640
642
|
ds = eo[:dataset] || associated_eager_dataset
|
641
|
-
|
642
|
-
ds = ds.where(eager_loading_predicate_condition(id_map.keys))
|
643
|
-
end
|
643
|
+
ds = eager_loading_set_predicate_condition(ds, eo)
|
644
644
|
if associations = eo[:associations]
|
645
645
|
ds = ds.eager(associations)
|
646
646
|
end
|
@@ -667,6 +667,15 @@ module Sequel
|
|
667
667
|
self[:model].default_eager_limit_strategy || :ruby
|
668
668
|
end
|
669
669
|
|
670
|
+
# Set the predicate condition for the eager loading dataset based on the id map
|
671
|
+
# in the eager loading options.
|
672
|
+
def eager_loading_set_predicate_condition(ds, eo)
|
673
|
+
if id_map = eo[:id_map]
|
674
|
+
ds = ds.where(eager_loading_predicate_condition(id_map.keys))
|
675
|
+
end
|
676
|
+
ds
|
677
|
+
end
|
678
|
+
|
670
679
|
# The predicate condition to use for the eager_loader.
|
671
680
|
def eager_loading_predicate_condition(keys)
|
672
681
|
{predicate_key=>keys}
|
@@ -1318,7 +1327,7 @@ module Sequel
|
|
1318
1327
|
|
1319
1328
|
# many_to_many associations need to select a key in an associated table to eagerly load
|
1320
1329
|
def eager_loading_use_associated_key?
|
1321
|
-
|
1330
|
+
!separate_query_per_table?
|
1322
1331
|
end
|
1323
1332
|
|
1324
1333
|
# The source of the join table. This is the join table itself, unless it
|
@@ -1375,10 +1384,30 @@ module Sequel
|
|
1375
1384
|
cached_fetch(:select){default_select}
|
1376
1385
|
end
|
1377
1386
|
|
1387
|
+
# Whether a separate query should be used for the join table.
|
1388
|
+
def separate_query_per_table?
|
1389
|
+
self[:join_table_db]
|
1390
|
+
end
|
1391
|
+
|
1378
1392
|
private
|
1379
1393
|
|
1394
|
+
# Join to the the join table, unless using a separate query per table.
|
1380
1395
|
def _associated_dataset
|
1381
|
-
|
1396
|
+
if separate_query_per_table?
|
1397
|
+
super
|
1398
|
+
else
|
1399
|
+
super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
|
1400
|
+
end
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
# Use the right_keys from the eager loading options if
|
1404
|
+
# using a separate query per table.
|
1405
|
+
def eager_loading_set_predicate_condition(ds, eo)
|
1406
|
+
if separate_query_per_table?
|
1407
|
+
ds.where(right_primary_key=>eo[:right_keys])
|
1408
|
+
else
|
1409
|
+
super
|
1410
|
+
end
|
1382
1411
|
end
|
1383
1412
|
|
1384
1413
|
# The default selection for associations that require joins. These do not use the default
|
@@ -1606,6 +1635,8 @@ module Sequel
|
|
1606
1635
|
# after an item is set using the association setter method.
|
1607
1636
|
# :allow_eager :: If set to false, you cannot load the association eagerly
|
1608
1637
|
# via eager or eager_graph
|
1638
|
+
# :allow_eager_graph :: If set to false, you cannot load the association eagerly via eager_graph.
|
1639
|
+
# :allow_filtering_by :: If set to false, you cannot use the association when filtering
|
1609
1640
|
# :before_add :: Symbol, Proc, or array of both/either specifying a callback to call
|
1610
1641
|
# before a new item is added to the association.
|
1611
1642
|
# :before_remove :: Symbol, Proc, or array of both/either specifying a callback to call
|
@@ -1773,6 +1804,9 @@ module Sequel
|
|
1773
1804
|
# underscored, sorted, and joined with '_'.
|
1774
1805
|
# :join_table_block :: proc that can be used to modify the dataset used in the add/remove/remove_all
|
1775
1806
|
# methods. Should accept a dataset argument and return a modified dataset if present.
|
1807
|
+
# :join_table_db :: When retrieving records when using lazy loading or eager loading via +eager+, instead of
|
1808
|
+
# a join between to the join table and the associated table, use a separate query for the
|
1809
|
+
# join table using the given Database object.
|
1776
1810
|
# :left_key :: foreign key in join table that points to current model's
|
1777
1811
|
# primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
|
1778
1812
|
# Can use an array of symbols for a composite key association.
|
@@ -2045,7 +2079,7 @@ module Sequel
|
|
2045
2079
|
raise(Error, "mismatched number of right keys: #{rcks.inspect} vs #{rcpks.inspect}") unless rcks.length == rcpks.length
|
2046
2080
|
end
|
2047
2081
|
opts[:uses_left_composite_keys] = lcks.length > 1
|
2048
|
-
opts[:uses_right_composite_keys] = rcks.length > 1
|
2082
|
+
uses_rcks = opts[:uses_right_composite_keys] = rcks.length > 1
|
2049
2083
|
opts[:cartesian_product_number] ||= one_through_one ? 0 : 1
|
2050
2084
|
join_table = (opts[:join_table] ||= opts.default_join_table)
|
2051
2085
|
opts[:left_key_alias] ||= opts.default_associated_key_alias
|
@@ -2054,8 +2088,75 @@ module Sequel
|
|
2054
2088
|
opts[:after_load] ||= []
|
2055
2089
|
opts[:after_load].unshift(:array_uniq!)
|
2056
2090
|
end
|
2057
|
-
opts[:
|
2058
|
-
|
2091
|
+
if join_table_db = opts[:join_table_db]
|
2092
|
+
opts[:use_placeholder_loader] = false
|
2093
|
+
opts[:allow_eager_graph] = false
|
2094
|
+
opts[:allow_filtering_by] = false
|
2095
|
+
opts[:eager_limit_strategy] = nil
|
2096
|
+
join_table_ds = join_table_db.from(join_table)
|
2097
|
+
opts[:dataset] ||= proc do |r|
|
2098
|
+
vals = join_table_ds.where(lcks.zip(lcpks.map{|k| get_column_value(k)})).select_map(right)
|
2099
|
+
ds = r.associated_dataset.where(opts.right_primary_key => vals)
|
2100
|
+
if uses_rcks
|
2101
|
+
vals.delete_if{|v| v.any?(&:nil?)}
|
2102
|
+
else
|
2103
|
+
vals.delete(nil)
|
2104
|
+
end
|
2105
|
+
ds = ds.clone(:no_results=>true) if vals.empty?
|
2106
|
+
ds
|
2107
|
+
end
|
2108
|
+
opts[:eager_loader] ||= proc do |eo|
|
2109
|
+
h = eo[:id_map]
|
2110
|
+
assign_singular = opts.assign_singular?
|
2111
|
+
rpk = opts.right_primary_key
|
2112
|
+
name = opts[:name]
|
2113
|
+
|
2114
|
+
join_map = join_table_ds.where(left=>h.keys).select_hash_groups(right, left)
|
2115
|
+
|
2116
|
+
if uses_rcks
|
2117
|
+
join_map.delete_if{|v,| v.any?(&:nil?)}
|
2118
|
+
else
|
2119
|
+
join_map.delete(nil)
|
2120
|
+
end
|
2121
|
+
|
2122
|
+
eo = Hash[eo]
|
2123
|
+
|
2124
|
+
if join_map.empty?
|
2125
|
+
eo[:no_results] = true
|
2126
|
+
else
|
2127
|
+
join_map.each_value do |vs|
|
2128
|
+
vs.replace(vs.flat_map{|v| h[v]})
|
2129
|
+
vs.uniq!
|
2130
|
+
end
|
2131
|
+
|
2132
|
+
eo[:loader] = false
|
2133
|
+
eo[:right_keys] = join_map.keys
|
2134
|
+
end
|
2135
|
+
|
2136
|
+
opts[:model].eager_load_results(opts, eo) do |assoc_record|
|
2137
|
+
rpkv = if uses_rcks
|
2138
|
+
assoc_record.values.values_at(*rpk)
|
2139
|
+
else
|
2140
|
+
assoc_record.values[rpk]
|
2141
|
+
end
|
2142
|
+
|
2143
|
+
objects = join_map[rpkv]
|
2144
|
+
|
2145
|
+
if assign_singular
|
2146
|
+
objects.each do |object|
|
2147
|
+
object.associations[name] ||= assoc_record
|
2148
|
+
end
|
2149
|
+
else
|
2150
|
+
objects.each do |object|
|
2151
|
+
object.associations[name].push(assoc_record)
|
2152
|
+
end
|
2153
|
+
end
|
2154
|
+
end
|
2155
|
+
end
|
2156
|
+
else
|
2157
|
+
opts[:dataset] ||= opts.association_dataset_proc
|
2158
|
+
opts[:eager_loader] ||= opts.method(:default_eager_loader)
|
2159
|
+
end
|
2059
2160
|
|
2060
2161
|
join_type = opts[:graph_join_type]
|
2061
2162
|
select = opts[:graph_select]
|
@@ -2417,7 +2518,7 @@ module Sequel
|
|
2417
2518
|
|
2418
2519
|
# Dataset for the join table of the given many to many association reflection
|
2419
2520
|
def _join_table_dataset(opts)
|
2420
|
-
ds = model.db.from(opts.join_table_source)
|
2521
|
+
ds = (opts[:join_table_db] || model.db).from(opts.join_table_source)
|
2421
2522
|
opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
|
2422
2523
|
end
|
2423
2524
|
|
@@ -2438,7 +2539,12 @@ module Sequel
|
|
2438
2539
|
if loader = _associated_object_loader(opts, dynamic_opts)
|
2439
2540
|
loader.all(*opts.predicate_key_values(self))
|
2440
2541
|
else
|
2441
|
-
_associated_dataset(opts, dynamic_opts)
|
2542
|
+
ds = _associated_dataset(opts, dynamic_opts)
|
2543
|
+
if ds.opts[:no_results]
|
2544
|
+
[]
|
2545
|
+
else
|
2546
|
+
ds.all
|
2547
|
+
end
|
2442
2548
|
end
|
2443
2549
|
end
|
2444
2550
|
|
@@ -2899,6 +3005,8 @@ module Sequel
|
|
2899
3005
|
(multiple = ((op == :IN || op == :'NOT IN') && ((is_ds = r.is_a?(Sequel::Dataset)) || (r.respond_to?(:all?) && r.all?{|x| x.is_a?(Sequel::Model)})))))
|
2900
3006
|
l = args[0]
|
2901
3007
|
if ar = model.association_reflections[l]
|
3008
|
+
raise Error, "filtering by associations is not allowed for #{ar.inspect}" if ar[:allow_filtering_by] == false
|
3009
|
+
|
2902
3010
|
if multiple
|
2903
3011
|
klass = ar.associated_class
|
2904
3012
|
if is_ds
|
@@ -3394,7 +3502,7 @@ module Sequel
|
|
3394
3502
|
# Allow associations that are eagerly graphed to be specified as an SQL::AliasedExpression, for
|
3395
3503
|
# per-call determining of the alias base.
|
3396
3504
|
def eager_graph_check_association(model, association)
|
3397
|
-
if association.is_a?(SQL::AliasedExpression)
|
3505
|
+
reflection = if association.is_a?(SQL::AliasedExpression)
|
3398
3506
|
expr = association.expression
|
3399
3507
|
if expr.is_a?(SQL::Identifier)
|
3400
3508
|
expr = expr.value
|
@@ -3403,10 +3511,17 @@ module Sequel
|
|
3403
3511
|
end
|
3404
3512
|
end
|
3405
3513
|
|
3406
|
-
|
3514
|
+
check_reflection = check_association(model, expr)
|
3515
|
+
SQL::AliasedExpression.new(check_reflection, association.alias || expr, association.columns)
|
3407
3516
|
else
|
3408
|
-
check_association(model, association)
|
3517
|
+
check_reflection = check_association(model, association)
|
3518
|
+
end
|
3519
|
+
|
3520
|
+
if check_reflection && check_reflection[:allow_eager_graph] == false
|
3521
|
+
raise Error, "eager_graph not allowed for #{reflection.inspect}"
|
3409
3522
|
end
|
3523
|
+
|
3524
|
+
reflection
|
3410
3525
|
end
|
3411
3526
|
|
3412
3527
|
# The EagerGraphLoader instance used for converting eager_graph results.
|
@@ -123,16 +123,25 @@ module Sequel
|
|
123
123
|
nil
|
124
124
|
end
|
125
125
|
|
126
|
+
# Whether a separate query should be used for each join table.
|
127
|
+
def separate_query_per_table?
|
128
|
+
self[:separate_query_per_table]
|
129
|
+
end
|
130
|
+
|
126
131
|
private
|
127
132
|
|
128
133
|
def _associated_dataset
|
129
134
|
ds = associated_class
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
135
|
+
if separate_query_per_table?
|
136
|
+
ds = ds.dataset
|
137
|
+
else
|
138
|
+
(reverse_edges + [final_reverse_edge]).each do |t|
|
139
|
+
h = {:qualify=>:deep}
|
140
|
+
if t[:alias] != t[:table]
|
141
|
+
h[:table_alias] = t[:alias]
|
142
|
+
end
|
143
|
+
ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), h)
|
134
144
|
end
|
135
|
-
ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), h)
|
136
145
|
end
|
137
146
|
ds
|
138
147
|
end
|
@@ -208,6 +217,7 @@ module Sequel
|
|
208
217
|
# :right (last array element) :: The key joining the table to the next table. Can use an
|
209
218
|
# array of symbols for a composite key association.
|
210
219
|
# If a hash is provided, the following keys are respected when using eager_graph:
|
220
|
+
# :db :: The Database containing the table. This changes lookup to use a separate query for each join table.
|
211
221
|
# :block :: A proc to use as the block argument to join.
|
212
222
|
# :conditions :: Extra conditions to add to the JOIN ON clause. Must be a hash or array of two pairs.
|
213
223
|
# :join_type :: The join type to use for the join, defaults to :left_outer.
|
@@ -233,32 +243,121 @@ module Sequel
|
|
233
243
|
opts[:after_load].unshift(:array_uniq!)
|
234
244
|
end
|
235
245
|
opts[:cartesian_product_number] ||= one_through_many ? 0 : 2
|
236
|
-
|
246
|
+
separate_query_per_table = false
|
247
|
+
through = opts[:through] = opts[:through].map do |e|
|
237
248
|
case e
|
238
249
|
when Array
|
239
250
|
raise(Error, "array elements of the through option/argument for many_through_many associations must have at least three elements") unless e.length == 3
|
240
251
|
{:table=>e[0], :left=>e[1], :right=>e[2]}
|
241
252
|
when Hash
|
242
253
|
raise(Error, "hash elements of the through option/argument for many_through_many associations must contain :table, :left, and :right keys") unless e[:table] && e[:left] && e[:right]
|
254
|
+
separate_query_per_table = true if e[:db]
|
243
255
|
e
|
244
256
|
else
|
245
257
|
raise(Error, "the through option/argument for many_through_many associations must be an enumerable of arrays or hashes")
|
246
258
|
end
|
247
259
|
end
|
260
|
+
opts[:separate_query_per_table] = separate_query_per_table
|
248
261
|
|
249
262
|
left_key = opts[:left_key] = opts[:through].first[:left]
|
250
263
|
opts[:left_keys] = Array(left_key)
|
251
|
-
opts[:uses_left_composite_keys] = left_key.is_a?(Array)
|
264
|
+
uses_lcks = opts[:uses_left_composite_keys] = left_key.is_a?(Array)
|
252
265
|
left_pk = (opts[:left_primary_key] ||= self.primary_key)
|
253
266
|
raise(Error, "no primary key specified for #{inspect}") unless left_pk
|
254
267
|
opts[:eager_loader_key] = left_pk unless opts.has_key?(:eager_loader_key)
|
255
268
|
opts[:left_primary_keys] = Array(left_pk)
|
256
269
|
lpkc = opts[:left_primary_key_column] ||= left_pk
|
257
270
|
lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
|
258
|
-
opts[:dataset] ||= opts.association_dataset_proc
|
259
271
|
|
260
272
|
opts[:left_key_alias] ||= opts.default_associated_key_alias
|
261
|
-
|
273
|
+
if separate_query_per_table
|
274
|
+
opts[:use_placeholder_loader] = false
|
275
|
+
opts[:allow_eager_graph] = false
|
276
|
+
opts[:allow_filtering_by] = false
|
277
|
+
opts[:eager_limit_strategy] = nil
|
278
|
+
|
279
|
+
opts[:dataset] ||= proc do |r|
|
280
|
+
def_db = r.associated_class.db
|
281
|
+
vals = uses_lcks ? [lpkcs.map{|k| get_column_value(k)}] : get_column_value(left_pk)
|
282
|
+
|
283
|
+
has_results = through.each do |edge|
|
284
|
+
ds = (edge[:db] || def_db).from(edge[:table]).where(edge[:left]=>vals)
|
285
|
+
ds = ds.where(edge[:conditions]) if edge[:conditions]
|
286
|
+
right = edge[:right]
|
287
|
+
vals = ds.select_map(right)
|
288
|
+
if right.is_a?(Array)
|
289
|
+
vals.delete_if{|v| v.any?(&:nil?)}
|
290
|
+
else
|
291
|
+
vals.delete(nil)
|
292
|
+
end
|
293
|
+
break if vals.empty?
|
294
|
+
end
|
295
|
+
|
296
|
+
ds = r.associated_dataset.where(opts.right_primary_key=>vals)
|
297
|
+
ds = ds.clone(:no_results=>true) unless has_results
|
298
|
+
ds
|
299
|
+
end
|
300
|
+
opts[:eager_loader] ||= proc do |eo|
|
301
|
+
h = eo[:id_map]
|
302
|
+
assign_singular = opts.assign_singular?
|
303
|
+
uses_rcks = opts.right_primary_key.is_a?(Array)
|
304
|
+
rpk = uses_rcks ? opts.right_primary_keys : opts.right_primary_key
|
305
|
+
name = opts[:name]
|
306
|
+
def_db = opts.associated_class.db
|
307
|
+
join_map = h
|
308
|
+
|
309
|
+
run_query = through.each do |edge|
|
310
|
+
ds = (edge[:db] || def_db).from(edge[:table])
|
311
|
+
ds = ds.where(edge[:conditions]) if edge[:conditions]
|
312
|
+
left = edge[:left]
|
313
|
+
right = edge[:right]
|
314
|
+
prev_map = join_map
|
315
|
+
join_map = ds.where(left=>join_map.keys).select_hash_groups(right, left)
|
316
|
+
if right.is_a?(Array)
|
317
|
+
join_map.delete_if{|v,| v.any?(&:nil?)}
|
318
|
+
else
|
319
|
+
join_map.delete(nil)
|
320
|
+
end
|
321
|
+
break if join_map.empty?
|
322
|
+
join_map.each_value do |vs|
|
323
|
+
vs.replace(vs.flat_map{|v| prev_map[v]})
|
324
|
+
vs.uniq!
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
eo = Hash[eo]
|
329
|
+
|
330
|
+
if run_query
|
331
|
+
eo[:loader] = false
|
332
|
+
eo[:right_keys] = join_map.keys
|
333
|
+
else
|
334
|
+
eo[:no_results] = true
|
335
|
+
end
|
336
|
+
|
337
|
+
opts[:model].eager_load_results(opts, eo) do |assoc_record|
|
338
|
+
rpkv = if uses_rcks
|
339
|
+
assoc_record.values.values_at(*rpk)
|
340
|
+
else
|
341
|
+
assoc_record.values[rpk]
|
342
|
+
end
|
343
|
+
|
344
|
+
objects = join_map[rpkv]
|
345
|
+
|
346
|
+
if assign_singular
|
347
|
+
objects.each do |object|
|
348
|
+
object.associations[name] ||= assoc_record
|
349
|
+
end
|
350
|
+
else
|
351
|
+
objects.each do |object|
|
352
|
+
object.associations[name].push(assoc_record)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
else
|
358
|
+
opts[:dataset] ||= opts.association_dataset_proc
|
359
|
+
opts[:eager_loader] ||= opts.method(:default_eager_loader)
|
360
|
+
end
|
262
361
|
|
263
362
|
join_type = opts[:graph_join_type]
|
264
363
|
select = opts[:graph_select]
|
@@ -169,8 +169,17 @@ module Sequel
|
|
169
169
|
end
|
170
170
|
|
171
171
|
case type
|
172
|
-
when :insert, :
|
172
|
+
when :insert, :update
|
173
173
|
true
|
174
|
+
when :insert_select
|
175
|
+
# SQLite RETURNING support has a bug that doesn't allow for committing transactions
|
176
|
+
# when a prepared statement with RETURNING has been used on the connection:
|
177
|
+
#
|
178
|
+
# SQLite3::BusyException: cannot commit transaction - SQL statements in progress: COMMIT
|
179
|
+
#
|
180
|
+
# Disabling usage of prepared statements for insert_select on SQLite seems to be the
|
181
|
+
# simplest way to workaround the problem.
|
182
|
+
db.database_type != :sqlite
|
174
183
|
# :nocov:
|
175
184
|
when :delete, :refresh
|
176
185
|
Sequel::Deprecation.deprecate("The :delete and :refresh prepared statement types", "There should be no need to check if these types are supported")
|
@@ -19,7 +19,7 @@ module Sequel
|
|
19
19
|
#
|
20
20
|
# # Timestamp Artist instances, forcing an overwrite of the create
|
21
21
|
# # timestamp, and setting the update timestamp when creating
|
22
|
-
#
|
22
|
+
# Artist.plugin :timestamps, force: true, update_on_create: true
|
23
23
|
module Timestamps
|
24
24
|
# Configure the plugin by setting the available options. Note that
|
25
25
|
# if this method is run more than once, previous settings are ignored,
|
@@ -214,11 +214,11 @@ module Sequel
|
|
214
214
|
# reported by this plugin.
|
215
215
|
#
|
216
216
|
# This plugin only considers the public instance methods the
|
217
|
-
# association defines
|
218
|
-
# association
|
219
|
-
#
|
220
|
-
#
|
221
|
-
#
|
217
|
+
# association defines, and direct access to the related
|
218
|
+
# association reflection via Sequel::Model.association_reflection
|
219
|
+
# to determine if the association was used. If the association
|
220
|
+
# metadata was accessed another way, it's possible this plugin
|
221
|
+
# will show the association as unused.
|
222
222
|
#
|
223
223
|
# As this relies on the method coverage added in Ruby 2.5, it does
|
224
224
|
# not work on older versions of Ruby. It also does not work on
|
@@ -266,6 +266,18 @@ module Sequel
|
|
266
266
|
# unused_associations only on the class that is loading the plugin.
|
267
267
|
Plugins.inherited_instance_variables(self, :@unused_associations_data=>nil)
|
268
268
|
|
269
|
+
# Synchronize access to the used association reflections.
|
270
|
+
def used_association_reflections
|
271
|
+
Sequel.synchronize{@used_association_reflections ||= {}}
|
272
|
+
end
|
273
|
+
|
274
|
+
# Record access to association reflections to determine which associations are not used.
|
275
|
+
def association_reflection(association)
|
276
|
+
uar = used_association_reflections
|
277
|
+
Sequel.synchronize{uar[association] ||= true}
|
278
|
+
super
|
279
|
+
end
|
280
|
+
|
269
281
|
# If modifying associations, and this association is marked as not used,
|
270
282
|
# and the association does not include the specific :is_used option,
|
271
283
|
# skip defining the association.
|
@@ -277,6 +289,12 @@ module Sequel
|
|
277
289
|
super
|
278
290
|
end
|
279
291
|
|
292
|
+
# Setup the used_association_reflections storage before freezing
|
293
|
+
def freeze
|
294
|
+
used_association_reflections
|
295
|
+
super
|
296
|
+
end
|
297
|
+
|
280
298
|
# Parse the coverage result, and return the coverage data for the
|
281
299
|
# associations for descendants of this class. If the plugin
|
282
300
|
# uses the :coverage_file option, the existing coverage file will be loaded
|
@@ -298,7 +316,7 @@ module Sequel
|
|
298
316
|
([self] + descendents).each do |sc|
|
299
317
|
next if sc.associations.empty? || !sc.name
|
300
318
|
module_mapping[sc.send(:overridable_methods_module)] = sc
|
301
|
-
coverage_data[sc.name] ||= {}
|
319
|
+
coverage_data[sc.name] ||= {''=>sc.used_association_reflections.keys.map(&:to_s).sort}
|
302
320
|
end
|
303
321
|
|
304
322
|
coverage_result.each do |file, coverage|
|
@@ -331,10 +349,9 @@ module Sequel
|
|
331
349
|
|
332
350
|
([self] + descendents).each do |sc|
|
333
351
|
next unless cov_data = coverage_data[sc.name]
|
352
|
+
reflection_data = cov_data[''] || []
|
334
353
|
|
335
|
-
sc.
|
336
|
-
ref = sc.association_reflection(assoc)
|
337
|
-
|
354
|
+
sc.association_reflections.each do |assoc, ref|
|
338
355
|
# Only report associations for the class they are defined in
|
339
356
|
next unless ref[:model] == sc
|
340
357
|
|
@@ -343,6 +360,9 @@ module Sequel
|
|
343
360
|
next if ref[:methods_module]
|
344
361
|
|
345
362
|
info = {}
|
363
|
+
if reflection_data.include?(assoc.to_s)
|
364
|
+
info[:used] = [:reflection]
|
365
|
+
end
|
346
366
|
|
347
367
|
_update_association_coverage_info(info, cov_data, ref.dataset_method, :dataset_method)
|
348
368
|
_update_association_coverage_info(info, cov_data, ref.association_method, :association_method)
|
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 = 47
|
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.47.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: 2021-
|
11
|
+
date: 2021-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -190,6 +190,7 @@ extra_rdoc_files:
|
|
190
190
|
- doc/release_notes/5.44.0.txt
|
191
191
|
- doc/release_notes/5.45.0.txt
|
192
192
|
- doc/release_notes/5.46.0.txt
|
193
|
+
- doc/release_notes/5.47.0.txt
|
193
194
|
- doc/release_notes/5.5.0.txt
|
194
195
|
- doc/release_notes/5.6.0.txt
|
195
196
|
- doc/release_notes/5.7.0.txt
|
@@ -264,6 +265,7 @@ files:
|
|
264
265
|
- doc/release_notes/5.44.0.txt
|
265
266
|
- doc/release_notes/5.45.0.txt
|
266
267
|
- doc/release_notes/5.46.0.txt
|
268
|
+
- doc/release_notes/5.47.0.txt
|
267
269
|
- doc/release_notes/5.5.0.txt
|
268
270
|
- doc/release_notes/5.6.0.txt
|
269
271
|
- doc/release_notes/5.7.0.txt
|
@@ -575,7 +577,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
575
577
|
- !ruby/object:Gem::Version
|
576
578
|
version: '0'
|
577
579
|
requirements: []
|
578
|
-
rubygems_version: 3.2.
|
580
|
+
rubygems_version: 3.2.22
|
579
581
|
signing_key:
|
580
582
|
specification_version: 4
|
581
583
|
summary: The Database Toolkit for Ruby
|