seaquel 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f51aa6cb26dbabeed5e62e1544877377b44090bc
4
- data.tar.gz: 982a28e72454c041dd21129416c7def66b95ff32
3
+ metadata.gz: cb2280004405139dd8f18d4de19167e109237cbf
4
+ data.tar.gz: 6f69df626061268803471fcfd054ba3add31b9d6
5
5
  SHA512:
6
- metadata.gz: d50b0346f4288a0974d2b0364b3f6596cde261fbcb126662a3aaf9f92fcd5c0255d688c5707afcf2b0d6d4639861427172aac652c20ed5347667175904c97290
7
- data.tar.gz: cb41ddc3e7e14f77ab0f0774660fc66a414c5c6c4a4f02e7d42ad205a5ac1a5c6e32d08f48a88ea2c2a8189d6eaa1e9e12d338365a11ed6870791962ac1cef2f
6
+ metadata.gz: feb75d0e123f642bcf68cdd1e2806c2735b8600713702a1967d1db1e438df76100f28dafd35bff14fa7dbd4e5a1f6c613c3de8eac8bb24348901345801ddc897
7
+ data.tar.gz: 0b88723ab4fba22324ef8acf496a5edbdddd34ed16ca8e70c0af72aa495cc2333d5c7c0a4df139c2f9e839be3810ccf8d11e0ab6f2c8796ae0a9d645742c4134
@@ -2,6 +2,7 @@
2
2
  module Seaquel::AST
3
3
  end
4
4
 
5
+ require 'seaquel/ast/expression'
5
6
  require 'seaquel/ast/node'
6
7
  require 'seaquel/ast/bin_op'
7
8
  require 'seaquel/ast/join_op'
@@ -12,8 +13,8 @@ require 'seaquel/ast/column_list'
12
13
  require 'seaquel/ast/column'
13
14
  require 'seaquel/ast/table'
14
15
  require 'seaquel/ast/literal'
15
- require 'seaquel/ast/immediate'
16
16
  require 'seaquel/ast/binding'
17
17
  require 'seaquel/ast/funcall'
18
18
  require 'seaquel/ast/table_alias'
19
- require 'seaquel/ast/order'
19
+ require 'seaquel/ast/order'
20
+ require 'seaquel/ast/unary'
@@ -11,5 +11,9 @@ module Seaquel::AST
11
11
  def visit visitor
12
12
  visitor.visit_alias(name, expression)
13
13
  end
14
+
15
+ def inspect
16
+ "(alias #{name.inspect} #{expression.inspect})"
17
+ end
14
18
  end
15
19
  end
@@ -3,7 +3,7 @@ module Seaquel::AST
3
3
  # A binary statement as in left=right.
4
4
  # This class can be visited.
5
5
  #
6
- class BinOp
6
+ class BinOp < Expression
7
7
  attr_reader :op, :left, :right
8
8
 
9
9
  def initialize op, left, right
@@ -44,7 +44,11 @@ module Seaquel::AST
44
44
  end
45
45
 
46
46
  def inspect
47
- "(column #{name})"
47
+ if table
48
+ lisp_inspect(:column, name, table)
49
+ else
50
+ lisp_inspect(:column, name)
51
+ end
48
52
  end
49
53
  end
50
54
  end
@@ -3,24 +3,79 @@ module Seaquel::AST
3
3
  # Base class for all expression-type AST nodes.
4
4
  #
5
5
  class Expression
6
- def self.binop op
6
+ def self.binop op, *aliases
7
7
  define_method(op) do |exp|
8
8
  BinOp.new(op, self, exp)
9
9
  end
10
+
11
+ aliases.each do |op_alias|
12
+ alias_method(op_alias, op)
13
+ end
14
+ end
15
+ def self.joinop op, *aliases
16
+ define_method(op) do |exp|
17
+ JoinOp.new(op, [self, exp])
18
+ end
19
+
20
+ aliases.each do |op_alias|
21
+ alias_method(op_alias, op)
22
+ end
23
+ end
24
+ def self.unaryop op, *aliases
25
+ define_method(op) do
26
+ Unary.new(op, self)
27
+ end
28
+
29
+ aliases.each do |op_alias|
30
+ alias_method(op_alias, op)
31
+ end
10
32
  end
