seaquel 0.1 → 0.2

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 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