arel 3.0.0 → 3.0.3

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.
@@ -0,0 +1,24 @@
1
+ module Arel
2
+ module Visitors
3
+ module BindVisitor
4
+ def initialize target
5
+ @block = nil
6
+ super
7
+ end
8
+
9
+ def accept node, &block
10
+ @block = block if block_given?
11
+ super
12
+ end
13
+
14
+ private
15
+ def visit_Arel_Nodes_BindParam o
16
+ if @block
17
+ @block.call
18
+ else
19
+ super
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -70,6 +70,7 @@ module Arel
70
70
  alias :visit_Arel_Nodes_GreaterThan :binary
71
71
  alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
72
72
  alias :visit_Arel_Nodes_In :binary
73
+ alias :visit_Arel_Nodes_InfixOperation :binary
73
74
  alias :visit_Arel_Nodes_JoinSource :binary
74
75
  alias :visit_Arel_Nodes_InnerJoin :binary
75
76
  alias :visit_Arel_Nodes_LessThan :binary
@@ -109,6 +110,8 @@ module Arel
109
110
  alias :visit_Arel_Nodes_Lock :terminal
110
111
  alias :visit_Arel_Nodes_Node :terminal
111
112
  alias :visit_Arel_Nodes_SqlLiteral :terminal
113
+ alias :visit_Arel_Nodes_BindParam :terminal
114
+ alias :visit_Arel_Nodes_Window :terminal
112
115
  alias :visit_Arel_SqlLiteral :terminal
113
116
  alias :visit_BigDecimal :terminal
114
117
  alias :visit_Bignum :terminal
@@ -135,6 +138,7 @@ module Arel
135
138
  visit o.source
136
139
  visit o.wheres
137
140
  visit o.groups
141
+ visit o.windows
138
142
  visit o.having
139
143
  end
140
144
 
@@ -65,6 +65,7 @@ module Arel
65
65
  visit_edge o, "expr"
66
66
  end
67
67
  alias :visit_Arel_Nodes_Group :unary
68
+ alias :visit_Arel_Nodes_BindParam :unary
68
69
  alias :visit_Arel_Nodes_Grouping :unary
69
70
  alias :visit_Arel_Nodes_Having :unary
70
71
  alias :visit_Arel_Nodes_Limit :unary
@@ -73,6 +74,23 @@ module Arel
73
74
  alias :visit_Arel_Nodes_On :unary
74
75
  alias :visit_Arel_Nodes_Top :unary
75
76
  alias :visit_Arel_Nodes_UnqualifiedColumn :unary
77
+ alias :visit_Arel_Nodes_Preceding :unary
78
+ alias :visit_Arel_Nodes_Following :unary
79
+ alias :visit_Arel_Nodes_Rows :unary
80
+ alias :visit_Arel_Nodes_Range :unary
81
+
82
+ def window o
83
+ visit_edge o, "orders"
84
+ visit_edge o, "framing"
85
+ end
86
+ alias :visit_Arel_Nodes_Window :window
87
+
88
+ def named_window o
89
+ visit_edge o, "orders"
90
+ visit_edge o, "framing"
91
+ visit_edge o, "name"
92
+ end
93
+ alias :visit_Arel_Nodes_NamedWindow :named_window
76
94
 
77
95
  def function o
78
96
  visit_edge o, "expressions"
@@ -85,6 +103,12 @@ module Arel
85
103
  alias :visit_Arel_Nodes_Avg :function
86
104
  alias :visit_Arel_Nodes_Sum :function
87
105
 
106
+ def extract o
107
+ visit_edge o, "expressions"
108
+ visit_edge o, "alias"
109
+ end
110
+ alias :visit_Arel_Nodes_Extract :extract
111
+
88
112
  def visit_Arel_Nodes_NamedFunction o
89
113
  visit_edge o, "name"
90
114
  visit_edge o, "expressions"
@@ -102,6 +126,7 @@ module Arel
102
126
  visit_edge o, "source"
103
127
  visit_edge o, "projections"
104
128
  visit_edge o, "wheres"