11
33
 
12
- binop :eq
13
- binop :noteq
34
+ binop :eq, :==
35
+ binop :noteq, :"!="
14
36
 
15
- binop :lt
16
- binop :lteq
37
+ binop :lt, :<
38
+ binop :lteq, :<=
17
39
 
18
- binop :gt
19
- binop :gteq
40
+ binop :gt, :>
41
+ binop :gteq, :>=
20
42
 
21
43
  binop :is
22
44
  binop :isnot
23
45
 
46
+ binop :in
47
+
48
+ binop :like
49
+ binop :not_like
50
+ binop :ilike
51
+ binop :not_ilike
52
+ binop :similar_to
53
+ binop :not_similar_to
54
+
55
+ binop :plus, :+
56
+ binop :minus, :-
57
+ binop :times, :*
58
+ binop :div, :/
59
+ binop :mod, :%
60
+
61
+ binop :pow, :**
62
+ binop :sqrt
63
+ binop :cbrt
64
+
65
+ binop :bitand
66
+ binop :bitor
67
+ binop :bitxor
68
+
69
+ binop :sleft, :<<
70
+ binop :sright, :>>
71
+
72
+ joinop :and, :&
73
+ joinop :or, :|
74
+
75
+ unaryop :not, :!
76
+ unaryop :fact
77
+ unaryop :bitnot
78
+
24
79
  def as name
25
80
  Alias.new(name, self)
26
81
  end
@@ -31,5 +86,10 @@ module Seaquel::AST
31
86
  def desc
32
87
  Order.new(:desc, self)
33
88
  end
89
+
90
+ def lisp_inspect type, *args
91
+ "(" +
92
+ [type.to_s, args.map(&:inspect)].join(' ') + ")"
93
+ end
34
94
  end
35
95
  end
@@ -1,8 +1,13 @@
1
+
2
+ require 'forwardable'
3
+
1
4
  module Seaquel::AST
2
5
  # A binary operation that can be used to join things together, like 'AND' or
3
6
  # 'OR'.
4
7
  #
5
- class JoinOp
8
+ class JoinOp < Expression
9
+ extend Forwardable
10
+
6
11
  attr_reader :op
7
12
  attr_reader :elements
8
13
 
@@ -10,8 +15,10 @@ module Seaquel::AST
10
15
  @op, @elements = op, elements
11
16
  end
12
17
 
13
- def concat element
14
- elements << element
18
+ def concat *more
19
+ more.each do |el|
20
+ elements << el
21
+ end
15
22
  end
16
23
 
17
24
  def empty?
@@ -23,7 +30,7 @@ module Seaquel::AST
23
30
  end
24
31
 
25
32
  def inspect
26
- [:join_op, op, *elements].inspect
33
+ lisp_inspect(:join_op, op, elements)
27
34
  end
28
35
  end
29
36
  end
@@ -51,6 +51,13 @@ module Seaquel::AST
51
51
  node(:offset, n)
52
52
  end
53
53
 
54
+ def having *exps
55
+ node(:having, exps)
56
+ end
57
+ def group_by *fields
58
+ node(:group_by, fields)
59
+ end
60
+
54
61
  # Replaces previous ORDER BY specification with this one.
55
62
  #
56
63
  # @param list [Array<Column>]
@@ -71,7 +78,7 @@ module Seaquel::AST
71
78
  #
72
79
  # Example:
73
80
  # include Seaquel
74
- # insert.into(table('foo')).values(immediate(1), immediate(2))
81
+ # insert.into(table('foo')).values(1, 2)
75
82
  # # => INSERT INTO "foo" VALUES (1, 2)
76
83
  #
77
84
  # @param list [Array<AST::Expression>] values to insert
