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