sequel 5.30.0 → 5.31.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22c562c1227d83d1425ef6db9d8bd503595cd97e35036cca335aeb588a00f3df
4
- data.tar.gz: 18c96ea22fe23f65b6f85c6efc17ce9b17e783fc8bfaefa498edb7ada25499aa
3
+ metadata.gz: f7419480f1c01043fb35c490c25cfbbcfaa9b5694ad41462f7af1c5e5e57bdf5
4
+ data.tar.gz: 7fe799521a27993775f197f15cc38b780c04a3d8d4a9a8ef931da1bea2308779
5
5
  SHA512:
6
- metadata.gz: 890ae35cd097875a8cab776d7db7760ccb8b21fa0317dd26011a5a49996d378bdd5db68c299967c47c7b066718aca642fb7b606979753a1ac9833ac2d004b02d
7
- data.tar.gz: bb685f30c7c4db37441f38cc1e6a729e5d87db86996474bbcb18dd551310ff7751e2abc56db146483e319197bb6381cbf12c6e4e2e6a2079da82ff74ae4aadc9
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)
@@ -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 of keeping it becomes a support issue. The minimum Ruby 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
@@ -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.
@@ -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
- sql = super
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 += " INHERITS (#{Array(inherits).map{|t| quote_schema_table(t)}.join(', ')})"
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 += " ON COMMIT #{ON_COMMIT[on_commit]}"
1193
+ sql << " ON COMMIT #{ON_COMMIT[on_commit]}"
1053
1194
  end
1054
1195
 
1055
1196
  if tablespace = options[:tablespace]
1056
- sql += " TABLESPACE #{quote_identifier(tablespace)}"
1197
+ sql << " TABLESPACE #{quote_identifier(tablespace)}"
1057
1198
  end
1058
1199
 
1059
1200
  if server = options[:foreign]
1060
- sql += " SERVER #{quote_identifier(server)}"
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 table_info(?)", input_identifier_meth(opts[:dataset]).call(table_name))
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){|columns| columns.each{|s| s[:primary_key] = s[:auto_increment] = nil}}
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, 'irreversible migration method used, you may need to write your own down method'}
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
- # If the pg_array extension is used, arrays of enums are returned as a
44
- # PGArray:
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
- col_opts[:default] = if schema[:ruby_default].nil?
203
- column_schema_to_ruby_default_fallback(schema[:default], options)
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]}
@@ -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
- cc = changed_columns
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
- columns_updated = _save_update_all_columns_hash
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
@@ -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 = 30
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.30.0
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-03-01 00:00:00.000000000 Z
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