vorax 0.1.0pre

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.
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
+