@@ -1,5 +1,5 @@
1
1
  module Seaquel::AST
2
- class Table
2
+ class Table < Expression
3
3
  attr_reader :name
4
4
 
5
5
  def initialize name
@@ -32,5 +32,9 @@ module Seaquel::AST
32
32
  def as_column_prefix quoter
33
33
  quote(quoter)
34
34
  end
35
+
36
+ def inspect
37
+ lisp_inspect(:table, name)
38
+ end
35
39
  end
36
40
  end
@@ -0,0 +1,20 @@
1
+
2
+ module Seaquel::AST
3
+ class Unary < Expression
4
+ attr_reader :exp
5
+ attr_reader :op
6
+
7
+ def initialize op, exp
8
+ @exp = exp
9
+ @op = op
10
+ end
11
+
12
+ def visit visitor
13
+ visitor.visit_unary op, exp
14
+ end
15
+
16
+ def inspect
17
+ "(#{op.inspect} #{exp.inspect})"
18
+ end
19
+ end
20
+ end
@@ -17,7 +17,7 @@ module Seaquel
17
17
  # bit is lower than target_precedence, it will not be put in parenthesis.
18
18
  #
19
19
  def at target_precedence
20
- if precedence != :inf && target_precedence >= precedence
20
+ if precedence == :inf || target_precedence <= precedence
21
21
  @str
22
22
  else
23
23
  "(#{@str})"
@@ -30,5 +30,11 @@ module Seaquel
30
30
  def toplevel
31
31
  str
32
32
  end
33
+
34
+ def == other
35
+ return toplevel == other if other.kind_of?(String)
36
+
37
+ super
38
+ end
33
39
  end
34
40
  end
@@ -5,26 +5,15 @@ module Seaquel
5
5
  extend Forwardable
6
6
 
7
7
  attr_reader :quoter
8
- attr_reader :symbols
9
8
 
10
9
  def initialize quoter
11
10
  @quoter = quoter
12
- @symbols = {
13
- :eq => '=',
14
- :gt => '>',
15
- :gteq => '>=',
16
- :lt => '<',
17
- :lteq => '<=',
18
- :is => ' IS ',
19
- :isnot => ' IS NOT ',
20
- :noteq => '!='
21
- }
22
11
  end
23
12
 
24
13
  # Gets called whenever the expression contains a statement-type AST::Node.
25
14
  #
26
15
  def visit_node node
27
- bit(node.to_sql, :inf)
16
+ bit(node.to_sql, 0)
28
17
  end
29
18
 
30
19
  def_delegator :@quoter, :string, :quote_string
@@ -40,10 +29,6 @@ module Seaquel
40
29
  end
41
30
  end
42
31
 
43
- def visit_immediate sql_val
44
- bit(sql_val)
45
- end
46
-
47
32
  def visit_new_statement *args
48
33
  ::Seaquel::Generator.new(self).compact_sql
49
34
  end
@@ -86,8 +71,21 @@ module Seaquel
86
71
  op = :isnot if op==:noteq
87
72
  end
88
73
 
89
- if symbols.has_key?(op)
90
- symbol = symbols[op]
74
+ if op==:in && right.kind_of?(Range)
75
+ raise "Ranges excluding the right side are not permitted in Seaquel." \
76
+ if right.exclude_end?
77
+
78
+ return bit([
79
+ sql(left).at(0),
80
+ 'BETWEEN',
81
+ right.begin,
82
+ 'AND',
83
+ right.end].join(' '))
84
+ end
85
+
86
+ if binops.has_key?(op)
87
+ symbol = binops[op]
88
+
91
89
  return bit(
92
90
  [sql(left).at(prec), symbol, sql(right).at(prec)].join,
93
91
  prec)
@@ -109,11 +107,31 @@ module Seaquel
109
107
  sql = case op
110
108
  when :and
111
109
  parts.join(' AND ')
