oraora 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ module Oraora
2
+ class Meta
3
+ class Object
4
+ attr_reader :type
5
+
6
+ def initialize(schema, name, type = nil)
7
+ @schema = schema
8
+ @name = name
9
+ @type = type
10
+ end
11
+
12
+ def load_from_oci(oci)
13
+ if !@type
14
+ @id, @type = oci.select_one("SELECT object_id, object_type FROM all_objects
15
+ WHERE owner = :schema AND object_name = :name
16
+ ORDER BY decode(namespace, 19, 0, 99)", @schema, @name) if !@type
17
+ raise NotExists if !@id
18
+ @id = @id.to_i
19
+ end
20
+ case @type
21
+ when 'TABLE' then Table.from_oci(oci, @schema, @name)
22
+ when 'VIEW' then View.from_oci(oci, @schema, @name)
23
+ when 'MATERIALIZED VIEW' then MaterializedView.from_oci(oci, @schema, @name)
24
+ when 'SEQUENCE' then Sequence.from_oci(oci, @schema, @name)
25
+ else self
26
+ end
27
+ end
28
+
29
+ def self.from_oci(oci, schema, name, type = nil)
30
+ new(schema, name, type).load_from_oci(oci)
31
+ end
32
+
33
+ def describe(options = {})
34
+ <<-HERE.reset_indentation
35
+ Object #{@schema}.#{@name}
36
+ Id: #{@id}
37
+ Type: #{@type}
38
+ HERE
39
+ end
40
+
41
+ def list(options = {}, filter = nil)
42
+ raise NotApplicable, "Cannot list for this object"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,40 @@
1
+ module Oraora
2
+ class Meta
3
+ class Schema
4
+ def initialize(name)
5
+ @name = name
6
+ end
7
+
8
+ def load_from_oci(oci)
9
+ @id, @created = oci.select_one("SELECT user_id, created FROM all_users WHERE username = :name", @name)
10
+ raise NotExists if !@id
11
+ @id = @id.to_i
12
+ @objects = oci.pluck("SELECT object_name, min(object_type) object_type FROM all_objects
13
+ WHERE owner = :name
14
+ AND object_type IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW', 'SEQUENCE')
15
+ GROUP BY object_name
16
+ ORDER BY object_name", @name)
17
+ self
18
+ end
19
+
20
+ def self.from_oci(oci, name)
21
+ new(name).load_from_oci(oci)
22
+ end
23
+
24
+ def describe(options = {})
25
+ <<-HERE.reset_indentation
26
+ Schema #{@name}
27
+ Id: #{@id}
28
+ Created: #{@created}
29
+ HERE
30
+ end
31
+
32
+ def list(options = {}, filter = nil)
33
+ objects = @objects.collect(&:first)
34
+ objects.reject! { |o| o =~ /^ISEQ\$\$/ || o =~ /^SYS_/ || o =~ /^ORA_/ } unless options['a']
35
+ objects.select! { |o| o =~ /^#{Regexp.escape(filter).gsub('\*', '.*').gsub('\?', '.')}$/ } if filter
36
+ objects
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ require_relative './object.rb'
2
+
3
+ module Oraora
4
+ class Meta
5
+ class Sequence < Object
6
+ def type
7
+ 'SEQUENCE'
8
+ end
9
+
10
+ def load_from_oci(oci)
11
+ @min, @max, @inc, @last = oci.select_one("SELECT min_value, max_value, increment_by, last_number
12
+ FROM all_sequences
13
+ WHERE sequence_owner = :schema AND sequence_name = :name", @schema, @name)
14
+ raise NotExists if !@min
15
+ self
16
+ end
17
+
18
+ def describe(options = {})
19
+ <<-HERE.reset_indentation
20
+ Sequence #{@schema}.#{@name}
21
+ Min value: #{@min.to_i}
22
+ Max value: #{@max.to_i}
23
+ Increment by: #{@inc.to_i}
24
+ Last value: #{@last.to_i}
25
+ HERE
26
+ end
27
+
28
+ def list(options = {}, filter = nil)
29
+ raise NotApplicable, "Nothing to list for sequence"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,36 @@
1
+ module Oraora
2
+ class Meta
3
+ class Subprogram
4
+ attr_reader :id, :schema, :package, :name
5
+
6
+ def initialize(schema, package, name)
7
+ @schema = schema
8
+ @package = package
9
+ @name = name
10
+ end
11
+
12
+ def load_from_oci(oci)
13
+ @id =
14
+ oci.select_one("SELECT subprogram_id FROM all_procedues WHERE owner = :schema AND object_name = :package AND procedure_name = :name", @schema, @package, @name)
15
+ self
16
+ end
17
+
18
+ def self.from_oci(oci, schema, package, name)
19
+ new(schema, package, name).load_from_oci(oci)
20
+ end
21
+
22
+ def describe(options = {})
23
+ <<-HERE.reset_indentation
24
+ Schema: #{@schema}
25
+ Package: #{@package}
26
+ Name: #{@name}
27
+ Id: #{@id}
28
+ HERE
29
+ end
30
+
31
+ def list(options = {}, filter = nil)
32
+ raise NotApplicable, "Nothing to list for subprogram"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ require_relative './object.rb'
2
+
3
+ module Oraora
4
+ class Meta
5
+ class Table < Object
6
+ def type
7
+ 'TABLE'
8
+ end
9
+
10
+ def load_from_oci(oci)
11
+ @partitioned = oci.select_one("SELECT partitioned FROM all_tables WHERE owner = :schema AND table_name = :name", @schema, @name).first
12
+ raise NotExists if !@partitioned
13
+ @columns = oci.pluck("SELECT column_name, column_id, data_type, data_length, data_precision, data_scale, char_used, char_length " +
14
+ "FROM all_tab_columns WHERE owner = :schema AND table_name = :name ORDER BY column_id", @schema, @name).collect do |col|
15
+ Column.new(@schema, @name, col[0], id: col[1].to_i, type: col[2], length: col[3] && col[3].to_i,
16
+ precision: col[4] && col[4].to_i, scale: col[5] && col[5].to_i, char_used: col[6],
17
+ char_length: col[7] && col[7].to_i)
18
+ end
19
+ @columns_hash = Hash[@columns.collect { |col| [col.name, col] }]
20
+ self
21
+ end
22
+
23
+ def describe(options = {})
24
+ <<-HERE.reset_indentation
25
+ Table #{@schema}.#{@name}
26
+ Partitioned: #{@partitioned}
27
+ HERE
28
+ end
29
+
30
+ def list(options = {}, filter = nil)
31
+ columns = @columns_hash.keys
32
+ columns.select! { |c| c =~ /^#{Regexp.escape(filter).gsub('\*', '.*').gsub('\?', '.')}$/ } if filter
33
+ columns
34
+ end
35
+
36
+ def columns(column)
37
+ raise NotExists if !@columns_hash[column]
38
+ @columns_hash[column]
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ require_relative './object.rb'
2
+
3
+ module Oraora
4
+ class Meta
5
+ class View < Object
6
+ def type
7
+ 'VIEW'
8
+ end
9
+
10
+ def load_from_oci(oci)
11
+ x = oci.select_one("SELECT 1 FROM all_views WHERE owner = :schema AND view_name = :name", @schema, @name).first
12
+ raise NotExists if !x
13
+ @columns = oci.pluck("SELECT column_name, column_id, data_type, data_length, data_precision, data_scale, char_used, char_length " +
14
+ "FROM all_tab_columns WHERE owner = :schema AND table_name = :name ORDER BY column_id", @schema, @name).collect do |col|
15
+ Column.new(@schema, @name, col[0], id: col[1].to_i, type: col[2], length: col[3] && col[3].to_i,
16
+ precision: col[4] && col[4].to_i, scale: col[5] && col[5].to_i, char_used: col[6],
17
+ char_length: col[7] && col[7].to_i)
18
+ end
19
+ @columns_hash = Hash[@columns.collect { |col| [col.name, col] }]
20
+ self
21
+ end
22
+
23
+ def describe(options = {})
24
+ <<-HERE.reset_indentation
25
+ View #{@schema}.#{@name}
26
+ HERE
27
+ end
28
+
29
+ def list(options = {}, filter = nil)
30
+ columns = @columns_hash.keys
31
+ columns.select! { |c| c =~ /^#{Regexp.escape(filter).gsub('\*', '.*').gsub('\?', '.')}$/ } if filter
32
+ columns
33
+ end
34
+
35
+ def columns(column)
36
+ raise NotExists if !@columns_hash[column]
37
+ @columns_hash[column]
38
+ end
39
+ end
40
+ end
41
+ end
data/lib/oraora/oci.rb ADDED
@@ -0,0 +1,56 @@
1
+ module Oraora
2
+ # Wrapper around OCI8 to add some extra stuff
3
+ class OCI < OCI8
4
+ # Wrapped in a separate thread as OCI8 seems to ignore interrupts
5
+ def initialize(*args)
6
+ ret = nil
7
+ thread = Thread.new { ret = super }
8
+ thread.join
9
+ ret
10
+ end
11
+
12
+ # Wrapped in a separate thread as OCI8 seems to ignore interrupts
13
+ def logoff
14
+ ret = nil
15
+ thread = Thread.new { ret = super }
16
+ thread.join
17
+ ret
18
+ end
19
+
20
+ # Wrapped in a separate thread with Interrupt handling
21
+ def exec(sql, *bindvars, &block)
22
+ ret = nil
23
+ thread = Thread.new { ret = super }
24
+ thread.join
25
+ ret
26
+ rescue Interrupt
27
+ self.break
28
+ raise
29
+ end
30
+
31
+ # Wrapped in a separate thread with Interrupt handling
32
+ def select_one(sql, *bindvars)
33
+ ret = nil
34
+ thread = Thread.new { ret = super }
35
+ thread.join
36
+ ret
37
+ rescue Interrupt
38
+ self.break
39
+ raise
40
+ end
41
+
42
+ # Returns the query result as an array of arrays
43
+ def pluck(sql, *bindvars)
44
+ result = []
45
+ exec(sql, *bindvars) { |row| result << row }
46
+ result
47
+ end
48
+
49
+ # Returns first column of a query as an array
50
+ def pluck_one(sql, *bindvars)
51
+ result = []
52
+ exec(sql, *bindvars) { |row| result << row.first }
53
+ result
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,55 @@
1
+ module Oraora
2
+ module Terminal
3
+ def self.width
4
+ HighLine::SystemExtensions.terminal_size[0]
5
+ end
6
+
7
+ def self.height
8
+ HighLine::SystemExtensions.terminal_size[1]
9
+ end
10
+
11
+ def self.puts_grid(items)
12
+ # TODO: Disable terminal size check when not reading from terminal
13
+ terminal_cols = [width || 0, 32].max
14
+ object_cols = terminal_cols / 32
15
+ # TODO: Determine optimal object_cols
16
+ num_rows = (items.length + object_cols - 1) / object_cols
17
+ #@logger.debug "Determined #{num_rows} rows of #{object_cols} objects for #{items.count} objects and #{terminal_cols} terminal width"
18
+ (0...num_rows).each do |row|
19
+ line = ''
20
+ (0...object_cols).each do |col|
21
+ index = num_rows * col + row
22
+ line += items[index].ljust(32) if items[index]
23
+ end
24
+ puts line
25
+ end
26
+ end
27
+
28
+ def self.puts_cursor(cursor)
29
+ # Column metadata
30
+ column_names = cursor.get_col_names
31
+
32
+ cursor.prefetch_rows = 1000
33
+ begin
34
+ # Fetch 1000 rows
35
+ output = []
36
+ column_lengths = Array.new(column_names.length, 1)
37
+ while output.length < 1000 && record = cursor.fetch
38
+ record.collect! { |val| val.is_a?(BigDecimal) ? val.to_s('F').gsub(/\.0+$/, '') : val.to_s }
39
+ output << record
40
+ column_lengths = column_lengths.zip(record.collect { |v| v.length}).collect(&:max)
41
+ end
42
+
43
+ # Output
44
+ if !output.empty?
45
+ puts "%-*.*s " * column_names.length % column_lengths.zip(column_lengths, column_names).flatten
46
+ puts "%-*s " * column_names.length % column_lengths.zip(column_lengths.collect { |c| '-' * c }).flatten
47
+ output.each do |row|
48
+ puts "%-*s " * row.length % column_lengths.zip(row).flatten
49
+ end
50
+ puts
51
+ end
52
+ end while record
53
+ end
54
+ end
55
+ end
data/oraora.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'oraora'
3
+ s.version = '0.1.0'
4
+ s.summary = "Interactive command-line utility for Oracle"
5
+ s.description = "Interactive command-line utility for Oracle"
6
+
7
+ s.author = "kmehkeri"
8
+ s.email = 'kmehkeri@gmail.com'
9
+ s.homepage = 'http://rubygems.org/gems/oraora'
10
+ s.license = 'MIT'
11
+
12
+ s.files = `git ls-files`.split($/)
13
+ s.executables = `git ls-files -- bin`.split($/).map { |f| File.basename(f) }
14
+
15
+ s.add_runtime_dependency 'highline'
16
+ s.add_runtime_dependency 'ruby-oci8'
17
+ s.add_runtime_dependency 'indentation'
18
+ s.add_runtime_dependency 'colorize'
19
+ s.add_runtime_dependency 'bigdecimal'
20
+
21
+ s.add_development_dependency 'rspec'
22
+ end
@@ -0,0 +1,132 @@
1
+ require 'oraora/context'
2
+
3
+ describe Oraora::Context do
4
+
5
+ describe '.initialize' do
6
+ it "should initialize from hash" do
7
+ context = Oraora::Context.new('V', schema: 'X', object: 'Y', object_type: 'TABLE')
8
+ expect( context.level ).to eql :object
9
+ expect( context.user ).to eql 'V'
10
+ expect( context.schema ).to eql 'X'
11
+ expect( context.object ).to eql 'Y'
12
+ expect( context.object_type ).to eql 'TABLE'
13
+ end
14
+ end
15
+
16
+ describe '.su' do
17
+ it "should return a different context object" do
18
+ context = Oraora::Context.new('V', schema: 'X', object: 'Y', object_type: 'TABLE')
19
+ expect( context.su('Z') ).not_to eql context
20
+ end
21
+
22
+ it "should store specified user" do
23
+ context = Oraora::Context.new('V', schema: 'X', object: 'Y', object_type: 'TABLE').su('Z')
24
+ expect( context.user ).to eql 'Z'
25
+ end
26
+
27
+ it "should store original context's data" do
28
+ context = Oraora::Context.new('V', schema: 'X', object: 'Y', object_type: 'TABLE').su('Z')
29
+ expect( context.schema ).to eql 'X'
30
+ expect( context.object ).to eql 'Y'
31
+ end
32
+ end
33
+
34
+ describe '.dup' do
35
+ it "should return equal context" do
36
+ context = Oraora::Context.new('V', schema: 'X', object: 'Y', object_type: 'TABLE').dup
37
+ expect( context.user ).to eql 'V'
38
+ expect( context.schema ).to eql 'X'
39
+ expect( context.object ).to eql 'Y'
40
+ expect( context.object_type ).to eql 'TABLE'
41
+ end
42
+ end
43
+
44
+ describe '#set & #traverse' do
45
+ it "should set root context correctly" do
46
+ context = Oraora::Context.new.set({})
47
+ expect( context.level ).to be_nil
48
+ expect( context.schema ).to be_nil
49
+ end
50
+
51
+ it "should set schema context correctly" do
52
+ context = Oraora::Context.new.set(schema: 'A')
53
+ expect( context.level ).to eql :schema
54
+ expect( context.schema ).to eql 'A'
55
+ end
56
+
57
+ it "should set column context correctly" do
58
+ context = Oraora::Context.new.set(schema: 'A', object: 'B', object_type: 'TABLE', column: 'C')
59
+ expect( context.level ).to eql :column
60
+ expect( context.schema ).to eql 'A'
61
+ expect( context.object ).to eql 'B'
62
+ expect( context.column ).to eql 'C'
63
+ end
64
+
65
+ it "should set user correctly on the context" do
66
+ context = Oraora::Context.new('AA').set(schema: 'A')
67
+ expect( context.level ).to eql :schema
68
+ expect( context.user ).to eql 'AA'
69
+ expect( context.schema ).to eql 'A'
70
+ end
71
+
72
+ it "should save user when switching context" do
73
+ context = Oraora::Context.new('AA').set(schema: 'A')
74
+ context.set(schema: 'B')
75
+ expect( context.level ).to eql :schema
76
+ expect( context.user ).to eql 'AA'
77
+ expect( context.schema ).to eql 'B'
78
+ end
79
+
80
+ it "should raise error on invalid key" do
81
+ expect { Oraora::Context.new.set(foo: 'A') }.to raise_exception(Oraora::Context::InvalidKey)
82
+ end
83
+
84
+ it "should raise error on missing object_type when object is present" do
85
+ expect { Oraora::Context.new.set(schema: 'A', object: 'B') }.to raise_exception(Oraora::Context::InvalidKey)
86
+ end
87
+
88
+ it "should raise error on invalid combination of keys" do
89
+ expect { Oraora::Context.new.set(schema: 'A', column: 'B', subprogram: 'C') }.to raise_exception(Oraora::Context::InvalidKey)
90
+ end
91
+ end
92
+
93
+ describe "#up" do
94
+ it "should do nothing when already at root" do
95
+ context = Oraora::Context.new.up
96
+ expect( context.level ).to be_nil
97
+ end
98
+
99
+ it "should go up from object to schema level" do
100
+ context = Oraora::Context.new(nil, schema: 'A', object: 'B', object_type: 'TABLE').up
101
+ expect( context.level ).to eql :schema
102
+ expect( context.schema ).to eql 'A'
103
+ expect( context.object ).to be_nil
104
+ end
105
+
106
+ it "should go up from column to object level" do
107
+ context = Oraora::Context.new(nil, schema: 'X', object: 'Y', object_type: 'TABLE', column: 'Z').up
108
+ expect( context.level ).to eql :object
109
+ expect( context.schema ).to eql 'X'
110
+ expect( context.object ).to be_eql 'Y'
111
+ expect( context.column ).to be_nil
112
+ end
113
+ end
114
+
115
+ describe '#prompt' do
116
+ it "should display correct prompt for context" do
117
+ {
118
+ {} => '/',
119
+ { schema: 'Q' } => 'Q',
120
+ { schema: 'U' } => '~',
121
+ { schema: 'A', object: 'B', object_type: 'TABLE' } => 'A.B',
122
+ { schema: 'U', object: 'B', object_type: 'PACKAGE' } => '~.B',
123
+ { schema: 'U', object: 'Y', object_type: 'TABLE', column: 'Z' } => '~.Y.Z',
124
+ { schema: 'MMM', object: 'NNN', object_type: 'VIEW', column: 'TEST' } => 'MMM.NNN.TEST'
125
+ }.each do |hash, prompt|
126
+ expect( Oraora::Context.new('U', hash).prompt ).to eql prompt
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+