vorax 0.1.0pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +1 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +45 -0
  5. data/Rakefile +30 -0
  6. data/lib/vorax/base_funnel.rb +30 -0
  7. data/lib/vorax/output/html_convertor.rb +120 -0
  8. data/lib/vorax/output/html_funnel.rb +79 -0
  9. data/lib/vorax/output/pagezip_convertor.rb +20 -0
  10. data/lib/vorax/output/tablezip_convertor.rb +22 -0
  11. data/lib/vorax/output/vertical_convertor.rb +53 -0
  12. data/lib/vorax/output/zip_convertor.rb +117 -0
  13. data/lib/vorax/parser/argument.rb~ +125 -0
  14. data/lib/vorax/parser/body_split.rb +168 -0
  15. data/lib/vorax/parser/conn_string.rb +104 -0
  16. data/lib/vorax/parser/grammars/alias.rb +912 -0
  17. data/lib/vorax/parser/grammars/alias.rl +146 -0
  18. data/lib/vorax/parser/grammars/column.rb +454 -0
  19. data/lib/vorax/parser/grammars/column.rl +64 -0
  20. data/lib/vorax/parser/grammars/common.rl +98 -0
  21. data/lib/vorax/parser/grammars/package_spec.rb +1186 -0
  22. data/lib/vorax/parser/grammars/package_spec.rl +78 -0
  23. data/lib/vorax/parser/grammars/plsql_def.rb +469 -0
  24. data/lib/vorax/parser/grammars/plsql_def.rl +59 -0
  25. data/lib/vorax/parser/grammars/statement.rb +925 -0
  26. data/lib/vorax/parser/grammars/statement.rl +83 -0
  27. data/lib/vorax/parser/parser.rb +320 -0
  28. data/lib/vorax/parser/plsql_structure.rb +158 -0
  29. data/lib/vorax/parser/plsql_walker.rb +143 -0
  30. data/lib/vorax/parser/statement_inspector.rb~ +52 -0
  31. data/lib/vorax/parser/stmt_inspector.rb +78 -0
  32. data/lib/vorax/parser/target_ref.rb +110 -0
  33. data/lib/vorax/sqlplus.rb +281 -0
  34. data/lib/vorax/version.rb +7 -0
  35. data/lib/vorax/vorax_io.rb +70 -0
  36. data/lib/vorax.rb +60 -0
  37. data/spec/column_spec.rb +40 -0
  38. data/spec/conn_string_spec.rb +53 -0
  39. data/spec/package_spec_spec.rb +48 -0
  40. data/spec/pagezip_spec.rb +153 -0
  41. data/spec/parser_spec.rb +299 -0
  42. data/spec/plsql_structure_spec.rb +44 -0
  43. data/spec/spec_helper.rb +13 -0
  44. data/spec/sql/create_objects.sql +69 -0
  45. data/spec/sql/dbms_crypto.spc +339 -0
  46. data/spec/sql/dbms_crypto.~spc +339 -0
  47. data/spec/sql/dbms_stats.spc +4097 -0
  48. data/spec/sql/drop_user.sql +10 -0
  49. data/spec/sql/muci.spc +24 -0
  50. data/spec/sql/setup_user.sql +22 -0
  51. data/spec/sql/test.pkg +67 -0
  52. data/spec/sqlplus_spec.rb +52 -0
  53. data/spec/stmt_inspector_spec.rb +84 -0
  54. data/spec/tablezip_spec.rb +111 -0
  55. data/spec/vertical_spec.rb +150 -0
  56. data/vorax.gemspec +21 -0
  57. metadata +139 -0
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+
3
+ module Vorax
4
+
5
+ module Parser
6
+
7
+ class StatementInspector
8
+
9
+ def initialize(statement)
10
+ @statement = statement
11
+ end
12
+
13
+ def type
14
+ @type ||= Statement.new.type(statement)
15
+ end
16
+
17
+ def data_source
18
+ descriptor.refs
19
+ end
20
+
21
+ def recursive_data_source(refs, collect, position)
22
+ inner = refs.find { |r| r.respond_to?(:range) && r.range.include?(position) }
23
+ collect.unshift(refs).flatten!
24
+ if inner
25
+ desc = Parser::Alias.new
26
+ desc.walk(inner.base)
27
+ data_source(desc.refs, collect, position - inner_expr.range.first)
28
+ else
29
+ return collect
30
+ end
31
+ end
32
+
33
+ def query_fields
34
+ @query_fields ||= Column.new.walk(descriptor.query_fields)
35
+ end
36
+
37
+ private
38
+
39
+ def descriptor
40
+ unless @desc
41
+ @desc = Parser::Alias.new
42
+ @desc.walk(@statement)
43
+ end
44
+ @desc
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
@@ -0,0 +1,78 @@
1
+ # encoding: utf-8
2
+
3
+ module Vorax
4
+
5
+ module Parser
6
+
7
+ # A class used to gather metadata information for an SQL statement. This is needed
8
+ # especially for implementing VoraX code completion.
9
+ class StmtInspector
10
+
11
+ # Creates a new statement inspector.
12
+ #
13
+ # @param statement [String] the statement to be inspected
14
+ def initialize(statement)
15
+ @statement = statement
16
+ end
17
+
18
+ # Get the type of the statement
19
+ #
20
+ # (see Parser.statement_type)
21
+ def type
22
+ @type ||= Parser.statement_type(statement)
23
+ end
24
+
25
+ # Get all tableref/exprref for this statement, taking into account
26
+ # the current position within that statement.
27
+ #
28
+ # @param position [int] the current position.
29
+ # @return an array of TableRef/ExprRef objects
30
+ def data_source(position=0)
31
+ recursive_data_source(descriptor.refs, position)
32
+ end
33
+
34
+ # If it's a query, the corresponding columns are returned.
35
+ #
36
+ # @return an array of columns
37
+ def query_fields
38
+ @query_fields ||= Column.new.walk(descriptor.query_fields)
39
+ end
40
+
41
+ # Find the provided alias for the statement, taking into account the
42
+ # current position.
43
+ #
44
+ # @param name [String] the alias name
45
+ # @param position [int] the current position
46
+ def find_alias(name, position=0)
47
+ data_source(position).find { |r| r.pointer && r.pointer.upcase == name.upcase }
48
+ end
49
+
50
+ private
51
+
52
+ def descriptor
53
+ unless @desc
54
+ @desc = Parser::Alias.new
55
+ @desc.walk(@statement)
56
+ end
57
+ @desc
58
+ end
59
+
60
+ def recursive_data_source(refs, position, collect = [])
61
+ inner = refs.find { |r| r.respond_to?(:range) && r.range.include?(position) }
62
+ collect.unshift(refs).flatten!
63
+ if inner
64
+ desc = Parser::Alias.new
65
+ desc.walk(inner.base)
66
+ recursive_data_source(desc.refs, position - inner.range.first, collect)
67
+ else
68
+ return collect
69
+ end
70
+ end
71
+
72
+
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
@@ -0,0 +1,110 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vorax
4
+
5
+ module Parser
6
+
7
+ # An abstraction for a table reference within an SQL statement. This class is used
8
+ # by the StmtInspector to model the FROM clause of a query or the target table of
9
+ # an INSERT or UPDATE.
10
+ class TableRef
11
+
12
+ attr_reader :base, :pointer
13
+
14
+ # Creates a new TableRef object.
15
+ #
16
+ # @param base [String] is the actual table/view name from the SQL statement.
17
+ # @param pointer [String] is the alias of the table, if there's any
18
+ def initialize(base, pointer = nil)
19
+ @base = base
20
+ @pointer = pointer
21
+ end
22
+
23
+ # Return the columns of this table ref.
24
+ #
25
+ # @return the columns of the table, in an unexpanded form (e.g. tbl.*)
26
+ def columns
27
+ ["#{@base}.*"]
28
+ end
29
+
30
+ # Compare too table ref objects.
31
+ #
32
+ # param obj [TableRef] the other tableref used for comparison.
33
+ def ==(obj)
34
+ self.base == obj.base && self.pointer == obj.pointer
35
+ end
36
+
37
+ end
38
+
39
+ # This class is used to model a reference within a SQL statement, given as an
40
+ # expression (e.g. "select * from (select * from dual);")
41
+ class ExprRef
42
+
43
+ attr_reader :base, :range, :pointer
44
+
45
+ # Creates a new ExprRef object.
46
+ #
47
+ # @param base [String] the actual expresion
48
+ # @param range [Range] the bounderies of this expression within the parent statement
49
+ # @param pointer [String] the alias of the expresion, if there is any
50
+ def initialize(base, range, pointer = nil)
51
+ @base = base
52
+ @range = range
53
+ @pointer = pointer
54
+ end
55
+
56
+ # Get all columns for this expression.
57
+ #
58
+ # @return all columns of the query expression
59
+ def columns
60
+ collect = []
61
+ recursive_columns(@base, collect)
62
+ end
63
+
64
+ # Compare too table ref objects.
65
+ #
66
+ # param obj [TableRef] the other tableref used for comparison.
67
+ def ==(obj)
68
+ self.base == obj.base && self.range == obj.range && self.pointer == obj.pointer
69
+ end
70
+
71
+ private
72
+
73
+ def recursive_columns(statement, collect)
74
+ inspector = StmtInspector.new(statement)
75
+ columns_data = inspector.query_fields
76
+ columns_data.each do |column|
77
+ if column =~ /([a-z0-9#$_]+\.)?\*/i
78
+ #might be an alias
79
+ alias_name = column[/[a-z0-9#$_]+/]
80
+ ds = []
81
+ if alias_name
82
+ src = inspector.data_source.find do |r|
83
+ (r.pointer && r.pointer.upcase == alias_name.upcase) || (r.base.upcase == alias_name.upcase)
84
+ end
85
+ ds << src if src
86
+ elsif column == '*'
87
+ ds = inspector.data_source
88
+ end
89
+ if ds.size > 0
90
+ ds.each do |source|
91
+ if source.respond_to?(:range)
92
+ recursive_columns(source.base, collect)
93
+ else
94
+ collect << "#{source.base}.*"
95
+ end
96
+ end
97
+ end
98
+ else
99
+ collect << column
100
+ end
101
+ end
102
+ return collect
103
+ end
104
+
105
+ end
106
+
107
+ end
108
+
109
+ end
110
+
@@ -0,0 +1,281 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vorax
4
+
5
+ # Provides integration with Oracle SqlPlus CLI tool.
6
+ class Sqlplus
7
+
8
+ # the tail size of the output chunk to be searched
9
+ # for the output END_MARKER.
10
+ TAIL_LENGTH = 100 unless defined?(TAIL_LENGTH)
11
+ private_constant :TAIL_LENGTH
12
+
13
+ attr_reader :bin_file, :default_funnel_name, :process
14
+
15
+ # Creates a new sqlplus instance.
16
+ #
17
+ # @param bin_file [String] the path to the SqlPlus executable. By
18
+ # default is "sqlplus", which requires that the executable to be
19
+ # in $PATH.
20
+ def initialize(bin_file = "sqlplus")
21
+ @bin_file = bin_file
22
+ @busy = false
23
+ @start_marker, @end_marker, @cancel_marker = [2.chr, 3.chr, 4.chr]
24
+ @process = ChildProcess.build(@bin_file, "/nolog")
25
+ # On Unix we may abort the currently executing query by sending a
26
+ # INT signal to the Sqlplus process, but we need access to the
27
+ # send_term private method.
28
+ class << @process; public :send_signal; end if ChildProcess.unix?
29
+ @process.duplex = true
30
+ @process.detach = true
31
+ @process.io.inherit!
32
+ @io_read, @io_write = VoraxIO.pipe
33
+ @process.io.stdout = @io_write
34
+ @process.start
35
+ @process.io.stdin.sync = true
36
+ @current_funnel = nil
37
+ @default_convertor_name = nil
38
+ @registered_convertors = {:vertical => Output::VerticalConvertor,
39
+ :pagezip => Output::PagezipConvertor,
40
+ :tablezip => Output::TablezipConvertor}
41
+ end
42
+
43
+ # Set the default convertor for the output returned by sqlplus.
44
+ #
45
+ # @param convertor_name [Symbol] the default funnel name. The
46
+ # valid values are: :vertical, :pagezip and :tablezip
47
+ def default_convertor=(convertor_name)
48
+ Vorax.debug("default_convertor=#{convertor_name.inspect}")
49
+ @default_convertor_name = convertor_name
50
+ end
51
+
52
+ # Register a new convertor for the sqlplus output.
53
+ #
54
+ # @param convertor_name [Symbol] the name of the convertor (key)
55
+ # @param convertor_class [Class] the class which implements the
56
+ # convertor. It must be a subclass of BaseConvertor.
57
+ def register_convertor(convertor_name, convertor_class)
58
+ @registered_convertors[convertor_name] = convertor_class
59
+ end
60
+
61
+ # Execute an sqlplus command.
62
+ #
63
+ # @param command [String] the command to be executed.
64
+ # @param params [Hash] additional parameters. You may use
65
+ # the following options:
66
+ # :prep => a string with commands to be executed just before
67
+ # running the provided command. For example, you may
68
+ # choose to set some sqlplus options.
69
+ # :post => a string with commands to be executed after the
70
+ # provided command was run. Here it's a good place
71
+ # to put commands that restores some options affected
72
+ # by the executed command.
73
+ # :convertor => the convertor used to convert the output received
74
+ # from Sqlplus. By default it's the convertor set with
75
+ # "default_convertor=" method.
76
+ # :pack_file => the file name into which the command(s) to be
77
+ # executed are wrapped into and then sent for
78
+ # execution to sqlplus using '@<file_name>'.
79
+ # Providing this option may prove to be a good
80
+ # thing for big commands. If this parameter is
81
+ # not provided then the command is sent directly
82
+ # to the input IO of the sqlplus process.
83
+ def exec(command, params = {})
84
+ Vorax.debug("exec: command=#{command.inspect} params=#{params.inspect}")
85
+ raise AnotherExecRunning if busy?
86
+ @tail = ""
87
+ opts = {
88
+ :prep => nil,
89
+ :post => nil,
90
+ :convertor => @default_convertor_name,
91
+ :pack_file => nil,
92
+ }.merge(params)
93
+ @busy = true
94
+ @look_for = @start_marker
95
+ prepare_funnel(opts[:convertor])
96
+ if @current_funnel && @current_funnel.is_a?(Output::HTMLFunnel)
97
+ # all HTML funnels expects html format
98
+ send_text("set markup html on\n")
99
+ else
100
+ send_text("set markup html off\n")
101
+ end
102
+ if opts[:pack_file]
103
+ send_text("@#{pack(command, opts)}\n")
104
+ else
105
+ send_text("#{opts[:prep]}\n") if opts[:prep]
106
+ capture { send_text("#{command}\n") }
107
+ send_text("#{opts[:post]}\n") if opts[:post]
108
+ end
109
+ end
110
+
111
+ # Send a text directly to the stdin of the sqlplus process.
112
+ #
113
+ # @param text [String] the text to be sent to sqlplus
114
+ def send_text(text)
115
+ @process.io.stdin.print(text)
116
+ end
117
+
118
+ # Check if the sqlplus process is busy executing something.
119
+ #
120
+ # @return true if the sqlplus is busy executing something,
121
+ # false otherwise
122
+ def busy?
123
+ @busy
124
+ end
125
+
126
+ # Check if the output of a previous executed sqlpus command
127
+ # was completely fetched out.
128
+ #
129
+ # @return true if the whole output was fetched, false otherwise
130
+ def eof?
131
+ not busy?
132
+ end
133
+
134
+ # Read the output spit by sqlplus process. If there is any default
135
+ # convertor, the returned output will be formatted according to
136
+ # that convertor.
137
+ #
138
+ # @param bytes [int] the maximum output chunk size
139
+ # @return the output chunk
140
+ def read_output(bytes=4086)
141
+ output = ""
142
+ raw_output = @tail
143
+ begin
144
+ raw_output << (@io_read.read_nonblock(bytes) || '').gsub(/\r/, '')
145
+ rescue Errno::EAGAIN
146
+ end
147
+ if raw_output
148
+ #p raw_output if raw_output
149
+ if @tail.empty?
150
+ # The logic of tail: when SET ECHO ON is used, the PRO <end_marker>
151
+ # statement will be also part of the output. The user will end up
152
+ # with something like "SQL> pro" as the last line. The solution would
153
+ # be to search for "pro <end_marker>" and to stop just before "pro", but
154
+ # the chunk may be split in the middle of the "pro <end_marker>" text and
155
+ # this is something which depends on the output of the executed
156
+ # statement (e.g. bytes=4086, output=4084 => chunk="...SQL> p"). The
157
+ # workaround is to delay sending the last TAIL_LENGTH characters until
158
+ # the next fetch.
159
+ raw_output, @tail = raw_output.slice!(0...raw_output.length-TAIL_LENGTH), raw_output
160
+ else
161
+ @tail = ''
162
+ end
163
+ scanner = StringScanner.new(raw_output)
164
+ while not scanner.eos?
165
+ if @look_for == @start_marker
166
+ if text = scanner.scan_until(/#{@look_for}/)
167
+ if text !~ /pro #{@look_for}/
168
+ # Only if it's not part of a PROMPT sqlplus command.
169
+ # This might happen when the "echo" sqlplus option
170
+ # is ON and the begin marker is included into the
171
+ # sql pack file. Because we are using big chunks to
172
+ # read data it's very unlikely that the echoing of the
173
+ # prompt command to be split in the middle.
174
+ @look_for = @end_marker
175
+ end
176
+ else
177
+ scanner.terminate
178
+ end
179
+ end
180
+ if @look_for == @end_marker
181
+ output = scanner.scan(/[^#{@look_for}]*/)
182
+ if scanner.scan(/#{@look_for}/)
183
+ output.gsub!(/\n.*?pro \z/, "")
184
+ # end of output reached
185
+ scanner.terminate
186
+ @busy = false
187
+ end
188
+ end
189
+ end
190
+ end
191
+ chunk = output.force_encoding('UTF-8')
192
+ if @current_funnel && !chunk.empty?
193
+ # nokogiri may be confused about those unclosed <p> tags
194
+ # sqlplus emits, so it's better to get rid of them and use
195
+ # <br> instead.
196
+ @current_funnel.write(br_only(chunk))
197
+ chunk = @current_funnel.read
198
+ end
199
+ return chunk
200
+ end
201
+
202
+ # Cancel the currently executing statement. This is supported on Unix
203
+ # only. On Windows there's no way to send a CTRL+C signal to Sqlplus
204
+ # without aborting the process. There's an old enhancement request on
205
+ # Oracle support:
206
+ #
207
+ # Bug 8890996: ENH: CONTROL-C SHOULD NOT EXIT WINDOWS CONSOLE SQLPLUS
208
+ #
209
+ # So, as soon as we have some fixes from the Oracle guys I will come
210
+ # back to this method.
211
+ def cancel
212
+ raise PlatformNotSupported if ChildProcess.windows?
213
+ if busy?
214
+ @process.send_signal 'INT'
215
+ mark_cancel
216
+ # read until the cancel marker
217
+ raw_output = ""
218
+ until raw_output =~ /#{@cancel_marker}/
219
+ begin
220
+ raw_output = @io_read.read_nonblock(1024)
221
+ yield if block_given?
222
+ rescue Errno::EAGAIN
223
+ sleep 0.1
224
+ end
225
+ end
226
+ @busy = false
227
+ end
228
+ end
229
+
230
+ # Kill the sqlplus process.
231
+ def terminate
232
+ @process.stop
233
+ end
234
+
235
+ private
236
+
237
+ def br_only(chunk)
238
+ # be prepared for chunks with <p> tag broken in the middle
239
+ chunk.gsub(/<p>/, "<br>").gsub(/<p\z/, "<br").gsub(/\Ap>/, "br>")
240
+ end
241
+
242
+ def prepare_funnel(convertor_name)
243
+ convertor = @registered_convertors[convertor_name]
244
+ if convertor
245
+ @current_funnel = Output::HTMLFunnel.new(convertor.new)
246
+ else
247
+ @current_funnel = nil
248
+ end
249
+ end
250
+
251
+ def capture
252
+ @process.io.stdin.puts("#pro #{@start_marker}")
253
+ yield
254
+ @process.io.stdin.puts("#pro #{@end_marker}")
255
+ @process.io.stdin.puts(".")
256
+ end
257
+
258
+ def mark_cancel
259
+ @process.io.stdin.puts
260
+ @process.io.stdin.puts("pro #{@cancel_marker}")
261
+ end
262
+
263
+ def pack(command, opts)
264
+ pack_file = opts[:pack_file]
265
+ if pack_file
266
+ File.open(pack_file, 'w') do |f|
267
+ f.puts opts[:prep]
268
+ f.puts "#pro #@start_marker"
269
+ f.puts command.strip
270
+ f.puts "#pro #@end_marker"
271
+ f.puts "."
272
+ f.puts opts[:post]
273
+ end
274
+ end
275
+ pack_file
276
+ end
277
+
278
+
279
+ end
280
+
281
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ module Vorax
4
+
5
+ VERSION = '0.1.0pre' unless defined?(VERSION)
6
+
7
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vorax
4
+
5
+ # Implements an IO pipe to link the stdin and stdout handlers of the
6
+ # sqlplus process to the ends of this pipe. This is required for Windows
7
+ # processes only. On Unix it's enough to use a builtin IO object.
8
+ class VoraxIO < IO
9
+
10
+ # A proxy for the original read_nonblock method
11
+ alias :old_read_nonblock :read_nonblock
12
+
13
+ # Creates a new IO.
14
+ def initialize(*args)
15
+ super(*args)
16
+ if ChildProcess.windows?
17
+ require 'Win32API'
18
+ @hFile = ChildProcess::Windows::Lib.get_osfhandle(fileno)
19
+ peek_params = [
20
+ 'L', # handle to pipe to copy from
21
+ 'L', # pointer to data buffer
22
+ 'L', # size, in bytes, of data buffer
23
+ 'L', # pointer to number of bytes read
24
+ 'P', # pointer to total number of bytes available
25
+ 'L'] # pointer to unread bytes in this message
26
+ @peekNamedPipe = Win32API.new("kernel32", "PeekNamedPipe", peek_params, 'I')
27
+ read_params = [
28
+ 'L', # handle of file to read
29
+ 'P', # pointer to buffer that receives data
30
+ 'L', # number of bytes to read
31
+ 'P', # pointer to number of bytes read
32
+ 'L'] #pointer to structure for data
33
+ @readFile = Win32API.new("kernel32", "ReadFile", read_params, 'I')
34
+ end
35
+ end
36
+
37
+ # Read in nonblock mode from the pipe.
38
+ #
39
+ # @param bytes [int] the number of bytes to be read at once
40
+ # @see IO.read_nonblock
41
+ def read_nonblock(bytes)
42
+ if ChildProcess.windows?
43
+ read_file(peek)
44
+ else
45
+ old_read_nonblock(bytes)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def peek
52
+ available = [0].pack('I')
53
+ if @peekNamedPipe.Call(@hFile, 0, 0, 0, available, 0).zero?
54
+ raise IOError, 'Named pipe unavailable'
55
+ end
56
+ available.unpack('I')[0]
57
+ end
58
+
59
+ def read_file(bytes)
60
+ if bytes > 0
61
+ number = [0].pack('I')
62
+ buffer = ' ' * bytes
63
+ return '' if @readFile.call(@hFile, buffer, bytes, number, 0).zero?
64
+ buffer[0...number.unpack('I')[0]]
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ end
data/lib/vorax.rb ADDED
@@ -0,0 +1,60 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rubygems' unless defined? Gem
4
+ require 'logger'
5
+ require 'childprocess'
6
+ require 'antlr3'
7
+
8
+ # The main Vorax namespace. Everything related to VoraX is part
9
+ # of this module.
10
+ module Vorax
11
+
12
+ # Sets the logger to be used for debug purposes.
13
+ # @param logger [Logger] the logger object.
14
+ def self.logger=(logger)
15
+ @logger = logger
16
+ end
17
+
18
+ # Get the current logger.
19
+ def self.logger
20
+ @logger
21
+ end
22
+
23
+ # Log a debug entry.
24
+ # @param message [String] the message to be logged.
25
+ def self.debug(message)
26
+ if @logger
27
+ @logger.add(Logger::DEBUG, nil, 'rby') { message }
28
+ end
29
+ end
30
+
31
+ # Raised when another SqlPlus is already executing.
32
+ class AnotherExecRunning < StandardError; end
33
+
34
+ # Raised when the platform is not supported (see cancel)
35
+ class PlatformNotSupported < StandardError; end
36
+
37
+ end
38
+
39
+ require 'vorax/version.rb'
40
+ require 'vorax/vorax_io.rb'
41
+ require 'vorax/sqlplus.rb'
42
+ require 'vorax/base_funnel.rb'
43
+ require 'vorax/output/html_funnel.rb'
44
+ require 'vorax/output/html_convertor.rb'
45
+ require 'vorax/output/vertical_convertor.rb'
46
+ require 'vorax/output/zip_convertor.rb'
47
+ require 'vorax/output/pagezip_convertor.rb'
48
+ require 'vorax/output/tablezip_convertor.rb'
49
+ require 'vorax/parser/parser.rb'
50
+ require 'vorax/parser/conn_string.rb'
51
+ require 'vorax/parser/target_ref.rb'
52
+ require 'vorax/parser/stmt_inspector.rb'
53
+ require 'vorax/parser/plsql_walker.rb'
54
+ require 'vorax/parser/plsql_structure.rb'
55
+ require 'vorax/parser/grammars/statement.rb'
56
+ require 'vorax/parser/grammars/alias.rb'
57
+ require 'vorax/parser/grammars/column.rb'
58
+ require 'vorax/parser/grammars/package_spec.rb'
59
+ require 'vorax/parser/grammars/plsql_def.rb'
60
+ require 'vorax/parser/body_split.rb'
@@ -0,0 +1,40 @@
1
+ # encoding: UTF-8
2
+
3
+ include Vorax
4
+
5
+ describe 'column' do
6
+
7
+ it 'should work with multiple columns' do
8
+ Parser::Column.new.walk('col1, col2, f(a, b, c) x, 3+5, t.*, owner.tab.col y').should eq(["col1", "col2", "x", "t.*", "y"])
9
+ end
10
+
11
+ it 'should work with one column' do
12
+ Parser::Column.new.walk('col1').should eq(["col1"])
13
+ end
14
+
15
+ it 'should work with one column with alias' do
16
+ Parser::Column.new.walk('col1 as x').should eq(["x"])
17
+ Parser::Column.new.walk('col1 y').should eq(["y"])
18
+ end
19
+
20
+ it 'should work with referenced columns' do
21
+ Parser::Column.new.walk('tab.col1').should eq(["tab.col1"])
22
+ Parser::Column.new.walk('owner.tab.col2').should eq(["owner.tab.col2"])
23
+ Parser::Column.new.walk('"owner"."tab wow".col2').should eq(['"owner"."tab wow".col2'])
24
+ end
25
+
26
+ it 'should work with expressions' do
27
+ Parser::Column.new.walk('(select 1 from dual) x, 3+2').should eq(["x"])
28
+ end
29
+
30
+ it 'should ignore functions without alias' do
31
+ Parser::Column.new.walk('f(1, 2, 3), my_func(col)').should eq([])
32
+ end
33
+
34
+ it 'should work with nested expressions' do
35
+ Parser::Column.new.walk('f(1, g(2), x(3, 2, f(10))), my_func(col)').should eq([])
36
+ end
37
+
38
+ end
39
+
40
+