110
+ when :or
111
+ parts.join(' OR ')
112
+ else
113
+ raise "AF: JoinOp called for #{op.inspect}, but has no branch for it."
112
114
  end
113
115
 
114
116
  bit(sql, prec)
115
117
  end
116
118
 
119
+ def visit_unary op, exp
120
+ raise "No such unary operation #{op.inspect}." \
121
+ unless unaryops.has_key?(op)
122
+
123
+ symbol, prefix = unaryops[op]
124
+ prec = precedences[op]
125
+
126
+ parts = []
127
+
128
+ parts << symbol if prefix
129
+ parts << sql(exp).at(0)
130
+ parts << symbol unless prefix
131
+
132
+ bit(parts.join, prec)
133
+ end
134
+
117
135
  def visit_column col
118
136
  bit(col.as_full_reference(quoter))
119
137
  end
@@ -136,6 +154,9 @@ module Seaquel
136
154
  case node
137
155
  when nil
138
156
  bit('NULL')
157
+ when Array
158
+ sql_bits = node.map { |el| sql(el).at(0) }
159
+ bit("(" + sql_bits.join(', ') + ")")
139
160
  when String
140
161
  bit(quote_string(node))
141
162
  when Fixnum
@@ -148,7 +169,7 @@ module Seaquel
148
169
  end
149
170
 
150
171
  private
151
- def bit str, precedence=0
172
+ def bit str, precedence=:inf
152
173
  Bit.new(str, precedence)
153
174
  end
154
175
 
@@ -156,6 +177,48 @@ module Seaquel
156
177
  precedences[op]
157
178
  end
158
179
 
180
+ def binops
181
+ @binops ||= {
182
+ :eq => '=',
183
+ :gt => '>',
184
+ :gteq => '>=',
185
+ :lt => '<',
186
+ :lteq => '<=',
187
+ :is => ' IS ',
188
+ :isnot => ' IS NOT ',
189
+ :noteq => '!=',
190
+ :in => ' IN ',
191
+ :like => ' LIKE ',
192
+ :not_like => ' NOT LIKE ',
193
+ :ilike => ' ILIKE ',
194
+ :not_ilike => ' NOT ILIKE ',
195
+ :similar_to => ' SIMILAR TO ',
196
+ :not_similar_to => ' NOT SIMILAR TO ',
197
+ :plus => '+',
198
+ :minus => '-',
199
+ :div => '/',
200
+ :times => '*',
201
+ :mod => '%',
202
+ :pow => '^',
203
+ :sqrt => '|/',
204
+ :cbrt => '||/',
205
+ :bitand => '&',
206
+ :bitor => '|',
207
+ :bitxor => '#',
208
+ :sleft => '<<',
209
+ :sright => '>>'
210
+ }
211
+ end
212
+
213
+ def unaryops
214
+ @unaryops ||= {
215
+ # sym, prefix?
216
+ :not => ['NOT ', true],
217
+ :bitnot => ['~', true],
218
+ :fact => ['!', false],
219
+ }
220
+ end
221
+
159
222
  def precedences
160
223
  @precedences ||= begin
161
224
  prec = 1 # 0 reserved for simple values
@@ -169,10 +232,13 @@ module Seaquel
169
232
  # By listing something above something else, it gets a lower precedence
170
233
  # assigned. List from lower to higher precedences. Equivalence classes
171
234
  # by extending the argument list.
235
+ assign[:not]
172
236
  assign[:as, :is, :isnot]
173
237
  assign[:or]
174
238
  assign[:and]
175
239
  assign[:eq, :gt, :gteq, :lt, :lteq]
240
+ assign[:plus, :minus]
241
+ assign[:times, :div]
176
242
 
177
243
  precs
178
244
  end
@@ -27,9 +27,6 @@ module Seaquel
27
27
  def literal text
28
28
  AST::Literal.new(text)
29
29
  end
30
- def immediate ruby_obj
31
- AST::Immediate.new(ruby_obj)
32
- end
33
30
  def binding position
34
31
  AST::Binding.new(position)
