sqlpp 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +55 -0
- data/Rakefile +9 -0
- data/bin/sqlpp +8 -0
- data/lib/sqlpp/ast.rb +30 -0
- data/lib/sqlpp/formatter.rb +163 -0
- data/lib/sqlpp/parser.rb +499 -0
- data/lib/sqlpp/tokenizer.rb +124 -0
- data/lib/sqlpp/version.rb +9 -0
- data/lib/sqlpp.rb +8 -0
- data/sqlpp.gemspec +25 -0
- data/test/formatter_test.rb +41 -0
- data/test/parser_test.rb +280 -0
- data/test/test_helper.rb +2 -0
- data/test/tokenizer_test.rb +130 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 24d6f928aab73c2d65cf5eefc714dfac7f441641
|
4
|
+
data.tar.gz: df2f51ed9d6aef74270f7dfb64322154d94dc0b3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dda5183a4e28c854494b164f6a725a2e35f788c1def25f256693f557cb9cdf2a95f49915d8ceb2d6c4a3491b81634f1253c30989cd913047b90597f42e39366a
|
7
|
+
data.tar.gz: 980e4630ec6de08556260f31ac361366b14a250f3ee8af89d77cbbc96c734e2a4d3426d3e0e283feb9f1e3f690d2d2e71898e6fb1e17545c142c45efd24cb6c1
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 Jamis Buck
|
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/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# SQLPP
|
2
|
+
|
3
|
+
SQLPP is a simplistic SQL parser and pretty-printer.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
require 'sqlpp'
|
9
|
+
|
10
|
+
sql = "..."
|
11
|
+
ast = SQLPP::Parser.parse(sql)
|
12
|
+
|
13
|
+
puts SQLPP::Formatter.new.format(ast)
|
14
|
+
```
|
15
|
+
|
16
|
+
Or, you can use the included `bin/sqlpp` script to format SQL via STDIN:
|
17
|
+
|
18
|
+
```sh
|
19
|
+
$ sqlpp < query.sql
|
20
|
+
```
|
21
|
+
|
22
|
+
## Output
|
23
|
+
|
24
|
+
The formatter is not particularly sophisticated, and is optimized primarily for displaying queries with deeply nested subselects. The major query components (`FROM`, `WHERE`, `GROUP BY`, and `ORDER BY`) are printed on separate lines, with subselects indented.
|
25
|
+
|
26
|
+
```sql
|
27
|
+
SELECT a, b, sum(c)
|
28
|
+
FROM (
|
29
|
+
SELECT d, e, f
|
30
|
+
FROM (
|
31
|
+
SELECT g, h, i
|
32
|
+
FROM table
|
33
|
+
WHERE id IN (1, 2, 3)
|
34
|
+
) a
|
35
|
+
WHERE a.e = 5
|
36
|
+
OR a.e = 7
|
37
|
+
) b
|
38
|
+
WHERE b.c > 5
|
39
|
+
GROUP BY a, b
|
40
|
+
ORDER BY a ASC, b DESC
|
41
|
+
```
|
42
|
+
|
43
|
+
## Caveats
|
44
|
+
|
45
|
+
This implementation is far, far, far from complete. It currently accepts only `SELECT` statements, and even then will only recognize a subset of the valid SQL syntax. That said, it should be a pretty big subset. It's done well enough for what I've needed it for.
|
46
|
+
|
47
|
+
If, however, you find that it doesn't recognize some syntax that you need, pull requests would be appreciated!
|
48
|
+
|
49
|
+
## License
|
50
|
+
|
51
|
+
MIT. See `MIT-LICENSE`.
|
52
|
+
|
53
|
+
## Author
|
54
|
+
|
55
|
+
Jamis Buck (jamis@jamisbuck.org)
|
data/Rakefile
ADDED
data/bin/sqlpp
ADDED
data/lib/sqlpp/ast.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module SQLPP
|
2
|
+
module AST
|
3
|
+
class Select < Struct.new(:projections, :froms, :wheres, :groups, :orders)
|
4
|
+
end
|
5
|
+
|
6
|
+
class Expr < Struct.new(:left, :op, :right)
|
7
|
+
end
|
8
|
+
|
9
|
+
class Unary < Struct.new(:op, :expr)
|
10
|
+
end
|
11
|
+
|
12
|
+
class Atom < Struct.new(:type, :left, :right)
|
13
|
+
end
|
14
|
+
|
15
|
+
class Parens < Struct.new(:value)
|
16
|
+
end
|
17
|
+
|
18
|
+
class As < Struct.new(:name, :expr)
|
19
|
+
end
|
20
|
+
|
21
|
+
class Alias < Struct.new(:name, :expr)
|
22
|
+
end
|
23
|
+
|
24
|
+
class Join < Struct.new(:type, :left, :right, :on)
|
25
|
+
end
|
26
|
+
|
27
|
+
class SortKey < Struct.new(:key, :options)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module SQLPP
|
2
|
+
class Formatter
|
3
|
+
def initialize
|
4
|
+
@indent = nil
|
5
|
+
@state = nil
|
6
|
+
end
|
7
|
+
|
8
|
+
def format(node)
|
9
|
+
name = node.class.to_s.split(/::/).last
|
10
|
+
send(:"_format_#{name}", node)
|
11
|
+
end
|
12
|
+
|
13
|
+
def _format_Select(node)
|
14
|
+
output = ""
|
15
|
+
|
16
|
+
if @indent.nil?
|
17
|
+
@indent = 0
|
18
|
+
else
|
19
|
+
@indent += 2
|
20
|
+
output << "\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
output << "#{_indent}SELECT "
|
24
|
+
output << node.projections.map { |c| format(c) }.join(", ")
|
25
|
+
output << "\n"
|
26
|
+
|
27
|
+
if node.froms
|
28
|
+
output << "#{_indent}FROM "
|
29
|
+
output << node.froms.map { |c| format(c) }.join(", ")
|
30
|
+
output << "\n"
|
31
|
+
end
|
32
|
+
|
33
|
+
if node.wheres
|
34
|
+
save, @state = @state, :where
|
35
|
+
output << "#{_indent}WHERE "
|
36
|
+
output << format(node.wheres)
|
37
|
+
output << "\n"
|
38
|
+
@state = save
|
39
|
+
end
|
40
|
+
|
41
|
+
if node.groups
|
42
|
+
output << "#{_indent}GROUP BY "
|
43
|
+
output << node.groups.map { |c| format(c) }.join(", ")
|
44
|
+
output << "\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
if node.orders
|
48
|
+
output << "#{_indent}ORDER BY "
|
49
|
+
output << node.orders.map { |c| format(c) }.join(", ")
|
50
|
+
output << "\n"
|
51
|
+
end
|
52
|
+
|
53
|
+
@indent -= 2
|
54
|
+
@indent = nil if @indent < 0
|
55
|
+
|
56
|
+
output << _indent
|
57
|
+
end
|
58
|
+
|
59
|
+
def _format_Expr(node)
|
60
|
+
output = format(node.left)
|
61
|
+
if node.op
|
62
|
+
op = node.op.to_s.upcase
|
63
|
+
|
64
|
+
if @state == :where && %w(AND OR).include?(op)
|
65
|
+
output << "\n#{_indent}"
|
66
|
+
else
|
67
|
+
output << " "
|
68
|
+
end
|
69
|
+
|
70
|
+
output << op << " "
|
71
|
+
output << format(node.right)
|
72
|
+
end
|
73
|
+
output
|
74
|
+
end
|
75
|
+
|
76
|
+
def _format_Unary(node)
|
77
|
+
op = node.op.to_s.upcase
|
78
|
+
output = op
|
79
|
+
output << " " if op =~ /\w/
|
80
|
+
output << format(node.expr)
|
81
|
+
end
|
82
|
+
|
83
|
+
def _format_Atom(node)
|
84
|
+
output = ""
|
85
|
+
|
86
|
+
case node.type
|
87
|
+
when :range
|
88
|
+
output << format(node.left) << " AND " << format(node.right)
|
89
|
+
when :list
|
90
|
+
output << "(" << node.left.map { |c| format(c) }.join(", ") << ")"
|
91
|
+
when :func
|
92
|
+
output << format(node.left) << "("
|
93
|
+
output << node.right.map { |c| format(c) }.join(", ")
|
94
|
+
output << ")"
|
95
|
+
when :lit
|
96
|
+
output << node.left
|
97
|
+
when :attr
|
98
|
+
output << node.left
|
99
|
+
output << "." << node.right if node.right
|
100
|
+
when :case
|
101
|
+
output << "CASE "
|
102
|
+
output << format(node.left) << " " if node.left
|
103
|
+
node.right.each do |child|
|
104
|
+
if child.is_a?(Array)
|
105
|
+
output << "WHEN " << format(child[0]) << " "
|
106
|
+
output << "THEN " << format(child[1]) << " "
|
107
|
+
else
|
108
|
+
output << "ELSE " << format(child) << " "
|
109
|
+
end
|
110
|
+
end
|
111
|
+
output << "END"
|
112
|
+
else
|
113
|
+
raise ArgumentError, "unknown atom type #{node.type.inspect}"
|
114
|
+
end
|
115
|
+
|
116
|
+
output
|
117
|
+
end
|
118
|
+
|
119
|
+
def _format_Parens(node)
|
120
|
+
"(" + format(node.value) + ")"
|
121
|
+
end
|
122
|
+
|
123
|
+
def _format_As(node)
|
124
|
+
format(node.expr) + " AS " + format(node.name)
|
125
|
+
end
|
126
|
+
|
127
|
+
def _format_Alias(node)
|
128
|
+
format(node.expr) + " " + format(node.name)
|
129
|
+
end
|
130
|
+
|
131
|
+
def _format_Join(node)
|
132
|
+
output = ""
|
133
|
+
|
134
|
+
output << format(node.left)
|
135
|
+
output << "\n#{_indent}"
|
136
|
+
output << node.type.upcase << " JOIN "
|
137
|
+
output << format(node.right)
|
138
|
+
output << "\n#{_indent}ON " << format(node.on) if node.on
|
139
|
+
|
140
|
+
output
|
141
|
+
end
|
142
|
+
|
143
|
+
def _format_SortKey(node)
|
144
|
+
output = ""
|
145
|
+
output << format(node.key)
|
146
|
+
|
147
|
+
if node.options.any?
|
148
|
+
output << " "
|
149
|
+
output << node.options.map { |opt| opt.upcase }.join(", ")
|
150
|
+
end
|
151
|
+
|
152
|
+
output
|
153
|
+
end
|
154
|
+
|
155
|
+
def _format_String(string)
|
156
|
+
string
|
157
|
+
end
|
158
|
+
|
159
|
+
def _indent
|
160
|
+
" " * (@indent || 0)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|