each_sql 0.2.5 → 0.3.0
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/CHANGELOG.markdown +14 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +7 -4
- data/README.markdown +23 -11
- data/Rakefile +0 -8
- data/VERSION +1 -1
- data/each_sql.gemspec +0 -3
- data/lib/each_sql.rb +45 -109
- data/lib/each_sql/each_sql.rb +79 -144
- data/lib/each_sql/parser.rb +39 -0
- data/lib/each_sql/parser/sql.citrus.erb +239 -0
- data/test/test_each_sql.rb +76 -276
- data/test/yml/common.yml +601 -0
- data/test/yml/default.yml +21 -0
- data/test/yml/mysql.yml +99 -0
- data/test/yml/oracle.yml +200 -0
- data/test/yml/postgres.yml +234 -0
- metadata +70 -16
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'citrus'
|
2
|
+
require 'erubis'
|
3
|
+
require 'quote_unquote'
|
4
|
+
|
5
|
+
class EachSQL
|
6
|
+
module Parser
|
7
|
+
@@parser = {}
|
8
|
+
# @param[Symbol] type RDBMS type: :default|:mysql|:oracle|:postgres
|
9
|
+
# @param[String] delimiter SQL delimiter
|
10
|
+
# @return[Module] Citrus parser
|
11
|
+
def self.parser_for type, delimiter = ';'
|
12
|
+
# Is there any better way of handling dynamic changes?
|
13
|
+
|
14
|
+
return @@parser[[type, delimiter]] if @@parser[[type, delimiter]]
|
15
|
+
|
16
|
+
path = File.join( File.dirname(__FILE__), 'parser/sql.citrus.erb' )
|
17
|
+
erb = Erubis::Eruby.new( File.read path )
|
18
|
+
suffix = @@parser.length.to_s
|
19
|
+
|
20
|
+
Citrus.eval erb.result(binding)
|
21
|
+
|
22
|
+
@@parser[[type, delimiter]] =
|
23
|
+
case type
|
24
|
+
when :default
|
25
|
+
eval "EachSQL::Parser::Default#{suffix}"
|
26
|
+
when :mysql
|
27
|
+
eval "EachSQL::Parser::MySQL#{suffix}"
|
28
|
+
when :oracle
|
29
|
+
eval "EachSQL::Parser::Oracle#{suffix}"
|
30
|
+
when :postgres, :postgresql
|
31
|
+
eval "EachSQL::Parser::PostgreSQL#{suffix}"
|
32
|
+
else
|
33
|
+
raise NotImplementedError.new(
|
34
|
+
"Parser not implemented for #{type}. Try use :default instead.")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end#Parser
|
38
|
+
end#EachSQL
|
39
|
+
|
@@ -0,0 +1,239 @@
|
|
1
|
+
grammar EachSQL::Parser::Default<%= suffix %>
|
2
|
+
rule program
|
3
|
+
(execution_block | empty | delimiter)* leftover?
|
4
|
+
end
|
5
|
+
|
6
|
+
rule leftover
|
7
|
+
.+
|
8
|
+
end
|
9
|
+
|
10
|
+
rule execution_block
|
11
|
+
(begin_end_block | sql_statement)
|
12
|
+
end
|
13
|
+
|
14
|
+
rule begin_end_block
|
15
|
+
begin_block
|
16
|
+
(begin_end_block | sql_statement | empty)+
|
17
|
+
end_block
|
18
|
+
delimiters
|
19
|
+
end
|
20
|
+
|
21
|
+
rule declare
|
22
|
+
/\bdeclare\b/i
|
23
|
+
end
|
24
|
+
|
25
|
+
rule begin_block
|
26
|
+
/\bbegin\b/i
|
27
|
+
end
|
28
|
+
|
29
|
+
rule end_block
|
30
|
+
# end something
|
31
|
+
/\bend\b/i (empty+ chunk*)?
|
32
|
+
end
|
33
|
+
|
34
|
+
rule sql_statement
|
35
|
+
(empty | delimiter)*
|
36
|
+
|
37
|
+
!(begin_block | end_block) chunk
|
38
|
+
|
39
|
+
(chunk | empty)*
|
40
|
+
delimiters
|
41
|
+
end
|
42
|
+
|
43
|
+
rule delimiters
|
44
|
+
empty* delimiter
|
45
|
+
(empty | delimiter)*
|
46
|
+
end
|
47
|
+
|
48
|
+
rule chunk
|
49
|
+
(qword | dqword | btword | word)
|
50
|
+
end
|
51
|
+
|
52
|
+
rule qword
|
53
|
+
sq (!sq .)* sq
|
54
|
+
end
|
55
|
+
|
56
|
+
rule dqword
|
57
|
+
dq (!dq .)* dq
|
58
|
+
end
|
59
|
+
|
60
|
+
rule btword
|
61
|
+
bt (!bt .)* bt
|
62
|
+
end
|
63
|
+
|
64
|
+
rule word
|
65
|
+
!(space | delimiter | '/*' | "--" | sq | dq | bt) .
|
66
|
+
end
|
67
|
+
|
68
|
+
rule empty
|
69
|
+
c_comment | l_comment | space
|
70
|
+
end
|
71
|
+
|
72
|
+
rule c_comment
|
73
|
+
'/*' (!'*/' .)* '*/'
|
74
|
+
end
|
75
|
+
|
76
|
+
rule l_comment
|
77
|
+
'--' (![\n\Z] .)*
|
78
|
+
end
|
79
|
+
|
80
|
+
rule space
|
81
|
+
[\s]
|
82
|
+
end
|
83
|
+
|
84
|
+
rule sq
|
85
|
+
"'"
|
86
|
+
end
|
87
|
+
|
88
|
+
rule dq
|
89
|
+
'"'
|
90
|
+
end
|
91
|
+
|
92
|
+
# Not ANSI standard. but.. wouldn't hurt.
|
93
|
+
rule bt
|
94
|
+
'`'
|
95
|
+
end
|
96
|
+
|
97
|
+
rule delimiter
|
98
|
+
<%= delimiter.qq('\"') %>
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
grammar EachSQL::Parser::MySQL<%= suffix %>
|
103
|
+
include EachSQL::Parser::Default<%= suffix %>
|
104
|
+
|
105
|
+
rule root
|
106
|
+
(execution_block | empty | delimiter)* leftover?
|
107
|
+
end
|
108
|
+
|
109
|
+
rule execution_block
|
110
|
+
(begin_end_block | sql_statement)
|
111
|
+
end
|
112
|
+
|
113
|
+
rule begin_end_block
|
114
|
+
begin_block
|
115
|
+
(begin_end_block | sql_statement | empty)+
|
116
|
+
end_block
|
117
|
+
delimiters
|
118
|
+
end
|
119
|
+
|
120
|
+
rule end_block
|
121
|
+
# end something
|
122
|
+
/\bend\b/i (empty+ chunk*)?
|
123
|
+
end
|
124
|
+
|
125
|
+
rule sql_statement
|
126
|
+
(empty | delimiter)*
|
127
|
+
|
128
|
+
!(begin_block | end_block) chunk
|
129
|
+
|
130
|
+
(chunk | empty)*
|
131
|
+
delimiters
|
132
|
+
end
|
133
|
+
|
134
|
+
rule chunk
|
135
|
+
(qword | dqword | btword | word)
|
136
|
+
end
|
137
|
+
|
138
|
+
rule qword
|
139
|
+
# Order matters
|
140
|
+
sq ("\\'" | (!sq .))* sq
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
grammar EachSQL::Parser::PostgreSQL<%= suffix %>
|
145
|
+
include EachSQL::Parser::Default<%= suffix %>
|
146
|
+
|
147
|
+
# TODO: code dupe
|
148
|
+
|
149
|
+
rule root
|
150
|
+
(execution_block | empty | delimiter)* leftover?
|
151
|
+
end
|
152
|
+
|
153
|
+
rule execution_block
|
154
|
+
(begin_end_block | sql_statement)
|
155
|
+
end
|
156
|
+
|
157
|
+
rule begin_end_block
|
158
|
+
begin_block
|
159
|
+
(begin_end_block | sql_statement | empty)+
|
160
|
+
end_block
|
161
|
+
delimiters
|
162
|
+
end
|
163
|
+
|
164
|
+
rule end_block
|
165
|
+
# end something
|
166
|
+
/\bend\b/i (empty+ chunk*)?
|
167
|
+
end
|
168
|
+
|
169
|
+
rule sql_statement
|
170
|
+
(empty | delimiter)*
|
171
|
+
|
172
|
+
!(begin_block | end_block) chunk
|
173
|
+
|
174
|
+
(chunk | empty)*
|
175
|
+
delimiters
|
176
|
+
end
|
177
|
+
|
178
|
+
rule chunk
|
179
|
+
(dollar_tag | double_dollar | qword | dqword | btword | word)
|
180
|
+
end
|
181
|
+
|
182
|
+
rule word
|
183
|
+
!(space | delimiter | '/*' | "--" | /\$[^$\s]+\$/ | '$$' | sq | dq | bt) .
|
184
|
+
end
|
185
|
+
|
186
|
+
rule dollar_tag
|
187
|
+
/(\$[^$\s]+\$).*?\1/m
|
188
|
+
end
|
189
|
+
|
190
|
+
rule double_dollar
|
191
|
+
'$$' (!'$$' .)* '$$'
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
grammar EachSQL::Parser::Oracle<%= suffix %>
|
196
|
+
include EachSQL::Parser::Default<%= suffix %>
|
197
|
+
|
198
|
+
rule root
|
199
|
+
(execution_block | empty | '/' | delimiter)* leftover?
|
200
|
+
end
|
201
|
+
|
202
|
+
rule execution_block
|
203
|
+
(
|
204
|
+
creation |
|
205
|
+
declare_begin_end_block |
|
206
|
+
begin_end_block |
|
207
|
+
sql_statement
|
208
|
+
)
|
209
|
+
end
|
210
|
+
|
211
|
+
rule declare_begin_end_block
|
212
|
+
declare
|
213
|
+
sql_statement+
|
214
|
+
begin_end_block
|
215
|
+
end
|
216
|
+
|
217
|
+
rule sql_statement
|
218
|
+
(empty | delimiter)*
|
219
|
+
|
220
|
+
!('/' | create | declare | begin_block | end_block) chunk
|
221
|
+
|
222
|
+
(chunk | empty)*
|
223
|
+
delimiters
|
224
|
+
end
|
225
|
+
|
226
|
+
rule creation
|
227
|
+
create
|
228
|
+
sql_statement+
|
229
|
+
(
|
230
|
+
( end_block delimiters ) | begin_end_block
|
231
|
+
)
|
232
|
+
end
|
233
|
+
|
234
|
+
rule create
|
235
|
+
/\bcreate\b/i (empty+ /or/i empty+ /replace/i)? empty+
|
236
|
+
/\b(procedure|function|trigger|package)\b/i
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
data/test/test_each_sql.rb
CHANGED
@@ -2,257 +2,13 @@
|
|
2
2
|
|
3
3
|
$LOAD_PATH << File.dirname(__FILE__)
|
4
4
|
require 'helper'
|
5
|
+
require 'yaml'
|
5
6
|
|
6
7
|
class TestEachSql < Test::Unit::TestCase
|
7
|
-
|
8
|
-
@sql = [
|
9
|
-
"-------------- begin-end block;
|
10
|
-
declare
|
11
|
-
/* end; */
|
12
|
-
/* begin */
|
13
|
-
null;
|
14
|
-
null;
|
15
|
-
null;
|
16
|
-
begin
|
17
|
-
/* end */
|
18
|
-
null;
|
19
|
-
end",
|
20
|
-
"-------------- begin-end block;
|
21
|
-
begin
|
22
|
-
-- begin-end block;
|
23
|
-
-- line comment
|
24
|
-
-- line comment
|
25
|
-
-- line comment
|
26
|
-
begin
|
27
|
-
null;
|
28
|
-
begin
|
29
|
-
null;
|
30
|
-
end;
|
31
|
-
end;
|
32
|
-
-- end
|
33
|
-
/* end */
|
34
|
-
end",
|
35
|
-
"select * from a",
|
36
|
-
"select
|
37
|
-
*
|
38
|
-
from
|
39
|
-
b",
|
40
|
-
"select 'abc', 'abc;', 'abc''', 'abc/*', 'abc--' from c",
|
41
|
-
|
42
|
-
"select
|
43
|
-
/*+ help */ *
|
44
|
-
from
|
45
|
-
d",
|
46
|
-
"select * from /* block comment ; */ e",
|
47
|
-
"select *
|
48
|
-
from -- line comment ; /* ;; */
|
49
|
-
f",
|
50
|
-
"-------------- begin-end block;
|
51
|
-
declare
|
52
|
-
/* end; */
|
53
|
-
/* begin */
|
54
|
-
null;
|
55
|
-
null;
|
56
|
-
null;
|
57
|
-
begin
|
58
|
-
-- begin-end block;
|
59
|
-
-- line comment
|
60
|
-
-- line comment
|
61
|
-
-- line comment
|
62
|
-
begin
|
63
|
-
null;
|
64
|
-
begin
|
65
|
-
null;
|
66
|
-
end;
|
67
|
-
end;
|
68
|
-
-- end
|
69
|
-
/* end */
|
70
|
-
end",
|
71
|
-
"select * from dual",
|
72
|
-
"select b `begin` from dual",
|
73
|
-
'select b "begin" from dual',
|
74
|
-
'select
|
75
|
-
begin , begin.* from begin'
|
76
|
-
]
|
77
|
-
|
78
|
-
@oracle = [
|
79
|
-
'select * from dual',
|
80
|
-
'create /* procedure */ sequence a',
|
81
|
-
"create package something as
|
82
|
-
procedure log;
|
83
|
-
procedure log;
|
84
|
-
procedure log;
|
85
|
-
end something;",
|
86
|
-
"Create or replace Procedure tmmp(p1 number default 'begin', p2 number) as
|
87
|
-
str number(8, 2) := 1 / 4;
|
88
|
-
begin
|
89
|
-
1 / 2;
|
90
|
-
begin
|
91
|
-
1 / 4;
|
92
|
-
null;
|
93
|
-
end;
|
94
|
-
exception
|
95
|
-
when others then
|
96
|
-
raise;
|
97
|
-
end;",
|
98
|
-
"-- declaration
|
99
|
-
declare
|
100
|
-
a int;
|
101
|
-
begin
|
102
|
-
1 / 2;
|
103
|
-
begin
|
104
|
-
1 / 4;
|
105
|
-
null;
|
106
|
-
end;
|
107
|
-
exception
|
108
|
-
when others then
|
109
|
-
raise;
|
110
|
-
end;",
|
111
|
-
"begin
|
112
|
-
null;
|
113
|
-
end;",
|
114
|
-
"begin
|
115
|
-
null;
|
116
|
-
end;",
|
117
|
-
"select * from dual",
|
118
|
-
"select begin, end, create, procedure, end, from dual",
|
119
|
-
"select * from dual",
|
120
|
-
"-- TMP_DB_TOOLS_CONV
|
121
|
-
begin
|
122
|
-
execute immediate 'DROP TABLE TMP_DB_TOOLS_CONV CASCADE CONSTRAINTS';
|
123
|
-
exception
|
124
|
-
when others then
|
125
|
-
null;
|
126
|
-
end;"
|
127
|
-
]
|
128
|
-
|
129
|
-
@oracle_script = "
|
130
|
-
select * from dual;
|
131
|
-
;;;;;;;
|
132
|
-
;;;
|
133
|
-
;;
|
134
|
-
|
135
|
-
create /* procedure */ sequence a;
|
136
|
-
create package something as
|
137
|
-
procedure log;
|
138
|
-
procedure log;
|
139
|
-
procedure log;
|
140
|
-
end something;
|
141
|
-
/
|
142
|
-
|
143
|
-
Create or replace Procedure tmmp(p1 number default 'begin', p2 number) as
|
144
|
-
str number(8, 2) := 1 / 4;
|
145
|
-
begin
|
146
|
-
1 / 2;
|
147
|
-
begin
|
148
|
-
1 / 4;
|
149
|
-
null;
|
150
|
-
end;
|
151
|
-
exception
|
152
|
-
when others then
|
153
|
-
raise;
|
154
|
-
end;
|
155
|
-
/
|
156
|
-
-- declaration
|
157
|
-
declare
|
158
|
-
a int;
|
159
|
-
begin
|
160
|
-
1 / 2;
|
161
|
-
begin
|
162
|
-
1 / 4;
|
163
|
-
null;
|
164
|
-
end;
|
165
|
-
exception
|
166
|
-
when others then
|
167
|
-
raise;
|
168
|
-
end;
|
169
|
-
/
|
170
|
-
begin
|
171
|
-
null;
|
172
|
-
end;
|
173
|
-
/
|
174
|
-
begin
|
175
|
-
null;
|
176
|
-
end;
|
177
|
-
/
|
178
|
-
select * from dual;
|
179
|
-
;
|
180
|
-
;
|
181
|
-
;
|
182
|
-
|
183
|
-
|
184
|
-
;;;;;;
|
185
|
-
;
|
186
|
-
|
187
|
-
select begin, end, create, procedure, end, from dual;
|
188
|
-
select * from dual;
|
189
|
-
|
190
|
-
-- TMP_DB_TOOLS_CONV
|
191
|
-
begin
|
192
|
-
execute immediate 'DROP TABLE TMP_DB_TOOLS_CONV CASCADE CONSTRAINTS';
|
193
|
-
exception
|
194
|
-
when others then
|
195
|
-
null;
|
196
|
-
end;
|
197
|
-
/
|
198
|
-
"
|
199
|
-
|
200
|
-
@mysql = [
|
201
|
-
"drop procedure if exists proc",
|
202
|
-
"create procedure proc(p1 int, p2 int)
|
203
|
-
begin
|
204
|
-
null;
|
205
|
-
begin
|
206
|
-
null;
|
207
|
-
end;
|
208
|
-
end",
|
209
|
-
"drop procedure if exists proc2",
|
210
|
-
"create procedure proc(p1 int, p2 int)
|
211
|
-
begin
|
212
|
-
null;
|
213
|
-
|
214
|
-
end",
|
215
|
-
"select * from dual",
|
216
|
-
"select b `begin` from dual",
|
217
|
-
'select b "begin" from dual',
|
218
|
-
'select
|
219
|
-
begin , begin.* from begin'
|
220
|
-
]
|
221
|
-
@mysql_script = "
|
222
|
-
delimiter //
|
223
|
-
drop procedure if exists proc //
|
224
|
-
create procedure proc(p1 int, p2 int)
|
225
|
-
begin
|
226
|
-
null;
|
227
|
-
begin
|
228
|
-
null;
|
229
|
-
end;
|
230
|
-
end //
|
231
|
-
delimiter ;
|
232
|
-
|
233
|
-
delimiter $$
|
234
|
-
drop procedure if exists proc2 $$
|
235
|
-
create procedure proc(p1 int, p2 int)
|
236
|
-
begin
|
237
|
-
null;
|
238
|
-
|
239
|
-
end $$
|
240
|
-
delimiter ;
|
241
|
-
|
242
|
-
select * from dual;;;;;
|
243
|
-
;;;select b `begin` from dual;
|
244
|
-
select b \"begin\" from dual;
|
245
|
-
select
|
246
|
-
begin , begin.* from begin"
|
247
|
-
end
|
248
|
-
|
249
|
-
def test_sql
|
8
|
+
def _test_empty
|
250
9
|
[nil, "", " \n" * 10].each do |input|
|
251
10
|
EachSQL(input).each do |sql|
|
252
|
-
|
253
|
-
end
|
254
|
-
|
255
|
-
EachSQL(input) do |sql|
|
11
|
+
p sql
|
256
12
|
assert false, 'Should not enumerate'
|
257
13
|
end
|
258
14
|
|
@@ -262,36 +18,80 @@ select
|
|
262
18
|
end
|
263
19
|
assert true, 'No error expected'
|
264
20
|
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def _test_parser_cache
|
24
|
+
[:default, :mysql, :oracle, :postgres].each do |typ|
|
25
|
+
%w[';', '$$', '//'].each do |delim|
|
26
|
+
arr =
|
27
|
+
10.times.map {
|
28
|
+
EachSQL::Parser.parser_for typ, delim
|
29
|
+
}
|
30
|
+
p arr
|
31
|
+
assert_equal 10, arr.length
|
32
|
+
assert_equal 1, arr.uniq.length
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
265
37
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
38
|
+
def test_sql
|
39
|
+
common = YAML.load(
|
40
|
+
File.read(
|
41
|
+
File.join(
|
42
|
+
File.dirname(__FILE__), "yml/common.yml")))
|
43
|
+
[:default, :mysql, :oracle, :postgres].each do |typ|
|
44
|
+
data = YAML.load(
|
45
|
+
File.read(
|
46
|
+
File.join(
|
47
|
+
File.dirname(__FILE__), "yml/#{typ}.yml")))
|
48
|
+
|
49
|
+
script = nil
|
50
|
+
[common, data].each do |d|
|
51
|
+
script = d['all']
|
52
|
+
EachSQL(script, typ).each_with_index do |sql, idx|
|
53
|
+
expect = d['each'][idx].chomp
|
54
|
+
puts sql
|
55
|
+
puts '-' * 40
|
56
|
+
if typ == :oracle
|
57
|
+
assert expect == sql || sql == expect + ';',
|
58
|
+
[expect, 'x' * 80, sql].join($/)
|
59
|
+
else
|
60
|
+
assert_equal expect, sql
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
cnt = 0
|
66
|
+
EachSQL(script, typ) do |sql|
|
67
|
+
cnt += 1
|
68
|
+
end
|
69
|
+
assert_equal data['each'].length, cnt
|
70
|
+
assert_equal cnt, EachSQL(script, typ).to_a.length
|
71
|
+
assert_equal EachSQL(script, typ).to_a, EachSQL(script, typ).map { |e| e }
|
72
|
+
end
|
273
73
|
end
|
274
74
|
|
275
|
-
def test_oracle
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
end
|
282
|
-
|
283
|
-
def test_mysql
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
end
|
290
|
-
|
291
|
-
def _test_postgres
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
end
|
75
|
+
# def test_oracle
|
76
|
+
# EachSQL(@oracle_script, :oracle).each_with_index do |sql,idx|
|
77
|
+
# puts sql
|
78
|
+
# puts '-' * 40
|
79
|
+
# assert_equal @oracle[idx], sql
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
|
83
|
+
# def test_mysql
|
84
|
+
# EachSQL(@mysql_script, :mysql).each_with_index do |sql,idx|
|
85
|
+
# puts sql
|
86
|
+
# puts '-' * 40
|
87
|
+
# assert_equal @mysql[idx], sql
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
|
91
|
+
# def _test_postgres
|
92
|
+
# EachSQL(File.read(File.dirname(__FILE__) + '/postgres.sql'), :postgres).each_with_index do |sql,idx|
|
93
|
+
# puts sql
|
94
|
+
# puts '-' * 40
|
95
|
+
# end
|
96
|
+
# end
|
297
97
|
end
|