35
32
  end
@@ -16,7 +16,6 @@ module Seaquel
16
16
  attr_reader :expression_convertor
17
17
 
18
18
  attr_reader :from
19
- attr_reader :project
20
19
  attr_reader :where
21
20
  attr_reader :set
22
21
  attr_reader :target
@@ -25,7 +24,12 @@ module Seaquel
25
24
  attr_reader :joins
26
25
  attr_reader :limit
27
26
  attr_reader :offset
27
+ attr_reader :having
28
+
29
+ # These are overwritten, not appended to.
30
+ attr_reader :project
28
31
  attr_reader :order_by
32
+ attr_reader :group_by
29
33
 
30
34
  def initialize expression_convertor
31
35
  @expression_convertor = expression_convertor
@@ -38,6 +42,8 @@ module Seaquel
38
42
  @values = list()
39
43
  @fields = column_list()
40
44
  @joins = []
45
+ @having = AST::JoinOp.new(:and, [])
46
+ @group_by = list()
41
47
  end
42
48
 
43
49
  def set_limit n
@@ -204,6 +210,16 @@ module Seaquel
204
210
  parts << convert(where)
205
211
  end
206
212
 
213
+ unless group_by.empty?
214
+ parts << 'GROUP BY'
215
+ parts << convert(group_by)
216
+ end
217
+
218
+ unless having.empty?
219
+ parts << 'HAVING'
220
+ parts << convert(having)
221
+ end
222
+
207
223
  unless order_by.empty?
208
224
  parts << 'ORDER BY'
209
225
  parts << convert(order_by)
@@ -13,7 +13,7 @@ module Seaquel
13
13
  @ons = AST::JoinOp.new(:and, [])
14
14
  end
15
15
 
16
- def on exps
16
+ def on *exps
17
17
  ons.concat(*exps)
18
18
  end
19
19
 
@@ -63,7 +63,18 @@ module Seaquel
63
63
  raise InvalidExpression, ".on without a .join encoutered" \
64
64
  unless current_join
65
65
 
66
- current_join.on(exps)
66
+ current_join.on(*exps)
67
+ end
68
+
69
+ def visit_group_by parent, list
70
+ continue(parent)
71
+
72
+ s.group_by.replace(list)
73
+ end
74
+ def visit_having parent, exps
75
+ continue(parent)
76
+
77
+ s.having.concat(*exps)
67
78
  end
68
79
 
69
80
  def visit_insert parent
@@ -1,8 +1,8 @@
1
1
 
2
2
  # Overview
3
3
 
4
- This library deals with generation of valid SQL. What one would like to have is
5
- a programmatic interface to SQL that allows to create a query and turn it into
4
+ This library deals with generation of SQL. What one would like to have is a
5
+ programmatic interface to SQL that allows to create a query and turn it into
6
6
  SQL.
7
7
 
8
8
  ~~~ruby
@@ -20,6 +20,22 @@ describe 'SELECT statements' do
20
20
  SQL
21
21
  end
22
22
 
23
+ describe '#group_by' do
24
+ it 'works by replacing earlier group_bys' do
25
+ a = column(:a)
26
+ b = column(:b)
27
+ select.group_by(a).group_by(b).generates <<-SQL
28
+ SELECT * GROUP BY "b"
29
+ SQL
30
+ end
31
+ end
32
+ describe '#having' do
33
+ it 'works like #where' do
34
+ select.having(1).having(2).generates <<-SQL
35
+ SELECT * HAVING 1 AND 2
36
+ SQL
37
+ end
38
+ end
23
39
  describe '#join' do
24
40
  it 'allows joining tables (INNER JOIN being the default)' do
25
41
  foo = table(:foo)
@@ -5,14 +5,14 @@ describe 'UPDATE statements' do
5
5
  include Seaquel
6
6
 
7
7
  it 'can be used' do
8
- update(table('bar')).set(column('foo').to(immediate(1))).generates <<-SQL
8
+ update(table('bar')).set(column('foo').to(1)).generates <<-SQL
9
9
  UPDATE "bar" SET "foo"=1
