active_record_extended 1.2.0 → 1.3.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/README.md +77 -0
- data/lib/active_record_extended/active_record/relation_patch.rb +6 -4
- data/lib/active_record_extended/query_methods/window.rb +92 -0
- data/lib/active_record_extended/utilities/support.rb +12 -2
- data/lib/active_record_extended/version.rb +1 -1
- data/spec/query_methods/window_spec.rb +51 -0
- data/spec/sql_inspections/window_sql_spec.rb +86 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69a8ed712b33088a8aa79eb2d89ba86f645882d085d3bb752fb997eb1888b6af
|
4
|
+
data.tar.gz: a60306c9e2551606772007b683dee9477d7c87545569647c47a78e45a92b21bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5530aaaf8f2f45aa1f7daaed6398eeac033cd50c3fb542a427dac4b9097c6ebffe08928f6d7a13bacdf9e04509f26f85ed607d4622e16423ced1308e079a2cf7
|
7
|
+
data.tar.gz: 8ca7691c2c23da13e8f1a409eeb5528ed47f01736f4cabbefa06fe441f56cae24ff504b1a0b31996645ef6bba7e2c308e1a8ee6254ee795d3a5fdbd15b0de57b
|
data/README.md
CHANGED
@@ -36,6 +36,9 @@
|
|
36
36
|
- [Union As](#union-as)
|
37
37
|
- [Union Order](#union-order)
|
38
38
|
- [Union Reorder](#union-reorder)
|
39
|
+
- [Window Functions](#window-functions)
|
40
|
+
- [Define Window](#define-window)
|
41
|
+
- [Select Window](#select-window)
|
39
42
|
|
40
43
|
## Description and History
|
41
44
|
|
@@ -787,6 +790,80 @@ SELECT "people".*
|
|
787
790
|
) ) ORDER BY personal_id DESC, id DESC) people
|
788
791
|
```
|
789
792
|
|
793
|
+
#### Window Functions
|
794
|
+
[Postgres Window Functions](https://www.postgresql.org/docs/current/tutorial-window.html)
|
795
|
+
|
796
|
+
Let's address the elephant in the room. Arel has had, for a long time now, window function capabilities;
|
797
|
+
However they've never seen the lime light in ActiveRecord's query logic.
|
798
|
+
The following brings the dormant Arel methods up to the ActiveRecord Querying level.
|
799
|
+
|
800
|
+
#### Define Window
|
801
|
+
|
802
|
+
To set up a window function, we first must establish the window and we do this by using the `.define_window/1` method.
|
803
|
+
This method also requires you to call chain `.partition_by/2`
|
804
|
+
|
805
|
+
`.define_window/1` - Establishes the name of the window you'll reference later on in [.select_window](#select-window)
|
806
|
+
- Aliased name of window
|
807
|
+
|
808
|
+
`.partition_by/2` - Establishes the windows operations a [pre-defined window function](https://www.postgresql.org/docs/current/functions-window.html) will leverage.
|
809
|
+
- column name being partitioned against
|
810
|
+
- (**optional**) `order_by`: Processes how the window should be ordered
|
811
|
+
|
812
|
+
```ruby
|
813
|
+
User
|
814
|
+
.define_window(:number_window).partition_by(:number, order_by: { id: :desc })
|
815
|
+
.define_window(:name_window).partition_by(:name, order_by: :id)
|
816
|
+
.define_window(:no_order_name).partition_by(:name)
|
817
|
+
```
|
818
|
+
|
819
|
+
Query Output
|
820
|
+
```sql
|
821
|
+
SELECT *
|
822
|
+
FROM users
|
823
|
+
WINDOW number_window AS (PARTITION BY number ORDER BY id DESC),
|
824
|
+
name_window AS (PARTITION BY name ORDER BY id),
|
825
|
+
no_order_name AS (PARTITION BY name)
|
826
|
+
```
|
827
|
+
|
828
|
+
#### Select Window
|
829
|
+
|
830
|
+
Once you've define a window, the next step to to utilize it on one of the many provided postgres window functions.
|
831
|
+
|
832
|
+
`.select_window/3`
|
833
|
+
- [window function name](https://www.postgresql.org/docs/current/functions-window.html)
|
834
|
+
- (**optional**) Window function arguments (treated as a splatted array)
|
835
|
+
- (**optional**) `as:` : Alias name of the final result
|
836
|
+
- `over:` : name of [defined window](#define-window)
|
837
|
+
|
838
|
+
```ruby
|
839
|
+
User.create!(name: "Alice", number: 100) #=> id: 1
|
840
|
+
User.create!(name: "Randy", number: 100) #=> id: 2
|
841
|
+
User.create!(name: "Bob", number: 300) #=> id: 3
|
842
|
+
|
843
|
+
User
|
844
|
+
.define_window(:number_window).partition_by(:number, order_by: { id: :desc })
|
845
|
+
.select(:id, :name)
|
846
|
+
.select_window(:row_number, over: :number_window, as: :row_id)
|
847
|
+
.select_window(:first_value, :name, over: :number_window, as: :first_value_name)
|
848
|
+
#=> [
|
849
|
+
# { id: 1, name: "Alice", row_id: 2, first_value_name: "Randy" }
|
850
|
+
# { id: 2, name: "Randy", row_id: 1, first_value_name: "Randy" }
|
851
|
+
# { id: 3, name: "Bob", row_id: 1, first_value_name: "Bob" }
|
852
|
+
# ]
|
853
|
+
#
|
854
|
+
|
855
|
+
```
|
856
|
+
|
857
|
+
Query Output
|
858
|
+
```sql
|
859
|
+
SELECT "users"."id",
|
860
|
+
"users"."name",
|
861
|
+
(ROW_NUMBER() OVER number_window) AS "row_id",
|
862
|
+
(FIRST_VALUE(name) OVER number_window) AS "first_value_name"
|
863
|
+
FROM "users"
|
864
|
+
WINDOW number_window AS (PARTITION BY number ORDER BY id DESC)
|
865
|
+
```
|
866
|
+
|
790
867
|
## License
|
791
868
|
|
792
869
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -1,27 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_record_extended/query_methods/window"
|
3
4
|
require "active_record_extended/query_methods/unionize"
|
4
5
|
require "active_record_extended/query_methods/json"
|
5
6
|
|
6
7
|
module ActiveRecordExtended
|
7
8
|
module RelationPatch
|
8
9
|
module QueryDelegation
|
9
|
-
delegate :with, :foster_select, to: :all
|
10
|
+
delegate :with, :define_window, :select_window, :foster_select, to: :all
|
10
11
|
delegate(*::ActiveRecordExtended::QueryMethods::Unionize::UNIONIZE_METHODS, to: :all)
|
11
12
|
delegate(*::ActiveRecordExtended::QueryMethods::Json::JSON_QUERY_METHODS, to: :all)
|
12
13
|
end
|
13
14
|
|
14
15
|
module Merger
|
15
16
|
def normal_values
|
16
|
-
super + [:with, :union]
|
17
|
+
super + [:with, :union, :define_window]
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
21
|
module ArelBuildPatch
|
21
22
|
def build_arel(*aliases)
|
22
23
|
super.tap do |arel|
|
23
|
-
|
24
|
-
|
24
|
+
build_windows(arel) if window_values?
|
25
|
+
build_unions(arel) if union_values?
|
26
|
+
build_with(arel) if with_values?
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordExtended
|
4
|
+
module QueryMethods
|
5
|
+
module Window
|
6
|
+
class DefineWindowChain
|
7
|
+
include ::ActiveRecordExtended::Utilities::Support
|
8
|
+
include ::ActiveRecordExtended::Utilities::OrderBy
|
9
|
+
|
10
|
+
def initialize(scope, window_name)
|
11
|
+
@scope = scope
|
12
|
+
@window_name = window_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def partition_by(*partitions, order_by: nil)
|
16
|
+
@scope.window_values! << {
|
17
|
+
window_name: to_arel_sql(@window_name),
|
18
|
+
partition_by: flatten_to_sql(partitions),
|
19
|
+
order_by: order_by_expression(order_by),
|
20
|
+
}
|
21
|
+
|
22
|
+
@scope
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class WindowSelectBuilder
|
27
|
+
include ::ActiveRecordExtended::Utilities::Support
|
28
|
+
|
29
|
+
def initialize(window_function, args, window_name)
|
30
|
+
@window_function = window_function
|
31
|
+
@win_args = to_sql_array(args)
|
32
|
+
@over = to_arel_sql(window_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_select(alias_name = nil)
|
36
|
+
window_arel = generate_named_function(@window_function, *@win_args).over(@over)
|
37
|
+
|
38
|
+
if alias_name.nil?
|
39
|
+
window_arel
|
40
|
+
else
|
41
|
+
nested_alias_escape(window_arel, alias_name)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def window_values
|
47
|
+
@values.fetch(:window, [])
|
48
|
+
end
|
49
|
+
|
50
|
+
def window_values!
|
51
|
+
@values[:window] ||= []
|
52
|
+
end
|
53
|
+
|
54
|
+
def window_values?
|
55
|
+
!window_values.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
def window_values=(*values)
|
59
|
+
@values[:window] = values.flatten(1)
|
60
|
+
end
|
61
|
+
|
62
|
+
def define_window(name)
|
63
|
+
spawn.define_window!(name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def define_window!(name)
|
67
|
+
DefineWindowChain.new(self, name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def select_window(window_function, *args, over:, as: nil)
|
71
|
+
spawn.select_window!(window_function, args, over: over, as: as)
|
72
|
+
end
|
73
|
+
|
74
|
+
def select_window!(window_function, *args, over:, as: nil)
|
75
|
+
args.flatten!
|
76
|
+
args.compact!
|
77
|
+
|
78
|
+
select_statement = WindowSelectBuilder.new(window_function, args, over).build_select(as)
|
79
|
+
_select!(select_statement)
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_windows(arel)
|
83
|
+
window_values.each do |window_value|
|
84
|
+
window = arel.window(window_value[:window_name]).partition(window_value[:partition_by])
|
85
|
+
window.order(window_value[:order_by]) if window_value[:order_by]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::Window)
|
@@ -30,7 +30,7 @@ module ActiveRecordExtended
|
|
30
30
|
# Applies aliases to the given query
|
31
31
|
# Ex: `SELECT * FROM users` => `(SELECT * FROM users) AS "members"`
|
32
32
|
def nested_alias_escape(query, alias_name)
|
33
|
-
sql_query =
|
33
|
+
sql_query = generate_grouping(query)
|
34
34
|
Arel::Nodes::As.new(sql_query, to_arel_sql(double_quote(alias_name)))
|
35
35
|
end
|
36
36
|
|
@@ -160,13 +160,23 @@ module ActiveRecordExtended
|
|
160
160
|
|
161
161
|
def group_when_needed(arel_or_rel_query)
|
162
162
|
return arel_or_rel_query unless needs_to_be_grouped?(arel_or_rel_query)
|
163
|
-
|
163
|
+
generate_grouping(arel_or_rel_query)
|
164
164
|
end
|
165
165
|
|
166
166
|
def needs_to_be_grouped?(query)
|
167
167
|
query.respond_to?(:to_sql) || (query.is_a?(String) && /^SELECT.+/i.match?(query))
|
168
168
|
end
|
169
169
|
|
170
|
+
def generate_grouping(expr)
|
171
|
+
::Arel::Nodes::Grouping.new(to_arel_sql(expr))
|
172
|
+
end
|
173
|
+
|
174
|
+
def generate_named_function(function_name, *args)
|
175
|
+
args.map!(&method(:to_arel_sql))
|
176
|
+
function_name = function_name.to_s.upcase
|
177
|
+
::Arel::Nodes::NamedFunction.new(to_arel_sql(function_name), args)
|
178
|
+
end
|
179
|
+
|
170
180
|
def key_generator
|
171
181
|
A_TO_Z_KEYS.sample
|
172
182
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe "Active Record Window Function Query Methods" do
|
6
|
+
let!(:user_one) { User.create! }
|
7
|
+
let!(:user_two) { User.create! }
|
8
|
+
|
9
|
+
let!(:tag_one) { Tag.create!(user: user_one, tag_number: 1) }
|
10
|
+
let!(:tag_two) { Tag.create!(user: user_two, tag_number: 2) }
|
11
|
+
|
12
|
+
let!(:tag_three) { Tag.create!(user: user_one, tag_number: 3) }
|
13
|
+
let!(:tag_four) { Tag.create!(user: user_two, tag_number: 4) }
|
14
|
+
|
15
|
+
let(:tag_group1) { [tag_one, tag_three] }
|
16
|
+
let(:tag_group2) { [tag_two, tag_four] }
|
17
|
+
|
18
|
+
describe ".window_select" do
|
19
|
+
context "when using ROW_NUMBER() ordered in asc" do
|
20
|
+
let(:base_query) do
|
21
|
+
Tag.define_window(:w).partition_by(:user_id, order_by: :tag_number).select(:id)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return tag_one with r_id 1 and tag_three with r_id 2" do
|
25
|
+
results = base_query.select_window(:row_number, over: :w, as: :r_id).group_by(&:id)
|
26
|
+
tag_group1.each.with_index { |tag, idx| expect(results[tag.id].first.r_id).to eq(idx + 1) }
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return tag_two with r_id 1 and tag_four with r_id 2" do
|
30
|
+
results = base_query.select_window(:row_number, over: :w, as: :r_id).group_by(&:id)
|
31
|
+
tag_group2.each.with_index { |tag, idx| expect(results[tag.id].first.r_id).to eq(idx + 1) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when using ROW_NUMBER() ordered in desc" do
|
36
|
+
let(:base_query) do
|
37
|
+
Tag.define_window(:w).partition_by(:user_id, order_by: { tag_number: :desc }).select(:id)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return tag_one with r_id 2 and tag_three with r_id 1" do
|
41
|
+
results = base_query.select_window(:row_number, over: :w, as: :r_id).group_by(&:id)
|
42
|
+
tag_group1.reverse_each.with_index { |tag, idx| expect(results[tag.id].first.r_id).to eq(idx + 1) }
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should return tag_two with r_id 2 and tag_four with r_id 1" do
|
46
|
+
results = base_query.select_window(:row_number, over: :w, as: :r_id).group_by(&:id)
|
47
|
+
tag_group2.reverse_each.with_index { |tag, idx| expect(results[tag.id].first.r_id).to eq(idx + 1) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe "Active Record WINDOW Query inspection" do
|
6
|
+
describe "#define_window" do
|
7
|
+
context "when there is a single defined window" do
|
8
|
+
it "should only contain a single defined window statement at the bottom" do
|
9
|
+
query = Tag.define_window(:w_test).partition_by(:user_id).to_sql
|
10
|
+
expect(query).to eq('SELECT "tags".* FROM "tags" WINDOW w_test AS (PARTITION BY user_id)')
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return a single defined window with a defined ORDER BY" do
|
14
|
+
query = Tag.define_window(:w_test).partition_by(:user_id, order_by: { tags: { user_id: :desc } }).to_sql
|
15
|
+
expect(query).to end_with("WINDOW w_test AS (PARTITION BY user_id ORDER BY tags.user_id DESC)")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should place the window function below the WHERE and GROUP BY statements" do
|
19
|
+
query = Tag.define_window(:w_test).partition_by(:user_id).where(id: 1).group(:user_id).to_sql
|
20
|
+
expect(query).to eq('SELECT "tags".* FROM "tags" WHERE "tags"."id" = 1 GROUP BY "tags"."user_id" WINDOW w_test AS (PARTITION BY user_id)')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when there are multiple defined windows" do
|
25
|
+
it "should only contain a single defined window statement at the bottom" do
|
26
|
+
query =
|
27
|
+
Tag
|
28
|
+
.define_window(:test).partition_by(:user_id)
|
29
|
+
.define_window(:other_window).partition_by(:id)
|
30
|
+
.to_sql
|
31
|
+
|
32
|
+
expect(query).to end_with("WINDOW test AS (PARTITION BY user_id), other_window AS (PARTITION BY id)")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should contain each windows order by statements" do
|
36
|
+
query =
|
37
|
+
Tag
|
38
|
+
.define_window(:test).partition_by(:user_id, order_by: :id)
|
39
|
+
.define_window(:other_window).partition_by(:id, order_by: { tags: :user_id })
|
40
|
+
.to_sql
|
41
|
+
|
42
|
+
expect(query).to end_with("WINDOW test AS (PARTITION BY user_id ORDER BY id), other_window AS (PARTITION BY id ORDER BY tags.user_id ASC)")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#window_select" do
|
48
|
+
let(:query_base) { Tag.define_window(:w).partition_by(:user_id, order_by: :id) }
|
49
|
+
let(:expected_end) { "WINDOW w AS (PARTITION BY user_id ORDER BY id)" }
|
50
|
+
|
51
|
+
context "when using no argument window methods" do
|
52
|
+
[:row_to_number, :rank, :dense_rank, :percent_rank, :cume_dist].each do |window_function|
|
53
|
+
context "#{window_function.to_s.upcase}()" do
|
54
|
+
let(:expected_function) { "#{window_function.to_s.upcase}()" }
|
55
|
+
let(:query) do
|
56
|
+
query_base.select_window(window_function, over: :w, as: :window_response).to_sql
|
57
|
+
end
|
58
|
+
|
59
|
+
it "appends the function to the select query" do
|
60
|
+
expected_start = "SELECT (#{expected_function} OVER w) AS \"window_response\""
|
61
|
+
expect(query).to start_with(expected_start).and(end_with(expected_end))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when using an argument based window method" do
|
68
|
+
let(:argument_list) { ["a", 1, :sauce] }
|
69
|
+
|
70
|
+
{ ntile: 1, lag: 2, lead: 3, first_value: 1, last_value: 1, nth_value: 2 }.each_pair do |window_function, arg_count|
|
71
|
+
context "#{window_function.to_s.upcase}/#{arg_count}" do
|
72
|
+
let(:arguments) { argument_list.first(arg_count) }
|
73
|
+
let(:expected_function) { "#{window_function.to_s.upcase}(#{arguments.join(', ')})" }
|
74
|
+
let(:query) do
|
75
|
+
query_base.select_window(window_function, *arguments, over: :w, as: :window_response).to_sql
|
76
|
+
end
|
77
|
+
|
78
|
+
it "appends the function to the select query" do
|
79
|
+
expected_start = "SELECT (#{expected_function} OVER w) AS \"window_response\""
|
80
|
+
expect(query).to start_with(expected_start).and(end_with(expected_end))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_extended
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- George Protacio-Karaszi
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2019-
|
13
|
+
date: 2019-09-04 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -166,6 +166,7 @@ files:
|
|
166
166
|
- lib/active_record_extended/query_methods/select.rb
|
167
167
|
- lib/active_record_extended/query_methods/unionize.rb
|
168
168
|
- lib/active_record_extended/query_methods/where_chain.rb
|
169
|
+
- lib/active_record_extended/query_methods/window.rb
|
169
170
|
- lib/active_record_extended/query_methods/with_cte.rb
|
170
171
|
- lib/active_record_extended/utilities/order_by.rb
|
171
172
|
- lib/active_record_extended/utilities/support.rb
|
@@ -179,6 +180,7 @@ files:
|
|
179
180
|
- spec/query_methods/json_spec.rb
|
180
181
|
- spec/query_methods/select_spec.rb
|
181
182
|
- spec/query_methods/unionize_spec.rb
|
183
|
+
- spec/query_methods/window_spec.rb
|
182
184
|
- spec/query_methods/with_cte_spec.rb
|
183
185
|
- spec/spec_helper.rb
|
184
186
|
- spec/sql_inspections/any_of_sql_spec.rb
|
@@ -189,6 +191,7 @@ files:
|
|
189
191
|
- spec/sql_inspections/either_sql_spec.rb
|
190
192
|
- spec/sql_inspections/json_sql_spec.rb
|
191
193
|
- spec/sql_inspections/unionize_sql_spec.rb
|
194
|
+
- spec/sql_inspections/window_sql_spec.rb
|
192
195
|
- spec/sql_inspections/with_cte_sql_spec.rb
|
193
196
|
- spec/support/database_cleaner.rb
|
194
197
|
- spec/support/models.rb
|
@@ -226,6 +229,7 @@ test_files:
|
|
226
229
|
- spec/query_methods/json_spec.rb
|
227
230
|
- spec/query_methods/select_spec.rb
|
228
231
|
- spec/query_methods/unionize_spec.rb
|
232
|
+
- spec/query_methods/window_spec.rb
|
229
233
|
- spec/query_methods/with_cte_spec.rb
|
230
234
|
- spec/spec_helper.rb
|
231
235
|
- spec/sql_inspections/any_of_sql_spec.rb
|
@@ -236,6 +240,7 @@ test_files:
|
|
236
240
|
- spec/sql_inspections/either_sql_spec.rb
|
237
241
|
- spec/sql_inspections/json_sql_spec.rb
|
238
242
|
- spec/sql_inspections/unionize_sql_spec.rb
|
243
|
+
- spec/sql_inspections/window_sql_spec.rb
|
239
244
|
- spec/sql_inspections/with_cte_sql_spec.rb
|
240
245
|
- spec/support/database_cleaner.rb
|
241
246
|
- spec/support/models.rb
|