each_sql 0.1.0 → 0.2.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.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ === 0.2.0 / 2011/06/17
2
+ * Second release. Handles more cases.
3
+ * Ruby 1.8 compatible
4
+
5
+ === 0.1.0 / 2011/06/15
6
+ * Initial release. Turned out to be very flawed :p
7
+
data/Gemfile.lock CHANGED
@@ -8,8 +8,10 @@ GEM
8
8
  rake
9
9
  rake (0.8.7)
10
10
  rcov (0.9.9)
11
+ rcov (0.9.9-java)
11
12
 
12
13
  PLATFORMS
14
+ java
13
15
  ruby
14
16
 
15
17
  DEPENDENCIES
data/README.rdoc CHANGED
@@ -31,9 +31,7 @@ Enumerate each SQL statement in the given SQL script.
31
31
  - pgplsql support.
32
32
 
33
33
  == Warning
34
- Should be good enough for regular SQLs, but it is not guaranteed to parse stored procedures correctly.
35
- I thought this would be simple when I first started, but actually it wasn't, and the code now is a mess.
36
- Use it at your own risk.
34
+ Stored procedure handling is at best incomplete. Use it at your own risk.
37
35
 
38
36
  == Contributing to each_sql
39
37
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -8,7 +8,7 @@ class EachSQL
8
8
  def initialize input, options
9
9
  raise NotImplementedError.new if options.nil?
10
10
  # immutables