10
10
  SQL
11
11
  end
12
12
  it 'set statements can be chained' do
13
13
  update(table('bar')).
14
- set(column('foo').to(immediate(1))).
15
- set(column('bar').to(immediate(2))).generates <<-SQL
14
+ set(column('foo').to(1)).
15
+ set(column('bar').to(2)).generates <<-SQL
16
16
  UPDATE "bar" SET "foo"=1, "bar"=2
17
17
  SQL
18
18
  end
@@ -39,6 +39,11 @@ describe Seaquel::AST::Column do
39
39
  SELECT * WHERE "foo" IS NOT 1
40
40
  SQL
41
41
  end
42
+ it 'allows comparison "IN"' do
43
+ select.where(column('foo').in([1,2])).generates <<-SQL
44
+ SELECT * WHERE "foo" IN (1, 2)
45
+ SQL
46
+ end
42
47
 
43
48
  it 'allows ASC modifier for use in ORDER BY' do
44
49
  select.order_by(column('foo').asc).generates <<-SQL
@@ -50,4 +55,37 @@ describe Seaquel::AST::Column do
50
55
  SELECT * ORDER BY "foo" DESC
51
56
  SQL
52
57
  end
58
+
59
+ describe 'string comparison functions' do
60
+ it 'allows comparison "LIKE"' do
61
+ select.where(column('foo').like('test')).generates <<-SQL
62
+ SELECT * WHERE "foo" LIKE 'test'
63
+ SQL
64
+ end
65
+ it 'allows comparison "NOT LIKE"' do
66
+ select.where(column('foo').not_like('test')).generates <<-SQL
67
+ SELECT * WHERE "foo" NOT LIKE 'test'
68
+ SQL
69
+ end
70
+ it 'allows comparison "SIMILAR TO"' do
71
+ select.where(column('foo').similar_to('test')).generates <<-SQL
72
+ SELECT * WHERE "foo" SIMILAR TO 'test'
73
+ SQL
74
+ end
75
+ it 'allows comparison "NOT SIMILAR TO"' do
76
+ select.where(column('foo').not_similar_to('test')).generates <<-SQL
77
+ SELECT * WHERE "foo" NOT SIMILAR TO 'test'
78
+ SQL
79
+ end
80
+ it 'allows comparison "ILIKE"' do
81
+ select.where(column('foo').ilike('test')).generates <<-SQL
82
+ SELECT * WHERE "foo" ILIKE 'test'
83
+ SQL
84
+ end
85
+ it 'allows comparison "NOT ILIKE"' do
86
+ select.where(column('foo').not_ilike('test')).generates <<-SQL
87
+ SELECT * WHERE "foo" NOT ILIKE 'test'
88
+ SQL
89
+ end
90
+ end
53
91
  end
