seaquel 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/seaquel/ast.rb +3 -2
- data/lib/seaquel/ast/alias.rb +4 -0
- data/lib/seaquel/ast/bin_op.rb +1 -1
- data/lib/seaquel/ast/column.rb +5 -1
- data/lib/seaquel/ast/expression.rb +67 -7
- data/lib/seaquel/ast/join_op.rb +11 -4
- data/lib/seaquel/ast/node.rb +8 -1
- data/lib/seaquel/ast/table.rb +5 -1
- data/lib/seaquel/ast/unary.rb +20 -0
- data/lib/seaquel/bit.rb +7 -1
- data/lib/seaquel/expression_converter.rb +85 -19
- data/lib/seaquel/module.rb +0 -3
- data/lib/seaquel/statement.rb +17 -1
- data/lib/seaquel/statement/join.rb +1 -1
- data/lib/seaquel/statement_gatherer.rb +12 -1
- data/qed/generation.md +2 -2
- data/spec/functional/select_spec.rb +16 -0
- data/spec/functional/update_spec.rb +3 -3
- data/spec/lib/ast/column_spec.rb +38 -0
- data/spec/lib/ast/expression_spec.rb +86 -0
- data/spec/lib/expression_converter_spec.rb +23 -0
- metadata +7 -5
- data/lib/seaquel/ast/immediate.rb +0 -16
- data/spec/lib/ast/immediate_spec.rb +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb2280004405139dd8f18d4de19167e109237cbf
|
4
|
+
data.tar.gz: 6f69df626061268803471fcfd054ba3add31b9d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: feb75d0e123f642bcf68cdd1e2806c2735b8600713702a1967d1db1e438df76100f28dafd35bff14fa7dbd4e5a1f6c613c3de8eac8bb24348901345801ddc897
|
7
|
+
data.tar.gz: 0b88723ab4fba22324ef8acf496a5edbdddd34ed16ca8e70c0af72aa495cc2333d5c7c0a4df139c2f9e839be3810ccf8d11e0ab6f2c8796ae0a9d645742c4134
|
data/lib/seaquel/ast.rb
CHANGED
@@ -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'
|
data/lib/seaquel/ast/alias.rb
CHANGED
data/lib/seaquel/ast/bin_op.rb
CHANGED
data/lib/seaquel/ast/column.rb
CHANGED
@@ -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
|
data/lib/seaquel/ast/join_op.rb
CHANGED
@@ -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
|
14
|
-
|
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
|
-
|
33
|
+
lisp_inspect(:join_op, op, elements)
|
27
34
|
end
|
28
35
|
end
|
29
36
|
end
|
data/lib/seaquel/ast/node.rb
CHANGED
@@ -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(
|
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
|
data/lib/seaquel/ast/table.rb
CHANGED
@@ -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
|
data/lib/seaquel/bit.rb
CHANGED
@@ -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
|
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,
|
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
|
90
|
-
|
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
|
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
|
data/lib/seaquel/module.rb
CHANGED
data/lib/seaquel/statement.rb
CHANGED
@@ -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)
|
@@ -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
|
data/qed/generation.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
|
2
2
|
# Overview
|
3
3
|
|
4
|
-
This library deals with generation of
|
5
|
-
|
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(
|
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(
|
15
|
-
set(column('bar').to(
|
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
|
data/spec/lib/ast/column_spec.rb
CHANGED
@@ -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.
|
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-
|
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/
|
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/
|
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,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
|