sequel 5.10.0 → 5.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|