@@ -0,0 +1,86 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ describe Seaquel::AST::Expression do
5
+ let(:q) { Seaquel::Quoter.new }
6
+ let(:ec) { Seaquel::ExpressionConverter.new(q) }
7
+
8
+ # ----------------------------------------------------------------------------
9
+ # Using a, b and c (all columns) or e1, e2 and e3 (any expression), various
10
+ # patterns are formulated and tested against SQL that should be aequivalent.
11
+ #
12
+ extend Seaquel
13
+
14
+ a, b, c = [:a, :b, :c].map { |e| column(e) }
15
+
16
+ e1 = Seaquel::AST::Expression.new
17
+ e2 = Seaquel::AST::Expression.new
18
+ e3 = Seaquel::AST::Expression.new
19
+
20
+ # Define an ad-hoc #visit method for each of those expressions, so that we
21
+ # can test operators in general:
22
+ [e1, e2, e3].each_with_index do |e, i|
23
+ e.define_singleton_method(:visit) do |visitor|
24
+ Seaquel::Bit.new("e#{i+1}", :inf)
25
+ end
26
+ end
27
+
28
+ # Here are the test cases:
29
+ [
30
+ # basic column AND/OR
31
+ [a & b, '"a" AND "b"'], # 1,
32
+ [a | b, '"a" OR "b"'],
33
+ # basic expression AND/OR
34
+ [e1 & e2, 'e1 AND e2'],
35
+ [e1 | e2, 'e1 OR e2'],
36
+ [e1 & e2 | e3, 'e1 AND e2 OR e3'], # 5
37
+ [(e1 & e2) | e3, 'e1 AND e2 OR e3'],
38
+ [e1 & (e2 | e3), 'e1 AND (e2 OR e3)'],
39
+ # Basic algebra
40
+ [e1 + e2, 'e1+e2'],
41
+ [e1 - e2, 'e1-e2'],
42
+ [e1 * e2, 'e1*e2'], # 10
43
+ [e1 / e2, 'e1/e2'],
44
+ # NOT
45
+ [!e1, 'NOT e1'],
46
+ # Equalities, Inequalities
47
+ [e1 != e2, 'e1!=e2'],
48
+ [e1 == e2, 'e1=e2'],
49
+ [e1 < e2, 'e1<e2'], # 15
50
+ [e1 > e2, 'e1>e2'],
51
+ [e1 <= e2, 'e1<=e2'],
52
+ [e1 >= e2, 'e1>=e2'],
53
+ # Mixed bag
54
+ [(e1.not) == e2, '(NOT e1)=e2'],
55
+ [(!e1) == e2, '(NOT e1)=e2'], # 20
56
+ [!e1 == e2, '(NOT e1)=e2'],
57
+ # More algebra
58
+ [e1 % e2, 'e1%e2'],
59
+ [e1 ** e2, 'e1^e2'],
60
+ [e1.sqrt(e2), 'e1|/e2'],
61
+ [e1.cbrt(e2), 'e1||/e2'], # 25
62
+ [e1.fact, 'e1!'],
63
+ # Bitwise operators: more rare and thus more verbose.
64
+ [e1.bitand(e2), 'e1&e2'],
65
+ [e1.bitor(e2), 'e1|e2'],
66
+ [e1.bitxor(e2), 'e1#e2'],
67
+ [e1.bitnot, '~e1'], # 30
68
+ # Bit shifts
69
+ [e1 << e2, 'e1<<e2'],
70
+ [e1 >> e2, 'e1>>e2'],
71
+ # Precedences
72
+ [(e1+e2)*e3, '(e1+e2)*e3'],
73
+
74
+ ].each_with_index do |(exp, sql), idx|
75
+ begin
76
+ it "line #{"%2d:"%(idx+1)} turns into #{sql}" do
77
+ ec.sql(exp).toplevel.assert == sql
78
+ end
79
+ rescue Exception => ex
80
+ ex.set_backtrace(["example in spec at line #{idx+1}", *ex.backtrace])
81
+ raise ex
82
+ end
83
+ end
84
+
85
+
86
+ end
@@ -0,0 +1,23 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ describe Seaquel::ExpressionConverter do
5
+ include Seaquel
6
+
7
+ let(:quoter) { Seaquel::Quoter.new }
8
+ let(:ec) { Seaquel::ExpressionConverter.new(quoter) }
9
+
10
+ it 'converts arrays by iterating on them' do
11
+ ec.sql([1,2,3]).assert == '(1, 2, 3)'
12
+ end
13
+ it 'converts ranges' do
14
+ ec.visit_binop(:in, "left", 1..10).assert == "'left' BETWEEN 1 AND 10"
15
+ end
16
+
17
+ describe 'expressions' do
18
+ it '= binds higher than AND' do
19
+ exp = column(:a).eq(1).and(column(:b).eq(2))
20
+ ec.sql(exp).assert == '"a"=1 AND "b"=2'
21
+ end
22
+ end
23
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: seaquel
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: '0.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaspar Schiess
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-02-18 00:00:00.000000000 Z
12
+ date: 2015-02-19 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: " Generates SQL from Ruby code. \n"
15
15
  email: kaspar.schiess@technologyastronauts.ch
