vorax 0.4.2 → 5.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/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
|