11
- @org_input = input
11
+ @org_input = input.sub(/\A#{[65279].pack('U*')}/, '') # BOM
12
12
  @options = options
13
13
  @blocks = @options[:blocks]
14
14
  @nblocks = @options[:nesting_blocks]
@@ -16,21 +16,17 @@ class EachSQL
16
16
  end
17
17
 
18
18
  def each
19
- @input = @org_input
19
+ # Phase 1: comment out input
20
+ @input = @org_input.dup
21
+ @input_c = zero_out @org_input
22
+
20
23
  return nil if @input.nil? || @input.empty?
21
24
 
22
25
  @delimiter = @options[:delimiter]
23
26
 
24
- reset_cursor
25
- while @input
26
- # First look for next delimiter, this is to reduce the search space for blocks.
27
- extend_scope
28
-
29
- # We're done. Finished. Period. Out!
30
- break if scope.empty?
31
-
27
+ while @input && @input.length > 0
32
28
  # Extract a statement
33
- statement = extract_statement
29
+ statement = next_statement
34
30
 
35
31
  # When a non-empty statement is found
36
32
  statement = @options[:strip_delimiter].call self, statement if @options[:strip_delimiter]
@@ -48,7 +44,8 @@ class EachSQL
48
44
  end
49
45
 
50
46
  # Ignore
51
- if (@options[:ignore] || []).all? { |ipat| statement !~ ipat }
47
+ if statement.length > 0 &&
48
+ (@options[:ignore] || []).all? { |ipat| statement !~ ipat }
52
49
  yield statement
53
50
  @prev_statement = statement
54
51
  end
@@ -59,75 +56,84 @@ class EachSQL
59
56
 
60
57
  attr_accessor :delimiter, :delimiter_string
61
58
  private
59
+ def zero_out input
60
+ output = input.dup
61
+ idx = 0
62
+ # Look for the closest block
63
+ while true
64
+ block_start, opener_length, opener, closer = @blocks.map { |opener, closer|
65
+ md = match output, opener, idx
66
+ [md && md[:begin], md && md[:length], opener, closer]
67
+ }.reject { |e| e.first.nil? }.min_by(&:first)
68
+ break if block_start.nil?
62
69
 
63
- def extract_statement
64
- while process_next_block != :not_found
65
- end
66
-
67
- ret = scope.strip
68
- @input = @input[@next_head..-1]
69
- reset_cursor
70
- return ret
71
- end
72
-
73
- def reset_cursor
74
- @cur = @from = @to = 0
75
- end
70
+ md = match output, closer, block_start + opener_length
71
+ idx = block_end = md ? md[:end] : (output.length-1)
76
72
 
77
- def scope
78
- @to ? @input[0, @to] : @input
73
+ output[block_start...block_end] = ' ' * (block_end - block_start)
74
+ end
75
+ output
79
76
  end
80
77
 
81
- def extend_scope
82
- md = @input.match @delimiter, @to
78
+ def next_statement
79
+ @cur = 0
83
80
 
84
- if md
85
- @to = md.begin(0) + md[0].length
86
- @next_head = @to
87
- else
88
- @to = @input.length
89
- @next_head = @input.length
81
+ while process_next_block != :done
90
82
  end
91
- #puts "Extended: #{scope.inspect} #{@input[@next_head..-1]}"
83
+
84
+ ret = @input[0...@cur].strip
85
+ @input = @input[@cur..-1]
86
+ @input_c = @input_c[@cur..-1]
87
+ return ret
92
88
  end
93
89
 
94
90
  def process_next_block expect = nil
91
+ # Look for the closest delimiter
92
+ md = match @input_c, @delimiter, @cur
93
+ delim_start = md ? md[:begin] : @input.length
94
+ delim_end = md ? md[:end] : @input.length
95
+
95
96
  # Look for the closest block
96
- block_start, opener_length, opener, closer = @all_blocks.map { |opener, closer|
97
+ target_blocks =
98
+ if @options[:nesting_context].any? {|pat| @input_c.match pat }
99
+ @all_blocks
100
+ else
101
+ @blocks
102
+ end
103
+
104
+ block_start, body_start, opener, closer = target_blocks.map { |opener, closer|
97
105
  closer = closer[:closer] if closer.is_a? Hash
98
- md = scope.match(opener, @cur)
99
- [md && md.begin(0), md && md[0].length, opener, closer]
106
+ md = match @input_c, opener, @cur
107
+ [md && md[:begin], md && md[:end], opener, closer]
100
108
  }.reject { |e| e.first.nil? }.min_by(&:first)
101
109
 
102
- # p [scope, scope[@cur..-1], expect, scope.index(expect, @cur), block_start] if expect
103
- # We found a block, but after the end of the nesting block
104
- if expect &&
105
- (prev_end = scope.index(expect, @cur)) &&
106
- (block_start.nil? || prev_end <= block_start)
107
- skip_through_block expect, prev_end == block_start
108
- return :end_nest
110
+ # If we're nested, look for the parent's closer as well
111
+ if expect && (md = match @input_c, expect, @cur) &&
112
+ (block_start.nil? || md[:begin] < block_start)
113
+
114
+ @cur = md[:end]
115
+ return :nest_closer
109
116
  end
110
117
 
111
- # If no block in this scope
112
- return :not_found if block_start.nil?
118
+ # No block until the next delimiter
119
+ if block_start.nil? || block_start > delim_start
120
+ @cur = delim_end
121
+ return :done
122
+ end
123
+
124
+ # #####################################
113
125
 
114
126
  # We found a block. Look for the end of it
115
- @cur = block_start + opener_length
127
+ @cur = body_start
116
128
 
117
129
  # If nesting block, we go deeper
118
130
  if @nblocks.keys.include? opener
119
- @prev_delimiter = @delimiter
120
- if @nblocks[opener].is_a? Hash
121
- @delimiter = @nblocks[opener][:delimiter] || @delimiter
122
- end
123
131
  while true
124
132
  ret = process_next_block(closer)
125
-
126
- break if ret == :end_nest
127
- extend_scope if ret == :not_found
128
- throw_exception(closer) if scope.length == @input.length
133
+ break if ret == :nest_closer
134
+ throw_exception(closer) if @cur >= @input.length - 1
129
135
  end
130
- @delimiter = @prev_delimiter
136
+ return :done if @nblocks[opener].is_a?(Hash) && @nblocks[opener][:pop]
131
137
 
132
138
  # If non-nesting block, just skip through it
133
139
  else
@@ -137,22 +143,29 @@ private
137
143
  return :continue
138
144
  end
139
145
 
140
- def skip_through_block closer, rewind_delimiter = false
141
- md = @input.match closer, @cur
142
- block_end = md && md.begin(0)
146
+ def match str, pat, idx
147
+ md = str[idx..-1].match(pat)
148
+ return nil if md.nil?
143
149
 
144
- throw_exception(closer) if block_end.nil?
150
+ result = {
151
+ :begin => md && (md.begin(0) + idx),
152
+ :length => md && md[0].length,
153
+ :end => md && (md.end(0) + idx)
154
+ }
155
+ result
156
+ end
145
157
 
146
- @cur = block_end + (rewind_delimiter ? 0 : md[0].length)
147
- while @cur > scope.length
148
- extend_scope
149
- end
158
+ def skip_through_block closer
159
+ md = match @input_c, closer, @cur
160
+ throw_exception(closer) if md.nil?
161
+
162
+ @cur = md[:end]
150
163
  end
151
164
 
152
165
  def throw_exception closer
153
166
  raise ArgumentError.new(
154
167
  "Unclosed block: was expecting #{closer.inspect} " +
155
- "while processing #{$/ + scope.inspect}" +
168
+ "while processing #{(@input[0, 60] + ' ... ').inspect}" +
156
169
  (@prev_statement ?
157
170
  " after #{@prev_statement.inspect}" : ""))
158
171
  end
data/lib/each_sql.rb CHANGED
@@ -20,14 +20,19 @@ class EachSQL
20
20
  :default => {
21
21
  :delimiter => /;+/,
22
22
  :blocks => {
23
+ /`/ => /`/,
24
+ /"/ => /"/,
23
25
  /'/ => /'/,
24
26
  /\/\*[^+]/ => /\*\//,
25
27
  /--+/ => $/,
26
28
  },
27
29
  :nesting_blocks => {
28
- /\bdeclare\b/i => /\bbegin\b/i,
29
- /\bbegin\b/i => /\bend\b/i
30
+ /\bdeclare.*?;\s*?begin\b/im => /;\s*?end\b/i,
31
+ /\bbegin\b/i => /;\s*?end\b/i,
30
32
  },
33
+ :nesting_context => [
34
+ /\A\s*(begin|declare|create\b[^;]+?\b(procedure|function|trigger|package))\b/im
35
+ ],
31
36
  :callbacks => {},
32
37
  :ignore => [],
33
38
  :replace => {},
@@ -38,6 +43,8 @@ class EachSQL
38
43
  :mysql => {
39
44
  :delimiter => /;+|delimiter\s+\S+/i,
40
45
  :blocks => {
46
+ /`/ => /`/,
47
+ /"/ => /"/,
41
48
  /'/ => /'/,
42
49
  /\/\*[^+]/ => /\*\//,
43
50
  /--+/ => $/,
@@ -45,11 +52,14 @@ class EachSQL
45
52
  :nesting_blocks => {
46
53
  /\bbegin\b/i => /\bend\b/i
47
54
  },
55
+ :nesting_context => [
56
+ /\A\s*(begin|create\b[^;]+?\b(procedure|function|trigger))\b/im
57
+ ],
48
58
  # We need to change delimiter on `delimiter' command
49
59
  :callbacks => {
50
60
  /^\s*delimiter\s+(\S+)/i => lambda { |obj, stmt, md|
51
61
  new_delimiter = Regexp.new(Regexp.escape md[1])
52
- obj.delimiter = /#{new_delimiter}+|delimiter\s+\S+/i
62
+ obj.delimiter = /(#{new_delimiter})+|delimiter\s+\S+/i
53
63
  obj.delimiter_string = md[1]
54
64
  }
55
65
  },
@@ -58,29 +68,45 @@ class EachSQL
58
68
  ],
59
69
  :replace => {},
60
70
  :strip_delimiter => lambda { |obj, stmt|
61
- stmt.chomp(obj.delimiter_string || ';')
71
+ stmt.gsub(/(#{Regexp.escape(obj.delimiter_string || ';')})+\Z/, '')
62
72
  }
63
73
  },
64
74
 
65
75
  :oracle => {
66
76
  :delimiter => /;+/,
67
77
  :blocks => {
78
+ /`/ => /`/,
79
+ /"/ => /"/,
68
80
  /'/ => /'/,
69
81
  /\/\*[^+]/ => /\*\//,
70
82
  /--+/ => $/,
71
83
  },
72
84
  :nesting_blocks => {
73
85
  /\bbegin\b/i => /\bend\b/i,
74
- /\bcreate[^;]*\b(procedure|function|trigger|package)\b/im => {
86
+ /\bdeclare.*?;\s*?begin\b/im => {
75
87
  :closer => %r{;\s*/}m,
76
- :delimiter => /;\s*\//
88
+ # Stops immediately
89
+ :pop => true
90
+ },
91
+ /\bcreate[^;]+?\b(procedure|function|trigger|package)\b/im => {
92
+ :closer => %r{;\s*/}m,
93
+ # Stops immediately
94
+ :pop => true
95
+ }
96
+ },
97
+ :nesting_context => [
98
+ /\A\s*(begin|declare|create\b[^;]+?\b(procedure|function|trigger|package))\b/im
99
+ ],
100
+ :callbacks => {
101
+ /\Abegin\b/ => lambda { |obj, stmt, md|
102
+ # Oracle needs this
103
+ stmt << ';' if stmt !~ /;\Z/
77
104
  }
78
105
  },
79
- :callbacks => {},
80
106
  :ignore => [],
81
- :replace => {},
107
+ :replace => { %r[\A/] => '' },
82
108
  :strip_delimiter => lambda { |obj, stmt| obj
83
- stmt.chomp( stmt =~ /;\s*\// ? '/' : ';' )
109
+ stmt.gsub(/(#{stmt =~ /;\s*\// ? '/' : ';'})+\Z/, '')
84
110
  }
85
111
  }
86
112
  }
@@ -1,24 +1,22 @@
1
+ # encoding: UTF-8
2
+
1
3
  $LOAD_PATH << File.dirname(__FILE__)
2
4
  require 'helper'
3
5
 
4
6
  class TestEachSql < Test::Unit::TestCase
5
7
  def setup
6
8
  @sql = [
7
- "select * from a",
8
- "select
9
- *
10
- from
11
- b",
12
- "select 'abc', 'abc;', 'abc''', 'abc/*', 'abc--' from c",
13
-
14
- "select
15
- /*+ help */ *
16
- from
17
- d",
18
- "select * from /* block comment ; */ e",
19
- "select *
20
- from -- line comment ; /* ;; */
21
- f",
9
+ "-------------- begin-end block;
10
+ declare
11
+ /* end; */
12
+ /* begin */
13
+ null;
14
+ null;
15
+ null;
16
+ begin
17
+ /* end */
18
+ null;
19
+ end",
22
20
  "-------------- begin-end block;
23
21
  begin
24
22
  -- begin-end block;
@@ -34,16 +32,21 @@ begin
34
32
  -- end
35
33
  /* end */
36
34
  end",
37
- "-------------- begin-end block;
38
- declare
39
- /* end; */
40
- /* begin */
41
- null;
42
- null;
43
- null;
44
- begin
45
- /* end */
46
- 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",
47
50
  "-------------- begin-end block;
48
51
  declare
49
52
  /* end; */
@@ -66,13 +69,79 @@ begin
66
69
  /* end */
67
70
  end",
68
71
  "select * from dual",
69
- "select * from dual"]
72
+ "select b `begin` from dual",
73
+ 'select b "begin" from dual',
74
+ 'select
75
+ begin , begin.* from begin'
76
+ ]
70
77
 
71
- @oracle = "
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
+ "declare
99
+ a int;
100
+ begin
101
+ 1 / 2;
102
+ begin
103
+ 1 / 4;
104
+ null;
105
+ end;
106
+ exception
107
+ when others then
108
+ raise;
109
+ end;",
110
+ "begin
111
+ null;
112
+ end;",
113
+ "select * from dual",
114
+ "select begin, end, create, procedure, end, from dual",
115
+ "select * from dual"
116
+ ]
117
+
118
+ @oracle_script = "
72
119
  select * from dual;
73
- Create or replace Procedure tmmp(p1 number, p2 number) as
120
+ create /* procedure */ sequence a;
121
+ create package something as
122
+ procedure log;
123
+ procedure log;
124
+ procedure log;
125
+ end something;
126
+ /
127
+
128
+ Create or replace Procedure tmmp(p1 number default 'begin', p2 number) as
74
129
  str number(8, 2) := 1 / 4;
75
130
  begin
131
+ 1 / 2;
132
+ begin
133
+ 1 / 4;
134
+ null;
135
+ end;
136
+ exception
137
+ when others then
138
+ raise;
139
+ end;
140
+ /
141
+ declare
142
+ a int;
143
+ begin
144
+ 1 / 2;
76
145
  begin
77
146
  1 / 4;
78
147
  null;
@@ -82,15 +151,53 @@ exception
82
151
  raise;
83
152
  end;
84
153
  /
85
- select * from dual;"
154
+ begin
155
+ null;
156
+ end;
157
+ /
158
+ select * from dual;
159
+ ;
160
+ ;
161
+ ;
86
162
 
87
- @mysql = "
163
+
164
+ ;;;;;;
165
+ ;
166
+
167
+ select begin, end, create, procedure, end, from dual;
168
+ select * from dual;
169
+ "
170
+
171
+ @mysql = [
172
+ "drop procedure if exists proc",
173
+ "create procedure proc(p1 int, p2 int)
174
+ begin
175
+ null;
176
+ begin
177
+ null;
178
+ end;
179
+ end",
180
+ "drop procedure if exists proc2",
181
+ "create procedure proc(p1 int, p2 int)
182
+ begin
183
+ null;
184
+
185
+ end",
186
+ "select * from dual",
187
+ "select b `begin` from dual",
188
+ 'select b "begin" from dual',
189
+ 'select
190
+ begin , begin.* from begin'
191
+ ]
192
+ @mysql_script = "
88
193
  delimiter //
89
194
  drop procedure if exists proc //
90
195
  create procedure proc(p1 int, p2 int)
91
196
  begin
92
197
  null;
93
-
198
+ begin
199
+ null;
200
+ end;
94
201
  end //
95
202
  delimiter ;
96
203
 
@@ -103,7 +210,11 @@ begin
103
210
  end $$
104
211
  delimiter ;
105
212
 
106
- select * from dual;"
213
+ select * from dual;
214
+ select b `begin` from dual;
215
+ select b \"begin\" from dual;
216
+ select
217
+ begin , begin.* from begin"
107
218
  end
108
219
 
109
220
  def test_sql
@@ -117,16 +228,18 @@ select * from dual;"
117
228
  end
118
229
 
119
230
  def test_oracle
120
- EachSQL(@oracle, :oracle).each_with_index do |sql,idx|
231
+ EachSQL(@oracle_script, :oracle).each_with_index do |sql,idx|
121
232
  puts sql
122
233
  puts '-' * 40
234
+ assert_equal @oracle[idx], sql
123
235
  end
124
236
  end
125
237
 
126
238
  def test_mysql
127
- EachSQL(@mysql, :mysql).each_with_index do |sql,idx|
239
+ EachSQL(@mysql_script, :mysql).each_with_index do |sql,idx|
128
240
  puts sql
129
241
  puts '-' * 40
242
+ assert_equal @mysql[idx], sql
130
243
  end
131
244
  end
132
245
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: each_sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-06-15 00:00:00.000000000Z
12
+ date: 2011-06-16 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
16
- requirement: &2161738720 !ruby/object:Gem::Requirement
16
+ requirement: &2161248000 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 1.0.0
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *2161738720
24
+ version_requirements: *2161248000
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: jeweler
27
- requirement: &2161738240 !ruby/object:Gem::Requirement
27
+ requirement: &2161247520 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 1.6.2
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *2161738240
35
+ version_requirements: *2161247520
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rcov
38
- requirement: &2161737760 !ruby/object:Gem::Requirement
38
+ requirement: &2161247040 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *2161737760
46
+ version_requirements: *2161247040
47
47
  description: Enumerate through each SQL statement in SQL scripts.
48
48
  email: junegunn.c@gmail.com
49
49
  executables: []
@@ -53,6 +53,7 @@ extra_rdoc_files:
53
53
  - README.rdoc
54
54
  files:
55
55
  - .document
56
+ - CHANGELOG.rdoc
56
57
  - Gemfile
57
58
  - Gemfile.lock
58
59
  - LICENSE.txt
@@ -79,7 +80,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
80
  version: '0'
80
81
  segments:
81
82
  - 0
82
- hash: -4303771573274782197
83
+ hash: 2241318967940806787
83
84
  required_rubygems_version: !ruby/object:Gem::Requirement
84
85
  none: false
85
86
  requirements: