sequel 5.10.0 → 5.11.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/doc/release_notes/5.11.0.txt +83 -0
- data/doc/security.rdoc +10 -0
- data/doc/sql.rdoc +12 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +1 -1
- data/lib/sequel/adapters/shared/mysql.rb +18 -2
- data/lib/sequel/adapters/shared/postgres.rb +18 -24
- data/lib/sequel/adapters/shared/sqlanywhere.rb +9 -1
- data/lib/sequel/adapters/shared/sqlite.rb +72 -6
- data/lib/sequel/dataset/features.rb +18 -0
- data/lib/sequel/dataset/query.rb +12 -0
- data/lib/sequel/dataset/sql.rb +108 -17
- data/lib/sequel/extensions/eval_inspect.rb +1 -1
- data/lib/sequel/plugins/list.rb +4 -2
- data/lib/sequel/plugins/static_cache.rb +15 -0
- data/lib/sequel/sql.rb +28 -4
- data/lib/sequel/version.rb +3 -1
- data/spec/adapters/postgres_spec.rb +11 -33
- data/spec/adapters/sqlite_spec.rb +52 -0
- data/spec/core/dataset_spec.rb +22 -1
- data/spec/core/expression_filters_spec.rb +60 -3
- data/spec/extensions/eval_inspect_spec.rb +7 -0
- data/spec/extensions/schema_dumper_spec.rb +2 -2
- data/spec/extensions/static_cache_spec.rb +35 -0
- data/spec/extensions/tree_spec.rb +7 -0
- data/spec/integration/dataset_test.rb +67 -0
- data/spec/integration/plugin_test.rb +7 -0
- data/spec/integration/schema_test.rb +20 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 935bcf2280ce67a62dfd6e705308c85324f9d82303f5144ccbede8883d0956e5
|
4
|
+
data.tar.gz: 91dc87ca6afcaa82372233829919bab98227e2f7a7555bc0229811cfc829b482
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0540143323390f812a543e8a28fa2ec3184faad7ad5508e9d64cf49bf6642f09dd636f0356d8bd2a4ad632a21e5a4358eda096d86b08ddaae0f62cdf8bfc9fa
|
7
|
+
data.tar.gz: f85a4c4880b34f737b00affaab544473e02d70052e70ddc438597455974b411a65093f2aa511af84558a15121f3032192df191677bc7ea4b1c0b779cda1714ae
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
=== 5.11.0 (2018-08-01)
|
2
|
+
|
3
|
+
* Fix using the jdbc/sqlserver adapter on JRuby 9.2+ (jeremyevans)
|
4
|
+
|
5
|
+
* Fix dumping schema for numeric/decimal columns with default values, broken starting in 5.9.0 (jeremyevans)
|
6
|
+
|
7
|
+
* Recognize additional check constraint violations on certain versions of SQLite (jeremyevans)
|
8
|
+
|
9
|
+
* Use cached model instances for Model.first calls without an argument or with a single integer argument in the static_cache plugin (AlexWayfer) (#1529)
|
10
|
+
|
11
|
+
* Support ON CONFLICT clause for INSERT on SQLite 3.24+ (jeremyevans)
|
12
|
+
|
13
|
+
* Support Dataset#window for WINDOW clause on MySQL 8 and SQLAnywhere (jeremyevans)
|
14
|
+
|
15
|
+
* Enable window function support on SQLAnywhere (jeremyevans)
|
16
|
+
|
17
|
+
* Support using a hash as a window function :frame option value, with support for ROWS/RANGE/GROUPS, numeric offsets, and EXCLUDE (jeremyevans)
|
18
|
+
|
19
|
+
* Allow using set_column_default with a nil value to remove the default value for a column on MySQL when the column is NOT NULL (jeremyevans)
|
20
|
+
|
1
21
|
=== 5.10.0 (2018-07-01)
|
2
22
|
|
3
23
|
* Use input type casts when using the postgres adapter with pg 0.18+ to reduce string allocations for some primitive types used as prepared statement arguments (jeremyevans)
|
@@ -0,0 +1,83 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* Sequel now supports more window frame specification types when
|
4
|
+
using window functions. You can now provide the window frame
|
5
|
+
specification as a hash, and Sequel will format the correct
|
6
|
+
SQL. Specifically, this adds support for RANGE and GROUPS,
|
7
|
+
numeric offsets, and EXCLUDE on a database that supports it
|
8
|
+
(e.g. PostgreSQL 11+). Examples:
|
9
|
+
|
10
|
+
DB[:albums].select{function(c1).over(:partition=>c2, :order=>:c3,
|
11
|
+
:frame=>{:type=>:range, :start=>1, :end=>1})}
|
12
|
+
# SELECT function(c1) OVER (PARTITION BY c2 ORDER BY c3
|
13
|
+
# RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM albums
|
14
|
+
|
15
|
+
DB[:albums].select{function(c1).over(:partition=>c2, :order=>:c3,
|
16
|
+
:frame=>{:type=>:groups, :start=>[2, :preceding], :end=>[1, :preceding]})}
|
17
|
+
# SELECT function(c1) OVER (PARTITION BY c2 ORDER BY c3
|
18
|
+
# GROUPS BETWEEN 2 PRECEDING AND 1 PRECEDING) FROM albums
|
19
|
+
|
20
|
+
DB[:albums].select{function(c1).over(:partition=>c2, :order=>:c3,
|
21
|
+
:frame=>{:type=>:range, :start=>:preceding, :exclude=>:current})}
|
22
|
+
# SELECT function(c1) OVER (PARTITION BY c2 ORDER BY c3
|
23
|
+
# RANGE UNBOUNDED PRECEDING EXCLUDE CURRENT ROW) FROM albums
|
24
|
+
|
25
|
+
* The SQLite 3.24+ ON CONFLICT clause to INSERT is now supported.
|
26
|
+
This support is very similar to the PostgreSQL support for the
|
27
|
+
same feature, also known as UPSERT (UPDATE if the row already
|
28
|
+
exists, INSERT if it does not). This support is different than
|
29
|
+
the previous support for INSERT ON CONFLICT REPLACE (also known as
|
30
|
+
INSERT OR REPLACE), but it uses the same method name in order to
|
31
|
+
be compatible with the PostgreSQL support. The new syntax requires
|
32
|
+
passing a hash to Dataset#insert_conflict. Examples:
|
33
|
+
|
34
|
+
DB[:table].insert_conflict({}).insert(a: 1, b: 2)
|
35
|
+
# INSERT INTO TABLE (a, b) VALUES (1, 2)
|
36
|
+
# ON CONFLICT DO NOTHING
|
37
|
+
|
38
|
+
DB[:table].insert_conflict(target: :a).insert(a: 1, b: 2)
|
39
|
+
# INSERT INTO TABLE (a, b) VALUES (1, 2)
|
40
|
+
# ON CONFLICT (a) DO NOTHING
|
41
|
+
|
42
|
+
DB[:table].insert_conflict(target: :a,
|
43
|
+
conflict_where: {c: true}).insert(a: 1, b: 2)
|
44
|
+
# INSERT INTO TABLE (a, b) VALUES (1, 2)
|
45
|
+
# ON CONFLICT (a) WHERE (c IS TRUE) DO NOTHING
|
46
|
+
|
47
|
+
DB[:table].insert_conflict(target: :a,
|
48
|
+
update: {b: Sequel[:excluded][:b]}).insert(a: 1, b: 2)
|
49
|
+
# INSERT INTO TABLE (a, b) VALUES (1, 2)
|
50
|
+
# ON CONFLICT (a) DO UPDATE SET b = excluded.b
|
51
|
+
|
52
|
+
DB[:table].insert_conflict(target: :a,
|
53
|
+
update: {b: Sequel[:excluded][:b]},
|
54
|
+
update_where: {Sequel[:table][:status_id] => 1}).insert(a: 1, b: 2)
|
55
|
+
# INSERT INTO TABLE (a, b) VALUES (1, 2) ON CONFLICT (a)
|
56
|
+
# DO UPDATE SET b = excluded.b WHERE (table.status_id = 1)
|
57
|
+
|
58
|
+
* Dataset#window for the WINDOW clause has been moved from the
|
59
|
+
PostgreSQL-specific support to core, and has been enabled on
|
60
|
+
MySQL 8+ and SQLAnywhere. This allows you to specify a shared
|
61
|
+
window specification in a query, which can be used by multiple
|
62
|
+
window functions.
|
63
|
+
|
64
|
+
= Other Improvements
|
65
|
+
|
66
|
+
* When using the static_cache plugin, Model.first when called without
|
67
|
+
a block and without arguments or with a single Integer argument now
|
68
|
+
uses the cached values instead of issuing a query.
|
69
|
+
|
70
|
+
* Using set_column_default with a nil value now correctly removes an
|
71
|
+
existing default value on MySQL when the column is NOT NULL.
|
72
|
+
|
73
|
+
* Window function support has been enabled on SQLAnywhere, since it
|
74
|
+
works correctly.
|
75
|
+
|
76
|
+
* Dumping schema for numeric/decimal columns with default values
|
77
|
+
now works correctly. This was broken starting in Sequel 5.9.0
|
78
|
+
due to changes to use BigDecimal() instead of BigDecimal.new().
|
79
|
+
|
80
|
+
* The jdbc/sqlserver adapter now works correctly on JRuby 9.2+.
|
81
|
+
|
82
|
+
* An additional check constraint violation failure message is now
|
83
|
+
recognized on SQLite.
|
data/doc/security.rdoc
CHANGED
@@ -173,6 +173,16 @@ user input for function names.
|
|
173
173
|
|
174
174
|
DB[:table].select(Sequel.function(params[:id])) # SQL injection!
|
175
175
|
|
176
|
+
==== SQL Window Frames
|
177
|
+
|
178
|
+
For backwards compatibility, Sequel supports regular strings in the
|
179
|
+
window function :frame option, which will be treated as a literal string:
|
180
|
+
|
181
|
+
DB[:table].select{fun(arg).over(:frame=>'SQL Here')}
|
182
|
+
|
183
|
+
You should make sure the frame argument is not derived from user input,
|
184
|
+
or switch to using a hash as the :frame option value.
|
185
|
+
|
176
186
|
==== auto_literal_strings extension
|
177
187
|
|
178
188
|
If the auto_literal_strings extension is used for backwards compatibility,
|
data/doc/sql.rdoc
CHANGED
@@ -229,6 +229,18 @@ If the database supports window functions, Sequel can handle them by calling the
|
|
229
229
|
DB[:albums].select{function(c1, c2).over(:partition=>[c3, c4], :order=>[c5, c6.desc])}
|
230
230
|
# SELECT function(c1, c2) OVER (PARTITION BY c3, c4 ORDER BY c5, c6 DESC) FROM albums
|
231
231
|
|
232
|
+
DB[:albums].select{function(c1).over(:partition=>c2, :order=>:c3, :frame=>:rows)}
|
233
|
+
# SELECT function(c1) OVER (PARTITION BY c2 ORDER BY c3 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM albums
|
234
|
+
|
235
|
+
DB[:albums].select{function(c1).over(:partition=>c2, :order=>:c3, :frame=>{:type=>:range, :start=>1, :end=>1})}
|
236
|
+
# SELECT function(c1) OVER (PARTITION BY c2 ORDER BY c3 RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM albums
|
237
|
+
|
238
|
+
DB[:albums].select{function(c1).over(:partition=>c2, :order=>:c3, :frame=>{:type=>:groups, :start=>[2, :preceding], :end=>[1, :preceding]})}
|
239
|
+
# SELECT function(c1) OVER (PARTITION BY c2 ORDER BY c3 GROUPS BETWEEN 2 PRECEDING AND 1 PRECEDING) FROM albums
|
240
|
+
|
241
|
+
DB[:albums].select{function(c1).over(:partition=>c2, :order=>:c3, :frame=>{:type=>:range, :start=>:preceding, :exclude=>:current})}
|
242
|
+
# SELECT function(c1) OVER (PARTITION BY c2 ORDER BY c3 RANGE UNBOUNDED PRECEDING EXCLUDE CURRENT ROW) FROM albums
|
243
|
+
|
232
244
|
=== Schema Qualified Functions
|
233
245
|
|
234
246
|
If the database supports schema qualified functions, Sequel can handle them by calling the +function+ method on a qualified identifier:
|
@@ -232,7 +232,18 @@ module Sequel
|
|
232
232
|
alias alter_table_rename_column_sql alter_table_change_column_sql
|
233
233
|
alias alter_table_set_column_type_sql alter_table_change_column_sql
|
234
234
|
alias alter_table_set_column_null_sql alter_table_change_column_sql
|
235
|
-
|
235
|
+
|
236
|
+
def alter_table_set_column_default_sql(table, op)
|
237
|
+
return super unless op[:default].nil?
|
238
|
+
|
239
|
+
opts = schema(table).find{|x| x[0] == op[:name]}
|
240
|
+
|
241
|
+
if opts && opts[1][:allow_null] == false
|
242
|
+
"ALTER COLUMN #{quote_identifier(op[:name])} DROP DEFAULT"
|
243
|
+
else
|
244
|
+
super
|
245
|
+
end
|
246
|
+
end
|
236
247
|
|
237
248
|
def alter_table_add_constraint_sql(table, op)
|
238
249
|
if op[:type] == :foreign_key
|
@@ -591,7 +602,7 @@ module Sequel
|
|
591
602
|
|
592
603
|
Dataset.def_sql_method(self, :delete, %w'with delete from where order limit')
|
593
604
|
Dataset.def_sql_method(self, :insert, %w'insert ignore into columns values on_duplicate_key_update')
|
594
|
-
Dataset.def_sql_method(self, :select, %w'with select distinct calc_found_rows columns from join where group having compounds order limit lock')
|
605
|
+
Dataset.def_sql_method(self, :select, %w'with select distinct calc_found_rows columns from join where group having window compounds order limit lock')
|
595
606
|
Dataset.def_sql_method(self, :update, %w'with update ignore table set where order limit')
|
596
607
|
|
597
608
|
include Sequel::Dataset::Replace
|
@@ -835,6 +846,11 @@ module Sequel
|
|
835
846
|
db.supports_timestamp_usecs?
|
836
847
|
end
|
837
848
|
|
849
|
+
# MySQL 8+ supports WINDOW clause.
|
850
|
+
def supports_window_clause?
|
851
|
+
!db.mariadb? && db.server_version >= 80000
|
852
|
+
end
|
853
|
+
|
838
854
|
# MariaDB 10.2+ and MySQL 8+ support window functions
|
839
855
|
def supports_window_functions?
|
840
856
|
db.server_version >= (db.mariadb? ? 100200 : 80000)
|
@@ -1673,11 +1673,29 @@ module Sequel
|
|
1673
1673
|
true
|
1674
1674
|
end
|
1675
1675
|
|
1676
|
+
# PostgreSQL 8.4+ supports WINDOW clause.
|
1677
|
+
def supports_window_clause?
|
1678
|
+
server_version >= 80400
|
1679
|
+
end
|
1680
|
+
|
1676
1681
|
# PostgreSQL 8.4+ supports window functions
|
1677
1682
|
def supports_window_functions?
|
1678
1683
|
server_version >= 80400
|
1679
1684
|
end
|
1680
1685
|
|
1686
|
+
# Base support added in 8.4, offset supported added in 9.0,
|
1687
|
+
# GROUPS and EXCLUDE support added in 11.0.
|
1688
|
+
def supports_window_function_frame_option?(option)
|
1689
|
+
case option
|
1690
|
+
when :rows, :range
|
1691
|
+
true
|
1692
|
+
when :offset
|
1693
|
+
server_version >= 90000
|
1694
|
+
when :groups, :exclude
|
1695
|
+
server_version >= 110000
|
1696
|
+
end
|
1697
|
+
end
|
1698
|
+
|
1681
1699
|
# Truncates the dataset. Returns nil.
|
1682
1700
|
#
|
1683
1701
|
# Options:
|
@@ -1702,13 +1720,6 @@ module Sequel
|
|
1702
1720
|
end
|
1703
1721
|
end
|
1704
1722
|
|
1705
|
-
# Return a clone of the dataset with an addition named window that can be
|
1706
|
-
# referenced in window functions. See Sequel::SQL::Window for a list of
|
1707
|
-
# options that can be passed in.
|
1708
|
-
def window(name, opts)
|
1709
|
-
clone(:window=>(@opts[:window]||[]) + [[name, SQL::Window.new(opts)]])
|
1710
|
-
end
|
1711
|
-
|
1712
1723
|
protected
|
1713
1724
|
|
1714
1725
|
# If returned primary keys are requested, use RETURNING unless already set on the
|
@@ -1900,23 +1911,6 @@ module Sequel
|
|
1900
1911
|
expression_list_append(sql, opts[:values])
|
1901
1912
|
end
|
1902
1913
|
|
1903
|
-
# SQL fragment for named window specifications
|
1904
|
-
def select_window_sql(sql)
|
1905
|
-
if ws = @opts[:window]
|
1906
|
-
sql << " WINDOW "
|
1907
|
-
c = false
|
1908
|
-
co = ', '
|
1909
|
-
as = ' AS '
|
1910
|
-
ws.map do |name, window|
|
1911
|
-
sql << co if c
|
1912
|
-
literal_append(sql, name)
|
1913
|
-
sql << as
|
1914
|
-
literal_append(sql, window)
|
1915
|
-
c ||= true
|
1916
|
-
end
|
1917
|
-
end
|
1918
|
-
end
|
1919
|
-
|
1920
1914
|
# Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
|
1921
1915
|
def select_with_sql_base
|
1922
1916
|
opts[:with].any?{|w| w[:recursive]} ? "WITH RECURSIVE " : super
|
@@ -233,7 +233,7 @@ module Sequel
|
|
233
233
|
|
234
234
|
module DatasetMethods
|
235
235
|
Dataset.def_sql_method(self, :insert, %w'insert into columns values')
|
236
|
-
Dataset.def_sql_method(self, :select, %w'with select distinct limit columns into from join where group having compounds order lock')
|
236
|
+
Dataset.def_sql_method(self, :select, %w'with select distinct limit columns into from join where group having window compounds order lock')
|
237
237
|
|
238
238
|
# Whether to convert smallint to boolean arguments for this dataset.
|
239
239
|
# Defaults to the IBMDB module setting.
|
@@ -275,6 +275,14 @@ module Sequel
|
|
275
275
|
false
|
276
276
|
end
|
277
277
|
|
278
|
+
def supports_window_clause?
|
279
|
+
true
|
280
|
+
end
|
281
|
+
|
282
|
+
def supports_window_functions?
|
283
|
+
true
|
284
|
+
end
|
285
|
+
|
278
286
|
# Uses CROSS APPLY to join the given table into the current dataset.
|
279
287
|
def cross_apply(table)
|
280
288
|
join_table(:cross_apply, table)
|
@@ -299,7 +299,7 @@ module Sequel
|
|
299
299
|
DATABASE_ERROR_REGEXPS = {
|
300
300
|
/(is|are) not unique\z|PRIMARY KEY must be unique\z|UNIQUE constraint failed: .+\z/ => UniqueConstraintViolation,
|
301
301
|
/foreign key constraint failed\z/i => ForeignKeyConstraintViolation,
|
302
|
-
/\
|
302
|
+
/\A(SQLITE ERROR 275 \(CONSTRAINT_CHECK\) : )?CHECK constraint failed/ => CheckConstraintViolation,
|
303
303
|
/\A(SQLITE ERROR 19 \(CONSTRAINT\) : )?constraint failed\z/ => ConstraintViolation,
|
304
304
|
/may not be NULL\z|NOT NULL constraint failed: .+\z/ => NotNullConstraintViolation,
|
305
305
|
/\ASQLITE ERROR \d+ \(\) : CHECK constraint failed: / => CheckConstraintViolation
|
@@ -500,7 +500,7 @@ module Sequel
|
|
500
500
|
EXTRACT_MAP.each_value(&:freeze)
|
501
501
|
|
502
502
|
Dataset.def_sql_method(self, :delete, [['if db.sqlite_version >= 30803', %w'with delete from where'], ["else", %w'delete from where']])
|
503
|
-
Dataset.def_sql_method(self, :insert, [['if db.sqlite_version >= 30803', %w'with insert conflict into columns values'], ["else", %w'insert conflict into columns values']])
|
503
|
+
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']])
|
504
504
|
Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'with values compounds'], ['else', %w'with select distinct columns from join where group having compounds order limit lock']])
|
505
505
|
Dataset.def_sql_method(self, :update, [['if db.sqlite_version >= 30803', %w'with update table set where'], ["else", %w'update table set where']])
|
506
506
|
|
@@ -620,6 +620,14 @@ module Sequel
|
|
620
620
|
# supports the following conflict resolution algoriths: ROLLBACK, ABORT,
|
621
621
|
# FAIL, IGNORE and REPLACE.
|
622
622
|
#
|
623
|
+
# On SQLite 3.24.0+, you can pass a hash to use an ON CONFLICT clause.
|
624
|
+
# With out :update option, uses ON CONFLICT DO NOTHING. Options:
|
625
|
+
#
|
626
|
+
# :conflict_where :: The index filter, when using a partial index to determine uniqueness.
|
627
|
+
# :target :: The column name or expression to handle uniqueness violations on.
|
628
|
+
# :update :: A hash of columns and values to set. Uses ON CONFLICT DO UPDATE.
|
629
|
+
# :update_where :: A WHERE condition to use for the update.
|
630
|
+
#
|
623
631
|
# Examples:
|
624
632
|
#
|
625
633
|
# DB[:table].insert_conflict.insert(a: 1, b: 2)
|
@@ -627,11 +635,39 @@ module Sequel
|
|
627
635
|
#
|
628
636
|
# DB[:table].insert_conflict(:replace).insert(a: 1, b: 2)
|
629
637
|
# # INSERT OR REPLACE INTO TABLE (a, b) VALUES (1, 2)
|
630
|
-
|
631
|
-
|
632
|
-
|
638
|
+
#
|
639
|
+
# DB[:table].insert_conflict({}).insert(a: 1, b: 2)
|
640
|
+
# # INSERT INTO TABLE (a, b) VALUES (1, 2)
|
641
|
+
# # ON CONFLICT DO NOTHING
|
642
|
+
#
|
643
|
+
# DB[:table].insert_conflict(target: :a).insert(a: 1, b: 2)
|
644
|
+
# # INSERT INTO TABLE (a, b) VALUES (1, 2)
|
645
|
+
# # ON CONFLICT (a) DO NOTHING
|
646
|
+
#
|
647
|
+
# DB[:table].insert_conflict(target: :a, conflict_where: {c: true}).insert(a: 1, b: 2)
|
648
|
+
# # INSERT INTO TABLE (a, b) VALUES (1, 2)
|
649
|
+
# # ON CONFLICT (a) WHERE (c IS TRUE) DO NOTHING
|
650
|
+
#
|
651
|
+
# DB[:table].insert_conflict(target: :a, update: {b: Sequel[:excluded][:b]}).insert(a: 1, b: 2)
|
652
|
+
# # INSERT INTO TABLE (a, b) VALUES (1, 2)
|
653
|
+
# # ON CONFLICT (a) DO UPDATE SET b = excluded.b
|
654
|
+
#
|
655
|
+
# DB[:table].insert_conflict(target: :a,
|
656
|
+
# update: {b: Sequel[:excluded][:b]}, update_where: {Sequel[:table][:status_id] => 1}).insert(a: 1, b: 2)
|
657
|
+
# # INSERT INTO TABLE (a, b) VALUES (1, 2)
|
658
|
+
# # ON CONFLICT (a) DO UPDATE SET b = excluded.b WHERE (table.status_id = 1)
|
659
|
+
def insert_conflict(opts = :ignore)
|
660
|
+
case opts
|
661
|
+
when Symbol, String
|
662
|
+
unless INSERT_CONFLICT_RESOLUTIONS.include?(opts.to_s.upcase)
|
663
|
+
raise Error, "Invalid symbol or string passed to Dataset#insert_conflict: #{opts.inspect}. The allowed values are: :rollback, :abort, :fail, :ignore, or :replace"
|
664
|
+
end
|
665
|
+
clone(:insert_conflict => opts)
|
666
|
+
when Hash
|
667
|
+
clone(:insert_on_conflict => opts)
|
668
|
+
else
|
669
|
+
raise Error, "Invalid value passed to Dataset#insert_conflict: #{opts.inspect}, should use a symbol or a hash"
|
633
670
|
end
|
634
|
-
clone(:insert_conflict => resolution)
|
635
671
|
end
|
636
672
|
|
637
673
|
# Ignore uniqueness/exclusion violations when inserting, using INSERT OR IGNORE.
|
@@ -729,6 +765,36 @@ module Sequel
|
|
729
765
|
end
|
730
766
|
end
|
731
767
|
|
768
|
+
# Add ON CONFLICT clause if it should be used
|
769
|
+
def insert_on_conflict_sql(sql)
|
770
|
+
if opts = @opts[:insert_on_conflict]
|
771
|
+
sql << " ON CONFLICT"
|
772
|
+
|
773
|
+
if target = opts[:constraint]
|
774
|
+
sql << " ON CONSTRAINT "
|
775
|
+
identifier_append(sql, target)
|
776
|
+
elsif target = opts[:target]
|
777
|
+
sql << ' '
|
778
|
+
identifier_append(sql, Array(target))
|
779
|
+
if conflict_where = opts[:conflict_where]
|
780
|
+
sql << " WHERE "
|
781
|
+
literal_append(sql, conflict_where)
|
782
|
+
end
|
783
|
+
end
|
784
|
+
|
785
|
+
if values = opts[:update]
|
786
|
+
sql << " DO UPDATE SET "
|
787
|
+
update_sql_values_hash(sql, values)
|
788
|
+
if update_where = opts[:update_where]
|
789
|
+
sql << " WHERE "
|
790
|
+
literal_append(sql, update_where)
|
791
|
+
end
|
792
|
+
else
|
793
|
+
sql << " DO NOTHING"
|
794
|
+
end
|
795
|
+
end
|
796
|
+
end
|
797
|
+
|
732
798
|
# SQLite uses a preceding X for hex escaping strings
|
733
799
|
def literal_blob_append(sql, v)
|
734
800
|
sql << "X'" << v.unpack("H*").first << "'"
|
@@ -178,11 +178,29 @@ module Sequel
|
|
178
178
|
true
|
179
179
|
end
|
180
180
|
|
181
|
+
# Whether the dataset supports the WINDOW clause to define windows used by multiple
|
182
|
+
# window functions, false by default.
|
183
|
+
def supports_window_clause?
|
184
|
+
false
|
185
|
+
end
|
186
|
+
|
181
187
|
# Whether the dataset supports window functions, false by default.
|
182
188
|
def supports_window_functions?
|
183
189
|
false
|
184
190
|
end
|
185
191
|
|
192
|
+
# Whether the dataset supports the given window function option. True by default.
|
193
|
+
# This should only be called if supports_window_functions? is true. Possible options
|
194
|
+
# are :rows, :range, :groups, :offset, :exclude.
|
195
|
+
def supports_window_function_frame_option?(option)
|
196
|
+
case option
|
197
|
+
when :rows, :range, :offset
|
198
|
+
true
|
199
|
+
else
|
200
|
+
false
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
186
204
|
# Whether the dataset supports WHERE TRUE (or WHERE 1 for databases that
|
187
205
|
# that use 1 for true), true by default.
|
188
206
|
def supports_where_true?
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -82,6 +82,8 @@ module Sequel
|
|
82
82
|
# columns are deleted. This method should generally not be called
|
83
83
|
# directly by user code.
|
84
84
|
def clone(opts = (return self; nil))
|
85
|
+
# return self used above because clone is called by almost all
|
86
|
+
# other query methods, and it is the fastest approach
|
85
87
|
c = super(:freeze=>false)
|
86
88
|
c.opts.merge!(opts)
|
87
89
|
unless opts.each_key{|o| break if COLUMN_CHANGE_OPTS.include?(o)}
|
@@ -1044,6 +1046,16 @@ module Sequel
|
|
1044
1046
|
add_filter(:where, cond, &block)
|
1045
1047
|
end
|
1046
1048
|
|
1049
|
+
# Return a clone of the dataset with an addition named window that can be
|
1050
|
+
# referenced in window functions. See Sequel::SQL::Window for a list of
|
1051
|
+
# options that can be passed in. Example:
|
1052
|
+
#
|
1053
|
+
# DB[:items].window(:w, :partition=>:c1, :order=>:c2)
|
1054
|
+
# # SELECT * FROM items WINDOW w AS (PARTITION BY c1 ORDER BY c2)
|
1055
|
+
def window(name, opts)
|
1056
|
+
clone(:window=>((@opts[:window]||EMPTY_ARRAY) + [[name, SQL::Window.new(opts)].freeze]).freeze)
|
1057
|
+
end
|
1058
|
+
|
1047
1059
|
# Add a common table expression (CTE) with the given name and a dataset that defines the CTE.
|
1048
1060
|
# A common table expression acts as an inline view for the query.
|
1049
1061
|
# Options:
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -718,41 +718,86 @@ module Sequel
|
|
718
718
|
# Append literalization of windows (for window functions) to SQL string.
|
719
719
|
def window_sql_append(sql, opts)
|
720
720
|
raise(Error, 'This dataset does not support window functions') unless supports_window_functions?
|
721
|
-
sql << '('
|
722
|
-
window, part, order, frame = opts.values_at(:window, :partition, :order, :frame)
|
723
721
|
space = false
|
724
722
|
space_s = ' '
|
725
|
-
|
723
|
+
|
724
|
+
sql << '('
|
725
|
+
|
726
|
+
if window = opts[:window]
|
726
727
|
literal_append(sql, window)
|
727
728
|
space = true
|
728
729
|
end
|
729
|
-
|
730
|
+
|
731
|
+
if part = opts[:partition]
|
730
732
|
sql << space_s if space
|
731
733
|
sql << "PARTITION BY "
|
732
734
|
expression_list_append(sql, Array(part))
|
733
735
|
space = true
|
734
736
|
end
|
735
|
-
|
737
|
+
|
738
|
+
if order = opts[:order]
|
736
739
|
sql << space_s if space
|
737
740
|
sql << "ORDER BY "
|
738
741
|
expression_list_append(sql, Array(order))
|
739
742
|
space = true
|
740
743
|
end
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
sql << "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING"
|
747
|
-
when :rows
|
748
|
-
sql << space_s if space
|
749
|
-
sql << "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
|
750
|
-
when String
|
751
|
-
sql << space_s if space
|
744
|
+
|
745
|
+
if frame = opts[:frame]
|
746
|
+
sql << space_s if space
|
747
|
+
|
748
|
+
if frame.is_a?(String)
|
752
749
|
sql << frame
|
753
750
|
else
|
754
|
-
|
751
|
+
case frame
|
752
|
+
when :all
|
753
|
+
frame_type = :rows
|
754
|
+
frame_start = :preceding
|
755
|
+
frame_end = :following
|
756
|
+
when :rows, :range, :groups
|
757
|
+
frame_type = frame
|
758
|
+
frame_start = :preceding
|
759
|
+
frame_end = :current
|
760
|
+
when Hash
|
761
|
+
frame_type = frame[:type]
|
762
|
+
unless frame_type == :rows || frame_type == :range || frame_type == :groups
|
763
|
+
raise Error, "invalid window :frame :type option: #{frame_type.inspect}"
|
764
|
+
end
|
765
|
+
unless frame_start = frame[:start]
|
766
|
+
raise Error, "invalid window :frame :start option: #{frame_start.inspect}"
|
767
|
+
end
|
768
|
+
frame_end = frame[:end]
|
769
|
+
frame_exclude = frame[:exclude]
|
770
|
+
else
|
771
|
+
raise Error, "invalid window :frame option: #{frame.inspect}"
|
772
|
+
end
|
773
|
+
|
774
|
+
sql << frame_type.to_s.upcase << " "
|
775
|
+
sql << 'BETWEEN ' if frame_end
|
776
|
+
window_frame_boundary_sql_append(sql, frame_start, :preceding)
|
777
|
+
if frame_end
|
778
|
+
sql << " AND "
|
779
|
+
window_frame_boundary_sql_append(sql, frame_end, :following)
|
780
|
+
end
|
781
|
+
|
782
|
+
if frame_exclude
|
783
|
+
sql << " EXCLUDE "
|
784
|
+
|
785
|
+
case frame_exclude
|
786
|
+
when :current
|
787
|
+
sql << "CURRENT ROW"
|
788
|
+
when :group
|
789
|
+
sql << "GROUP"
|
790
|
+
when :ties
|
791
|
+
sql << "TIES"
|
792
|
+
when :no_others
|
793
|
+
sql << "NO OTHERS"
|
794
|
+
else
|
795
|
+
raise Error, "invalid window :frame :exclude option: #{frame_exclude.inspect}"
|
796
|
+
end
|
797
|
+
end
|
798
|
+
end
|
755
799
|
end
|
800
|
+
|
756
801
|
sql << ')'
|
757
802
|
end
|
758
803
|
|
@@ -1412,6 +1457,22 @@ module Sequel
|
|
1412
1457
|
alias delete_where_sql select_where_sql
|
1413
1458
|
alias update_where_sql select_where_sql
|
1414
1459
|
|
1460
|
+
def select_window_sql(sql)
|
1461
|
+
if ws = @opts[:window]
|
1462
|
+
sql << " WINDOW "
|
1463
|
+
c = false
|
1464
|
+
co = ', '
|
1465
|
+
as = ' AS '
|
1466
|
+
ws.map do |name, window|
|
1467
|
+
sql << co if c
|
1468
|
+
literal_append(sql, name)
|
1469
|
+
sql << as
|
1470
|
+
literal_append(sql, window)
|
1471
|
+
c ||= true
|
1472
|
+
end
|
1473
|
+
end
|
1474
|
+
end
|
1475
|
+
|
1415
1476
|
def select_with_sql(sql)
|
1416
1477
|
return unless supports_cte?
|
1417
1478
|
ws = opts[:with]
|
@@ -1535,5 +1596,35 @@ module Sequel
|
|
1535
1596
|
def update_update_sql(sql)
|
1536
1597
|
sql << 'UPDATE'
|
1537
1598
|
end
|
1599
|
+
|
1600
|
+
def window_frame_boundary_sql_append(sql, boundary, direction)
|
1601
|
+
case boundary
|
1602
|
+
when :current
|
1603
|
+
sql << "CURRENT ROW"
|
1604
|
+
when :preceding
|
1605
|
+
sql << "UNBOUNDED PRECEDING"
|
1606
|
+
when :following
|
1607
|
+
sql << "UNBOUNDED FOLLOWING"
|
1608
|
+
else
|
1609
|
+
if boundary.is_a?(Array)
|
1610
|
+
offset, direction = boundary
|
1611
|
+
unless boundary.length == 2 && (direction == :preceding || direction == :following)
|
1612
|
+
raise Error, "invalid window :frame boundary (:start or :end) option: #{boundary.inspect}"
|
1613
|
+
end
|
1614
|
+
else
|
1615
|
+
offset = boundary
|
1616
|
+
end
|
1617
|
+
|
1618
|
+
case offset
|
1619
|
+
when Numeric, String, SQL::Cast
|
1620
|
+
# nothing
|
1621
|
+
else
|
1622
|
+
raise Error, "invalid window :frame boundary (:start or :end) option: #{boundary.inspect}"
|
1623
|
+
end
|
1624
|
+
|
1625
|
+
literal_append(sql, offset)
|
1626
|
+
sql << (direction == :preceding ? " PRECEDING" : " FOLLOWING")
|
1627
|
+
end
|
1628
|
+
end
|
1538
1629
|
end
|
1539
1630
|
end
|
@@ -27,7 +27,7 @@ module Sequel
|
|
27
27
|
def eval_inspect(obj)
|
28
28
|
case obj
|
29
29
|
when BigDecimal
|
30
|
-
"BigDecimal(#{obj.to_s.inspect})"
|
30
|
+
"Kernel::BigDecimal(#{obj.to_s.inspect})"
|
31
31
|
when Sequel::SQL::Blob, Sequel::LiteralString
|
32
32
|
"#{obj.class}.new(#{obj.to_s.inspect})"
|
33
33
|
when Sequel::SQL::ValueList
|
data/lib/sequel/plugins/list.rb
CHANGED
@@ -124,8 +124,10 @@ module Sequel
|
|
124
124
|
move_to(position_value + n)
|
125
125
|
end
|
126
126
|
|
127
|
-
# Move this instance to the given place in the list.
|
128
|
-
#
|
127
|
+
# Move this instance to the given place in the list. If lp is not
|
128
|
+
# given or greater than the last list position, uses the last list
|
129
|
+
# position. If lp is less than the top list position, uses the
|
130
|
+
# top list position.
|
129
131
|
def move_to(target, lp = nil)
|
130
132
|
current = position_value
|
131
133
|
if target != current
|
@@ -24,6 +24,7 @@ module Sequel
|
|
24
24
|
# * Primary key lookups (e.g. Model[1])
|
25
25
|
# * Model.all
|
26
26
|
# * Model.each
|
27
|
+
# * Model.first (without block, only supporting no arguments or single integer argument)
|
27
28
|
# * Model.count (without an argument or block)
|
28
29
|
# * Model.map
|
29
30
|
# * Model.as_hash
|
@@ -80,6 +81,20 @@ module Sequel
|
|
80
81
|
end
|
81
82
|
end
|
82
83
|
|
84
|
+
# If a block is given, multiple arguments are given, or a single
|
85
|
+
# non-Integer argument is given, performs the default behavior of
|
86
|
+
# issuing a database query. Otherwise, uses the cached values
|
87
|
+
# to return either the first cached instance (no arguments) or an
|
88
|
+
# array containing the number of instances specified (single integer
|
89
|
+
# argument).
|
90
|
+
def first(*args)
|
91
|
+
if block_given? || args.length > 1 || (args.length == 1 && !args[0].is_a?(Integer))
|
92
|
+
super
|
93
|
+
else
|
94
|
+
@all.first(*args)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
83
98
|
# Get the number of records in the cache, without issuing a database query.
|
84
99
|
def count(*a, &block)
|
85
100
|
if a.empty? && !block
|
data/lib/sequel/sql.rb
CHANGED
@@ -1903,16 +1903,40 @@ module Sequel
|
|
1903
1903
|
# # (PARTITION BY col7 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
|
1904
1904
|
# Sequel::SQL::Window.new(partition: :col7, frame: :rows)
|
1905
1905
|
# # (PARTITION BY col7 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
|
1906
|
-
# Sequel::SQL::Window.new(partition: :col7, frame:
|
1906
|
+
# Sequel::SQL::Window.new(partition: :col7, frame: {type: :range, start: current})
|
1907
1907
|
# # (PARTITION BY col7 RANGE CURRENT ROW)
|
1908
|
+
# Sequel::SQL::Window.new(partition: :col7, frame: {type: :range, start: 1, end: 1})
|
1909
|
+
# # (PARTITION BY col7 RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING)
|
1910
|
+
# Sequel::SQL::Window.new(partition: :col7, frame: {type: :range, start: 2, end: [1, :preceding]})
|
1911
|
+
# # (PARTITION BY col7 RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING)
|
1912
|
+
# Sequel::SQL::Window.new(partition: :col7, frame: {type: :range, start: 1, end: [2, :following]})
|
1913
|
+
# # (PARTITION BY col7 RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING)
|
1914
|
+
# Sequel::SQL::Window.new(partition: :col7, frame: {type: :range, start: :preceding, exclude: :current})
|
1915
|
+
# # (PARTITION BY col7 RANGE UNBOUNDED PRECEDING EXCLUDE CURRENT ROW)
|
1908
1916
|
#
|
1909
1917
|
# Sequel::SQL::Window.new(window: :named_window) # you can create a named window with Dataset#window
|
1910
1918
|
# # (named_window)
|
1911
1919
|
class Window < Expression
|
1912
1920
|
# The options for this window. Options currently supported:
|
1913
|
-
# :frame :: if specified, should be :all, :rows,
|
1914
|
-
#
|
1915
|
-
#
|
1921
|
+
# :frame :: if specified, should be :all, :rows, :range, :groups, a String, or a Hash.
|
1922
|
+
# :all :: Always operates over all rows in the partition
|
1923
|
+
# :rows :: Includes rows in the partition up to and including the current row
|
1924
|
+
# :range, :groups :: Includes rows in the partition up to and including the current group
|
1925
|
+
# String :: Used as literal SQL code, try to avoid
|
1926
|
+
# Hash :: Hash of options for the frame:
|
1927
|
+
# :type :: The type of frame, must be :rows, :range, or :groups (required)
|
1928
|
+
# :start :: The start of the frame (required). Possible values:
|
1929
|
+
# :preceding :: UNBOUNDED PRECEDING
|
1930
|
+
# :following :: UNBOUNDED FOLLOWING
|
1931
|
+
# :current :: CURRENT ROW
|
1932
|
+
# String, Numeric, or Cast :: Used as the offset of rows/values preceding
|
1933
|
+
# Array :: Must have two elements, with first element being String, Numeric, or
|
1934
|
+
# Cast and second element being :preceding or :following
|
1935
|
+
# :end :: The end of the frame. Can be left out. If present, takes the same values as
|
1936
|
+
# :start, except that when a String, Numeric, or Hash, it is used as the offset
|
1937
|
+
# for rows following
|
1938
|
+
# :exclude :: Which rows to exclude. Possible values are :current, :ties, :group
|
1939
|
+
# :no_others.
|
1916
1940
|
# :order :: order on the column(s) given
|
1917
1941
|
# :partition :: partition/group on the column(s) given
|
1918
1942
|
# :window :: base results on a previously specified named window
|
data/lib/sequel/version.rb
CHANGED
@@ -3,9 +3,11 @@
|
|
3
3
|
module Sequel
|
4
4
|
# The major version of Sequel. Only bumped for major changes.
|
5
5
|
MAJOR = 5
|
6
|
+
|
6
7
|
# The minor version of Sequel. Bumped for every non-patch level
|
7
8
|
# release, generally around once a month.
|
8
|
-
MINOR =
|
9
|
+
MINOR = 11
|
10
|
+
|
9
11
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
10
12
|
# releases that fix regressions from previous versions.
|
11
13
|
TINY = 0
|
@@ -462,6 +462,12 @@ describe "A PostgreSQL database " do
|
|
462
462
|
DB.drop_table?(:b, :a)
|
463
463
|
end
|
464
464
|
|
465
|
+
it "should handle non-ASCII column aliases" do
|
466
|
+
s = String.new("\u00E4").force_encoding(DB.get('a').encoding)
|
467
|
+
k, v = DB.select(Sequel.as(s, s)).first.shift
|
468
|
+
k.to_s.must_equal v
|
469
|
+
end
|
470
|
+
|
465
471
|
it "should parse foreign keys referencing current table using :reverse option" do
|
466
472
|
DB.create_table!(:a) do
|
467
473
|
primary_key :id
|
@@ -1665,36 +1671,6 @@ if DB.server_version >= 80300
|
|
1665
1671
|
end
|
1666
1672
|
end
|
1667
1673
|
|
1668
|
-
if DB.dataset.supports_window_functions?
|
1669
|
-
describe "Postgres::Dataset named windows" do
|
1670
|
-
before do
|
1671
|
-
@db = DB
|
1672
|
-
@db.create_table!(:i1){Integer :id; Integer :group_id; Integer :amount}
|
1673
|
-
@ds = @db[:i1].order(:id)
|
1674
|
-
@ds.insert(:id=>1, :group_id=>1, :amount=>1)
|
1675
|
-
@ds.insert(:id=>2, :group_id=>1, :amount=>10)
|
1676
|
-
@ds.insert(:id=>3, :group_id=>1, :amount=>100)
|
1677
|
-
@ds.insert(:id=>4, :group_id=>2, :amount=>1000)
|
1678
|
-
@ds.insert(:id=>5, :group_id=>2, :amount=>10000)
|
1679
|
-
@ds.insert(:id=>6, :group_id=>2, :amount=>100000)
|
1680
|
-
end
|
1681
|
-
after do
|
1682
|
-
@db.drop_table?(:i1)
|
1683
|
-
end
|
1684
|
-
|
1685
|
-
it "should give correct results for window functions" do
|
1686
|
-
@ds.window(:win, :partition=>:group_id, :order=>:id).select(:id){sum(:amount).over(:window=>win)}.all.
|
1687
|
-
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
1688
|
-
@ds.window(:win, :partition=>:group_id).select(:id){sum(:amount).over(:window=>win, :order=>id)}.all.
|
1689
|
-
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
1690
|
-
@ds.window(:win, {}).select(:id){sum(:amount).over(:window=>:win, :order=>id)}.all.
|
1691
|
-
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>11111, :id=>5}, {:sum=>111111, :id=>6}]
|
1692
|
-
@ds.window(:win, :partition=>:group_id).select(:id){sum(:amount).over(:window=>:win, :order=>id, :frame=>:all)}.all.
|
1693
|
-
must_equal [{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>111000, :id=>6}]
|
1694
|
-
end
|
1695
|
-
end
|
1696
|
-
end
|
1697
|
-
|
1698
1674
|
describe "Postgres::Database functions, languages, schemas, and triggers" do
|
1699
1675
|
before do
|
1700
1676
|
@d = DB
|
@@ -3575,16 +3551,18 @@ describe 'PostgreSQL row-valued/composite types' do
|
|
3575
3551
|
Sequel.extension :pg_array_ops, :pg_row_ops
|
3576
3552
|
@ds = @db[:person]
|
3577
3553
|
|
3578
|
-
@db.
|
3554
|
+
@db.drop_table?(:company, :person, :address)
|
3555
|
+
|
3556
|
+
@db.create_table(:address) do
|
3579
3557
|
String :street
|
3580
3558
|
String :city
|
3581
3559
|
String :zip
|
3582
3560
|
end
|
3583
|
-
@db.create_table
|
3561
|
+
@db.create_table(:person) do
|
3584
3562
|
Integer :id
|
3585
3563
|
address :address
|
3586
3564
|
end
|
3587
|
-
@db.create_table
|
3565
|
+
@db.create_table(:company) do
|
3588
3566
|
Integer :id
|
3589
3567
|
column :employees, 'person[]'
|
3590
3568
|
end
|
@@ -598,3 +598,55 @@ describe "A SQLite database" do
|
|
598
598
|
@db.indexes(:test2).values.first[:columns].must_equal [:name]
|
599
599
|
end if DB.sqlite_version >= 30808
|
600
600
|
end
|
601
|
+
|
602
|
+
describe "SQLite", 'INSERT ON CONFLICT' do
|
603
|
+
before(:all) do
|
604
|
+
@db = DB
|
605
|
+
@db.create_table!(:ic_test){Integer :a; Integer :b; Integer :c; TrueClass :c_is_unique, :default=>false; unique :a, :name=>:ic_test_a_uidx; unique [:b, :c], :name=>:ic_test_b_c_uidx; index [:c], :where=>:c_is_unique, :unique=>true}
|
606
|
+
@ds = @db[:ic_test]
|
607
|
+
end
|
608
|
+
before do
|
609
|
+
@ds.delete
|
610
|
+
end
|
611
|
+
after(:all) do
|
612
|
+
@db.drop_table?(:ic_test)
|
613
|
+
end
|
614
|
+
|
615
|
+
it "Dataset#insert_ignore and insert_conflict should ignore uniqueness violations" do
|
616
|
+
@ds.insert(1, 2, 3, false)
|
617
|
+
@ds.insert(10, 11, 3, true)
|
618
|
+
proc{@ds.insert(1, 3, 4, false)}.must_raise Sequel::UniqueConstraintViolation
|
619
|
+
proc{@ds.insert(11, 12, 3, true)}.must_raise Sequel::UniqueConstraintViolation
|
620
|
+
@ds.insert_ignore.insert(1, 3, 4, false)
|
621
|
+
@ds.insert_conflict.insert(1, 3, 4, false)
|
622
|
+
@ds.insert_conflict.insert(11, 12, 3, true)
|
623
|
+
@ds.insert_conflict(:target=>:a).insert(1, 3, 4, false)
|
624
|
+
@ds.insert_conflict(:target=>:c, :conflict_where=>:c_is_unique).insert(11, 12, 3, true)
|
625
|
+
@ds.all.must_equal [{:a=>1, :b=>2, :c=>3, :c_is_unique=>false}, {:a=>10, :b=>11, :c=>3, :c_is_unique=>true}]
|
626
|
+
end
|
627
|
+
|
628
|
+
it "Dataset#insert_ignore and insert_conflict should work with multi_insert/import" do
|
629
|
+
@ds.insert(1, 2, 3, false)
|
630
|
+
@ds.insert_ignore.multi_insert([{:a=>1, :b=>3, :c=>4}])
|
631
|
+
@ds.insert_ignore.import([:a, :b, :c], [[1, 3, 4]])
|
632
|
+
@ds.all.must_equal [{:a=>1, :b=>2, :c=>3, :c_is_unique=>false}]
|
633
|
+
@ds.insert_conflict(:target=>:a, :update=>{:b=>3}).import([:a, :b, :c], [[1, 3, 4]])
|
634
|
+
@ds.all.must_equal [{:a=>1, :b=>3, :c=>3, :c_is_unique=>false}]
|
635
|
+
@ds.insert_conflict(:target=>:a, :update=>{:b=>4}).multi_insert([{:a=>1, :b=>5, :c=>6}])
|
636
|
+
@ds.all.must_equal [{:a=>1, :b=>4, :c=>3, :c_is_unique=>false}]
|
637
|
+
end
|
638
|
+
|
639
|
+
it "Dataset#insert_conflict should handle upserts" do
|
640
|
+
@ds.insert(1, 2, 3, false)
|
641
|
+
@ds.insert_conflict(:target=>:a, :update=>{:b=>3}).insert(1, 3, 4, false)
|
642
|
+
@ds.all.must_equal [{:a=>1, :b=>3, :c=>3, :c_is_unique=>false}]
|
643
|
+
@ds.insert_conflict(:target=>[:b, :c], :update=>{:c=>5}).insert(5, 3, 3, false)
|
644
|
+
@ds.all.must_equal [{:a=>1, :b=>3, :c=>5, :c_is_unique=>false}]
|
645
|
+
@ds.insert_conflict(:target=>:a, :update=>{:b=>4}).insert(1, 3, nil, false)
|
646
|
+
@ds.all.must_equal [{:a=>1, :b=>4, :c=>5, :c_is_unique=>false}]
|
647
|
+
@ds.insert_conflict(:target=>:a, :update=>{:b=>5}, :update_where=>{Sequel[:ic_test][:b]=>4}).insert(1, 3, 4, false)
|
648
|
+
@ds.all.must_equal [{:a=>1, :b=>5, :c=>5, :c_is_unique=>false}]
|
649
|
+
@ds.insert_conflict(:target=>:a, :update=>{:b=>6}, :update_where=>{Sequel[:ic_test][:b]=>4}).insert(1, 3, 4, false)
|
650
|
+
@ds.all.must_equal [{:a=>1, :b=>5, :c=>5, :c_is_unique=>false}]
|
651
|
+
end
|
652
|
+
end if DB.sqlite_version >= 32400
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -4189,7 +4189,7 @@ describe "Sequel::Dataset#qualify" do
|
|
4189
4189
|
end
|
4190
4190
|
end
|
4191
4191
|
|
4192
|
-
describe "
|
4192
|
+
describe "Dataset#with and #with_recursive" do
|
4193
4193
|
before do
|
4194
4194
|
@db = Sequel.mock
|
4195
4195
|
@ds = @db[:t].with_extend{def supports_cte?(*) true end}
|
@@ -4251,6 +4251,27 @@ describe "Sequel::Dataset #with and #with_recursive" do
|
|
4251
4251
|
end
|
4252
4252
|
end
|
4253
4253
|
|
4254
|
+
describe "Dataset#window" do
|
4255
|
+
before do
|
4256
|
+
@db = Sequel.mock
|
4257
|
+
@ds = @db[:t].with_extend do
|
4258
|
+
Sequel::Dataset.def_sql_method(self, :select, %w'select columns from window')
|
4259
|
+
def supports_window_clause?; true end
|
4260
|
+
def supports_window_functions?; true end
|
4261
|
+
end
|
4262
|
+
end
|
4263
|
+
|
4264
|
+
it "should not support window clause by default" do
|
4265
|
+
@db.dataset.supports_window_clause?.must_equal false
|
4266
|
+
end
|
4267
|
+
|
4268
|
+
it "should take a name and hash of window options" do
|
4269
|
+
ds = @ds.window(:w, :partition=>:a, :order=>:b)
|
4270
|
+
ds.sql.must_equal 'SELECT * FROM t WINDOW w AS (PARTITION BY a ORDER BY b)'
|
4271
|
+
ds.window(:w2, :partition=>:c, :order=>:d).sql.must_equal 'SELECT * FROM t WINDOW w AS (PARTITION BY a ORDER BY b), w2 AS (PARTITION BY c ORDER BY d)'
|
4272
|
+
end
|
4273
|
+
end
|
4274
|
+
|
4254
4275
|
describe Sequel::SQL::Constants do
|
4255
4276
|
before do
|
4256
4277
|
@db = Sequel::Database.new
|
@@ -583,18 +583,75 @@ describe Sequel::SQL::VirtualRow do
|
|
583
583
|
@d.l{count.function.*.over}.must_equal 'count(*) OVER ()'
|
584
584
|
end
|
585
585
|
|
586
|
-
it "should handle method.function.over(:frame=>:all) as a window function call" do
|
586
|
+
it "should handle method.function.over(:frame=>:all) as a window function call with frame for all rows" do
|
587
587
|
@d.l{rank.function.over(:frame=>:all)}.must_equal 'rank() OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)'
|
588
588
|
end
|
589
589
|
|
590
|
-
it "should handle method.function.over(:frame=>:rows) as a window function call" do
|
590
|
+
it "should handle method.function.over(:frame=>:rows) as a window function call with frame for all rows before current row" do
|
591
591
|
@d.l{rank.function.over(:frame=>:rows)}.must_equal 'rank() OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)'
|
592
592
|
end
|
593
593
|
|
594
|
-
it "should handle method.function.over(:frame
|
594
|
+
it "should handle method.function.over(:frame=>:groups) as a window function call with frame for all groups before current row" do
|
595
|
+
@d.l{rank.function.over(:frame=>:groups)}.must_equal 'rank() OVER (GROUPS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)'
|
596
|
+
end
|
597
|
+
|
598
|
+
it "should handle method.function.over(:frame=>:range) as a window function call with frame for all groups before current row" do
|
599
|
+
@d.l{rank.function.over(:frame=>:range)}.must_equal 'rank() OVER (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)'
|
600
|
+
end
|
601
|
+
|
602
|
+
it "should handle window function with :frame hash argument with :type option" do
|
603
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding})}.must_equal 'rank() OVER (ROWS UNBOUNDED PRECEDING)'
|
604
|
+
@d.l{rank.function.over(:frame=>{:type=>:range, :start=>:preceding})}.must_equal 'rank() OVER (RANGE UNBOUNDED PRECEDING)'
|
605
|
+
@d.l{rank.function.over(:frame=>{:type=>:groups, :start=>:preceding})}.must_equal 'rank() OVER (GROUPS UNBOUNDED PRECEDING)'
|
606
|
+
end
|
607
|
+
|
608
|
+
it "should handle window function with :frame hash argument with :start option" do
|
609
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding})}.must_equal 'rank() OVER (ROWS UNBOUNDED PRECEDING)'
|
610
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:following})}.must_equal 'rank() OVER (ROWS UNBOUNDED FOLLOWING)'
|
611
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:current})}.must_equal 'rank() OVER (ROWS CURRENT ROW)'
|
612
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>1})}.must_equal 'rank() OVER (ROWS 1 PRECEDING)'
|
613
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>[1, :following]})}.must_equal 'rank() OVER (ROWS 1 FOLLOWING)'
|
614
|
+
end
|
615
|
+
|
616
|
+
it "should handle window function with :frame hash argument with :end option" do
|
617
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding, :end=>:preceding})}.must_equal 'rank() OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED PRECEDING)'
|
618
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding, :end=>:following})}.must_equal 'rank() OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)'
|
619
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding, :end=>:current})}.must_equal 'rank() OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)'
|
620
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding, :end=>1})}.must_equal 'rank() OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING)'
|
621
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding, :end=>[1, :preceding]})}.must_equal 'rank() OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)'
|
622
|
+
end
|
623
|
+
|
624
|
+
it "should handle window function with :frame hash argument with :exclude option" do
|
625
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding,:exclude=>:current})}.must_equal 'rank() OVER (ROWS UNBOUNDED PRECEDING EXCLUDE CURRENT ROW)'
|
626
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding, :exclude=>:group})}.must_equal 'rank() OVER (ROWS UNBOUNDED PRECEDING EXCLUDE GROUP)'
|
627
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding, :exclude=>:ties})}.must_equal 'rank() OVER (ROWS UNBOUNDED PRECEDING EXCLUDE TIES)'
|
628
|
+
@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding, :exclude=>:no_others})}.must_equal 'rank() OVER (ROWS UNBOUNDED PRECEDING EXCLUDE NO OTHERS)'
|
629
|
+
end
|
630
|
+
|
631
|
+
it "should handle window function with :frame hash argument with invalid options" do
|
632
|
+
proc{@d.l{rank.function.over(:frame=>{:type=>:blah, :start=>:preceding})}}.must_raise Sequel::Error
|
633
|
+
proc{@d.l{rank.function.over(:frame=>{:type=>:rows})}}.must_raise Sequel::Error
|
634
|
+
proc{@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:blah})}}.must_raise Sequel::Error
|
635
|
+
proc{@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>[1, :blah]})}}.must_raise Sequel::Error
|
636
|
+
proc{@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>[1, :preceding, 3]})}}.must_raise Sequel::Error
|
637
|
+
proc{@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding, :end=>:blah})}}.must_raise Sequel::Error
|
638
|
+
proc{@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding, :end=>[1, :blah]})}}.must_raise Sequel::Error
|
639
|
+
proc{@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding, :end=>[1, :following, 3]})}}.must_raise Sequel::Error
|
640
|
+
proc{@d.l{rank.function.over(:frame=>{:type=>:rows, :start=>:preceding, :exclude=>:blah})}}.must_raise Sequel::Error
|
641
|
+
end
|
642
|
+
|
643
|
+
it "should handle method.function.over(:frame=>'some string') as a window function call with explicit frame" do
|
595
644
|
@d.l{rank.function.over(:frame=>'RANGE BETWEEN 3 PRECEDING AND CURRENT ROW')}.must_equal 'rank() OVER (RANGE BETWEEN 3 PRECEDING AND CURRENT ROW)'
|
596
645
|
end
|
597
646
|
|
647
|
+
it "should support window functions options" do
|
648
|
+
@d.supports_window_function_frame_option?(:rows).must_equal true
|
649
|
+
@d.supports_window_function_frame_option?(:range).must_equal true
|
650
|
+
@d.supports_window_function_frame_option?(:groups).must_equal false
|
651
|
+
@d.supports_window_function_frame_option?(:offset).must_equal true
|
652
|
+
@d.supports_window_function_frame_option?(:exclude).must_equal false
|
653
|
+
end
|
654
|
+
|
598
655
|
it "should raise an error if an invalid :frame option is used" do
|
599
656
|
proc{@d.l{rank.function.over(:frame=>:blah)}}.must_raise(Sequel::Error)
|
600
657
|
end
|
@@ -69,6 +69,13 @@ describe "eval_inspect extension" do
|
|
69
69
|
v = eval(o.inspect)
|
70
70
|
v.must_equal o
|
71
71
|
@ds.literal(v).must_equal @ds.literal(o)
|
72
|
+
|
73
|
+
ds = @ds
|
74
|
+
@ds.db.create_table(:test) do
|
75
|
+
v = eval(o.inspect)
|
76
|
+
v.must_equal o
|
77
|
+
ds.literal(v).must_equal ds.literal(o)
|
78
|
+
end
|
72
79
|
end
|
73
80
|
end
|
74
81
|
end
|
@@ -673,8 +673,8 @@ END_MIG
|
|
673
673
|
s
|
674
674
|
end
|
675
675
|
e = RUBY_VERSION >= '2.4' ? 'e' : 'E'
|
676
|
-
@d.dump_table_schema(:t4).gsub(/[+-]\d\d\d\d"\)/, '")').gsub(/\.0+/, '.0').must_equal "create_table(:t4) do\n TrueClass :c1, :default=>false\n String :c2, :default=>\"blah\"\n Integer :c3, :default=>-1\n Float :c4, :default=>1.0\n BigDecimal :c5, :default=>BigDecimal(\"0.1005#{e}3\")\n File :c6, :default=>Sequel::SQL::Blob.new(\"blah\")\n Date :c7, :default=>Date.new(2008, 10, 29)\n DateTime :c8, :default=>DateTime.parse(\"2008-10-29T10:20:30.0\")\n Time :c9, :default=>Sequel::SQLTime.parse(\"10:20:30.0\"), :only_time=>true\n String :c10\n Date :c11, :default=>Sequel::CURRENT_DATE\n DateTime :c12, :default=>Sequel::CURRENT_TIMESTAMP\nend"
|
677
|
-
@d.dump_table_schema(:t4, :same_db=>true).gsub(/[+-]\d\d\d\d"\)/, '")').gsub(/\.0+/, '.0').must_equal "create_table(:t4) do\n column :c1, \"boolean\", :default=>false\n column :c2, \"varchar\", :default=>\"blah\"\n column :c3, \"integer\", :default=>-1\n column :c4, \"float\", :default=>1.0\n column :c5, \"decimal\", :default=>BigDecimal(\"0.1005#{e}3\")\n column :c6, \"blob\", :default=>Sequel::SQL::Blob.new(\"blah\")\n column :c7, \"date\", :default=>Date.new(2008, 10, 29)\n column :c8, \"datetime\", :default=>DateTime.parse(\"2008-10-29T10:20:30.0\")\n column :c9, \"time\", :default=>Sequel::SQLTime.parse(\"10:20:30.0\")\n column :c10, \"foo\", :default=>Sequel::LiteralString.new(\"'6 weeks'\")\n column :c11, \"date\", :default=>Sequel::CURRENT_DATE\n column :c12, \"timestamp\", :default=>Sequel::CURRENT_TIMESTAMP\nend"
|
676
|
+
@d.dump_table_schema(:t4).gsub(/[+-]\d\d\d\d"\)/, '")').gsub(/\.0+/, '.0').must_equal "create_table(:t4) do\n TrueClass :c1, :default=>false\n String :c2, :default=>\"blah\"\n Integer :c3, :default=>-1\n Float :c4, :default=>1.0\n BigDecimal :c5, :default=>Kernel::BigDecimal(\"0.1005#{e}3\")\n File :c6, :default=>Sequel::SQL::Blob.new(\"blah\")\n Date :c7, :default=>Date.new(2008, 10, 29)\n DateTime :c8, :default=>DateTime.parse(\"2008-10-29T10:20:30.0\")\n Time :c9, :default=>Sequel::SQLTime.parse(\"10:20:30.0\"), :only_time=>true\n String :c10\n Date :c11, :default=>Sequel::CURRENT_DATE\n DateTime :c12, :default=>Sequel::CURRENT_TIMESTAMP\nend"
|
677
|
+
@d.dump_table_schema(:t4, :same_db=>true).gsub(/[+-]\d\d\d\d"\)/, '")').gsub(/\.0+/, '.0').must_equal "create_table(:t4) do\n column :c1, \"boolean\", :default=>false\n column :c2, \"varchar\", :default=>\"blah\"\n column :c3, \"integer\", :default=>-1\n column :c4, \"float\", :default=>1.0\n column :c5, \"decimal\", :default=>Kernel::BigDecimal(\"0.1005#{e}3\")\n column :c6, \"blob\", :default=>Sequel::SQL::Blob.new(\"blah\")\n column :c7, \"date\", :default=>Date.new(2008, 10, 29)\n column :c8, \"datetime\", :default=>DateTime.parse(\"2008-10-29T10:20:30.0\")\n column :c9, \"time\", :default=>Sequel::SQLTime.parse(\"10:20:30.0\")\n column :c10, \"foo\", :default=>Sequel::LiteralString.new(\"'6 weeks'\")\n column :c11, \"date\", :default=>Sequel::CURRENT_DATE\n column :c12, \"timestamp\", :default=>Sequel::CURRENT_TIMESTAMP\nend"
|
678
678
|
end
|
679
679
|
|
680
680
|
it "should not use a literal string as a fallback if using MySQL with the :same_db option" do
|
@@ -45,6 +45,41 @@ describe "Sequel::Plugins::StaticCache" do
|
|
45
45
|
@db.sqls.must_equal []
|
46
46
|
end
|
47
47
|
|
48
|
+
it "should have first just returns instances without sending a query" do
|
49
|
+
@c.first.must_equal @c1
|
50
|
+
@c.first(2).must_equal [@c1, @c2]
|
51
|
+
@c.first(0).must_equal []
|
52
|
+
@db.sqls.must_equal []
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should have first just returns instances with sending a query" do
|
56
|
+
@db.fetch = lambda do |s|
|
57
|
+
case s
|
58
|
+
when /id = '?(\d+)'?/
|
59
|
+
id = $1.to_i
|
60
|
+
id <= 2 ? { id: id } : nil
|
61
|
+
when /id >= '?(\d+)'?/
|
62
|
+
id = $1.to_i
|
63
|
+
id <= 2 ? (id..2).map { |i| { id: i } } : []
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
@c.first(id: 2).must_equal @c2
|
68
|
+
@c.first(id: '2').must_equal @c2
|
69
|
+
@c.first(id: 3).must_be_nil
|
70
|
+
@c.first { id >= 2 }.must_equal @c2
|
71
|
+
@c.first(2) { id >= 1 }.must_equal [@c1, @c2]
|
72
|
+
@c.first(Sequel.lit('id = ?', 2)).must_equal @c2
|
73
|
+
@db.sqls.must_equal [
|
74
|
+
"SELECT * FROM t WHERE (id = 2) LIMIT 1",
|
75
|
+
"SELECT * FROM t WHERE (id = '2') LIMIT 1",
|
76
|
+
"SELECT * FROM t WHERE (id = 3) LIMIT 1",
|
77
|
+
"SELECT * FROM t WHERE (id >= 2) LIMIT 1",
|
78
|
+
"SELECT * FROM t WHERE (id >= 1) LIMIT 2",
|
79
|
+
"SELECT * FROM t WHERE (id = 2) LIMIT 1"
|
80
|
+
]
|
81
|
+
end
|
82
|
+
|
48
83
|
it "should have each just iterate over the hash's values without sending a query" do
|
49
84
|
a = []
|
50
85
|
@c.each{|o| a << o}
|
@@ -108,6 +108,13 @@ describe Sequel::Model, "tree plugin" do
|
|
108
108
|
"SELECT * FROM nodes WHERE (nodes.parent_id = 1)"]
|
109
109
|
end
|
110
110
|
|
111
|
+
it "should have self_and_siblings return the roots if the current object is a root" do
|
112
|
+
h = {:id=>2, :parent_id=>nil, :name=>'AA'}
|
113
|
+
@c.dataset = @c.dataset.with_fetch(h)
|
114
|
+
@c.load(h).self_and_siblings.must_equal [@c.load(h)]
|
115
|
+
@db.sqls.must_equal ["SELECT * FROM nodes WHERE (parent_id IS NULL)"]
|
116
|
+
end
|
117
|
+
|
111
118
|
it "should have siblings return the children of the current node's parent, except for the current node" do
|
112
119
|
@c.dataset = @c.dataset.with_fetch([[{:id=>1, :parent_id=>3, :name=>'r'}], [{:id=>7, :parent_id=>1, :name=>'r2'}, @o.values.dup]])
|
113
120
|
@o.siblings.must_equal [@c.load(:id=>7, :parent_id=>1, :name=>'r2')]
|
@@ -902,6 +902,8 @@ if DB.dataset.supports_window_functions?
|
|
902
902
|
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
903
903
|
@ds.select(:id){sum(:amount).over(:order=>:id).as(:sum)}.all.
|
904
904
|
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>11111, :id=>5}, {:sum=>111111, :id=>6}]
|
905
|
+
@ds.select(:id){sum(:amount).over(:partition=>:group_id, :order=>id.desc, :frame=>:rows).as(:sum)}.all.
|
906
|
+
must_equal [{:sum=>111, :id=>1}, {:sum=>110, :id=>2}, {:sum=>100, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>110000, :id=>5}, {:sum=>100000, :id=>6}]
|
905
907
|
end
|
906
908
|
|
907
909
|
it "should give correct results for aggregate window functions with frames" do
|
@@ -915,6 +917,71 @@ if DB.dataset.supports_window_functions?
|
|
915
917
|
@ds.select(:id){sum(:amount).over(:order=>:id, :frame=>:rows).as(:sum)}.all.
|
916
918
|
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>11111, :id=>5}, {:sum=>111111, :id=>6}]
|
917
919
|
end
|
920
|
+
|
921
|
+
it "should give correct results for aggregate window functions with ranges" do
|
922
|
+
@ds.select(:id){sum(:amount).over(:partition=>:group_id, :order=>:id, :frame=>:range).as(:sum)}.all.
|
923
|
+
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
924
|
+
@ds.select(:id){sum(:amount).over(:partition=>:group_id, :order=>:group_id, :frame=>:range).as(:sum)}.all.
|
925
|
+
must_equal [{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>111000, :id=>6}]
|
926
|
+
end if DB.dataset.supports_window_function_frame_option?(:range)
|
927
|
+
|
928
|
+
it "should give correct results for aggregate window functions with groups" do
|
929
|
+
@ds.select(:id){sum(:amount).over(:partition=>:group_id, :order=>:id, :frame=>:groups).as(:sum)}.all.
|
930
|
+
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
931
|
+
@ds.select(:id){sum(:amount).over(:partition=>:group_id, :frame=>:groups).as(:sum)}.all.
|
932
|
+
must_equal [{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>111000, :id=>6}]
|
933
|
+
end if DB.dataset.supports_window_function_frame_option?(:groups)
|
934
|
+
|
935
|
+
if DB.dataset.supports_window_function_frame_option?(:offset)
|
936
|
+
it "should give correct results for aggregate window functions with offsets for ROWS" do
|
937
|
+
@ds.select(:id){sum(:amount).over(:order=>:id, :frame=>{:type=>:rows, :start=>1}).as(:sum)}.all.
|
938
|
+
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>110, :id=>3}, {:sum=>1100, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>110000, :id=>6}]
|
939
|
+
@ds.select(:id){sum(:amount).over(:order=>:id, :frame=>{:type=>:rows, :start=>1, :end=>1}).as(:sum)}.all.
|
940
|
+
must_equal [{:sum=>11, :id=>1}, {:sum=>111, :id=>2}, {:sum=>1110, :id=>3}, {:sum=>11100, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>110000, :id=>6}]
|
941
|
+
@ds.select(:id){sum(:amount).over(:order=>:id, :frame=>{:type=>:rows, :start=>2, :end=>[1, :preceding]}).as(:sum)}.all.
|
942
|
+
must_equal [{:sum=>nil, :id=>1}, {:sum=>1, :id=>2}, {:sum=>11, :id=>3}, {:sum=>110, :id=>4}, {:sum=>1100, :id=>5}, {:sum=>11000, :id=>6}]
|
943
|
+
@ds.select(:id){sum(:amount).over(:order=>:id, :frame=>{:type=>:rows, :start=>[1, :following], :end=>2}).as(:sum)}.order(:id).all.
|
944
|
+
must_equal [{:sum=>110, :id=>1}, {:sum=>1100, :id=>2}, {:sum=>11000, :id=>3}, {:sum=>110000, :id=>4}, {:sum=>100000, :id=>5}, {:sum=>nil, :id=>6}]
|
945
|
+
end
|
946
|
+
|
947
|
+
cspecify "should give correct results for aggregate window functions with offsets for RANGES", :mssql, [proc{DB.server_version < 110000}, :postgres] do
|
948
|
+
@ds.select(:id){sum(:amount).over(:order=>:group_id, :frame=>{:type=>:range, :start=>1}).as(:sum)}.all.
|
949
|
+
must_equal [{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111111, :id=>4}, {:sum=>111111, :id=>5}, {:sum=>111111, :id=>6}]
|
950
|
+
@ds.select(:id){sum(:amount).over(:order=>:group_id, :frame=>{:type=>:range, :start=>0, :end=>1}).as(:sum)}.all.
|
951
|
+
must_equal [{:sum=>111111, :id=>1}, {:sum=>111111, :id=>2}, {:sum=>111111, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>111000, :id=>6}]
|
952
|
+
@ds.select(:id){sum(:amount).over(:order=>:amount, :frame=>{:type=>:range, :start=>100, :end=>1000}).as(:sum)}.all.
|
953
|
+
must_equal [{:sum=>1111, :id=>1}, {:sum=>1111, :id=>2}, {:sum=>1111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>10000, :id=>5}, {:sum=>100000, :id=>6}]
|
954
|
+
end if DB.dataset.supports_window_function_frame_option?(:range)
|
955
|
+
|
956
|
+
it "should give correct results for aggregate window functions with offsets for GROUPS" do
|
957
|
+
@ds.select(:id){sum(:amount).over(:order=>:group_id, :frame=>{:type=>:groups, :start=>1}).as(:sum)}.all.
|
958
|
+
must_equal [{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111111, :id=>4}, {:sum=>111111, :id=>5}, {:sum=>111111, :id=>6}]
|
959
|
+
@ds.select(:id){sum(:amount).over(:order=>:group_id, :frame=>{:type=>:groups, :start=>0, :end=>1}).as(:sum)}.all.
|
960
|
+
must_equal [{:sum=>111111, :id=>1}, {:sum=>111111, :id=>2}, {:sum=>111111, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>111000, :id=>6}]
|
961
|
+
end if DB.dataset.supports_window_function_frame_option?(:groups)
|
962
|
+
end
|
963
|
+
|
964
|
+
it "should give correct results for aggregate window functions with exclusions" do
|
965
|
+
@ds.select(:id){sum(:amount).over(:order=>:id, :frame=>{:type=>:rows, :start=>:preceding, :exclude=>:current}).as(:sum)}.all.
|
966
|
+
must_equal [{:sum=>nil, :id=>1}, {:sum=>1, :id=>2}, {:sum=>11, :id=>3}, {:sum=>111, :id=>4}, {:sum=>1111, :id=>5}, {:sum=>11111, :id=>6}]
|
967
|
+
@ds.select(:id){sum(:amount).over(:order=>:group_id, :frame=>{:type=>:rows, :start=>:preceding, :exclude=>:group}).as(:sum)}.all.
|
968
|
+
must_equal [{:sum=>nil, :id=>1}, {:sum=>nil, :id=>2}, {:sum=>nil, :id=>3}, {:sum=>111, :id=>4}, {:sum=>111, :id=>5}, {:sum=>111, :id=>6}]
|
969
|
+
@ds.select(:id){sum(:amount).over(:order=>:group_id, :frame=>{:type=>:rows, :start=>:preceding, :exclude=>:ties}).as(:sum)}.all.
|
970
|
+
must_equal [{:sum=>1, :id=>1}, {:sum=>10, :id=>2}, {:sum=>100, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>10111, :id=>5}, {:sum=>100111, :id=>6}]
|
971
|
+
@ds.select(:id){sum(:amount).over(:order=>:id, :frame=>{:type=>:rows, :start=>:preceding, :exclude=>:no_others}).as(:sum)}.all.
|
972
|
+
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>11111, :id=>5}, {:sum=>111111, :id=>6}]
|
973
|
+
end if DB.dataset.supports_window_function_frame_option?(:exclude)
|
974
|
+
|
975
|
+
it "should give correct results for window functions" do
|
976
|
+
@ds.window(:win, :partition=>:group_id, :order=>:id).select(:id){sum(:amount).over(:window=>win, :frame=>:rows).as(:sum)}.all.
|
977
|
+
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
978
|
+
@ds.window(:win, :partition=>:group_id).select(:id){sum(:amount).over(:window=>win, :order=>id, :frame=>:rows).as(:sum)}.all.
|
979
|
+
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
980
|
+
@ds.window(:win, {}).select(:id){sum(:amount).over(:window=>:win, :order=>id, :frame=>:rows).as(:sum)}.all.
|
981
|
+
must_equal [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>11111, :id=>5}, {:sum=>111111, :id=>6}]
|
982
|
+
@ds.window(:win, :partition=>:group_id).select(:id){sum(:amount).over(:window=>:win, :order=>id, :frame=>:all).as(:sum)}.all.
|
983
|
+
must_equal [{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>111000, :id=>6}]
|
984
|
+
end if DB.dataset.supports_window_clause?
|
918
985
|
end
|
919
986
|
end
|
920
987
|
|
@@ -1989,6 +1989,13 @@ describe "Caching plugins" do
|
|
1989
1989
|
end
|
1990
1990
|
|
1991
1991
|
include CachingPluginSpecs
|
1992
|
+
|
1993
|
+
it "should have first retrieve correct values" do
|
1994
|
+
@Artist.first.must_equal @Artist.load(:id=>1)
|
1995
|
+
@Artist.first(1).must_equal [@Artist.load(:id=>1)]
|
1996
|
+
@Artist.first(:id=>1).must_equal @Artist.load(:id=>1)
|
1997
|
+
@Artist.first{id =~ 1}.must_equal @Artist.load(:id=>1)
|
1998
|
+
end
|
1992
1999
|
end
|
1993
2000
|
end
|
1994
2001
|
|
@@ -634,6 +634,26 @@ describe "Database schema modifiers" do
|
|
634
634
|
@ds.all.must_equal [{:id=>10}, {:id=>20}]
|
635
635
|
end
|
636
636
|
|
637
|
+
it "should set column defaults to nil correctly" do
|
638
|
+
@db.create_table!(:items){Integer :id}
|
639
|
+
@ds.insert(:id=>10)
|
640
|
+
@db.alter_table(:items){set_column_default :id, nil}
|
641
|
+
@db.schema(:items, :reload=>true).map{|x| x.first}.must_equal [:id]
|
642
|
+
@ds.columns!.must_equal [:id]
|
643
|
+
@ds.insert
|
644
|
+
@ds.all.must_equal [{:id=>10}, {:id=>nil}]
|
645
|
+
end
|
646
|
+
|
647
|
+
it "should set column defaults to nil for NOT NULL columns correctly" do
|
648
|
+
@db.create_table!(:items){Integer :id, :null=>false}
|
649
|
+
@ds.insert(:id=>10)
|
650
|
+
@db.alter_table(:items){set_column_default :id, nil}
|
651
|
+
@db.schema(:items, :reload=>true).map{|x| x.first}.must_equal [:id]
|
652
|
+
@ds.columns!.must_equal [:id]
|
653
|
+
@ds.insert(20)
|
654
|
+
@ds.all.must_equal [{:id=>10}, {:id=>20}]
|
655
|
+
end
|
656
|
+
|
637
657
|
cspecify "should set column types correctly", [:jdbc, :db2], :oracle do
|
638
658
|
@db.create_table!(:items){Integer :id}
|
639
659
|
@ds.insert(:id=>10)
|
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.11.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: 2018-
|
11
|
+
date: 2018-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -194,6 +194,7 @@ extra_rdoc_files:
|
|
194
194
|
- doc/release_notes/5.7.0.txt
|
195
195
|
- doc/release_notes/5.9.0.txt
|
196
196
|
- doc/release_notes/5.10.0.txt
|
197
|
+
- doc/release_notes/5.11.0.txt
|
197
198
|
files:
|
198
199
|
- CHANGELOG
|
199
200
|
- MIT-LICENSE
|
@@ -274,6 +275,7 @@ files:
|
|
274
275
|
- doc/release_notes/5.0.0.txt
|
275
276
|
- doc/release_notes/5.1.0.txt
|
276
277
|
- doc/release_notes/5.10.0.txt
|
278
|
+
- doc/release_notes/5.11.0.txt
|
277
279
|
- doc/release_notes/5.2.0.txt
|
278
280
|
- doc/release_notes/5.3.0.txt
|
279
281
|
- doc/release_notes/5.4.0.txt
|