sequel 5.43.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 +40 -0
- data/README.rdoc +1 -2
- data/doc/association_basics.rdoc +70 -11
- data/doc/migration.rdoc +11 -5
- data/doc/release_notes/5.44.0.txt +32 -0
- data/doc/release_notes/5.45.0.txt +34 -0
- data/doc/release_notes/5.46.0.txt +87 -0
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/sql.rdoc +12 -0
- data/doc/testing.rdoc +5 -0
- data/doc/virtual_rows.rdoc +1 -1
- data/lib/sequel/adapters/odbc.rb +5 -1
- data/lib/sequel/adapters/shared/mysql.rb +17 -0
- data/lib/sequel/adapters/shared/postgres.rb +0 -12
- data/lib/sequel/adapters/shared/sqlite.rb +55 -9
- data/lib/sequel/core.rb +11 -0
- data/lib/sequel/database/schema_generator.rb +25 -46
- data/lib/sequel/database/schema_methods.rb +1 -1
- data/lib/sequel/dataset/query.rb +2 -4
- data/lib/sequel/dataset/sql.rb +7 -0
- data/lib/sequel/extensions/date_arithmetic.rb +29 -15
- data/lib/sequel/extensions/pg_enum.rb +1 -1
- data/lib/sequel/extensions/pg_loose_count.rb +3 -1
- data/lib/sequel/extensions/schema_dumper.rb +11 -0
- data/lib/sequel/model/associations.rb +275 -89
- data/lib/sequel/plugins/async_thread_pool.rb +1 -1
- data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
- data/lib/sequel/plugins/column_encryption.rb +20 -3
- data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
- data/lib/sequel/plugins/many_through_many.rb +108 -9
- data/lib/sequel/plugins/pg_array_associations.rb +52 -38
- data/lib/sequel/plugins/prepared_statements.rb +10 -1
- data/lib/sequel/plugins/rcte_tree.rb +27 -19
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/unused_associations.rb +520 -0
- data/lib/sequel/version.rb +1 -1
- metadata +14 -3
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
@@ -162,6 +162,7 @@ SEQUEL_ASYNC_THREAD_POOL_PREEMPT :: Use the async_thread_pool extension when run
|
|
162
162
|
SEQUEL_COLUMNS_INTROSPECTION :: Use the columns_introspection extension when running the specs
|
163
163
|
SEQUEL_CONNECTION_VALIDATOR :: Use the connection validator extension when running the specs
|
164
164
|
SEQUEL_DUPLICATE_COLUMNS_HANDLER :: Use the duplicate columns handler extension with value given when running the specs
|
165
|
+
SEQUEL_CONCURRENT_EAGER_LOADING :: Use the async_thread_pool extension and concurrent_eager_loading plugin when running the specs
|
165
166
|
SEQUEL_ERROR_SQL :: Use the error_sql extension when running the specs
|
166
167
|
SEQUEL_INDEX_CACHING :: Use the index_caching extension when running the specs
|
167
168
|
SEQUEL_FIBER_CONCURRENCY :: Use the fiber_concurrency extension when running the adapter and integration specs
|
@@ -174,6 +175,10 @@ SEQUEL_NO_CACHE_ASSOCIATIONS :: Don't cache association metadata when running th
|
|
174
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
|
175
176
|
SEQUEL_NO_PENDING :: Don't skip any specs, try running all specs (note, can cause lockups for some adapters)
|
176
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)
|
177
182
|
SEQUEL_SPLIT_SYMBOLS :: Turn on symbol splitting when running the adapter and integration specs
|
178
183
|
SEQUEL_SYNCHRONIZE_SQL :: Use the synchronize_sql extension when running the specs
|
179
184
|
SEQUEL_TZINFO_VERSION :: Force the given tzinfo version when running the specs (e.g. '>=2')
|
data/doc/virtual_rows.rdoc
CHANGED
data/lib/sequel/adapters/odbc.rb
CHANGED
@@ -94,7 +94,11 @@ module Sequel
|
|
94
94
|
self.columns = columns
|
95
95
|
s.each do |row|
|
96
96
|
hash = {}
|
97
|
-
cols.each
|
97
|
+
cols.each do |n,t,j|
|
98
|
+
v = row[j]
|
99
|
+
# We can assume v is not false, so this shouldn't convert false to nil.
|
100
|
+
hash[n] = (convert_odbc_value(v, t) if v)
|
101
|
+
end
|
98
102
|
yield hash
|
99
103
|
end
|
100
104
|
end
|
@@ -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
|
@@ -2141,18 +2141,6 @@ module Sequel
|
|
2141
2141
|
opts[:with].any?{|w| w[:recursive]} ? "WITH RECURSIVE " : super
|
2142
2142
|
end
|
2143
2143
|
|
2144
|
-
# Support WITH AS [NOT] MATERIALIZED if :materialized option is used.
|
2145
|
-
def select_with_sql_prefix(sql, w)
|
2146
|
-
super
|
2147
|
-
|
2148
|
-
case w[:materialized]
|
2149
|
-
when true
|
2150
|
-
sql << "MATERIALIZED "
|
2151
|
-
when false
|
2152
|
-
sql << "NOT MATERIALIZED "
|
2153
|
-
end
|
2154
|
-
end
|
2155
|
-
|
2156
2144
|
# The version of the database server
|
2157
2145
|
def server_version
|
2158
2146
|
db.server_version(@opts[:server])
|
@@ -239,8 +239,12 @@ module Sequel
|
|
239
239
|
super
|
240
240
|
end
|
241
241
|
when :drop_column
|
242
|
-
|
243
|
-
|
242
|
+
if sqlite_version >= 33500
|
243
|
+
super
|
244
|
+
else
|
245
|
+
ocp = lambda{|oc| oc.delete_if{|c| c.to_s == op[:name].to_s}}
|
246
|
+
duplicate_table(table, :old_columns_proc=>ocp){|columns| columns.delete_if{|s| s[:name].to_s == op[:name].to_s}}
|
247
|
+
end
|
244
248
|
when :rename_column
|
245
249
|
if sqlite_version >= 32500
|
246
250
|
super
|
@@ -424,10 +428,10 @@ module Sequel
|
|
424
428
|
skip_indexes = []
|
425
429
|
indexes(table, :only_autocreated=>true).each do |name, h|
|
426
430
|
skip_indexes << name
|
427
|
-
if h[:unique]
|
431
|
+
if h[:unique] && !opts[:no_unique]
|
428
432
|
if h[:columns].length == 1
|
429
433
|
unique_columns.concat(h[:columns])
|
430
|
-
elsif h[:columns].map(&:to_s) != pks
|
434
|
+
elsif h[:columns].map(&:to_s) != pks
|
431
435
|
constraints << {:type=>:unique, :columns=>h[:columns]}
|
432
436
|
end
|
433
437
|
end
|
@@ -558,10 +562,10 @@ module Sequel
|
|
558
562
|
EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}.freeze
|
559
563
|
EXTRACT_MAP.each_value(&:freeze)
|
560
564
|
|
561
|
-
Dataset.def_sql_method(self, :delete, [['if db.sqlite_version >= 30803', %w'with delete from where'], ["else", %w'delete from where']])
|
562
|
-
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']])
|
563
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']])
|
564
|
-
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']])
|
565
569
|
|
566
570
|
def cast_sql_append(sql, expr, type)
|
567
571
|
if type == Time or type == DateTime
|
@@ -635,8 +639,8 @@ module Sequel
|
|
635
639
|
# SQLite performs a TRUNCATE style DELETE if no filter is specified.
|
636
640
|
# Since we want to always return the count of records, add a condition
|
637
641
|
# that is always true and then delete.
|
638
|
-
def delete
|
639
|
-
@opts[:where] ? super : where(1=>1).delete
|
642
|
+
def delete(&block)
|
643
|
+
@opts[:where] ? super : where(1=>1).delete(&block)
|
640
644
|
end
|
641
645
|
|
642
646
|
# Return an array of strings specifying a query explanation for a SELECT of the
|
@@ -657,6 +661,21 @@ module Sequel
|
|
657
661
|
super
|
658
662
|
end
|
659
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
|
+
|
660
679
|
# SQLite uses the nonstandard ` (backtick) for quoting identifiers.
|
661
680
|
def quoted_identifier_append(sql, c)
|
662
681
|
sql << '`' << c.to_s.gsub('`', '``') << '`'
|
@@ -738,6 +757,13 @@ module Sequel
|
|
738
757
|
insert_conflict(:ignore)
|
739
758
|
end
|
740
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
|
+
|
741
767
|
# SQLite 3.8.3+ supports common table expressions.
|
742
768
|
def supports_cte?(type=:select)
|
743
769
|
db.sqlite_version >= 30803
|
@@ -778,6 +804,11 @@ module Sequel
|
|
778
804
|
false
|
779
805
|
end
|
780
806
|
|
807
|
+
# SQLite 3.35.0 supports RETURNING on INSERT/UPDATE/DELETE.
|
808
|
+
def supports_returning?(_)
|
809
|
+
db.sqlite_version >= 33500
|
810
|
+
end
|
811
|
+
|
781
812
|
# SQLite supports timezones in literal timestamps, since it stores them
|
782
813
|
# as text. But using timezones in timestamps breaks SQLite datetime
|
783
814
|
# functions, so we allow the user to override the default per database.
|
@@ -810,6 +841,21 @@ module Sequel
|
|
810
841
|
|
811
842
|
private
|
812
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
|
+
|
813
859
|
# SQLite uses string literals instead of identifiers in AS clauses.
|
814
860
|
def as_sql_append(sql, aliaz, column_aliases=nil)
|
815
861
|
raise Error, "sqlite does not support derived column lists" if column_aliases
|
data/lib/sequel/core.rb
CHANGED
@@ -176,6 +176,17 @@ module Sequel
|
|
176
176
|
JSON.parse(json, :create_additions=>false)
|
177
177
|
end
|
178
178
|
|
179
|
+
# If a mutex is given, synchronize access using it. If nil is given, just
|
180
|
+
# yield to the block. This is designed for cases where a mutex may or may
|
181
|
+
# not be provided.
|
182
|
+
def synchronize_with(mutex)
|
183
|
+
if mutex
|
184
|
+
mutex.synchronize{yield}
|
185
|
+
else
|
186
|
+
yield
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
179
190
|
# Convert each item in the array to the correct type, handling multi-dimensional
|
180
191
|
# arrays. For each element in the array or subarrays, call the converter,
|
181
192
|
# unless the value is nil.
|
@@ -214,14 +214,12 @@ module Sequel
|
|
214
214
|
end
|
215
215
|
|
216
216
|
# Add a full text index on the given columns.
|
217
|
+
# See #index for additional options.
|
217
218
|
#
|
218
219
|
# PostgreSQL specific options:
|
219
220
|
# :index_type :: Can be set to :gist to use a GIST index instead of the
|
220
221
|
# default GIN index.
|
221
222
|
# :language :: Set a language to use for the index (default: simple).
|
222
|
-
#
|
223
|
-
# Microsoft SQL Server specific options:
|
224
|
-
# :key_index :: The KEY INDEX to use for the full text index.
|
225
223
|
def full_text_index(columns, opts = OPTS)
|
226
224
|
index(columns, opts.merge(:type => :full_text))
|
227
225
|
end
|
@@ -231,35 +229,43 @@ module Sequel
|
|
231
229
|
columns.any?{|c| c[:name] == name}
|
232
230
|
end
|
233
231
|
|
234
|
-
# Add an index on the given column(s) with the given options.
|
232
|
+
# Add an index on the given column(s) with the given options. Examples:
|
233
|
+
#
|
234
|
+
# index :name
|
235
|
+
# # CREATE INDEX table_name_index ON table (name)
|
236
|
+
#
|
237
|
+
# index [:artist_id, :name]
|
238
|
+
# # CREATE INDEX table_artist_id_name_index ON table (artist_id, name)
|
239
|
+
#
|
240
|
+
# index [:artist_id, :name], name: :foo
|
241
|
+
# # CREATE INDEX foo ON table (artist_id, name)
|
242
|
+
#
|
235
243
|
# General options:
|
236
244
|
#
|
245
|
+
# :include :: Include additional column values in the index, without
|
246
|
+
# actually indexing on those values (only supported by
|
247
|
+
# some databases).
|
237
248
|
# :name :: The name to use for the index. If not given, a default name
|
238
249
|
# based on the table and columns is used.
|
239
|
-
# :type :: The type of index to use (only supported by some databases
|
250
|
+
# :type :: The type of index to use (only supported by some databases,
|
251
|
+
# :full_text and :spatial values are handled specially).
|
240
252
|
# :unique :: Make the index unique, so duplicate values are not allowed.
|
241
|
-
# :where ::
|
253
|
+
# :where :: A filter expression, used to create a partial index (only
|
254
|
+
# supported by some databases).
|
242
255
|
#
|
243
256
|
# PostgreSQL specific options:
|
244
257
|
#
|
245
258
|
# :concurrently :: Create the index concurrently, so it doesn't block
|
246
259
|
# operations on the table while the index is being
|
247
260
|
# built.
|
248
|
-
# :
|
249
|
-
# :
|
250
|
-
#
|
261
|
+
# :if_not_exists :: Only create the index if an index of the same name doesn't already exist.
|
262
|
+
# :opclass :: Set an opclass to use for all columns (per-column opclasses require
|
263
|
+
# custom SQL).
|
251
264
|
# :tablespace :: Specify tablespace for index.
|
252
265
|
#
|
253
266
|
# Microsoft SQL Server specific options:
|
254
267
|
#
|
255
|
-
# :
|
256
|
-
# actually indexing on those values.
|
257
|
-
#
|
258
|
-
# index :name
|
259
|
-
# # CREATE INDEX table_name_index ON table (name)
|
260
|
-
#
|
261
|
-
# index [:artist_id, :name]
|
262
|
-
# # CREATE INDEX table_artist_id_name_index ON table (artist_id, name)
|
268
|
+
# :key_index :: Sets the KEY INDEX to the given value.
|
263
269
|
def index(columns, opts = OPTS)
|
264
270
|
indexes << {:columns => Array(columns)}.merge!(opts)
|
265
271
|
nil
|
@@ -325,6 +331,7 @@ module Sequel
|
|
325
331
|
end
|
326
332
|
|
327
333
|
# Add a spatial index on the given columns.
|
334
|
+
# See #index for additional options.
|
328
335
|
def spatial_index(columns, opts = OPTS)
|
329
336
|
index(columns, opts.merge(:type => :spatial))
|
330
337
|
end
|
@@ -451,7 +458,7 @@ module Sequel
|
|
451
458
|
end
|
452
459
|
|
453
460
|
# Add a full text index on the given columns.
|
454
|
-
# See CreateTableGenerator#
|
461
|
+
# See CreateTableGenerator#full_text_index for available options.
|
455
462
|
def add_full_text_index(columns, opts = OPTS)
|
456
463
|
add_index(columns, {:type=>:full_text}.merge!(opts))
|
457
464
|
end
|
@@ -460,34 +467,6 @@ module Sequel
|
|
460
467
|
# CreateTableGenerator#index for available options.
|
461
468
|
#
|
462
469
|
# add_index(:artist_id) # CREATE INDEX table_artist_id_index ON table (artist_id)
|
463
|
-
#
|
464
|
-
# Options:
|
465
|
-
#
|
466
|
-
# :name :: Give a specific name for the index. Highly recommended if you plan on
|
467
|
-
# dropping the index later.
|
468
|
-
# :where :: A filter expression, used to setup a partial index (if supported).
|
469
|
-
# :unique :: Create a unique index.
|
470
|
-
#
|
471
|
-
# PostgreSQL specific options:
|
472
|
-
#
|
473
|
-
# :concurrently :: Create the index concurrently, so it doesn't require an exclusive lock
|
474
|
-
# on the table.
|
475
|
-
# :index_type :: The underlying index type to use for a full_text index, gin by default).
|
476
|
-
# :language :: The language to use for a full text index (simple by default).
|
477
|
-
# :opclass :: Set an opclass to use for all columns (per-column opclasses require
|
478
|
-
# custom SQL).
|
479
|
-
# :type :: Set the index type (e.g. full_text, spatial, hash, gin, gist, btree).
|
480
|
-
# :if_not_exists :: Only create the index if an index of the same name doesn't already exists
|
481
|
-
#
|
482
|
-
# MySQL specific options:
|
483
|
-
#
|
484
|
-
# :type :: Set the index type, with full_text and spatial indexes handled specially.
|
485
|
-
#
|
486
|
-
# Microsoft SQL Server specific options:
|
487
|
-
#
|
488
|
-
# :include :: Includes additional columns in the index.
|
489
|
-
# :key_index :: Sets the KEY INDEX to the given value.
|
490
|
-
# :type :: clustered uses a clustered index, full_text uses a full text index.
|
491
470
|
def add_index(columns, opts = OPTS)
|
492
471
|
@operations << {:op => :add_index, :columns => Array(columns)}.merge!(opts)
|
493
472
|
nil
|
@@ -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
|
@@ -1062,10 +1062,8 @@ module Sequel
|
|
1062
1062
|
# Options:
|
1063
1063
|
# :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
|
1064
1064
|
# :recursive :: Specify that this is a recursive CTE
|
1065
|
-
#
|
1066
|
-
# PostgreSQL Specific Options:
|
1067
1065
|
# :materialized :: Set to false to force inlining of the CTE, or true to force not inlining
|
1068
|
-
# the CTE (PostgreSQL 12+).
|
1066
|
+
# the CTE (PostgreSQL 12+/SQLite 3.35+).
|
1069
1067
|
#
|
1070
1068
|
# DB[:items].with(:items, DB[:syx].where(Sequel[:name].like('A%')))
|
1071
1069
|
# # WITH items AS (SELECT * FROM syx WHERE (name LIKE 'A%' ESCAPE '\')) SELECT * FROM items
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -1567,6 +1567,13 @@ module Sequel
|
|
1567
1567
|
sql << ')'
|
1568
1568
|
end
|
1569
1569
|
sql << ' AS '
|
1570
|
+
|
1571
|
+
case w[:materialized]
|
1572
|
+
when true
|
1573
|
+
sql << "MATERIALIZED "
|
1574
|
+
when false
|
1575
|
+
sql << "NOT MATERIALIZED "
|
1576
|
+
end
|
1570
1577
|
end
|
1571
1578
|
|
1572
1579
|
# Whether the symbol cache should be skipped when literalizing the dataset
|
@@ -8,9 +8,10 @@
|
|
8
8
|
# DB.extension :date_arithmetic
|
9
9
|
#
|
10
10
|
# Then you can use the Sequel.date_add and Sequel.date_sub methods
|
11
|
-
# to return Sequel expressions
|
11
|
+
# to return Sequel expressions (this example shows the only supported
|
12
|
+
# keys for the second argument):
|
12
13
|
#
|
13
|
-
# add = Sequel.date_add(:date_column, years: 1, months: 2, days:
|
14
|
+
# add = Sequel.date_add(:date_column, years: 1, months: 2, weeks: 2, days: 1)
|
14
15
|
# sub = Sequel.date_sub(:date_column, hours: 1, minutes: 2, seconds: 3)
|
15
16
|
#
|
16
17
|
# In addition to specifying the interval as a hash, there is also
|
@@ -184,22 +185,35 @@ module Sequel
|
|
184
185
|
# ActiveSupport::Duration :: Converted to a hash using the interval's parts.
|
185
186
|
def initialize(expr, interval, opts=OPTS)
|
186
187
|
@expr = expr
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
188
|
+
|
189
|
+
h = Hash.new(0)
|
190
|
+
interval = interval.parts unless interval.is_a?(Hash)
|
191
|
+
interval.each do |unit, value|
|
192
|
+
# skip nil values
|
193
|
+
next unless value
|
194
|
+
|
195
|
+
# Convert weeks to days, as ActiveSupport::Duration can use weeks,
|
196
|
+
# but the database-specific literalizers only support days.
|
197
|
+
if unit == :weeks
|
198
|
+
unit = :days
|
199
|
+
value *= 7
|
200
|
+
end
|
201
|
+
|
202
|
+
unless DatasetMethods::DURATION_UNITS.include?(unit)
|
203
|
+
raise Sequel::Error, "Invalid key used in DateAdd interval hash: #{unit.inspect}"
|
194
204
|
end
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
205
|
+
|
206
|
+
# Attempt to prevent SQL injection by users who pass untrusted strings
|
207
|
+
# as interval values. It doesn't make sense to support literal strings,
|
208
|
+
# due to the numeric adding below.
|
209
|
+
if value.is_a?(String)
|
210
|
+
raise Sequel::InvalidValue, "cannot provide String value as interval part: #{value.inspect}"
|
211
|
+
end
|
212
|
+
|
213
|
+
h[unit] += value
|
200
214
|
end
|
201
215
|
|
202
|
-
@interval.freeze
|
216
|
+
@interval = Hash[h].freeze
|
203
217
|
@cast_type = opts[:cast] if opts[:cast]
|
204
218
|
freeze
|
205
219
|
end
|