@@ -30,7 +30,6 @@ files:
30
30
  - lib/seaquel/ast/column_list.rb
31
31
  - lib/seaquel/ast/expression.rb
32
32
  - lib/seaquel/ast/funcall.rb
33
- - lib/seaquel/ast/immediate.rb
34
33
  - lib/seaquel/ast/join_op.rb
35
34
  - lib/seaquel/ast/list.rb
36
35
  - lib/seaquel/ast/literal.rb
@@ -38,6 +37,7 @@ files:
38
37
  - lib/seaquel/ast/order.rb
39
38
  - lib/seaquel/ast/table.rb
40
39
  - lib/seaquel/ast/table_alias.rb
40
+ - lib/seaquel/ast/unary.rb
41
41
  - lib/seaquel/bit.rb
42
42
  - lib/seaquel/expression_converter.rb
43
43
  - lib/seaquel/generator.rb
@@ -54,7 +54,8 @@ files:
54
54
  - spec/functional/select_spec.rb
55
55
  - spec/functional/update_spec.rb
56
56
  - spec/lib/ast/column_spec.rb
57
- - spec/lib/ast/immediate_spec.rb
57
+ - spec/lib/ast/expression_spec.rb
58
+ - spec/lib/expression_converter_spec.rb
58
59
  - spec/spec_helper.rb
59
60
  homepage: https://bitbucket.org/technologyastronauts/seaquel
60
61
  licenses:
@@ -89,6 +90,7 @@ test_files:
89
90
  - spec/functional/select_spec.rb
90
91
  - spec/functional/update_spec.rb
91
92
  - spec/lib/ast/column_spec.rb
92
- - spec/lib/ast/immediate_spec.rb
93
+ - spec/lib/ast/expression_spec.rb
94
+ - spec/lib/expression_converter_spec.rb
93
95
  - spec/spec_helper.rb
94
96
  has_rdoc:
@@ -1,16 +0,0 @@
1
-
2
- require_relative 'expression'
3
-
4
- module Seaquel::AST
5
- class Immediate < Expression
6
- attr_reader :val
7
-
8
- def initialize ruby_val
9
- @val = ruby_val
10
- end
11
-
12
- def visit visitor
13
- visitor.visit_immediate val.to_s
14
- end
15
- end
16
- end
@@ -1,42 +0,0 @@
1
-
2
- require 'spec_helper'
3
-
4
- describe Seaquel::AST::Immediate do
5
- include Seaquel
6
-
7
- it 'allows comparison ">"' do
8
- select.where(immediate(2).gt(1)).generates <<-SQL
9
- SELECT * WHERE 2>1
10
- SQL
11
- end
12
- it 'allows comparison ">="' do
13
- select.where(immediate(2).gteq(1)).generates <<-SQL
14
- SELECT * WHERE 2>=1
15
- SQL
16
- end
17
- it 'allows comparison "<"' do
18
- select.where(immediate(2).lt(1)).generates <<-SQL
19
- SELECT * WHERE 2<1
20
- SQL
21
- end
22
- it 'allows comparison "<="' do
23
- select.where(immediate(2).lteq(1)).generates <<-SQL
24
- SELECT * WHERE 2<=1
25
- SQL
26
- end
27
- it 'allows comparison "!="' do
28
- select.where(immediate(2).noteq(1)).generates <<-SQL
29
- SELECT * WHERE 2!=1
30
- SQL
31
- end
32
- it 'allows comparison "IS"' do
33
- select.where(immediate(2).is(1)).generates <<-SQL
34
- SELECT * WHERE 2 IS 1
35
- SQL
36
- end
37
- it 'allows comparison "IS NOT"' do
38
- select.where(immediate(2).isnot(1)).generates <<-SQL
39
- SELECT * WHERE 2 IS NOT 1
40
- SQL
41
- end
42
- end