arel 3.0.0 → 3.0.3

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