vorax 0.4.2 → 5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +4 -29
- data/vorax.gemspec +3 -11
- metadata +4 -92
- data/.rspec +0 -1
- data/Rakefile +0 -30
- data/lib/vorax.rb +0 -60
- data/lib/vorax/base_funnel.rb +0 -30
- data/lib/vorax/output/html_convertor.rb +0 -120
- data/lib/vorax/output/html_funnel.rb +0 -79
- data/lib/vorax/output/pagezip_convertor.rb +0 -20
- data/lib/vorax/output/tablezip_convertor.rb +0 -22
- data/lib/vorax/output/vertical_convertor.rb +0 -53
- data/lib/vorax/output/zip_convertor.rb +0 -117
- data/lib/vorax/parser/argument.rb~ +0 -125
- data/lib/vorax/parser/conn_string.rb +0 -104
- data/lib/vorax/parser/grammars/alias.rb +0 -904
- data/lib/vorax/parser/grammars/alias.rl +0 -138
- data/lib/vorax/parser/grammars/column.rb +0 -454
- data/lib/vorax/parser/grammars/column.rl +0 -64
- data/lib/vorax/parser/grammars/common.rl +0 -107
- data/lib/vorax/parser/grammars/declare.rb +0 -9606
- data/lib/vorax/parser/grammars/declare.rl +0 -160
- data/lib/vorax/parser/grammars/for_block.rb +0 -440
- data/lib/vorax/parser/grammars/for_block.rl +0 -73
- data/lib/vorax/parser/grammars/plsql_def.rb +0 -539
- data/lib/vorax/parser/grammars/plsql_def.rl +0 -73
- data/lib/vorax/parser/grammars/statement.rb +0 -925
- data/lib/vorax/parser/grammars/statement.rl +0 -83
- data/lib/vorax/parser/parser.rb +0 -344
- data/lib/vorax/parser/plsql_structure.rb +0 -222
- data/lib/vorax/parser/plsql_walker.rb +0 -143
- data/lib/vorax/parser/statement_inspector.rb~ +0 -52
- data/lib/vorax/parser/stmt_inspector.rb +0 -78
- data/lib/vorax/parser/target_ref.rb +0 -110
- data/lib/vorax/sqlplus.rb +0 -273
- data/lib/vorax/version.rb +0 -7
- data/lib/vorax/vorax_io.rb +0 -70
- data/spec/column_spec.rb +0 -40
- data/spec/conn_string_spec.rb +0 -53
- data/spec/declare_spec.rb +0 -281
- data/spec/pagezip_spec.rb +0 -153
- data/spec/parser_spec.rb +0 -352
- data/spec/plsql_structure_spec.rb +0 -68
- data/spec/spec_helper.rb +0 -13
- data/spec/sql/create_objects.sql +0 -69
- data/spec/sql/dbms_crypto.spc +0 -339
- data/spec/sql/dbms_stats.spc +0 -4097
- data/spec/sql/drop_user.sql +0 -10
- data/spec/sql/muci.spc +0 -26
- data/spec/sql/setup_user.sql +0 -22
- data/spec/sql/test.fnc +0 -12
- data/spec/sql/test.pkg +0 -83
- data/spec/sqlplus_spec.rb +0 -52
- data/spec/stmt_inspector_spec.rb +0 -96
- data/spec/tablezip_spec.rb +0 -111
- data/spec/vertical_spec.rb +0 -150
@@ -1,143 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'strscan'
|
4
|
-
|
5
|
-
module Vorax
|
6
|
-
|
7
|
-
module Parser
|
8
|
-
|
9
|
-
# A PLSQL string scanner which is looking for interesting points within
|
10
|
-
# the provided source code. This is used instead of a fully fledged parser
|
11
|
-
# for speed considerations.
|
12
|
-
class PLSQLWalker
|
13
|
-
|
14
|
-
BEGIN_ML_COMMENT = /\/\*/ unless defined?(BEGIN_ML_COMMENT)
|
15
|
-
END_ML_COMMENT = /\*\// unless defined?(END_ML_COMMENT)
|
16
|
-
BEGIN_SL_COMMENT = /--/ unless defined?(BEGIN_SL_COMMENT)
|
17
|
-
END_SL_COMMENT = Parser::END_LINE unless defined?(END_SL_COMMENT)
|
18
|
-
BEGIN_PLSQL_SPECIAL_QUOTING = /q'[!\[{(<]/ unless defined?(BEGIN_PLSQL_SPECIAL_QUOTING)
|
19
|
-
BEGIN_DOUBLE_QUOTING = /[\"]/ unless defined?(BEGIN_DOUBLE_QUOTING)
|
20
|
-
BEGIN_SINGLE_QUOTING = /[']/ unless defined?(BEGIN_SINGLE_QUOTING)
|
21
|
-
|
22
|
-
# Create a new parse walker.
|
23
|
-
#
|
24
|
-
# @param text [String] the text to be walked/parsed
|
25
|
-
# @param create_default_spots [boolean] whenever or not to create default
|
26
|
-
# detection spots: multiline comments, singleline comments and quoted literals
|
27
|
-
def initialize(text, create_default_spots=true)
|
28
|
-
@text = text
|
29
|
-
@matchers = []
|
30
|
-
@ss = StringScanner.new(text)
|
31
|
-
create_default_spots() if create_default_spots
|
32
|
-
end
|
33
|
-
|
34
|
-
# Returns the string scanner used for walking the string.
|
35
|
-
#
|
36
|
-
# @return [StringScanner] the string scanner
|
37
|
-
def scanner
|
38
|
-
@ss
|
39
|
-
end
|
40
|
-
|
41
|
-
# Register a new detection spot. The order of specifying these spots is important.
|
42
|
-
#
|
43
|
-
# @param pattern [Regexp] the spot regular expression
|
44
|
-
# @param callback [Procedure] what to do when this spot is detected. The registered
|
45
|
-
# block is always called with the string scanner object. Please do not use "return"
|
46
|
-
# to exit from the defined block.
|
47
|
-
def register_spot(pattern, &callback)
|
48
|
-
@matchers << {:pattern => pattern, :callback => callback}
|
49
|
-
end
|
50
|
-
|
51
|
-
# Walk the text and trigger the registered callbacks. It returns the text which was
|
52
|
-
# successfully walked.
|
53
|
-
def walk
|
54
|
-
global_matcher = Regexp.new(@matchers.map { |e| e[:pattern].to_s }.join('|'),
|
55
|
-
Regexp::IGNORECASE)
|
56
|
-
while !@ss.eos?
|
57
|
-
if match = @ss.scan_until(global_matcher)
|
58
|
-
current_pos = @ss.pos
|
59
|
-
@matchers.each do |matcher|
|
60
|
-
if @ss.matched =~ matcher[:pattern]
|
61
|
-
matcher[:callback].call(@ss)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
else
|
65
|
-
@ss.terminate
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# Register a spot to walk a multiline comment.
|
71
|
-
def register_default_ml_comment_spot
|
72
|
-
register_spot(BEGIN_ML_COMMENT) do |scanner|
|
73
|
-
scanner.scan_until(END_ML_COMMENT)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Register a spot to walk a single line comment.
|
78
|
-
def register_default_sl_comment_spot
|
79
|
-
register_spot(BEGIN_SL_COMMENT) do |scanner|
|
80
|
-
scanner.scan_until(END_SL_COMMENT)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
# Register a spot to walk a plsql special quoting liternal.
|
85
|
-
def register_default_plsql_quoting_spot
|
86
|
-
register_spot(BEGIN_PLSQL_SPECIAL_QUOTING) do |scanner|
|
87
|
-
scanner.scan_until(END_SL_COMMENT)
|
88
|
-
if scanner.matched =~ /q'\[/
|
89
|
-
scanner.scan_until(/\]'/)
|
90
|
-
elsif scanner.matched =~ /q'[{]/
|
91
|
-
scanner.scan_until(/[}]'/)
|
92
|
-
elsif scanner.matched =~ /q'[(]/
|
93
|
-
scanner.scan_until(/[)]'/)
|
94
|
-
elsif scanner.matched =~ /q'[<]/
|
95
|
-
scanner.scan_until(/[>]'/)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Register a spot to walk a double quoted literal.
|
101
|
-
def register_default_double_quoting_spot
|
102
|
-
register_spot(BEGIN_DOUBLE_QUOTING) do |scanner|
|
103
|
-
scanner.scan_until(/"/)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
# Register a spot to walk a single quoted literal.
|
108
|
-
def register_default_single_quoting_spot
|
109
|
-
register_spot(BEGIN_SINGLE_QUOTING) do |scanner|
|
110
|
-
collector = ''
|
111
|
-
begin
|
112
|
-
if match = scanner.scan_until(/\'+/)
|
113
|
-
collector << match
|
114
|
-
end
|
115
|
-
end while (scanner.matched != "'" && !scanner.eos?)
|
116
|
-
collector
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
private
|
121
|
-
|
122
|
-
def create_default_spots
|
123
|
-
# define a multiline comment spot
|
124
|
-
register_default_ml_comment_spot()
|
125
|
-
|
126
|
-
# define a single line comment spot
|
127
|
-
register_default_sl_comment_spot()
|
128
|
-
|
129
|
-
# define special PLSQL quotes spot
|
130
|
-
register_default_plsql_quoting_spot()
|
131
|
-
|
132
|
-
# register a double quoted string spot
|
133
|
-
register_default_double_quoting_spot()
|
134
|
-
|
135
|
-
# register a single quoted string spot
|
136
|
-
register_default_single_quoting_spot()
|
137
|
-
end
|
138
|
-
|
139
|
-
end
|
140
|
-
|
141
|
-
end
|
142
|
-
|
143
|
-
end
|
@@ -1,52 +0,0 @@
|
|
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
|
-
|
@@ -1,78 +0,0 @@
|
|
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
|
-
|
@@ -1,110 +0,0 @@
|
|
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#$\_]+/i]
|
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
|
-
|
data/lib/vorax/sqlplus.rb
DELETED
@@ -1,273 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
module Vorax
|
4
|
-
|
5
|
-
# Provides integration with Oracle SqlPlus CLI tool.
|
6
|
-
class Sqlplus
|
7
|
-
|
8
|
-
attr_reader :bin_file, :default_funnel_name, :process
|
9
|
-
|
10
|
-
# Creates a new sqlplus instance.
|
11
|
-
#
|
12
|
-
# @param bin_file [String] the path to the SqlPlus executable. By
|
13
|
-
# default is "sqlplus", which requires that the executable to be
|
14
|
-
# in $PATH.
|
15
|
-
def initialize(bin_file = "sqlplus")
|
16
|
-
@bin_file = bin_file
|
17
|
-
@busy = false
|
18
|
-
@start_marker, @end_marker, @cancel_marker = [2.chr, 3.chr, 4.chr]
|
19
|
-
@process = ChildProcess.build(@bin_file, "/nolog")
|
20
|
-
# On Unix we may abort the currently executing query by sending a
|
21
|
-
# INT signal to the Sqlplus process, but we need access to the
|
22
|
-
# send_term private method.
|
23
|
-
class << @process; public :send_signal; end if ChildProcess.unix?
|
24
|
-
@process.duplex = true
|
25
|
-
@process.detach = true
|
26
|
-
@process.io.inherit!
|
27
|
-
@io_read, @io_write = VoraxIO.pipe
|
28
|
-
@process.io.stdout = @io_write
|
29
|
-
@process.start
|
30
|
-
@process.io.stdin.sync = true
|
31
|
-
@current_funnel = nil
|
32
|
-
@default_convertor_name = nil
|
33
|
-
@registered_convertors = {:vertical => Output::VerticalConvertor,
|
34
|
-
:pagezip => Output::PagezipConvertor,
|
35
|
-
:tablezip => Output::TablezipConvertor}
|
36
|
-
# warm up
|
37
|
-
sleep 0.2
|
38
|
-
# set the blockterm as the end_marker. The blockterm should
|
39
|
-
# not be touch by the Vorax user, otherwise nasty things
|
40
|
-
# may happen. This is also a workaround to mark the end of
|
41
|
-
# output when the "echo" setting of sqlplus is "on". See the
|
42
|
-
# implementation of pack().
|
43
|
-
send_text("set blockterm \"#@end_marker\"\n")
|
44
|
-
end
|
45
|
-
|
46
|
-
# Set the default convertor for the output returned by sqlplus.
|
47
|
-
#
|
48
|
-
# @param convertor_name [Symbol] the default funnel name. The
|
49
|
-
# valid values are: :vertical, :pagezip and :tablezip
|
50
|
-
def default_convertor=(convertor_name)
|
51
|
-
Vorax.debug("default_convertor=#{convertor_name.inspect}")
|
52
|
-
@default_convertor_name = convertor_name
|
53
|
-
end
|
54
|
-
|
55
|
-
# Register a new convertor for the sqlplus output.
|
56
|
-
#
|
57
|
-
# @param convertor_name [Symbol] the name of the convertor (key)
|
58
|
-
# @param convertor_class [Class] the class which implements the
|
59
|
-
# convertor. It must be a subclass of BaseConvertor.
|
60
|
-
def register_convertor(convertor_name, convertor_class)
|
61
|
-
@registered_convertors[convertor_name] = convertor_class
|
62
|
-
end
|
63
|
-
|
64
|
-
# Execute an sqlplus command.
|
65
|
-
#
|
66
|
-
# @param command [String] the command to be executed.
|
67
|
-
# @param params [Hash] additional parameters. You may use
|
68
|
-
# the following options:
|
69
|
-
# :prep => a string with commands to be executed just before
|
70
|
-
# running the provided command. For example, you may
|
71
|
-
# choose to set some sqlplus options.
|
72
|
-
# :post => a string with commands to be executed after the
|
73
|
-
# provided command was run. Here it's a good place
|
74
|
-
# to put commands that restores some options affected
|
75
|
-
# by the executed command.
|
76
|
-
# :convertor => the convertor used to convert the output received
|
77
|
-
# from Sqlplus. By default it's the convertor set with
|
78
|
-
# "default_convertor=" method.
|
79
|
-
# :pack_file => the file name into which the command(s) to be
|
80
|
-
# executed are wrapped into and then sent for
|
81
|
-
# execution to sqlplus using '@<file_name>'.
|
82
|
-
# Providing this option may prove to be a good
|
83
|
-
# thing for big commands. If this parameter is
|
84
|
-
# not provided then the command is sent directly
|
85
|
-
# to the input IO of the sqlplus process.
|
86
|
-
def exec(command, params = {})
|
87
|
-
Vorax.debug("exec: command=#{command.inspect} params=#{params.inspect}")
|
88
|
-
raise AnotherExecRunning if busy?
|
89
|
-
opts = {
|
90
|
-
:prep => nil,
|
91
|
-
:post => nil,
|
92
|
-
:convertor => @default_convertor_name,
|
93
|
-
:pack_file => nil,
|
94
|
-
}.merge(params)
|
95
|
-
@busy = true
|
96
|
-
@look_for = @start_marker
|
97
|
-
prepare_funnel(opts[:convertor])
|
98
|
-
if @current_funnel && @current_funnel.is_a?(Output::HTMLFunnel)
|
99
|
-
# all HTML funnels expects html format
|
100
|
-
send_text("set markup html on\n")
|
101
|
-
else
|
102
|
-
send_text("set markup html off\n")
|
103
|
-
end
|
104
|
-
if opts[:pack_file]
|
105
|
-
send_text("@#{pack(command, opts)}\n")
|
106
|
-
else
|
107
|
-
send_text("#{opts[:prep]}\n") if opts[:prep]
|
108
|
-
capture { send_text("#{command}\n") }
|
109
|
-
send_text("#{opts[:post]}\n") if opts[:post]
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
# Send a text directly to the stdin of the sqlplus process.
|
114
|
-
#
|
115
|
-
# @param text [String] the text to be sent to sqlplus
|
116
|
-
def send_text(text)
|
117
|
-
@process.io.stdin.print(text)
|
118
|
-
end
|
119
|
-
|
120
|
-
# Check if the sqlplus process is busy executing something.
|
121
|
-
#
|
122
|
-
# @return true if the sqlplus is busy executing something,
|
123
|
-
# false otherwise
|
124
|
-
def busy?
|
125
|
-
@busy
|
126
|
-
end
|
127
|
-
|
128
|
-
# Check if the output of a previous executed sqlpus command
|
129
|
-
# was completely fetched out.
|
130
|
-
#
|
131
|
-
# @return true if the whole output was fetched, false otherwise
|
132
|
-
def eof?
|
133
|
-
not busy?
|
134
|
-
end
|
135
|
-
|
136
|
-
# Read the output spit by sqlplus process. If there is any default
|
137
|
-
# convertor, the returned output will be formatted according to
|
138
|
-
# that convertor.
|
139
|
-
#
|
140
|
-
# @param bytes [int] the maximum output chunk size
|
141
|
-
# @return the output chunk
|
142
|
-
def read_output(bytes=4086)
|
143
|
-
output = ""
|
144
|
-
raw_output = nil
|
145
|
-
begin
|
146
|
-
raw_output = @io_read.read_nonblock(bytes)
|
147
|
-
rescue Errno::EAGAIN
|
148
|
-
end
|
149
|
-
if raw_output
|
150
|
-
raw_output.gsub!(/\r/, '')
|
151
|
-
scanner = StringScanner.new(raw_output)
|
152
|
-
while not scanner.eos?
|
153
|
-
if @look_for == @start_marker
|
154
|
-
if text = scanner.scan_until(/#{@look_for}/)
|
155
|
-
if text !~ /pro #{@look_for}/
|
156
|
-
# Only if it's not part of a PROMPT sqlplus command.
|
157
|
-
# This might happen when the "echo" sqlplus option
|
158
|
-
# is ON and the begin marker is included into the
|
159
|
-
# sql pack file. Because we are using big chunks to
|
160
|
-
# read data it's very unlikely that the echoing of the
|
161
|
-
# prompt command to be split in the middle.
|
162
|
-
@look_for = @end_marker
|
163
|
-
end
|
164
|
-
else
|
165
|
-
scanner.terminate
|
166
|
-
end
|
167
|
-
end
|
168
|
-
if @look_for == @end_marker
|
169
|
-
output = scanner.scan(/[^#{@look_for}]*/)
|
170
|
-
if scanner.scan(/#{@look_for}/)
|
171
|
-
# end of output reached
|
172
|
-
scanner.terminate
|
173
|
-
@busy = false
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
chunk = output.force_encoding('UTF-8')
|
179
|
-
if @current_funnel && !chunk.empty?
|
180
|
-
# nokogiri may be confused about those unclosed <p> tags
|
181
|
-
# sqlplus emits, so it's better to get rid of them and use
|
182
|
-
# <br> instead.
|
183
|
-
@current_funnel.write(br_only(chunk))
|
184
|
-
chunk = @current_funnel.read
|
185
|
-
end
|
186
|
-
return chunk
|
187
|
-
end
|
188
|
-
|
189
|
-
# Cancel the currently executing statement. This is supported on Unix
|
190
|
-
# only. On Windows there's no way to send a CTRL+C signal to Sqlplus
|
191
|
-
# without aborting the process. There's an old enhancement request on
|
192
|
-
# Oracle support:
|
193
|
-
#
|
194
|
-
# Bug 8890996: ENH: CONTROL-C SHOULD NOT EXIT WINDOWS CONSOLE SQLPLUS
|
195
|
-
#
|
196
|
-
# So, as soon as we have some fixes from the Oracle guys I will come
|
197
|
-
# back to this method.
|
198
|
-
def cancel
|
199
|
-
raise PlatformNotSupported if ChildProcess.windows?
|
200
|
-
if busy?
|
201
|
-
@process.send_signal 'INT'
|
202
|
-
mark_cancel
|
203
|
-
# read until the cancel marker
|
204
|
-
raw_output = ""
|
205
|
-
until raw_output =~ /#{@cancel_marker}/
|
206
|
-
begin
|
207
|
-
raw_output = @io_read.read_nonblock(1024)
|
208
|
-
yield if block_given?
|
209
|
-
rescue Errno::EAGAIN
|
210
|
-
sleep 0.1
|
211
|
-
end
|
212
|
-
end
|
213
|
-
@busy = false
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
# Kill the sqlplus process.
|
218
|
-
def terminate
|
219
|
-
@process.stop
|
220
|
-
end
|
221
|
-
|
222
|
-
private
|
223
|
-
|
224
|
-
def br_only(chunk)
|
225
|
-
# be prepared for chunks with <p> tag broken in the middle
|
226
|
-
chunk.gsub(/<p>/, "<br>").gsub(/<p\z/, "<br").gsub(/\Ap>/, "br>")
|
227
|
-
end
|
228
|
-
|
229
|
-
def prepare_funnel(convertor_name)
|
230
|
-
convertor = @registered_convertors[convertor_name]
|
231
|
-
if convertor
|
232
|
-
@current_funnel = Output::HTMLFunnel.new(convertor.new)
|
233
|
-
else
|
234
|
-
@current_funnel = nil
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def capture
|
239
|
-
@process.io.stdin.puts("#pro #{@start_marker}")
|
240
|
-
yield
|
241
|
-
@process.io.stdin.puts("#pro #{@end_marker}")
|
242
|
-
@process.io.stdin.puts(".")
|
243
|
-
end
|
244
|
-
|
245
|
-
def mark_cancel
|
246
|
-
@process.io.stdin.puts
|
247
|
-
@process.io.stdin.puts("pro #{@cancel_marker}")
|
248
|
-
end
|
249
|
-
|
250
|
-
def pack(command, opts)
|
251
|
-
pack_file = opts[:pack_file]
|
252
|
-
if pack_file
|
253
|
-
File.open(pack_file, 'w') do |f|
|
254
|
-
f.puts opts[:prep]
|
255
|
-
f.puts "#pro #@start_marker"
|
256
|
-
f.puts command.strip
|
257
|
-
# we assume that the @end_marker is also
|
258
|
-
# set as a block terminator. If "set echo on"
|
259
|
-
# the output region will end here since the
|
260
|
-
# block terminator command will be echoed. Otherwise,
|
261
|
-
# the next prompt statement will do the job.
|
262
|
-
f.puts "#{@end_marker}"
|
263
|
-
f.puts "#pro #@end_marker"
|
264
|
-
f.puts opts[:post]
|
265
|
-
end
|
266
|
-
end
|
267
|
-
pack_file
|
268
|
-
end
|
269
|
-
|
270
|
-
|
271
|
-
end
|
272
|
-
|
273
|
-
end
|