vsql_parser 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.
- data/MIT_LICENSE +20 -0
- data/lib/formatter.rb +81 -0
- data/lib/test_chamber.rb +63 -0
- data/lib/vsql_node_extensions.rb +246 -0
- data/lib/vsql_parser.rb +25 -0
- data/lib/vsql_parser.treetop +237 -0
- metadata +97 -0
data/MIT_LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 LeadTune
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/formatter.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative "./vsql_node_extensions"
|
2
|
+
|
3
|
+
module VSql
|
4
|
+
module Formatter
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def indent(value, amount = 2, first_line = false)
|
8
|
+
spacer = (" " * amount)
|
9
|
+
(first_line ? spacer : "") + value.gsub("\n", "\n#{spacer}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def eager_indent(value, amount = 2)
|
13
|
+
indent(value, amount, true)
|
14
|
+
end
|
15
|
+
|
16
|
+
def quote_alias_if_needed(a)
|
17
|
+
if a.match(/[^a-z0-9_]/)
|
18
|
+
'"' + a + '"'
|
19
|
+
else
|
20
|
+
a
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
DEFAULT_FORMATTER = lambda { |n|
|
25
|
+
n.pieces.map { |p|
|
26
|
+
p.is_a?(String) ? p : format_node(p)
|
27
|
+
}.join
|
28
|
+
}
|
29
|
+
|
30
|
+
NODE_FORMATTERS = {
|
31
|
+
SelectExpression => lambda { |n|
|
32
|
+
formatted_expr = format_node(n.elements[0])
|
33
|
+
if n.alias_node
|
34
|
+
expr_alias = n.alias_node.text_value
|
35
|
+
[formatted_expr, " AS ", quote_alias_if_needed(expr_alias)].join
|
36
|
+
else
|
37
|
+
formatted_expr
|
38
|
+
end
|
39
|
+
},
|
40
|
+
SelectStatement => lambda { |n|
|
41
|
+
statements = n.match(SelectExpression, Query)
|
42
|
+
"\nSELECT\n" + eager_indent(statements.map { |s| format_node(s) }.join(",\n"))
|
43
|
+
},
|
44
|
+
LimitStatement => lambda { |n|
|
45
|
+
"\nLIMIT" + n.elements[1..-1].map(&:text_value).join
|
46
|
+
},
|
47
|
+
WhereStatement => lambda { |n|
|
48
|
+
exprs = n.elements.select {|e| e.is_a?(Expression) }
|
49
|
+
"\nWHERE " + indent(exprs.map {|e| DEFAULT_FORMATTER[e] }.join)
|
50
|
+
},
|
51
|
+
FromStatement => lambda { |n|
|
52
|
+
expressions = n.match(FromExpression, Query)
|
53
|
+
"\nFROM " + indent(expressions.map {|e| format_node(e) }.join("\n"))
|
54
|
+
},
|
55
|
+
JoinStatement => lambda { |n|
|
56
|
+
join_keyword = n.match(JoinKeyword, Query)[0]
|
57
|
+
from, criteria = n.elements.select {|e| e.is_a?(Expression) }
|
58
|
+
["\n",
|
59
|
+
join_keyword.text_value.upcase,
|
60
|
+
" ",
|
61
|
+
indent(format_node(from)),
|
62
|
+
" ON ",
|
63
|
+
indent(format_node(criteria))].join
|
64
|
+
},
|
65
|
+
OrderByStatement => lambda { |n|
|
66
|
+
exprs = n.elements.select {|e| e.is_a?(OrderByExpression) }
|
67
|
+
"\nORDER BY " + indent(exprs.map {|e| DEFAULT_FORMATTER[e] }.join)
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
FORMATTERS = Hash.new(DEFAULT_FORMATTER).update(NODE_FORMATTERS)
|
72
|
+
|
73
|
+
def format_node(node)
|
74
|
+
FORMATTERS[node.class][node]
|
75
|
+
end
|
76
|
+
|
77
|
+
def format(node)
|
78
|
+
format_node(node).split("\n").map(&:rstrip).join("\n").strip + "\n"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/test_chamber.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# In file parser.rb
|
2
|
+
require_relative './vsql_parser.rb'
|
3
|
+
|
4
|
+
module TestChamber
|
5
|
+
module Helpers
|
6
|
+
NORMAL_COLOR ||= 37
|
7
|
+
def colorize(color, output)
|
8
|
+
"\e[0;#{color}m#{output}\e[0;#{NORMAL_COLOR}m"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
include Helpers
|
13
|
+
extend self
|
14
|
+
|
15
|
+
def pparse(sql, output_errors = true)
|
16
|
+
parse(sql, output_errors).tap { |q| q.prune! }
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse(sql, output_errors = true)
|
20
|
+
parser = ::VSqlParser.parser
|
21
|
+
VSqlParser.parse(sql).tap do |tree|
|
22
|
+
# If the AST is nil then there was an error during parsing
|
23
|
+
# we need to report a simple error message to help the user
|
24
|
+
if tree.nil?
|
25
|
+
output_error(sql, parser) if output_errors
|
26
|
+
raise Exception, parser.failure_reason
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def output_error(sql, parser)
|
34
|
+
fail_index = parser.max_terminal_failure_index
|
35
|
+
STDERR.flush
|
36
|
+
STDOUT.flush
|
37
|
+
STDERR.puts( "\n" +
|
38
|
+
((fail_index > 0) ? colorize(42, sql[0..(fail_index - 1)]) : "") +
|
39
|
+
colorize(41, sql[(fail_index)..-1]) +
|
40
|
+
"\n\n")
|
41
|
+
|
42
|
+
STDERR.flush
|
43
|
+
end
|
44
|
+
|
45
|
+
def clean_tree(root_node)
|
46
|
+
return if(root_node.elements.nil?)
|
47
|
+
root_node.elements.delete_if{|node| node.class.name == "Treetop::Runtime::SyntaxNode" }
|
48
|
+
root_node.elements.each {|node| self.clean_tree(node) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def reload
|
52
|
+
Object.send(:remove_const, :SqlParser) rescue nil
|
53
|
+
Object.send(:remove_const, :Sql) rescue nil
|
54
|
+
Object.send(:remove_const, :VSql) rescue nil
|
55
|
+
TestChamber.send(:remove_const, :PARSER) rescue nil
|
56
|
+
|
57
|
+
load(File.join(VSQLPARSER_BASE_PATH, 'vsql_node_extensions.rb'))
|
58
|
+
load(File.join(VSQLPARSER_BASE_PATH, 'formatter.rb'))
|
59
|
+
Treetop.load(File.join(VSQLPARSER_BASE_PATH, 'vsql_parser.treetop'))
|
60
|
+
VSqlParser.extend(VSqlParserHelpers)
|
61
|
+
load(__FILE__)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
module ScanHelpers
|
2
|
+
extend self
|
3
|
+
require 'strscan'
|
4
|
+
def gsub_replacements(string, pattern, replacement)
|
5
|
+
pattern = Regexp.new(Regexp.escape(pattern)) if pattern.is_a?(String)
|
6
|
+
[].tap do |matches|
|
7
|
+
scanner = StringScanner.new(string)
|
8
|
+
until scanner.eos?
|
9
|
+
return matches unless scanner.scan_until(pattern)
|
10
|
+
matches.push([(scanner.pos - scanner.matched_size)..(scanner.pos - 1),
|
11
|
+
replacement.size - scanner.matched_size,
|
12
|
+
replacement])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Replaceability
|
19
|
+
def index_of(str)
|
20
|
+
e.text_value.index(str) + e.interval.first
|
21
|
+
end
|
22
|
+
|
23
|
+
def adjust_intervals!(idx, delta)
|
24
|
+
case
|
25
|
+
when @interval.include?(idx)
|
26
|
+
@interval = (@interval.first)...[@interval.first, @interval.last + delta].max
|
27
|
+
when @interval.first > idx
|
28
|
+
@interval = (@interval.first + delta)...(@interval.last + delta)
|
29
|
+
end
|
30
|
+
elements && elements.each { |e| e.adjust_intervals!(idx, delta) }
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def gsub!(pattern, replacement)
|
35
|
+
ScanHelpers.gsub_replacements(text_value, pattern, replacement).reverse.each do |(range, delta, rep_str)|
|
36
|
+
end_idx = (@interval.min + range.max)
|
37
|
+
@input[(@interval.min + range.min)..end_idx] = rep_str
|
38
|
+
root.adjust_intervals!(end_idx, delta)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Treetop::Runtime::SyntaxNode
|
44
|
+
def _pieces_with_gaps(cursor, elements, results = [])
|
45
|
+
return [cursor, results] if elements.nil? || elements.empty?
|
46
|
+
element, interval, next_elements = elements[0], elements[0].interval, elements[1..-1]
|
47
|
+
next_results = [*results,
|
48
|
+
*(input[cursor...interval.first] if cursor != elements.first.interval.first),
|
49
|
+
element]
|
50
|
+
_pieces_with_gaps(interval.last,
|
51
|
+
next_elements,
|
52
|
+
next_results)
|
53
|
+
end
|
54
|
+
|
55
|
+
def pieces
|
56
|
+
last_pos, pieces = _pieces_with_gaps(interval.first, elements)
|
57
|
+
if last_pos != interval.last
|
58
|
+
[input[last_pos...(interval.last)], *pieces]
|
59
|
+
else
|
60
|
+
pieces
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def match(klass = Treetop::Runtime::SyntaxNode, skip = nil)
|
65
|
+
VSql::Helpers.find_elements(self, klass, skip)
|
66
|
+
end
|
67
|
+
|
68
|
+
def find(klass)
|
69
|
+
match(klass).first
|
70
|
+
end
|
71
|
+
|
72
|
+
def delete!
|
73
|
+
parent.elements.delete(self)
|
74
|
+
end
|
75
|
+
|
76
|
+
def vanilla?
|
77
|
+
(self.class == Treetop::Runtime::SyntaxNode) &&
|
78
|
+
(parent && parent.class == Treetop::Runtime::SyntaxNode || text_value.length == 0) &&
|
79
|
+
(elements.nil? || elements.all?(&:vanilla?))
|
80
|
+
end
|
81
|
+
|
82
|
+
def prune_if!(&block)
|
83
|
+
delete! if yield(self)
|
84
|
+
end
|
85
|
+
|
86
|
+
def prune!
|
87
|
+
es = match(Treetop::Runtime::SyntaxNode)
|
88
|
+
es.reverse.each do |e|
|
89
|
+
e.prune_if!(&:vanilla?)
|
90
|
+
end
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
def root
|
95
|
+
parent ? parent.root : self
|
96
|
+
end
|
97
|
+
|
98
|
+
def match_nearest(klass)
|
99
|
+
case
|
100
|
+
when parent.nil?
|
101
|
+
nil
|
102
|
+
when parent.is_a?(klass)
|
103
|
+
parent
|
104
|
+
else
|
105
|
+
parent.match_nearest(klass)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
include Replaceability
|
110
|
+
end
|
111
|
+
|
112
|
+
module VSql
|
113
|
+
module Helpers
|
114
|
+
def self.find_elements(node, klass, skip_klass = nil)
|
115
|
+
results = []
|
116
|
+
return results unless node.elements
|
117
|
+
node.elements.each do |e|
|
118
|
+
case
|
119
|
+
when e.is_a?(klass)
|
120
|
+
results << e
|
121
|
+
results.concat(find_elements(e, klass, skip_klass))
|
122
|
+
when skip_klass && e.is_a?(skip_klass)
|
123
|
+
next
|
124
|
+
else
|
125
|
+
results.concat(find_elements(e, klass, skip_klass))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
results
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class VSqlSyntaxNode < Treetop::Runtime::SyntaxNode
|
133
|
+
end
|
134
|
+
|
135
|
+
class Operator < VSqlSyntaxNode
|
136
|
+
end
|
137
|
+
|
138
|
+
class Statement < VSqlSyntaxNode
|
139
|
+
end
|
140
|
+
|
141
|
+
class SelectStatement < Statement
|
142
|
+
def expressions
|
143
|
+
Helpers.find_elements(self, SelectExpression)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class SelectExpression < VSqlSyntaxNode
|
148
|
+
def expression_sql
|
149
|
+
end
|
150
|
+
|
151
|
+
def alias_node
|
152
|
+
@alias_node ||= Helpers.find_elements(self, Alias, Query).first
|
153
|
+
end
|
154
|
+
|
155
|
+
def root_nodes
|
156
|
+
elements[0].elements.select { |e| ! e.text_value.empty? }
|
157
|
+
end
|
158
|
+
|
159
|
+
def name
|
160
|
+
case
|
161
|
+
when alias_node
|
162
|
+
alias_node.text_value
|
163
|
+
when root_nodes.length == 1 && root_nodes.first.is_a?(Function)
|
164
|
+
root_nodes.first.name
|
165
|
+
when root_nodes.length == 1 && root_nodes.first.is_a?(FieldRef)
|
166
|
+
element =
|
167
|
+
Helpers.find_elements(self, FieldGlob).last ||
|
168
|
+
Helpers.find_elements(self, Name).last
|
169
|
+
element.text_value
|
170
|
+
else "?column?"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class NameExpression < VSqlSyntaxNode
|
176
|
+
end
|
177
|
+
|
178
|
+
class FromStatement < Statement
|
179
|
+
end
|
180
|
+
|
181
|
+
class FromExpression < VSqlSyntaxNode
|
182
|
+
end
|
183
|
+
|
184
|
+
class JoinStatement < Statement
|
185
|
+
end
|
186
|
+
|
187
|
+
class JoinKeyword < VSqlSyntaxNode
|
188
|
+
end
|
189
|
+
|
190
|
+
class WhereStatement < Statement
|
191
|
+
end
|
192
|
+
|
193
|
+
class OrderByStatement < Statement
|
194
|
+
end
|
195
|
+
|
196
|
+
class LimitStatement < Statement
|
197
|
+
end
|
198
|
+
|
199
|
+
class OrderByExpression < VSqlSyntaxNode
|
200
|
+
end
|
201
|
+
|
202
|
+
class Name < VSqlSyntaxNode
|
203
|
+
end
|
204
|
+
|
205
|
+
class FieldRef < VSqlSyntaxNode
|
206
|
+
end
|
207
|
+
|
208
|
+
class TablePart < VSqlSyntaxNode
|
209
|
+
end
|
210
|
+
|
211
|
+
class FieldGlob < VSqlSyntaxNode
|
212
|
+
end
|
213
|
+
|
214
|
+
class Alias < VSqlSyntaxNode
|
215
|
+
end
|
216
|
+
|
217
|
+
class Function < VSqlSyntaxNode
|
218
|
+
def name
|
219
|
+
elements[0].text_value
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
class Entity < VSqlSyntaxNode
|
224
|
+
# def to_array
|
225
|
+
# return self.elements[0].to_array
|
226
|
+
# end
|
227
|
+
end
|
228
|
+
|
229
|
+
class Expression < VSqlSyntaxNode
|
230
|
+
end
|
231
|
+
|
232
|
+
class QuotedEntity < Entity
|
233
|
+
end
|
234
|
+
|
235
|
+
class Query < VSqlSyntaxNode
|
236
|
+
# def to_array
|
237
|
+
# return self.elements.map {|x| x.to_array}
|
238
|
+
# end
|
239
|
+
# def select_statement
|
240
|
+
# elements.detect { |e| e.is_a?(SelectStatement) }
|
241
|
+
# end
|
242
|
+
end
|
243
|
+
|
244
|
+
class SubQuery < VSqlSyntaxNode
|
245
|
+
end
|
246
|
+
end
|
data/lib/vsql_parser.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# In file parser.rb
|
2
|
+
require 'treetop'
|
3
|
+
require_relative './vsql_node_extensions.rb'
|
4
|
+
require_relative './formatter.rb'
|
5
|
+
|
6
|
+
VSQLPARSER_BASE_PATH ||= File.expand_path(File.dirname(__FILE__))
|
7
|
+
|
8
|
+
module VSqlParserHelpers
|
9
|
+
def parser
|
10
|
+
@parser ||= VSqlParser.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(sql)
|
14
|
+
d_sql = sql.downcase
|
15
|
+
parser.parse(d_sql).tap do
|
16
|
+
d_sql.replace(sql)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Find out what our base path is
|
22
|
+
Treetop.load(File.join(VSQLPARSER_BASE_PATH, 'vsql_parser.treetop')) # <- This creates the VSqlParser class
|
23
|
+
|
24
|
+
VSqlParser.extend(VSqlParserHelpers)
|
25
|
+
|
@@ -0,0 +1,237 @@
|
|
1
|
+
grammar VSql
|
2
|
+
|
3
|
+
rule query
|
4
|
+
space*
|
5
|
+
select_statement
|
6
|
+
(space from_statement (space join_statement)*
|
7
|
+
(space where_statement)?
|
8
|
+
(space group_by_statement)?
|
9
|
+
(space having_statement)?
|
10
|
+
(space window_statement)*
|
11
|
+
(space order_by_statement)?
|
12
|
+
(space limit_statement)?
|
13
|
+
)?
|
14
|
+
(space* 'union' space query)?
|
15
|
+
(space / ';')*
|
16
|
+
<Query>
|
17
|
+
end
|
18
|
+
|
19
|
+
rule select_statement
|
20
|
+
'select' space (distinct_predicate space)? select_expressions <SelectStatement>
|
21
|
+
end
|
22
|
+
|
23
|
+
rule join_keyword
|
24
|
+
('left' / 'outer' / 'inner' / 'right' / 'full' / space)* 'join' <JoinKeyword>
|
25
|
+
end
|
26
|
+
|
27
|
+
rule join_statement
|
28
|
+
join_keyword space expression (space alias)? space 'on' space expression <JoinStatement>
|
29
|
+
end
|
30
|
+
|
31
|
+
rule where_statement
|
32
|
+
'where' space expression <WhereStatement>
|
33
|
+
end
|
34
|
+
|
35
|
+
rule having_statement
|
36
|
+
'having' space expression
|
37
|
+
end
|
38
|
+
|
39
|
+
rule window_statement
|
40
|
+
'window' space name_expression space 'as' space window
|
41
|
+
end
|
42
|
+
|
43
|
+
rule group_by_statement
|
44
|
+
'group by' space expression (expression_separator expression)* <Statement>
|
45
|
+
end
|
46
|
+
|
47
|
+
rule order_by_statement
|
48
|
+
'order by' space order_by_expression (expression_separator order_by_expression)* <OrderByStatement>
|
49
|
+
end
|
50
|
+
|
51
|
+
rule limit_statement
|
52
|
+
'limit' space [0-9]+ <LimitStatement>
|
53
|
+
end
|
54
|
+
|
55
|
+
rule select_expressions
|
56
|
+
select_expression (expression_separator select_expression)* <Entity>
|
57
|
+
end
|
58
|
+
|
59
|
+
rule expression_separator
|
60
|
+
space* ',' space* <Entity>
|
61
|
+
end
|
62
|
+
|
63
|
+
rule order_by_expression
|
64
|
+
expression (space ('desc' / 'asc'))? <OrderByExpression>
|
65
|
+
end
|
66
|
+
|
67
|
+
rule select_expression
|
68
|
+
expression (space alias)? <SelectExpression>
|
69
|
+
end
|
70
|
+
|
71
|
+
rule alias
|
72
|
+
('as' space)?
|
73
|
+
(
|
74
|
+
'"' ([^\"]+ <Alias>) '"'
|
75
|
+
/
|
76
|
+
!(keyword word_boundary) [\w]+ <Alias>
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
rule expression
|
81
|
+
prefix_modifier?
|
82
|
+
(
|
83
|
+
'(' query ')' <Query>
|
84
|
+
/
|
85
|
+
'(' space? expression space? ')' <Entity>
|
86
|
+
/
|
87
|
+
sub_expression)
|
88
|
+
(space? inline_window)?
|
89
|
+
('::' [\w]+)? # cast
|
90
|
+
(space? set_operator space? (set))*
|
91
|
+
(space? operator space? expression)? <Expression>
|
92
|
+
end
|
93
|
+
|
94
|
+
rule prefix_modifier
|
95
|
+
'not' space
|
96
|
+
/
|
97
|
+
'-' space?
|
98
|
+
end
|
99
|
+
|
100
|
+
rule inline_window
|
101
|
+
'over' space (window / name_expression)
|
102
|
+
end
|
103
|
+
|
104
|
+
# poorman window parsing right now... we'll need to beef this up if we use windows with parentheses
|
105
|
+
rule window
|
106
|
+
'(' [^\)]* ')'
|
107
|
+
end
|
108
|
+
|
109
|
+
rule set
|
110
|
+
set_literal
|
111
|
+
/
|
112
|
+
'(' query ')'
|
113
|
+
end
|
114
|
+
|
115
|
+
rule distinct_predicate
|
116
|
+
'distinct' / 'all'
|
117
|
+
end
|
118
|
+
|
119
|
+
rule set_literal
|
120
|
+
'(' space? primitive_literal (space? ',' space? primitive_literal)* space? ')'
|
121
|
+
end
|
122
|
+
|
123
|
+
rule primitive_literal
|
124
|
+
interval_literal / numeric_literal / string_literal / date_literal
|
125
|
+
end
|
126
|
+
|
127
|
+
rule interval_literal
|
128
|
+
'interval' space "'" [\w ]+ "'"
|
129
|
+
end
|
130
|
+
|
131
|
+
rule date_literal
|
132
|
+
'{d' space+ "'" [^\'\}]+ "'}"
|
133
|
+
end
|
134
|
+
|
135
|
+
rule numeric_literal
|
136
|
+
[0-9]+ ("." [0-9]+)?
|
137
|
+
end
|
138
|
+
|
139
|
+
rule sub_expression
|
140
|
+
case_statement
|
141
|
+
/
|
142
|
+
function
|
143
|
+
/
|
144
|
+
primitive_literal
|
145
|
+
/
|
146
|
+
field_reference
|
147
|
+
end
|
148
|
+
|
149
|
+
rule set_operator
|
150
|
+
'not in' / 'in' &space
|
151
|
+
end
|
152
|
+
|
153
|
+
rule operator
|
154
|
+
[+\-/=\|><!]+
|
155
|
+
/
|
156
|
+
('is not' / 'is' / 'like' / 'between' / 'and' / 'or') &space
|
157
|
+
end
|
158
|
+
|
159
|
+
rule case_statement
|
160
|
+
'case'
|
161
|
+
(space !('when') expression)?
|
162
|
+
(space 'when' space expression space 'then' space expression)*
|
163
|
+
(space 'else' space expression)?
|
164
|
+
space 'end' <Entity>
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
rule function
|
169
|
+
[\w]+ space* '(' (distinct_predicate space)? (expression / ',' / space)* ')' <Function>
|
170
|
+
end
|
171
|
+
|
172
|
+
rule string_literal
|
173
|
+
"'" [^\']+ "'"
|
174
|
+
end
|
175
|
+
|
176
|
+
rule name_expression
|
177
|
+
'"' ([^\"]+ <Name>) '"' <NameExpression>
|
178
|
+
/
|
179
|
+
([\w]+ <Name>) <NameExpression>
|
180
|
+
end
|
181
|
+
|
182
|
+
rule field_glob
|
183
|
+
'*' <FieldGlob>
|
184
|
+
end
|
185
|
+
|
186
|
+
rule field_reference
|
187
|
+
(name_expression "." ' '* <TablePart>)? (name_expression / field_glob) <FieldRef>
|
188
|
+
end
|
189
|
+
|
190
|
+
rule from_statement
|
191
|
+
'from' space+ from_expression (expression_separator from_expression)* <FromStatement>
|
192
|
+
end
|
193
|
+
|
194
|
+
rule from_expression
|
195
|
+
( name_expression
|
196
|
+
/
|
197
|
+
'(' query ')' <SubQuery>) (space alias)? <FromExpression>
|
198
|
+
end
|
199
|
+
|
200
|
+
# matches at least one space. Handles comments, too.
|
201
|
+
rule space
|
202
|
+
([\s]+ / ('--' (!"\n" .)+ ))+
|
203
|
+
end
|
204
|
+
|
205
|
+
rule word_boundary
|
206
|
+
![\w]
|
207
|
+
end
|
208
|
+
|
209
|
+
rule keyword
|
210
|
+
# 'select' / 'from' / 'inner' / 'outer' / 'full' / 'left' / 'right' / 'join' / 'on' / 'where' / 'group by' / 'order by' / 'having' / 'limit' / 'union'
|
211
|
+
(
|
212
|
+
'all' / 'analyse' / 'analyze' / 'and' / 'any' / 'array' / 'asc' / 'as' /
|
213
|
+
'binary' / 'both' /
|
214
|
+
'case' / 'cast' / 'check' / 'column' / 'constraint' / 'correlation' / 'create' / 'current_database' / 'current_date' / 'current_schema' / 'current_timestamp' / 'current_time' / 'current_user' /
|
215
|
+
'default' / 'deferrable' / 'desc' / 'distinct' / 'do' /
|
216
|
+
'else' / 'encoded' / 'end' / 'except' /
|
217
|
+
'false' / 'foreign' / 'for' / 'from' /
|
218
|
+
'grant' / 'grouped' / 'group' /
|
219
|
+
'having' /
|
220
|
+
'initially' / 'intersect' / 'intervalym' / 'interval' / 'into' / 'in' /
|
221
|
+
'join' /
|
222
|
+
'ksafe' /
|
223
|
+
'leading' / 'left' / 'limit' / 'localtimestamp' / 'localtime'
|
224
|
+
'match' /
|
225
|
+
'new' / 'not' / 'nullsequal' / 'null' /
|
226
|
+
'offset' / 'off' / 'old' / 'only' / 'on' / 'order' / 'or' /
|
227
|
+
'pinned' / 'placing' / 'primary' / 'projection' /
|
228
|
+
'references' /
|
229
|
+
'schema' / 'segmented' / 'select' / 'session_user' / 'some' / 'sysdate' /
|
230
|
+
'table' / 'then' / 'timeseries' / 'to' / 'trailing' / 'true' /
|
231
|
+
'unbounded' / 'union' / 'unique' / 'unsegmented' / 'user' / 'using' /
|
232
|
+
'when' / 'where' / 'window' / 'within' / 'with'
|
233
|
+
)
|
234
|
+
word_boundary
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vsql_parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.2'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tim Harper
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-09-04 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: treetop
|
16
|
+
requirement: &70201796873120 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.4'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70201796873120
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ruby-debug19
|
27
|
+
requirement: &70201796872740 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70201796872740
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &70201796872140 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - =
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.10.0
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70201796872140
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: pry
|
49
|
+
requirement: &70201796871700 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70201796871700
|
58
|
+
description: Provides treetop grammar for Vertica SQL. (Most likely works with Postgres
|
59
|
+
SQL as well)
|
60
|
+
email:
|
61
|
+
- tim@redbrainlabs.com
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- lib/formatter.rb
|
67
|
+
- lib/test_chamber.rb
|
68
|
+
- lib/vsql_node_extensions.rb
|
69
|
+
- lib/vsql_parser.rb
|
70
|
+
- lib/vsql_parser.treetop
|
71
|
+
- MIT_LICENSE
|
72
|
+
homepage:
|
73
|
+
licenses: []
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.3.6
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.8.7
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Vertica SQL Parser
|
96
|
+
test_files: []
|
97
|
+
has_rdoc:
|