sequel 5.30.0 → 5.31.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 +20 -0
- data/README.rdoc +1 -1
- data/doc/postgresql.rdoc +71 -0
- data/doc/release_notes/5.31.0.txt +148 -0
- data/doc/testing.rdoc +0 -1
- data/lib/sequel/adapters/shared/postgres.rb +150 -5
- data/lib/sequel/adapters/shared/sqlite.rb +19 -3
- data/lib/sequel/extensions/migration.rb +1 -1
- data/lib/sequel/extensions/pg_enum.rb +5 -2
- data/lib/sequel/extensions/pg_hstore.rb +6 -0
- data/lib/sequel/extensions/schema_dumper.rb +10 -4
- data/lib/sequel/model/base.rb +12 -6
- data/lib/sequel/plugins/association_lazy_eager_option.rb +64 -0
- data/lib/sequel/plugins/forbid_lazy_load.rb +214 -0
- data/lib/sequel/plugins/skip_saving_columns.rb +108 -0
- data/lib/sequel/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f7419480f1c01043fb35c490c25cfbbcfaa9b5694ad41462f7af1c5e5e57bdf5
|
|
4
|
+
data.tar.gz: 7fe799521a27993775f197f15cc38b780c04a3d8d4a9a8ef931da1bea2308779
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3c37683caeb5390eaf614d481be330af8685a07250f4464487d45f0cc0758b94afc28c7008f6ba62f2945f92caf7dc83f2e43907d534e502d16c62e8b0d1c4d
|
|
7
|
+
data.tar.gz: 49eb980c909fb2490b7e0ec808c0594e84f80593baed607917cd40ed99abab69fc42daf8e7fc5d4691785a0681fa1689c9e1c5130813c0d89cb5e5d638e60cd8
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
=== 5.31.0 (2020-04-01)
|
|
2
|
+
|
|
3
|
+
* Fix alter_table drop_constraint :primary_key option on SQLite for non-integer primary keys (jeremyevans)
|
|
4
|
+
|
|
5
|
+
* Add skip_saving_columns plugin, which supports columns to skip when saving, and skips generated columns by default (joeosburn, jeremyevans) (#1681, #1682)
|
|
6
|
+
|
|
7
|
+
* Add support for creating partitioned tables in PostgreSQL 10+ using :partition_by and :partition_of options (jeremyevans)
|
|
8
|
+
|
|
9
|
+
* Dump generated columns as generated columns when using the schema_dumper with :same_db option on PostgreSQL 12+ (jeremyevans) (#1680)
|
|
10
|
+
|
|
11
|
+
* Ignore defaults for generated columns by default when using the schema dumper (jeremyevans) (#1680)
|
|
12
|
+
|
|
13
|
+
* Include generated columns in schema on SQLite 3.31+ (jeremyevans)
|
|
14
|
+
|
|
15
|
+
* Add :generated schema entry on PostgreSQL 12+ and SQLite 3.31+ for whether the columns is generated (jeremyevans)
|
|
16
|
+
|
|
17
|
+
* Add association_lazy_eager_option plugin for supporting :eager option for association method (jeremyevans)
|
|
18
|
+
|
|
19
|
+
* Add forbid_lazy_load plugin for forbidding lazy loading of associations, to help find N+1 issues (jeremyevans)
|
|
20
|
+
|
|
1
21
|
=== 5.30.0 (2020-03-01)
|
|
2
22
|
|
|
3
23
|
* Remove specs and old release notes from the gem to reduce gem size by over 40% (jeremyevans)
|
data/README.rdoc
CHANGED
|
@@ -894,7 +894,7 @@ in the most current release.
|
|
|
894
894
|
|
|
895
895
|
Sequel fully supports the currently supported versions of Ruby (MRI) and JRuby. It may
|
|
896
896
|
support unsupported versions of Ruby or JRuby, but such support may be dropped in any
|
|
897
|
-
minor version
|
|
897
|
+
minor version if keeping it becomes a support issue. The minimum Ruby version
|
|
898
898
|
required to run the current version of Sequel is 1.9.2.
|
|
899
899
|
|
|
900
900
|
== Maintainer
|
data/doc/postgresql.rdoc
CHANGED
|
@@ -139,6 +139,77 @@ conversion via a USING clause, and Sequel supports this using the <tt>:using</tt
|
|
|
139
139
|
# ALTER TABLE "table" ALTER COLUMN "unix_time" TYPE timestamp
|
|
140
140
|
# USING (CAST('epoch' AS timestamp) + (CAST('1 second' AS interval) * "unix_time"))
|
|
141
141
|
|
|
142
|
+
=== Creating Partitioned Tables
|
|
143
|
+
|
|
144
|
+
PostgreSQL allows marking tables as partitioned tables, and adding partitions to such tables. Sequel
|
|
145
|
+
offers support for this. You can create a partitioned table using the +:partition_by+ option and
|
|
146
|
+
+:partition_type+ options (the default partition type is range partitioning):
|
|
147
|
+
|
|
148
|
+
DB.create_table(:table1, partition_by: :column, partition_type: :range) do
|
|
149
|
+
Integer :id
|
|
150
|
+
Date :column
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
DB.create_table(:table2, partition_by: :column, partition_type: :list) do
|
|
154
|
+
Integer :id
|
|
155
|
+
String :column
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
DB.create_table(:table3, partition_by: :column, partition_type: :hash) do
|
|
159
|
+
Integer :id
|
|
160
|
+
Integer :column
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
To add partitions of other tables, you use the +:partition_of+ option. This option will use
|
|
164
|
+
a custom DSL specific to partitioning other tables. For range partitioning, you can use the
|
|
165
|
+
+from+ and +to+ methods to specify the inclusive beginning and exclusive ending of the
|
|
166
|
+
range of the partition. You can call the +minvalue+ and +maxvalue+ methods to get the minimum
|
|
167
|
+
and maximum values for the column(s) in the range, useful as arguments to +from+ and +to+:
|
|
168
|
+
|
|
169
|
+
DB.create_table(:table1a, partition_of: :table1) do
|
|
170
|
+
from minvalue
|
|
171
|
+
to 0
|
|
172
|
+
end
|
|
173
|
+
DB.create_table(:table1b, partition_of: :table1) do
|
|
174
|
+
from 0
|
|
175
|
+
to 100
|
|
176
|
+
end
|
|
177
|
+
DB.create_table(:table1c, partition_of: :table1) do
|
|
178
|
+
from 100
|
|
179
|
+
to maxvalue
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
For list partitioning, you use the +values_in+ method. You can also use the +default+ method
|
|
183
|
+
to mark a partition as the default partition:
|
|
184
|
+
|
|
185
|
+
DB.create_table(:table2a, partition_of: :table2) do
|
|
186
|
+
values_in 1, 2, 3
|
|
187
|
+
end
|
|
188
|
+
DB.create_table(:table2b, partition_of: :table2) do
|
|
189
|
+
values_in 4, 5, 6
|
|
190
|
+
end
|
|
191
|
+
DB.create_table(:table2c, partition_of: :table2) do
|
|
192
|
+
default
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
For hash partitioning, you use the +modulus+ and +remainder+ methods:
|
|
196
|
+
|
|
197
|
+
DB.create_table(:table3a, partition_of: :table3) do
|
|
198
|
+
modulus 3
|
|
199
|
+
remainder 0
|
|
200
|
+
end
|
|
201
|
+
DB.create_table(:table3b, partition_of: :table3) do
|
|
202
|
+
modulus 3
|
|
203
|
+
remainder 1
|
|
204
|
+
end
|
|
205
|
+
DB.create_table(:table3c, partition_of: :table3) do
|
|
206
|
+
modulus 3
|
|
207
|
+
remainder 2
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
There is currently no support for using custom column or table constraints in partitions of
|
|
211
|
+
other tables. Support may be added in the future.
|
|
212
|
+
|
|
142
213
|
=== Creating Unlogged Tables
|
|
143
214
|
|
|
144
215
|
PostgreSQL allows users to create unlogged tables, which are faster but not crash safe. Sequel
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
= New Features
|
|
2
|
+
|
|
3
|
+
* A forbid_lazy_load plugin has been added to forbid the lazy loading
|
|
4
|
+
of model associations if the current object was retreived with other
|
|
5
|
+
objects. This plugin helps detect N+1 query issues. This plugin
|
|
6
|
+
will raise an error if a lazy load is detected in such cases:
|
|
7
|
+
|
|
8
|
+
Album.plugin :forbid_lazy_load
|
|
9
|
+
Album.one_to_many :tracks
|
|
10
|
+
|
|
11
|
+
Album.each do |album|
|
|
12
|
+
album.tracks
|
|
13
|
+
# Could be N+1, raises Sequel::Plugins::ForbidLazyLoad::Error
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Album.first.tracks
|
|
17
|
+
# Could not be N+1, no error raised
|
|
18
|
+
|
|
19
|
+
The forbid_lazy_load plugin is designed to be loaded into the base
|
|
20
|
+
model class (generally Sequel::Model), and can be loaded only in
|
|
21
|
+
test mode, or only in certain test mode configurations, so that it
|
|
22
|
+
does not have any production performance impact.
|
|
23
|
+
|
|
24
|
+
Note that an alternative approach that Sequel has supported for many
|
|
25
|
+
years is the tactical_eager_loading plugin, which automatically
|
|
26
|
+
eager loads when an N+1 query issue is detected.
|
|
27
|
+
|
|
28
|
+
* An association_lazy_eager_option plugin has been added which supports
|
|
29
|
+
the :eager option for the association method. If the association has
|
|
30
|
+
not been loaded, this eagerly loads the associations specified by the
|
|
31
|
+
:eager option when loading the association. If the association has
|
|
32
|
+
already been loaded, this option is ignored, with the assumption that
|
|
33
|
+
whatever loaded the association already used the correct eager
|
|
34
|
+
loading. Example:
|
|
35
|
+
|
|
36
|
+
Album.plugin :association_lazy_eager_option
|
|
37
|
+
Album.one_to_many :tracks
|
|
38
|
+
Track.many_to_one :artist
|
|
39
|
+
|
|
40
|
+
album = Album.first
|
|
41
|
+
album.tracks(:eager=>:artist)
|
|
42
|
+
# Loads tracks for album, then artist for each track (2 queries)
|
|
43
|
+
|
|
44
|
+
album.tracks(:eager=>:artist)
|
|
45
|
+
# No query issued as association is cached
|
|
46
|
+
|
|
47
|
+
You could previously have similar behavior for uncached associations
|
|
48
|
+
by passing a block to the association method and calling eager on
|
|
49
|
+
the yielded dataset. However, that would ignore any cached
|
|
50
|
+
association, causing redundant loading of the association in such
|
|
51
|
+
cases.
|
|
52
|
+
|
|
53
|
+
* On PostgreSQL 10+, creating partitioned tables and partitions of
|
|
54
|
+
other tables is now supported.
|
|
55
|
+
|
|
56
|
+
To create a partitioned table, use the :partition_by option:
|
|
57
|
+
|
|
58
|
+
DB.create_table(:table1, partition_by: :date_column,
|
|
59
|
+
partition_type: :range) do
|
|
60
|
+
Integer :id
|
|
61
|
+
Date :date_column
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
DB.create_table(:table2, partition_by: :string_column,
|
|
65
|
+
partition_type: :list) do
|
|
66
|
+
Integer :id
|
|
67
|
+
String :string_column
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
DB.create_table(:table3, partition_by: :int_column,
|
|
71
|
+
partition_type: :hash) do
|
|
72
|
+
Integer :id
|
|
73
|
+
Integer :int_column
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
To add partitions of other tables, use the :partition_of option.
|
|
77
|
+
This option will use a custom DSL specific to partitions of other
|
|
78
|
+
tables.
|
|
79
|
+
|
|
80
|
+
For range partitioning, you can use the from and to methods to
|
|
81
|
+
specify the inclusive beginning and exclusive ending of the range
|
|
82
|
+
of the partition. You can call the minvalue and maxvalue methods
|
|
83
|
+
to get the minimum and maximum values for the column(s) in the
|
|
84
|
+
range, useful as arguments to from and to:
|
|
85
|
+
|
|
86
|
+
DB.create_table(:table1a, partition_of: :table1) do
|
|
87
|
+
from minvalue
|
|
88
|
+
to 0
|
|
89
|
+
end
|
|
90
|
+
DB.create_table(:table1b, partition_of: :table1) do
|
|
91
|
+
from 0
|
|
92
|
+
to 100
|
|
93
|
+
end
|
|
94
|
+
DB.create_table(:table1c, partition_of: :table1) do
|
|
95
|
+
from 100
|
|
96
|
+
to maxvalue
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
For list partitioning, you use the values_in method. You can also
|
|
100
|
+
use the default method to mark a partition as the default partition:
|
|
101
|
+
|
|
102
|
+
DB.create_table(:table2a, partition_of: :table2) do
|
|
103
|
+
values_in 1, 2, 3
|
|
104
|
+
end
|
|
105
|
+
DB.create_table(:table2b, partition_of: :table2) do
|
|
106
|
+
values_in 4, 5, 6
|
|
107
|
+
end
|
|
108
|
+
DB.create_table(:table2c, partition_of: :table2) do
|
|
109
|
+
default
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
For hash partitioning, you use the modulus and remainder methods:
|
|
113
|
+
|
|
114
|
+
DB.create_table(:table3a, partition_of: :table3) do
|
|
115
|
+
modulus 3
|
|
116
|
+
remainder 0
|
|
117
|
+
end
|
|
118
|
+
DB.create_table(:table3b, partition_of: :table3) do
|
|
119
|
+
modulus 3
|
|
120
|
+
remainder 1
|
|
121
|
+
end
|
|
122
|
+
DB.create_table(:table3c, partition_of: :table3) do
|
|
123
|
+
modulus 3
|
|
124
|
+
remainder 2
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
* On PostgreSQL 12+ and SQLite 3.31+, column schema hashes now have
|
|
128
|
+
a :generated entry for whether the column is a generated column.
|
|
129
|
+
|
|
130
|
+
* The schema_dumper extension now dumps generated columns correctly
|
|
131
|
+
when using the :same_db option on PostgreSQL 12+.
|
|
132
|
+
|
|
133
|
+
* A skip_saving_columns plugin has been added. This allows skipping
|
|
134
|
+
saving of specific columns for the model. By default, it skips
|
|
135
|
+
saving of generated columns, but you can customize the columns
|
|
136
|
+
that it skips:
|
|
137
|
+
|
|
138
|
+
Album.plugin :skip_saving_columns
|
|
139
|
+
Album.skip_saving_columns = [:some_column]
|
|
140
|
+
|
|
141
|
+
= Other Improvements
|
|
142
|
+
|
|
143
|
+
* The alter_table drop_constraint :primary_key option on SQLite now
|
|
144
|
+
works correctly for non-integer primary keys.
|
|
145
|
+
|
|
146
|
+
* When an error is raised due to an irreversible migration, the error
|
|
147
|
+
message now includes the file containing the migration for easier
|
|
148
|
+
debugging.
|
data/doc/testing.rdoc
CHANGED
|
@@ -168,7 +168,6 @@ SEQUEL_INTEGER64 :: Use the integer64 extension when running the adapter or inte
|
|
|
168
168
|
SEQUEL_MODEL_PREPARED_STATEMENTS :: Use the prepared_statements plugin when running the specs
|
|
169
169
|
SEQUEL_MODEL_THROW_FAILURES :: Use the throw_failures plugin when running the specs
|
|
170
170
|
SEQUEL_NO_CACHE_ASSOCIATIONS :: Don't cache association metadata when running the specs
|
|
171
|
-
SEQUEL_NO_CHECK_SQLS :: Don't check for specific SQL syntax when running the specs
|
|
172
171
|
SEQUEL_CHECK_PENDING :: Try running all specs (note, can cause lockups for some adapters), and raise errors for skipped specs that don't fail
|
|
173
172
|
SEQUEL_NO_PENDING :: Don't skip any specs, try running all specs (note, can cause lockups for some adapters)
|
|
174
173
|
SEQUEL_PG_TIMESTAMPTZ :: Use the pg_timestamptz extension when running the postgres specs
|
|
@@ -134,6 +134,96 @@ module Sequel
|
|
|
134
134
|
end
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
+
# Generator used for creating tables that are partitions of other tables.
|
|
138
|
+
class CreatePartitionOfTableGenerator
|
|
139
|
+
MINVALUE = Sequel.lit('MINVALUE').freeze
|
|
140
|
+
MAXVALUE = Sequel.lit('MAXVALUE').freeze
|
|
141
|
+
|
|
142
|
+
def initialize(&block)
|
|
143
|
+
instance_exec(&block)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# The minimum value of the data type used in range partitions, useful
|
|
147
|
+
# as an argument to #from.
|
|
148
|
+
def minvalue
|
|
149
|
+
MINVALUE
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# The minimum value of the data type used in range partitions, useful
|
|
153
|
+
# as an argument to #to.
|
|
154
|
+
def maxvalue
|
|
155
|
+
MAXVALUE
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Assumes range partitioning, sets the inclusive minimum value of the range for
|
|
159
|
+
# this partition.
|
|
160
|
+
def from(*v)
|
|
161
|
+
@from = v
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Assumes range partitioning, sets the exclusive maximum value of the range for
|
|
165
|
+
# this partition.
|
|
166
|
+
def to(*v)
|
|
167
|
+
@to = v
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Assumes list partitioning, sets the values to be included in this partition.
|
|
171
|
+
def values_in(*v)
|
|
172
|
+
@in = v
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Assumes hash partitioning, sets the modulus for this parition.
|
|
176
|
+
def modulus(v)
|
|
177
|
+
@modulus = v
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Assumes hash partitioning, sets the remainder for this parition.
|
|
181
|
+
def remainder(v)
|
|
182
|
+
@remainder = v
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Sets that this is a default partition, where values not in other partitions
|
|
186
|
+
# are stored.
|
|
187
|
+
def default
|
|
188
|
+
@default = true
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# The from and to values of this partition for a range partition.
|
|
192
|
+
def range
|
|
193
|
+
[@from, @to]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# The values to include in this partition for a list partition.
|
|
197
|
+
def list
|
|
198
|
+
@in
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# The modulus and remainder to use for this partition for a hash partition.
|
|
202
|
+
def hash_values
|
|
203
|
+
[@modulus, @remainder]
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Determine the appropriate partition type for this partition by which methods
|
|
207
|
+
# were called on it.
|
|
208
|
+
def partition_type
|
|
209
|
+
raise Error, "Unable to determine partition type, multiple different partitioning methods called" if [@from || @to, @list, @modulus || @remainder, @default].compact.length > 1
|
|
210
|
+
|
|
211
|
+
if @from || @to
|
|
212
|
+
raise Error, "must call both from and to when creating a partition of a table if calling either" unless @from && @to
|
|
213
|
+
:range
|
|
214
|
+
elsif @in
|
|
215
|
+
:list
|
|
216
|
+
elsif @modulus || @remainder
|
|
217
|
+
raise Error, "must call both modulus and remainder when creating a partition of a table if calling either" unless @modulus && @remainder
|
|
218
|
+
:hash
|
|
219
|
+
elsif @default
|
|
220
|
+
:default
|
|
221
|
+
else
|
|
222
|
+
raise Error, "unable to determine partition type, no partitioning methods called"
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
137
227
|
# Error raised when Sequel determines a PostgreSQL exclusion constraint has been violated.
|
|
138
228
|
class ExclusionConstraintViolation < Sequel::ConstraintViolation; end
|
|
139
229
|
|
|
@@ -359,6 +449,16 @@ module Sequel
|
|
|
359
449
|
self << create_schema_sql(name, opts)
|
|
360
450
|
end
|
|
361
451
|
|
|
452
|
+
# Support partitions of tables using the :partition_of option.
|
|
453
|
+
def create_table(name, options=OPTS, &block)
|
|
454
|
+
if options[:partition_of]
|
|
455
|
+
create_partition_of_table_from_generator(name, CreatePartitionOfTableGenerator.new(&block), options)
|
|
456
|
+
return
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
super
|
|
460
|
+
end
|
|
461
|
+
|
|
362
462
|
# Create a trigger in the database. Arguments:
|
|
363
463
|
# table :: the table on which this trigger operates
|
|
364
464
|
# name :: the name of this trigger
|
|
@@ -1018,6 +1118,36 @@ module Sequel
|
|
|
1018
1118
|
"CREATE#{' OR REPLACE' if opts[:replace] && server_version >= 90000}#{' TRUSTED' if opts[:trusted]} LANGUAGE #{name}#{" HANDLER #{opts[:handler]}" if opts[:handler]}#{" VALIDATOR #{opts[:validator]}" if opts[:validator]}"
|
|
1019
1119
|
end
|
|
1020
1120
|
|
|
1121
|
+
# Create a partition of another table, used when the create_table with
|
|
1122
|
+
# the :partition_of option is given.
|
|
1123
|
+
def create_partition_of_table_from_generator(name, generator, options)
|
|
1124
|
+
execute_ddl(create_partition_of_table_sql(name, generator, options))
|
|
1125
|
+
end
|
|
1126
|
+
|
|
1127
|
+
# SQL for creating a partition of another table.
|
|
1128
|
+
def create_partition_of_table_sql(name, generator, options)
|
|
1129
|
+
sql = create_table_prefix_sql(name, options).dup
|
|
1130
|
+
|
|
1131
|
+
sql << " PARTITION OF #{quote_schema_table(options[:partition_of])}"
|
|
1132
|
+
|
|
1133
|
+
case generator.partition_type
|
|
1134
|
+
when :range
|
|
1135
|
+
from, to = generator.range
|
|
1136
|
+
sql << " FOR VALUES FROM #{literal(from)} TO #{literal(to)}"
|
|
1137
|
+
when :list
|
|
1138
|
+
sql << " FOR VALUES IN #{literal(generator.list)}"
|
|
1139
|
+
when :hash
|
|
1140
|
+
mod, remainder = generator.hash_values
|
|
1141
|
+
sql << " FOR VALUES WITH (MODULUS #{literal(mod)}, REMAINDER #{literal(remainder)})"
|
|
1142
|
+
when :default
|
|
1143
|
+
sql << " DEFAULT"
|
|
1144
|
+
end
|
|
1145
|
+
|
|
1146
|
+
sql << create_table_suffix_sql(name, options)
|
|
1147
|
+
|
|
1148
|
+
sql
|
|
1149
|
+
end
|
|
1150
|
+
|
|
1021
1151
|
# SQL for creating a schema.
|
|
1022
1152
|
def create_schema_sql(name, opts=OPTS)
|
|
1023
1153
|
"CREATE SCHEMA #{'IF NOT EXISTS ' if opts[:if_not_exists]}#{quote_identifier(name)}#{" AUTHORIZATION #{literal(opts[:owner])}" if opts[:owner]}"
|
|
@@ -1039,25 +1169,36 @@ module Sequel
|
|
|
1039
1169
|
"CREATE #{prefix_sql}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{options[:temp] ? quote_identifier(name) : quote_schema_table(name)}"
|
|
1040
1170
|
end
|
|
1041
1171
|
|
|
1172
|
+
# SQL for creating a table with PostgreSQL specific options
|
|
1042
1173
|
def create_table_sql(name, generator, options)
|
|
1043
|
-
|
|
1174
|
+
"#{super}#{create_table_suffix_sql(name, options)}"
|
|
1175
|
+
end
|
|
1176
|
+
|
|
1177
|
+
# Handle various PostgreSQl specific table extensions such as inheritance,
|
|
1178
|
+
# partitioning, tablespaces, and foreign tables.
|
|
1179
|
+
def create_table_suffix_sql(name, options)
|
|
1180
|
+
sql = String.new
|
|
1044
1181
|
|
|
1045
1182
|
if inherits = options[:inherits]
|
|
1046
|
-
sql
|
|
1183
|
+
sql << " INHERITS (#{Array(inherits).map{|t| quote_schema_table(t)}.join(', ')})"
|
|
1184
|
+
end
|
|
1185
|
+
|
|
1186
|
+
if partition_by = options[:partition_by]
|
|
1187
|
+
sql << " PARTITION BY #{options[:partition_type]||'RANGE'} #{literal(Array(partition_by))}"
|
|
1047
1188
|
end
|
|
1048
1189
|
|
|
1049
1190
|
if on_commit = options[:on_commit]
|
|
1050
1191
|
raise(Error, "can't provide :on_commit without :temp to create_table") unless options[:temp]
|
|
1051
1192
|
raise(Error, "unsupported on_commit option: #{on_commit.inspect}") unless ON_COMMIT.has_key?(on_commit)
|
|
1052
|
-
sql
|
|
1193
|
+
sql << " ON COMMIT #{ON_COMMIT[on_commit]}"
|
|
1053
1194
|
end
|
|
1054
1195
|
|
|
1055
1196
|
if tablespace = options[:tablespace]
|
|
1056
|
-
sql
|
|
1197
|
+
sql << " TABLESPACE #{quote_identifier(tablespace)}"
|
|
1057
1198
|
end
|
|
1058
1199
|
|
|
1059
1200
|
if server = options[:foreign]
|
|
1060
|
-
sql
|
|
1201
|
+
sql << " SERVER #{quote_identifier(server)}"
|
|
1061
1202
|
if foreign_opts = options[:options]
|
|
1062
1203
|
sql << " OPTIONS (#{foreign_opts.map{|k, v| "#{k} #{literal(v.to_s)}"}.join(', ')})"
|
|
1063
1204
|
end
|
|
@@ -1273,6 +1414,10 @@ module Sequel
|
|
|
1273
1414
|
|
|
1274
1415
|
if server_version > 100000
|
|
1275
1416
|
ds = ds.select_append{pg_attribute[:attidentity]}
|
|
1417
|
+
|
|
1418
|
+
if server_version > 120000
|
|
1419
|
+
ds = ds.select_append{Sequel.~(pg_attribute[:attgenerated]=>'').as(:generated)}
|
|
1420
|
+
end
|
|
1276
1421
|
end
|
|
1277
1422
|
|
|
1278
1423
|
ds.map do |row|
|
|
@@ -184,7 +184,7 @@ module Sequel
|
|
|
184
184
|
|
|
185
185
|
# Dataset used for parsing schema
|
|
186
186
|
def _parse_pragma_ds(table_name, opts)
|
|
187
|
-
metadata_dataset.with_sql("PRAGMA
|
|
187
|
+
metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)", input_identifier_meth(opts[:dataset]).call(table_name))
|
|
188
188
|
end
|
|
189
189
|
|
|
190
190
|
# Run all alter_table commands in a transaction. This is technically only
|
|
@@ -252,7 +252,12 @@ module Sequel
|
|
|
252
252
|
when :drop_constraint
|
|
253
253
|
case op[:type]
|
|
254
254
|
when :primary_key
|
|
255
|
-
duplicate_table(table)
|
|
255
|
+
duplicate_table(table) do |columns|
|
|
256
|
+
columns.each do |s|
|
|
257
|
+
s[:unique] = false if s[:primary_key]
|
|
258
|
+
s[:primary_key] = s[:auto_increment] = nil
|
|
259
|
+
end
|
|
260
|
+
end
|
|
256
261
|
when :foreign_key
|
|
257
262
|
if op[:columns]
|
|
258
263
|
duplicate_table(table, :skip_foreign_key_columns=>op[:columns])
|
|
@@ -420,7 +425,7 @@ module Sequel
|
|
|
420
425
|
unless unique_columns.empty?
|
|
421
426
|
unique_columns.map!{|c| quote_identifier(c)}
|
|
422
427
|
def_columns.each do |c|
|
|
423
|
-
c[:unique] = true if unique_columns.include?(quote_identifier(c[:name]))
|
|
428
|
+
c[:unique] = true if unique_columns.include?(quote_identifier(c[:name])) && c[:unique] != false
|
|
424
429
|
end
|
|
425
430
|
end
|
|
426
431
|
|
|
@@ -466,6 +471,15 @@ module Sequel
|
|
|
466
471
|
def parse_pragma(table_name, opts)
|
|
467
472
|
pks = 0
|
|
468
473
|
sch = _parse_pragma_ds(table_name, opts).map do |row|
|
|
474
|
+
if sqlite_version > 33100
|
|
475
|
+
# table_xinfo PRAGMA used, remove hidden columns
|
|
476
|
+
# that are not generated columns
|
|
477
|
+
if row[:generated] = (row.delete(:hidden) != 0)
|
|
478
|
+
next unless row[:type].end_with?(' GENERATED ALWAYS')
|
|
479
|
+
row[:type] = row[:type].sub(' GENERATED ALWAYS', '')
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
469
483
|
row.delete(:cid)
|
|
470
484
|
row[:allow_null] = row.delete(:notnull).to_i == 0
|
|
471
485
|
row[:default] = row.delete(:dflt_value)
|
|
@@ -482,6 +496,8 @@ module Sequel
|
|
|
482
496
|
row
|
|
483
497
|
end
|
|
484
498
|
|
|
499
|
+
sch.compact!
|
|
500
|
+
|
|
485
501
|
if pks > 1
|
|
486
502
|
# SQLite does not allow use of auto increment for tables
|
|
487
503
|
# with composite primary keys, so remove auto_increment
|
|
@@ -176,7 +176,7 @@ module Sequel
|
|
|
176
176
|
just_raise = true
|
|
177
177
|
end
|
|
178
178
|
if just_raise
|
|
179
|
-
Proc.new{raise Sequel::Error,
|
|
179
|
+
Proc.new{raise Sequel::Error, "irreversible migration method used in #{block.source_location.first}, you may need to write your own down method"}
|
|
180
180
|
else
|
|
181
181
|
actions = @actions.reverse
|
|
182
182
|
Proc.new do
|
|
@@ -40,8 +40,11 @@
|
|
|
40
40
|
# DB.schema(:table_name)
|
|
41
41
|
# [[:column_name, {:type=>:enum, :enum_values=>['value1', 'value2']}]]
|
|
42
42
|
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
43
|
+
# This extension integrates with the pg_array extension. If you plan
|
|
44
|
+
# to use arrays of enum types, load the pg_array extension before the
|
|
45
|
+
# pg_interval extension:
|
|
46
|
+
#
|
|
47
|
+
# DB.extension :pg_array, :pg_enum
|
|
45
48
|
#
|
|
46
49
|
# DB.create_table(:table_name) do
|
|
47
50
|
# column :column_name, 'enum_type_name[]'
|
|
@@ -74,6 +74,12 @@
|
|
|
74
74
|
#
|
|
75
75
|
# DB.extension :pg_hstore
|
|
76
76
|
#
|
|
77
|
+
# This extension integrates with the pg_array extension. If you plan
|
|
78
|
+
# to use arrays of hstore types, load the pg_array extension before the
|
|
79
|
+
# pg_interval extension:
|
|
80
|
+
#
|
|
81
|
+
# DB.extension :pg_array, :pg_hstore
|
|
82
|
+
#
|
|
77
83
|
# See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
|
|
78
84
|
# for details on using hstore columns in CREATE/ALTER TABLE statements.
|
|
79
85
|
#
|
|
@@ -199,12 +199,18 @@ END_MIG
|
|
|
199
199
|
end
|
|
200
200
|
type = col_opts.delete(:type)
|
|
201
201
|
col_opts.delete(:size) if col_opts[:size].nil?
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
if schema[:generated]
|
|
203
|
+
if options[:same_db] && database_type == :postgres
|
|
204
|
+
col_opts[:generated_always_as] = column_schema_to_ruby_default_fallback(schema[:default], options)
|
|
205
|
+
end
|
|
204
206
|
else
|
|
205
|
-
schema[:ruby_default]
|
|
207
|
+
col_opts[:default] = if schema[:ruby_default].nil?
|
|
208
|
+
column_schema_to_ruby_default_fallback(schema[:default], options)
|
|
209
|
+
else
|
|
210
|
+
schema[:ruby_default]
|
|
211
|
+
end
|
|
212
|
+
col_opts.delete(:default) if col_opts[:default].nil?
|
|
206
213
|
end
|
|
207
|
-
col_opts.delete(:default) if col_opts[:default].nil?
|
|
208
214
|
col_opts[:null] = false if schema[:allow_null] == false
|
|
209
215
|
if table = schema[:table]
|
|
210
216
|
[:key, :on_delete, :on_update, :deferrable].each{|f| col_opts[f] = schema[f] if schema[f]}
|
data/lib/sequel/model/base.rb
CHANGED
|
@@ -1772,14 +1772,12 @@ module Sequel
|
|
|
1772
1772
|
before_update
|
|
1773
1773
|
columns = opts[:columns]
|
|
1774
1774
|
if columns.nil?
|
|
1775
|
-
if opts[:changed]
|
|
1776
|
-
|
|
1777
|
-
columns_updated = @values.reject{|k,v| !cc.include?(k)}
|
|
1778
|
-
cc.clear
|
|
1775
|
+
columns_updated = if opts[:changed]
|
|
1776
|
+
_save_update_changed_colums_hash
|
|
1779
1777
|
else
|
|
1780
|
-
|
|
1781
|
-
_clear_changed_columns(:update)
|
|
1778
|
+
_save_update_all_columns_hash
|
|
1782
1779
|
end
|
|
1780
|
+
_clear_changed_columns(:update)
|
|
1783
1781
|
else # update only the specified columns
|
|
1784
1782
|
columns = Array(columns)
|
|
1785
1783
|
columns_updated = @values.reject{|k, v| !columns.include?(k)}
|
|
@@ -1827,6 +1825,14 @@ module Sequel
|
|
|
1827
1825
|
v
|
|
1828
1826
|
end
|
|
1829
1827
|
|
|
1828
|
+
# Return a hash of values used when saving changed columns of an
|
|
1829
|
+
# existing object. Defaults to all of the objects current values
|
|
1830
|
+
# that are recorded as modified.
|
|
1831
|
+
def _save_update_changed_colums_hash
|
|
1832
|
+
cc = changed_columns
|
|
1833
|
+
@values.reject{|k,v| !cc.include?(k)}
|
|
1834
|
+
end
|
|
1835
|
+
|
|
1830
1836
|
# Validate the object if validating on save. Skips validation
|
|
1831
1837
|
# completely (including validation hooks) if
|
|
1832
1838
|
# skip_validation_on_save! has been called on the object,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
module Sequel
|
|
4
|
+
module Plugins
|
|
5
|
+
# The association_lazy_eager_option plugin supports passing
|
|
6
|
+
# an +:eager+ option to an association method. If the related
|
|
7
|
+
# association is already cached, the cached version will be
|
|
8
|
+
# returned. If the association is not already cached, it will
|
|
9
|
+
# be loaded, and the value of the +:eager+ option will be used
|
|
10
|
+
# to perform an eager load of the given associations.
|
|
11
|
+
# the plural versions.
|
|
12
|
+
#
|
|
13
|
+
# With Sequel's default behavior, you can already perform an
|
|
14
|
+
# eager load when lazy loading using a block:
|
|
15
|
+
#
|
|
16
|
+
# obj.association{|ds| ds.eager(:nested_association)}
|
|
17
|
+
#
|
|
18
|
+
# However, this will ignore any cached version. In more
|
|
19
|
+
# complex software, the association may already be cached
|
|
20
|
+
# and have the nested association cached inside of it, and
|
|
21
|
+
# using this callback approach then requires 2 unnecessary
|
|
22
|
+
# queries. This plugin will not perform any queries if the
|
|
23
|
+
# association is already cached, preventing duplicate work.
|
|
24
|
+
# However, you should make sure that an already loaded
|
|
25
|
+
# association has the nested association already eagerly
|
|
26
|
+
# loaded.
|
|
27
|
+
#
|
|
28
|
+
# Usage:
|
|
29
|
+
#
|
|
30
|
+
# # Make all model subclasses support the :eager association
|
|
31
|
+
# # method option (called before loading subclasses)
|
|
32
|
+
# Sequel::Model.plugin :association_lazy_eager_option
|
|
33
|
+
#
|
|
34
|
+
# # Make the Album class support the :eager association
|
|
35
|
+
# # method option
|
|
36
|
+
# Album.plugin :association_lazy_eager_option
|
|
37
|
+
module AssociationLazyEagerOption
|
|
38
|
+
module InstanceMethods
|
|
39
|
+
# Return a dataset for the association after applying any dynamic callback.
|
|
40
|
+
def _associated_dataset(opts, dynamic_opts)
|
|
41
|
+
ds = super
|
|
42
|
+
|
|
43
|
+
if eager = dynamic_opts[:eager]
|
|
44
|
+
ds = ds.eager(eager)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
ds
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# A placeholder literalizer that can be used to load the association, or nil to not use one.
|
|
51
|
+
def _associated_object_loader(opts, dynamic_opts)
|
|
52
|
+
return if dynamic_opts[:eager]
|
|
53
|
+
super
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Whether to use a simple primary key lookup on the associated class when loading.
|
|
57
|
+
def load_with_primary_key_lookup?(opts, dynamic_opts)
|
|
58
|
+
return false if dynamic_opts[:eager]
|
|
59
|
+
super
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
module Sequel
|
|
4
|
+
module Plugins
|
|
5
|
+
# The forbid_lazy_load plugin forbids lazy loading of associations
|
|
6
|
+
# for objects in cases where the object wasn't loaded with a
|
|
7
|
+
# method that only returns a single object.
|
|
8
|
+
#
|
|
9
|
+
# The main reason for doing this is it makes it easier to detect
|
|
10
|
+
# N+1 query issues. Note that Sequel also offers a
|
|
11
|
+
# tactical_eager_loading plugin which will automatically eagerly
|
|
12
|
+
# load associations for all objects retrived in the same query
|
|
13
|
+
# if any object would attempt to lazily load an association. That
|
|
14
|
+
# approach may be simpler if you are trying to prevent N+1 issues,
|
|
15
|
+
# though it does retain more objects in memory.
|
|
16
|
+
#
|
|
17
|
+
# This plugin offers multiple different ways to forbid lazy
|
|
18
|
+
# loading. You can forbid lazy loading associations for individual
|
|
19
|
+
# model instances:
|
|
20
|
+
#
|
|
21
|
+
# obj = Album[1]
|
|
22
|
+
# obj.forbid_lazy_load
|
|
23
|
+
# obj.artist # raises Sequel::Plugins::ForbidLazyLoad::Error
|
|
24
|
+
#
|
|
25
|
+
# +forbid_lazy_load+ is automatically called on instances if the
|
|
26
|
+
# instances are loaded via a method such as Dataset#all,
|
|
27
|
+
# Dataset#each, and other methods that load multiple instances
|
|
28
|
+
# at once. These are the cases where lazily loading associations
|
|
29
|
+
# for such instances can cause N+1 issues.
|
|
30
|
+
#
|
|
31
|
+
# Album.all.first.artist
|
|
32
|
+
# objs.first.artist # raises Sequel::Plugins::ForbidLazyLoad::Error
|
|
33
|
+
#
|
|
34
|
+
# Album.each do |obj|
|
|
35
|
+
# obj.artist # raises Sequel::Plugins::ForbidLazyLoad::Error
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# Album[1].artist # no error
|
|
39
|
+
#
|
|
40
|
+
# Album.first.artist # no error
|
|
41
|
+
#
|
|
42
|
+
# You can allow lazy loading associations for an instance that it
|
|
43
|
+
# was previously forbidden for:
|
|
44
|
+
#
|
|
45
|
+
# obj = Album.all.first
|
|
46
|
+
# obj.allow_lazy_load
|
|
47
|
+
# obj.artist # no error
|
|
48
|
+
#
|
|
49
|
+
# You can forbid lazy loading associations on a per-call basis,
|
|
50
|
+
# even if lazy loading of associations is allowed for the instance:
|
|
51
|
+
#
|
|
52
|
+
# obj = Album[1]
|
|
53
|
+
# obj.artist(forbid_lazy_load: true)
|
|
54
|
+
# # raises Sequel::Plugins::ForbidLazyLoad::Error
|
|
55
|
+
#
|
|
56
|
+
# This also works for allowing lazy loading associations for a
|
|
57
|
+
# specific association load even if it is forbidden for the instance:
|
|
58
|
+
#
|
|
59
|
+
# obj = Album.all.first
|
|
60
|
+
# obj.artist(forbid_lazy_load: false)
|
|
61
|
+
# # nothing raised
|
|
62
|
+
#
|
|
63
|
+
# You can also forbid lazy loading on a per-association basis using the
|
|
64
|
+
# +:forbid_lazy_load+ association option with a +true+ value:
|
|
65
|
+
#
|
|
66
|
+
# Album.many_to_one :artist, forbid_lazy_load: true
|
|
67
|
+
# Album[1].artist # raises Sequel::Plugins::ForbidLazyLoad::Error
|
|
68
|
+
#
|
|
69
|
+
# However, you probably don't want to do this as it will forbid any
|
|
70
|
+
# lazy loading of the association, even if the loading could not
|
|
71
|
+
# result in an N+1 issue.
|
|
72
|
+
#
|
|
73
|
+
# On the flip side, you can allow lazy loading using the
|
|
74
|
+
# +:forbid_lazy_load+ association option with a +false+ value:
|
|
75
|
+
#
|
|
76
|
+
# Album.many_to_one :artist, forbid_lazy_load: false
|
|
77
|
+
# Album.all.first.artist # no error
|
|
78
|
+
#
|
|
79
|
+
# One reason to do this is when using a plugin like static_cache
|
|
80
|
+
# on the associated model, where a query is not actually issued
|
|
81
|
+
# when doing a lazy association load. To make that particular
|
|
82
|
+
# case easier, this plugin makes Model.finalize_associations
|
|
83
|
+
# automatically set the association option if the associated
|
|
84
|
+
# class uses the static_cache plugin.
|
|
85
|
+
#
|
|
86
|
+
# Note that even with this plugin, there can still be N+1 issues,
|
|
87
|
+
# such as:
|
|
88
|
+
#
|
|
89
|
+
# Album.each do |obj| # 1 query for all albums
|
|
90
|
+
# Artist[obj.artist_id] # 1 query per album for each artist
|
|
91
|
+
# end
|
|
92
|
+
#
|
|
93
|
+
# Usage:
|
|
94
|
+
#
|
|
95
|
+
# # Make all model subclasses support forbidding lazy load
|
|
96
|
+
# # (called before loading subclasses)
|
|
97
|
+
# Sequel::Model.plugin :forbid_lazy_load
|
|
98
|
+
#
|
|
99
|
+
# # Make the Album class support forbidding lazy load
|
|
100
|
+
# Album.plugin :forbid_lazy_load
|
|
101
|
+
module ForbidLazyLoad
|
|
102
|
+
# Error raised when attempting to lazy load an association when
|
|
103
|
+
# lazy loading has been forbidden.
|
|
104
|
+
class Error < StandardError
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
module ClassMethods
|
|
108
|
+
Plugins.def_dataset_methods(self, :forbid_lazy_load)
|
|
109
|
+
|
|
110
|
+
# If the static_cache plugin is used by the associated class for
|
|
111
|
+
# an association, allow lazy loading that association, since the
|
|
112
|
+
# lazy association load will use a hash table lookup and not a query.
|
|
113
|
+
def allow_lazy_load_for_static_cache_associations
|
|
114
|
+
if defined?(::Sequel::Plugins::StaticCache::ClassMethods)
|
|
115
|
+
@association_reflections.each_value do |ref|
|
|
116
|
+
if ref.associated_class.is_a?(::Sequel::Plugins::StaticCache::ClassMethods)
|
|
117
|
+
ref[:forbid_lazy_load] = false
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Allow lazy loading for static cache associations before finalizing.
|
|
124
|
+
def finalize_associations
|
|
125
|
+
allow_lazy_load_for_static_cache_associations
|
|
126
|
+
super
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
module InstanceMethods
|
|
131
|
+
# Set this model instance to allow lazy loading of associations.
|
|
132
|
+
def allow_lazy_load
|
|
133
|
+
@forbid_lazy_load = false
|
|
134
|
+
self
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Set this model instance to not allow lazy loading of associations.
|
|
138
|
+
def forbid_lazy_load
|
|
139
|
+
@forbid_lazy_load = true
|
|
140
|
+
self
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
# Allow lazy loading for objects returned by singular associations.
|
|
146
|
+
def _load_associated_object(opts, dynamic_opts)
|
|
147
|
+
# The implementation that loads these associations does
|
|
148
|
+
# .all.first, which would result in the object returned being
|
|
149
|
+
# marked as forbidding lazy load.
|
|
150
|
+
obj = super
|
|
151
|
+
obj.allow_lazy_load if obj.is_a?(InstanceMethods)
|
|
152
|
+
obj
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Raise an Error if lazy loading has been forbidden for
|
|
156
|
+
# the instance, association, or call.
|
|
157
|
+
def _load_associated_objects(opts, dynamic_opts=OPTS)
|
|
158
|
+
case dynamic_opts[:forbid_lazy_load]
|
|
159
|
+
when false
|
|
160
|
+
# nothing
|
|
161
|
+
when nil
|
|
162
|
+
unless dynamic_opts[:reload]
|
|
163
|
+
case opts[:forbid_lazy_load]
|
|
164
|
+
when nil
|
|
165
|
+
raise Error, "lazy loading forbidden for this object (association: #{opts.inspect}, object: #{inspect})" if @forbid_lazy_load
|
|
166
|
+
when false
|
|
167
|
+
# nothing
|
|
168
|
+
else
|
|
169
|
+
raise Error, "lazy loading forbidden for this association (#{opts.inspect})"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
else
|
|
173
|
+
raise Error, "lazy loading forbidden for this association method call (association: #{opts.inspect})"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
super
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
module DatasetMethods
|
|
181
|
+
# Mark model instances retrieved in this call as forbidding lazy loading.
|
|
182
|
+
def each
|
|
183
|
+
if row_proc
|
|
184
|
+
super do |obj|
|
|
185
|
+
obj.forbid_lazy_load if obj.is_a?(InstanceMethods)
|
|
186
|
+
yield obj
|
|
187
|
+
end
|
|
188
|
+
else
|
|
189
|
+
super
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Mark model instances retrieved in this call as forbidding lazy loading.
|
|
194
|
+
def with_sql_each(sql)
|
|
195
|
+
if row_proc
|
|
196
|
+
super(sql) do |obj|
|
|
197
|
+
obj.forbid_lazy_load if obj.is_a?(InstanceMethods)
|
|
198
|
+
yield obj
|
|
199
|
+
end
|
|
200
|
+
else
|
|
201
|
+
super
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Mark model instances retrieved in this call as allowing lazy loading.
|
|
206
|
+
def with_sql_first(sql)
|
|
207
|
+
obj = super
|
|
208
|
+
obj.allow_lazy_load if obj.is_a?(InstanceMethods)
|
|
209
|
+
obj
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
module Sequel
|
|
4
|
+
module Plugins
|
|
5
|
+
# The skip_saving_columms plugin allows skipping specific columns when
|
|
6
|
+
# saving. By default, it skips columns that the database schema
|
|
7
|
+
# indicates are generated columns:
|
|
8
|
+
#
|
|
9
|
+
# # Assume id column, name column, and id2 generated column
|
|
10
|
+
# album = Album[1]
|
|
11
|
+
# album.id # => 1
|
|
12
|
+
# album.name # => 'X'
|
|
13
|
+
# album.id2 # => 2
|
|
14
|
+
# album.save
|
|
15
|
+
# # UPDATE album SET name = 'X' WHERE (id = 1)
|
|
16
|
+
#
|
|
17
|
+
# You can override which columns will be skipped:
|
|
18
|
+
#
|
|
19
|
+
# Album.skip_saving_columns = [:name]
|
|
20
|
+
# album.save
|
|
21
|
+
# # UPDATE album SET id2 = 2 WHERE (id = 1)
|
|
22
|
+
#
|
|
23
|
+
# The skipping happens for all usage of Model#save and callers of it (e.g.
|
|
24
|
+
# Model.create, Model.update). When using the plugin, the only way to get
|
|
25
|
+
# it to save a column marked for skipping is to explicitly specify it:
|
|
26
|
+
#
|
|
27
|
+
# album.save(columns: [:name, :id2])
|
|
28
|
+
# album.save
|
|
29
|
+
# # UPDATE album SET name = 'X', id2 = 2 WHERE (id = 1)
|
|
30
|
+
#
|
|
31
|
+
# Usage:
|
|
32
|
+
#
|
|
33
|
+
# # Support skipping saving columns in all Sequel::Model subclasses
|
|
34
|
+
# # (called before loading subclasses)
|
|
35
|
+
# Sequel::Model.plugin :skip_saving_columns
|
|
36
|
+
#
|
|
37
|
+
# # Support skipping saving columns in the Album class
|
|
38
|
+
# Album.plugin :skip_saving_columns
|
|
39
|
+
module SkipSavingColumns
|
|
40
|
+
# Setup skipping of the generated columns for a model with an existing dataset.
|
|
41
|
+
def self.configure(mod)
|
|
42
|
+
mod.instance_exec do
|
|
43
|
+
set_skip_saving_generated_columns if @dataset
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
module ClassMethods
|
|
48
|
+
# An array of column symbols for columns to skip when saving.
|
|
49
|
+
attr_reader :skip_saving_columns
|
|
50
|
+
|
|
51
|
+
# Over the default array of columns to skip. Once overridden, future
|
|
52
|
+
# changes to the class's dataset and future subclasses will automatically
|
|
53
|
+
# use these overridden columns, instead of introspecting the database schema.
|
|
54
|
+
def skip_saving_columns=(v)
|
|
55
|
+
@_skip_saving_columns_no_override = true
|
|
56
|
+
@skip_saving_columns = v.dup.freeze
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
Plugins.after_set_dataset(self, :set_skip_saving_generated_columns)
|
|
60
|
+
Plugins.inherited_instance_variables(self, :@skip_saving_columns=>:dup, :@_skip_saving_columns_no_override=>nil)
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# If the skip saving columns has not been overridden, check the database
|
|
65
|
+
# schema and automatically skip any generated columns.
|
|
66
|
+
def set_skip_saving_generated_columns
|
|
67
|
+
return if @_skip_saving_columns_no_override
|
|
68
|
+
s = []
|
|
69
|
+
db_schema.each do |k, v|
|
|
70
|
+
s << k if v[:generated]
|
|
71
|
+
end
|
|
72
|
+
@skip_saving_columns = s.freeze
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
module InstanceMethods
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
# Skip the columns the model has marked to skip when inserting.
|
|
81
|
+
def _insert_values
|
|
82
|
+
_save_removed_skipped_columns(Hash[super])
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Skip the columns the model has marked to skip when updating
|
|
86
|
+
# all columns.
|
|
87
|
+
def _save_update_all_columns_hash
|
|
88
|
+
_save_removed_skipped_columns(super)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Skip the columns the model has marked to skip when updating
|
|
92
|
+
# only changed columns.
|
|
93
|
+
def _save_update_changed_colums_hash
|
|
94
|
+
_save_removed_skipped_columns(super)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Remove any columns the model has marked to skip when saving.
|
|
98
|
+
def _save_removed_skipped_columns(hash)
|
|
99
|
+
model.skip_saving_columns.each do |column|
|
|
100
|
+
hash.delete(column)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
hash
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
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 = 31
|
|
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.31.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: 2020-
|
|
11
|
+
date: 2020-04-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -178,6 +178,7 @@ extra_rdoc_files:
|
|
|
178
178
|
- doc/release_notes/5.28.0.txt
|
|
179
179
|
- doc/release_notes/5.29.0.txt
|
|
180
180
|
- doc/release_notes/5.30.0.txt
|
|
181
|
+
- doc/release_notes/5.31.0.txt
|
|
181
182
|
files:
|
|
182
183
|
- CHANGELOG
|
|
183
184
|
- MIT-LICENSE
|
|
@@ -229,6 +230,7 @@ files:
|
|
|
229
230
|
- doc/release_notes/5.29.0.txt
|
|
230
231
|
- doc/release_notes/5.3.0.txt
|
|
231
232
|
- doc/release_notes/5.30.0.txt
|
|
233
|
+
- doc/release_notes/5.31.0.txt
|
|
232
234
|
- doc/release_notes/5.4.0.txt
|
|
233
235
|
- doc/release_notes/5.5.0.txt
|
|
234
236
|
- doc/release_notes/5.6.0.txt
|
|
@@ -417,6 +419,7 @@ files:
|
|
|
417
419
|
- lib/sequel/plugins/active_model.rb
|
|
418
420
|
- lib/sequel/plugins/after_initialize.rb
|
|
419
421
|
- lib/sequel/plugins/association_dependencies.rb
|
|
422
|
+
- lib/sequel/plugins/association_lazy_eager_option.rb
|
|
420
423
|
- lib/sequel/plugins/association_multi_add_remove.rb
|
|
421
424
|
- lib/sequel/plugins/association_pks.rb
|
|
422
425
|
- lib/sequel/plugins/association_proxies.rb
|
|
@@ -443,6 +446,7 @@ files:
|
|
|
443
446
|
- lib/sequel/plugins/empty_failure_backtraces.rb
|
|
444
447
|
- lib/sequel/plugins/error_splitter.rb
|
|
445
448
|
- lib/sequel/plugins/finder.rb
|
|
449
|
+
- lib/sequel/plugins/forbid_lazy_load.rb
|
|
446
450
|
- lib/sequel/plugins/force_encoding.rb
|
|
447
451
|
- lib/sequel/plugins/hook_class_methods.rb
|
|
448
452
|
- lib/sequel/plugins/input_transformer.rb
|
|
@@ -471,6 +475,7 @@ files:
|
|
|
471
475
|
- lib/sequel/plugins/single_table_inheritance.rb
|
|
472
476
|
- lib/sequel/plugins/singular_table_names.rb
|
|
473
477
|
- lib/sequel/plugins/skip_create_refresh.rb
|
|
478
|
+
- lib/sequel/plugins/skip_saving_columns.rb
|
|
474
479
|
- lib/sequel/plugins/split_values.rb
|
|
475
480
|
- lib/sequel/plugins/static_cache.rb
|
|
476
481
|
- lib/sequel/plugins/static_cache_cache.rb
|