129
+ visit_edge o, "windows"
105
130
  end
106
131
 
107
132
  def visit_Arel_Nodes_SelectStatement o
@@ -158,6 +183,7 @@ module Arel
158
183
  alias :visit_Arel_Nodes_NotEqual :binary
159
184
  alias :visit_Arel_Nodes_NotIn :binary
160
185
  alias :visit_Arel_Nodes_Or :binary
186
+ alias :visit_Arel_Nodes_Over :binary
161
187
 
162
188
  def visit_String o
163
189
  @node_stack.last.fields << o
@@ -15,7 +15,7 @@ module Arel
15
15
  def visit_Arel_Nodes_SelectCore o
16
16
  [
17
17
  "#{o.projections.map { |x| visit x }.join ', '}",
18
- ("FROM #{visit o.froms}" if o.froms),
18
+ ("FROM #{visit(o.source)}" if o.source && !o.source.empty?),
19
19
  ("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
20
20
  ("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
21
21
  (visit(o.having) if o.having),
@@ -108,7 +108,7 @@ key on UpdateManager using UpdateManager#key=
108
108
  def visit_Arel_Nodes_Values o
109
109
  "VALUES (#{o.expressions.zip(o.columns).map { |value, attr|
110
110
  if Nodes::SqlLiteral === value
111
- visit_Arel_Nodes_SqlLiteral value
111
+ visit value
112
112
  else
113
113
  quote(value, attr && column_for(attr))
114
114
  end
@@ -133,9 +133,10 @@ key on UpdateManager using UpdateManager#key=
133
133
  (visit(o.set_quantifier) if o.set_quantifier),
134
134
  ("#{o.projections.map { |x| visit x }.join ', '}" unless o.projections.empty?),
135
135
  ("FROM #{visit(o.source)}" if o.source && !o.source.empty?),
136
- ("WHERE #{o.wheres.map { |x| accept x }.join ' AND ' }" unless o.wheres.empty?),
136
+ ("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
137
137
  ("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
138
138
  (visit(o.having) if o.having),
139
+ ("WINDOW #{o.windows.map { |x| visit x }.join ', ' }" unless o.windows.empty?)
139
140
  ].compact.join ' '
140
141
  end
141
142
 
@@ -175,6 +176,59 @@ key on UpdateManager using UpdateManager#key=
175
176
  "( #{visit o.left} EXCEPT #{visit o.right} )"
176
177
  end
177
178
 
179
+ def visit_Arel_Nodes_NamedWindow o
180
+ "#{quote_column_name o.name} AS #{visit_Arel_Nodes_Window o}"
181
+ end
182
+
183
+ def visit_Arel_Nodes_Window o
184
+ s = [
185
+ ("ORDER BY #{o.orders.map { |x| visit(x) }.join(', ')}" unless o.orders.empty?),
186
+ (visit o.framing if o.framing)
187
+ ].compact.join ' '
188
+ "(#{s})"
189
+ end
190
+
191
+ def visit_Arel_Nodes_Rows o
192
+ if o.expr
193
+ "ROWS #{visit o.expr}"
194
+ else
195
+ "ROWS"
196
+ end
197
+ end
198
+
199
+ def visit_Arel_Nodes_Range o
200
+ if o.expr
201
+ "RANGE #{visit o.expr}"
202
+ else
203
+ "RANGE"
204
+ end
205
+ end
206
+
207
+ def visit_Arel_Nodes_Preceding o
208
+ "#{o.expr ? visit(o.expr) : 'UNBOUNDED'} PRECEDING"
209
+ end
210
+
211
+ def visit_Arel_Nodes_Following o
212
+ "#{o.expr ? visit(o.expr) : 'UNBOUNDED'} FOLLOWING"
213
+ end
214
+
215
+ def visit_Arel_Nodes_CurrentRow o
216
+ "CURRENT ROW"
217
+ end
218
+
219
+ def visit_Arel_Nodes_Over o
220
+ case o.right
221
+ when nil
222
+ "#{visit o.left} OVER ()"
223
+ when Arel::Nodes::SqlLiteral
224
+ "#{visit o.left} OVER #{visit o.right}"
225
+ when String, Symbol
226
+ "#{visit o.left} OVER #{quote_column_name o.right.to_s}"
227
+ else
228
+ "#{visit o.left} OVER #{visit o.right}"
229
+ end
230
+ end
231
+
178
232
  def visit_Arel_Nodes_Having o
179
233
  "HAVING #{visit o.expr}"
180
234
  end
@@ -218,6 +272,10 @@ key on UpdateManager using UpdateManager#key=
218
272
  }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
219
273
  end
220
274
 
275
+ def visit_Arel_Nodes_Extract o
276
+ "EXTRACT(#{o.field.to_s.upcase} FROM #{visit o.expr})#{o.alias ? " AS #{visit o.alias}" : ''}"
277
+ end
278
+
221
279
  def visit_Arel_Nodes_Count o
222
280
  "COUNT(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
223
281
  visit x
@@ -312,11 +370,19 @@ key on UpdateManager using UpdateManager#key=
312
370
  end
313
371
 
314
372
  def visit_Arel_Nodes_In o
315
- "#{visit o.left} IN (#{visit o.right})"
373
+ if Array === o.right && o.right.empty?
374
+ '1=0'
375
+ else
376
+ "#{visit o.left} IN (#{visit o.right})"
377
+ end
316
378
  end
317
379
 
318
380
  def visit_Arel_Nodes_NotIn o
319
- "#{visit o.left} NOT IN (#{visit o.right})"
381
+ if Array === o.right && o.right.empty?
382
+ '1=1'
383
+ else
384
+ "#{visit o.left} NOT IN (#{visit o.right})"
385
+ end
320
386
  end
321
387
 
322
388
  def visit_Arel_Nodes_And o
@@ -374,6 +440,7 @@ key on UpdateManager using UpdateManager#key=
374
440
 
375
441
  def literal o; o end
376
442
 
443
+ alias :visit_Arel_Nodes_BindParam :literal
377
444
  alias :visit_Arel_Nodes_SqlLiteral :literal
378
445
  alias :visit_Arel_SqlLiteral :literal # This is deprecated
379
446
  alias :visit_Bignum :literal
@@ -0,0 +1,9 @@
1
+ module Arel
2
+ module WindowPredications
3
+
4
+ def over(expr = nil)
5
+ Nodes::Over.new(self, expr)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ require 'helper'
2
+
3
+ describe Arel::Nodes::Extract do
4
+ it "should extract field" do
5
+ table = Arel::Table.new :users
6
+ table[:timestamp].extract('date').to_sql.must_be_like %{
7
+ EXTRACT(DATE FROM "users"."timestamp")
8
+ }
9
+ end
10
+
11
+ describe "as" do
12
+ it 'should alias the extract' do
13
+ table = Arel::Table.new :users
14
+ table[:timestamp].extract('date').as('foo').to_sql.must_be_like %{
15
+ EXTRACT(DATE FROM "users"."timestamp") AS foo
16
+ }
17
+ end
18
+ end
19
+ end
@@ -11,6 +11,7 @@ module Arel
11
11
  Nodes.const_get(k)
12
12
  }.grep(Class).each do |klass|
13
13
  next if Nodes::SqlLiteral == klass
14
+ next if Nodes::BindParam == klass
14
15
  next if klass.name =~ /^Arel::Nodes::Test/
15
16
  assert klass.ancestors.include?(Nodes::Node), klass.name
16
17
  end
@@ -0,0 +1,49 @@
1
+ require 'helper'
2
+
3
+ describe Arel::Nodes::Over do
4
+ describe 'as' do
5
+ it 'should alias the expression' do
6
+ table = Arel::Table.new :users
7
+ table[:id].count.over.as('foo').to_sql.must_be_like %{
8
+ COUNT("users"."id") OVER () AS foo
9
+ }
10
+ end
11
+ end
12
+
13
+ describe 'with literal' do
14
+ it 'should reference the window definition by name' do
15
+ table = Arel::Table.new :users
16
+ table[:id].count.over('foo').to_sql.must_be_like %{
17
+ COUNT("users"."id") OVER "foo"
18
+ }
19
+ end
20
+ end
21
+
22
+ describe 'with SQL literal' do
23
+ it 'should reference the window definition by name' do
24
+ table = Arel::Table.new :users
25
+ table[:id].count.over(Arel.sql('foo')).to_sql.must_be_like %{
26
+ COUNT("users"."id") OVER foo
27
+ }
28
+ end
29
+ end
30
+
31
+ describe 'with no expression' do
32
+ it 'should use empty definition' do
33
+ table = Arel::Table.new :users
34
+ table[:id].count.over.to_sql.must_be_like %{
35
+ COUNT("users"."id") OVER ()
36
+ }
37
+ end
38
+ end
39
+
40
+ describe 'with expression' do
41
+ it 'should use definition in sub-expression' do
42
+ table = Arel::Table.new :users
43
+ window = Arel::Nodes::Window.new.order(table['foo'])
44
+ table[:id].count.over(window).to_sql.must_be_like %{
45
+ COUNT("users"."id") OVER (ORDER BY \"users\".\"foo\")
46
+ }
47
+ end
48
+ end
49
+ end
@@ -731,6 +731,162 @@ module Arel
731
731
  end
732
732
  end
733
733
 
734
+ describe 'window definition' do
735
+ it 'can be empty' do
736
+ table = Table.new :users
737
+ manager = Arel::SelectManager.new Table.engine
738
+ manager.from table
739
+ manager.window('a_window')
740
+ manager.to_sql.must_be_like %{
741
+ SELECT FROM "users" WINDOW "a_window" AS ()
742
+ }
743
+ end
744
+
745
+ it 'takes an order' do
746
+ table = Table.new :users
747
+ manager = Arel::SelectManager.new Table.engine
748
+ manager.from table
749
+ manager.window('a_window').order(table['foo'].asc)
750
+ manager.to_sql.must_be_like %{
751
+ SELECT FROM "users" WINDOW "a_window" AS (ORDER BY "users"."foo" ASC)
752
+ }
753
+ end
754
+
755
+ it 'takes a rows frame, unbounded preceding' do
756
+ table = Table.new :users
757
+ manager = Arel::SelectManager.new Table.engine
758
+ manager.from table
759
+ manager.window('a_window').rows(Arel::Nodes::Preceding.new)
760
+ manager.to_sql.must_be_like %{
761
+ SELECT FROM "users" WINDOW "a_window" AS (ROWS UNBOUNDED PRECEDING)
762
+ }
763
+ end
764
+
765
+ it 'takes a rows frame, bounded preceding' do
766
+ table = Table.new :users
767
+ manager = Arel::SelectManager.new Table.engine
768
+ manager.from table
769
+ manager.window('a_window').rows(Arel::Nodes::Preceding.new(5))
770
+ manager.to_sql.must_be_like %{
771
+ SELECT FROM "users" WINDOW "a_window" AS (ROWS 5 PRECEDING)
772
+ }
773
+ end
774
+
775
+ it 'takes a rows frame, unbounded following' do
776
+ table = Table.new :users
777
+ manager = Arel::SelectManager.new Table.engine
778
+ manager.from table
779
+ manager.window('a_window').rows(Arel::Nodes::Following.new)
780
+ manager.to_sql.must_be_like %{
781
+ SELECT FROM "users" WINDOW "a_window" AS (ROWS UNBOUNDED FOLLOWING)
782
+ }
783
+ end
784
+
785
+ it 'takes a rows frame, bounded following' do
786
+ table = Table.new :users
787
+ manager = Arel::SelectManager.new Table.engine
788
+ manager.from table
789
+ manager.window('a_window').rows(Arel::Nodes::Following.new(5))
790
+ manager.to_sql.must_be_like %{
791
+ SELECT FROM "users" WINDOW "a_window" AS (ROWS 5 FOLLOWING)
792
+ }
793
+ end
794
+
795
+ it 'takes a rows frame, current row' do
796
+ table = Table.new :users
797
+ manager = Arel::SelectManager.new Table.engine
798
+ manager.from table
799
+ manager.window('a_window').rows(Arel::Nodes::CurrentRow.new)
800
+ manager.to_sql.must_be_like %{
801
+ SELECT FROM "users" WINDOW "a_window" AS (ROWS CURRENT ROW)
802
+ }
803
+ end
804
+
805
+ it 'takes a rows frame, between two delimiters' do
806
+ table = Table.new :users
807
+ manager = Arel::SelectManager.new Table.engine
808
+ manager.from table
809
+ window = manager.window('a_window')
810
+ window.frame(
811
+ Arel::Nodes::Between.new(
812
+ window.rows,
813
+ Nodes::And.new([
814
+ Arel::Nodes::Preceding.new,
815
+ Arel::Nodes::CurrentRow.new
816
+ ])))
817
+ manager.to_sql.must_be_like %{
818
+ SELECT FROM "users" WINDOW "a_window" AS (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
819
+ }
820
+ end
821
+
822
+ it 'takes a range frame, unbounded preceding' do
823
+ table = Table.new :users
824
+ manager = Arel::SelectManager.new Table.engine
825
+ manager.from table
826
+ manager.window('a_window').range(Arel::Nodes::Preceding.new)
827
+ manager.to_sql.must_be_like %{
828
+ SELECT FROM "users" WINDOW "a_window" AS (RANGE UNBOUNDED PRECEDING)
829
+ }
830
+ end
831
+
832
+ it 'takes a range frame, bounded preceding' do
833
+ table = Table.new :users
834
+ manager = Arel::SelectManager.new Table.engine
835
+ manager.from table
836
+ manager.window('a_window').range(Arel::Nodes::Preceding.new(5))
837
+ manager.to_sql.must_be_like %{
838
+ SELECT FROM "users" WINDOW "a_window" AS (RANGE 5 PRECEDING)
839
+ }
840
+ end
841
+
842
+ it 'takes a range frame, unbounded following' do
843
+ table = Table.new :users
844
+ manager = Arel::SelectManager.new Table.engine
845
+ manager.from table
846
+ manager.window('a_window').range(Arel::Nodes::Following.new)
847
+ manager.to_sql.must_be_like %{
848
+ SELECT FROM "users" WINDOW "a_window" AS (RANGE UNBOUNDED FOLLOWING)
849
+ }
850
+ end
851
+
852
+ it 'takes a range frame, bounded following' do
853
+ table = Table.new :users
854
+ manager = Arel::SelectManager.new Table.engine
855
+ manager.from table
856
+ manager.window('a_window').range(Arel::Nodes::Following.new(5))
857
+ manager.to_sql.must_be_like %{
858
+ SELECT FROM "users" WINDOW "a_window" AS (RANGE 5 FOLLOWING)
859
+ }
860
+ end
861
+
862
+ it 'takes a range frame, current row' do
863
+ table = Table.new :users
864
+ manager = Arel::SelectManager.new Table.engine
865
+ manager.from table
866
+ manager.window('a_window').range(Arel::Nodes::CurrentRow.new)
867
+ manager.to_sql.must_be_like %{
868
+ SELECT FROM "users" WINDOW "a_window" AS (RANGE CURRENT ROW)
869
+ }
870
+ end
871
+
872
+ it 'takes a range frame, between two delimiters' do
873
+ table = Table.new :users
874
+ manager = Arel::SelectManager.new Table.engine
875
+ manager.from table
876
+ window = manager.window('a_window')
877
+ window.frame(
878
+ Arel::Nodes::Between.new(
879
+ window.range,
880
+ Nodes::And.new([
881
+ Arel::Nodes::Preceding.new,
882
+ Arel::Nodes::CurrentRow.new
883
+ ])))
884
+ manager.to_sql.must_be_like %{
885
+ SELECT FROM "users" WINDOW "a_window" AS (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
886
+ }
887
+ end
888
+ end
889
+
734
890
  describe 'delete' do
735
891
  it "copies from" do
736
892
  engine = EngineProxy.new Table.engine