teradata-cli 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/COPYING +515 -0
- data/Gemfile +4 -0
- data/README.md +47 -0
- data/Rakefile +1 -0
- data/examples/query.rb +38 -0
- data/examples/show-queryband.rb +26 -0
- data/examples/tu/excel/excel.rb +86 -0
- data/examples/tu/excel/fill.rb +94 -0
- data/examples/tu/excel/template.xls +0 -0
- data/examples/tu/tusample1.rb +6 -0
- data/examples/tu/tusample2.rb +7 -0
- data/examples/tu/web/bitdao.rb +197 -0
- data/examples/tu/web/bitdao/teradata.rb +23 -0
- data/examples/tu/web/bitweb.rb +575 -0
- data/examples/tu/web/messages +0 -0
- data/examples/tu/web/server.rb +94 -0
- data/examples/tu/web/tdwalker.rb +42 -0
- data/examples/tu/web/template/database/show +56 -0
- data/examples/tu/web/template/footer +2 -0
- data/examples/tu/web/template/header +7 -0
- data/examples/update.rb +31 -0
- data/ext/teradata/cli/cli.c +363 -0
- data/ext/teradata/cli/extconf.rb +20 -0
- data/lib/teradata.rb +14 -0
- data/lib/teradata/cli.rb +4 -0
- data/lib/teradata/cli/version.rb +5 -0
- data/lib/teradata/connection.rb +1125 -0
- data/lib/teradata/dbobject.rb +453 -0
- data/lib/teradata/exception.rb +15 -0
- data/lib/teradata/utils.rb +184 -0
- data/teradata-cli.gemspec +24 -0
- data/test/all +7 -0
- data/test/rubyclitestutils.rb +99 -0
- data/test/test_connection.rb +298 -0
- data/test/test_dbobject.rb +153 -0
- data/test/test_record.rb +210 -0
- metadata +115 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Teradata::Cli
|
2
|
+
|
3
|
+
Teradata::Cli is an access module which allows Ruby scripts
|
4
|
+
to access Teradata RDBMS by CLIv2 interface.
|
5
|
+
|
6
|
+
It's based on the [Ruby/CLIv2](http://sourceforge.net/projects/rubycli/) library.
|
7
|
+
|
8
|
+
|
9
|
+
## Requirements
|
10
|
+
|
11
|
+
CLIv2 (32bit / 64bit)
|
12
|
+
C compiler
|
13
|
+
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
gem 'teradata-cli'
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install teradata-cli
|
28
|
+
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
Look the examples folder
|
33
|
+
|
34
|
+
|
35
|
+
## Tests
|
36
|
+
|
37
|
+
$ export TEST_LOGON_STRING=dbc/user,password
|
38
|
+
$ ruby -Ilib:test test/all
|
39
|
+
|
40
|
+
|
41
|
+
## Contributing
|
42
|
+
|
43
|
+
1. Fork it
|
44
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
45
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
46
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
47
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/examples/query.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Dispatches SQL query and shows result records.
|
4
|
+
#
|
5
|
+
# Usage:
|
6
|
+
# $ export LOGON_STRING=dbc/user,pass
|
7
|
+
# $ ruby example/query.rb 'SELECT * FROM x'
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'teradata'
|
11
|
+
require 'logger'
|
12
|
+
require 'pp'
|
13
|
+
|
14
|
+
logon_string = ENV['LOGON_STRING']
|
15
|
+
unless logon_string
|
16
|
+
$stderr.puts "set environment variable LOGON_STRING"
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
|
20
|
+
sql = ARGV[0]
|
21
|
+
unless sql
|
22
|
+
$stderr.puts "Usage: ruby #{File.basename($0)} QUERY"
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
|
26
|
+
log = Logger.new($stderr)
|
27
|
+
log.sev_threshold = $DEBUG ? Logger::DEBUG : Logger::INFO
|
28
|
+
|
29
|
+
Teradata.connect(logon_string, :logger => log) {|conn|
|
30
|
+
conn.query(sql) {|result_sets|
|
31
|
+
result_sets.each_result_set do |rs|
|
32
|
+
pp rs
|
33
|
+
rs.each_record do |rec|
|
34
|
+
pp rec
|
35
|
+
end
|
36
|
+
end
|
37
|
+
}
|
38
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Show Query Bands of current sessions.
|
4
|
+
#
|
5
|
+
# Usage:
|
6
|
+
# $ export LOGON_STRING=dbc/user,pass
|
7
|
+
# $ ruby example/show-queryband.rb
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'teradata'
|
11
|
+
|
12
|
+
logon_string = ENV['LOGON_STRING']
|
13
|
+
unless logon_string
|
14
|
+
$stderr.puts "set environment variable LOGON_STRING"
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
|
18
|
+
Teradata.connect(logon_string) {|conn|
|
19
|
+
conn.query("SELECT * FROM dbc.sessionInfo") {|rs|
|
20
|
+
rs.each do |rec|
|
21
|
+
user = rec[:UserName].strip
|
22
|
+
band = rec[:QueryBand]
|
23
|
+
puts "#{user}\t#{band}"
|
24
|
+
end
|
25
|
+
}
|
26
|
+
}
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'win32ole'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Excel
|
5
|
+
|
6
|
+
def Excel.open_worksheet(path, sheet_name, mode, &block)
|
7
|
+
open {|app| app.open_worksheet(path, sheet_name, mode, &block) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def Excel.open_book(path, mode, &block)
|
11
|
+
open {|app| app.open_book(path, mode, &block) }
|
12
|
+
end
|
13
|
+
|
14
|
+
FileSystem = WIN32OLE.new('Scripting.FileSystemObject')
|
15
|
+
|
16
|
+
def Excel.open(visible = $DEBUG, &block)
|
17
|
+
new(visible, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
@@const_loaded = false
|
21
|
+
|
22
|
+
def initialize(visible = $DEBUG)
|
23
|
+
@app = WIN32OLE.new('Excel.Application')
|
24
|
+
unless @@const_loaded
|
25
|
+
WIN32OLE.const_load @app, Excel
|
26
|
+
@@const_loaded = true
|
27
|
+
end
|
28
|
+
@app.visible = true if visible
|
29
|
+
yield self if block_given?
|
30
|
+
ensure
|
31
|
+
quit if block_given? and @app
|
32
|
+
end
|
33
|
+
|
34
|
+
def quit
|
35
|
+
@app.quit
|
36
|
+
end
|
37
|
+
|
38
|
+
def open_book(path, mode)
|
39
|
+
begin
|
40
|
+
book = @app.workbooks.open(win_extend_path(path))
|
41
|
+
yield book
|
42
|
+
book.save if mode == 'w'
|
43
|
+
ensure
|
44
|
+
book.saved = true # avoid confirmation message
|
45
|
+
@app.workbooks.close
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def open_worksheet(path, sheet_name, mode)
|
50
|
+
open_book(path, mode) {|book|
|
51
|
+
sheet = book.worksheets.item(1)
|
52
|
+
sheet.extend WorkSheetMethods
|
53
|
+
yield sheet
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def win_extend_path(path)
|
58
|
+
FileSystem.getAbsolutePathName(path)
|
59
|
+
end
|
60
|
+
private :win_extend_path
|
61
|
+
|
62
|
+
module WorkSheetMethods
|
63
|
+
def [](y, x)
|
64
|
+
cell = cells().item(y, x)
|
65
|
+
if cell.mergeCells
|
66
|
+
cell.mergeArea.item(1, 1).value
|
67
|
+
else
|
68
|
+
cell.value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def []=(y, x, value)
|
73
|
+
cell = cells().item(y, x)
|
74
|
+
if cell.mergeCells
|
75
|
+
cell.mergeArea.item(1, 1).value = value
|
76
|
+
else
|
77
|
+
cell.value = value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def last_cell
|
82
|
+
cells().specialCells(Excel::XlLastCell)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'excel'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'pp'
|
4
|
+
require 'teradata'
|
5
|
+
|
6
|
+
def main
|
7
|
+
logon_string, template, output = ARGV
|
8
|
+
usage_exit unless logon_string
|
9
|
+
usage_exit unless template
|
10
|
+
output ||= 'out.xls'
|
11
|
+
|
12
|
+
FileUtils.cp template, output
|
13
|
+
Excel.open(true) {|app|
|
14
|
+
app.open_worksheet(output, 'Sheet1', 'w') {|sheet|
|
15
|
+
fill_sheet sheet, logon_string
|
16
|
+
}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def fill_sheet(sheet, logon_string)
|
21
|
+
# Get SQL Cell
|
22
|
+
sql_cell = sheet.cells.find('%SQL')
|
23
|
+
p ['sql_cell', sql_cell.address]
|
24
|
+
sql = sql_cell.value.slice(/%SQL\s+(.*)/m, 1).strip
|
25
|
+
p sql
|
26
|
+
|
27
|
+
# Get Value Cells
|
28
|
+
value_cell = sheet.cells.find('%=', sql_cell)
|
29
|
+
p ['value', value_cell.address]
|
30
|
+
exprs = [expr_proc(value_cell)]
|
31
|
+
c = value_cell
|
32
|
+
while true
|
33
|
+
next_c = sheet.range(address(c.row.to_i, c.column.to_i + 1))
|
34
|
+
break unless /^%=/ =~ next_c.value
|
35
|
+
c = next_c
|
36
|
+
p ['value', c.address]
|
37
|
+
exprs.push expr_proc(c)
|
38
|
+
end
|
39
|
+
pp exprs
|
40
|
+
|
41
|
+
tmpl_range = sheet.range(value_cell.address + ':' + c.address)
|
42
|
+
xl = value_cell.column
|
43
|
+
xr = c.column
|
44
|
+
y = value_cell.row + 1
|
45
|
+
|
46
|
+
# Execute SQL and fill cells by data
|
47
|
+
Teradata.connect(logon_string) {|conn|
|
48
|
+
conn.query(sql) {|rs|
|
49
|
+
rs.each do |rec|
|
50
|
+
pp rec
|
51
|
+
values = exprs.map {|expr| expr.call(rec) }
|
52
|
+
pp values
|
53
|
+
tmpl_range.copy
|
54
|
+
sheet.range(address(y, xl) + ':' + address(y, xr)).insert Excel::XlShiftDown
|
55
|
+
sheet.range(address(y, xl) + ':' + address(y, xr)).value = values
|
56
|
+
y += 1
|
57
|
+
end
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
# remove metadata cells
|
62
|
+
tmpl_range.delete Excel::XlShiftUp
|
63
|
+
sheet.rows(sql_cell.row).delete
|
64
|
+
end
|
65
|
+
|
66
|
+
def usage_exit
|
67
|
+
$stderr.puts "Usage: #{$0} LOGON_STRING TEMPLATE [OUTPUT]"
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
|
71
|
+
def expr_proc(cell)
|
72
|
+
expr = cell.value.slice(/%=(.*)/m, 1).strip
|
73
|
+
lambda {|_| eval(expr).to_s }
|
74
|
+
end
|
75
|
+
|
76
|
+
def address(row, col)
|
77
|
+
"$#{column_string(col)}$#{row}"
|
78
|
+
end
|
79
|
+
|
80
|
+
ALPHA = ('a'..'z').to_a
|
81
|
+
|
82
|
+
def column_string(col)
|
83
|
+
result = []
|
84
|
+
n = col - 1
|
85
|
+
while n >= 26
|
86
|
+
n, mod = n.divmod(26)
|
87
|
+
result.unshift mod
|
88
|
+
n -= 1
|
89
|
+
end
|
90
|
+
result.unshift n
|
91
|
+
result.map {|i| ALPHA[i] }.join('')
|
92
|
+
end
|
93
|
+
|
94
|
+
main
|
Binary file
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# $Id: bitdao.rb 184 2009-08-12 08:46:22Z aamine $
|
2
|
+
|
3
|
+
class BitDAO
|
4
|
+
module Error; end
|
5
|
+
|
6
|
+
class BaseError < ::StandardError
|
7
|
+
include Error
|
8
|
+
end
|
9
|
+
|
10
|
+
class IntegrityError < BaseError; end
|
11
|
+
class ObjectNotExist < BaseError; end
|
12
|
+
|
13
|
+
def error
|
14
|
+
@connection.error_class
|
15
|
+
end
|
16
|
+
|
17
|
+
def sql_error
|
18
|
+
@connection.sql_error_class
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(log, connection)
|
22
|
+
@log = log
|
23
|
+
@connection = connection
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_object(_class, sql)
|
27
|
+
list = load_objects(_class, sql)
|
28
|
+
if list.empty?
|
29
|
+
raise ObjectNotExist, "no record exist: #{_class}"
|
30
|
+
end
|
31
|
+
if list.size > 1
|
32
|
+
raise IntegrityError, "too many #{_class} loaded: #{list.size} for 1"
|
33
|
+
end
|
34
|
+
list.first
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_objects(_class, sql)
|
38
|
+
@log.info(self.class) { "[SEL] #{sql}" }
|
39
|
+
result = []
|
40
|
+
@connection.execute_query(sql) {|rs|
|
41
|
+
rs.each_record do |rec|
|
42
|
+
result.push _class.for_record(self, rec)
|
43
|
+
end
|
44
|
+
}
|
45
|
+
@log.info(self.class) { "#{result.size} records" }
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
def exec_sql(sql, level = Logger::INFO)
|
50
|
+
@log.add(level, nil, self.class) { "[UPD] #{sql}" } if level
|
51
|
+
@connection.execute_update sql
|
52
|
+
end
|
53
|
+
|
54
|
+
def transaction
|
55
|
+
aborting = false
|
56
|
+
exec_sql "BEGIN TRANSACTION;"
|
57
|
+
begin
|
58
|
+
yield
|
59
|
+
rescue Teradata::CLI::UserAbort => err
|
60
|
+
aborting = true
|
61
|
+
raise err
|
62
|
+
ensure
|
63
|
+
if $@
|
64
|
+
begin
|
65
|
+
abort unless aborting
|
66
|
+
rescue Teradata::CLI::UserAbort # do not override original exception
|
67
|
+
end
|
68
|
+
else
|
69
|
+
exec_sql "END TRANSACTION;"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def abort
|
75
|
+
exec_sql "ABORT;"
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def int(n)
|
81
|
+
return 'NULL' unless n
|
82
|
+
n
|
83
|
+
end
|
84
|
+
|
85
|
+
def string(str)
|
86
|
+
return 'NULL' unless str
|
87
|
+
"'" + str.gsub(/'/, "''") + "'"
|
88
|
+
end
|
89
|
+
|
90
|
+
def date(d)
|
91
|
+
return 'NULL' unless d
|
92
|
+
"DATE '#{d.strftime('%Y-%m-%d')}'"
|
93
|
+
end
|
94
|
+
|
95
|
+
def timestamp(t)
|
96
|
+
return 'NULL' unless t
|
97
|
+
"TIMESTAMP '#{t.strftime('%Y-%m-%d %H:%M:%S')}'"
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
def BitDAO.define(&block)
|
102
|
+
PersistentObject.define(&block)
|
103
|
+
end
|
104
|
+
|
105
|
+
class PersistentObject
|
106
|
+
|
107
|
+
def PersistentObject.define(&block)
|
108
|
+
c = Class.new(PersistentObject)
|
109
|
+
c.module_eval(&block)
|
110
|
+
c.define_initialize c.slots
|
111
|
+
c
|
112
|
+
end
|
113
|
+
|
114
|
+
def PersistentObject.slot(name, _class, column = nil)
|
115
|
+
attr_reader name
|
116
|
+
(@slots ||= []).push Slot.new(name, _class, column)
|
117
|
+
end
|
118
|
+
|
119
|
+
class << self
|
120
|
+
attr_reader :slots
|
121
|
+
end
|
122
|
+
|
123
|
+
class Slot
|
124
|
+
def initialize(name, _class, column)
|
125
|
+
@name = name
|
126
|
+
@class = _class
|
127
|
+
@column = column || name
|
128
|
+
end
|
129
|
+
|
130
|
+
attr_reader :name
|
131
|
+
attr_reader :column
|
132
|
+
|
133
|
+
def parse(s)
|
134
|
+
@class.parse(s)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def PersistentObject.sql_integer
|
139
|
+
SQLInteger.new
|
140
|
+
end
|
141
|
+
|
142
|
+
class SQLInteger
|
143
|
+
def parse(i)
|
144
|
+
i
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def PersistentObject.sql_string
|
149
|
+
SQLString.new
|
150
|
+
end
|
151
|
+
|
152
|
+
class SQLString
|
153
|
+
# CHAR/VARCHAR field returns extra spaces, remove it always.
|
154
|
+
def parse(str)
|
155
|
+
str ? str.rstrip : nil
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def PersistentObject.sql_date
|
160
|
+
SQLDate.new
|
161
|
+
end
|
162
|
+
|
163
|
+
def PersistentObject.sql_timestamp
|
164
|
+
SQLDate.new
|
165
|
+
end
|
166
|
+
|
167
|
+
class SQLDate
|
168
|
+
def parse(str)
|
169
|
+
# "2009-01-23"
|
170
|
+
Time.parse(str)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def PersistentObject.for_record(dao, rec)
|
175
|
+
unless rec.size >= @slots.size
|
176
|
+
raise DatabaseError, "wrong column number of record (#{rec.size} for #{@slots.size})"
|
177
|
+
end
|
178
|
+
obj = new(* @slots.map {|slot| slot.parse(rec[slot.column]) })
|
179
|
+
obj._dao = dao
|
180
|
+
obj
|
181
|
+
end
|
182
|
+
|
183
|
+
def PersistentObject.define_initialize(slots)
|
184
|
+
param_list = slots.map {|s| s.name }.join(', ')
|
185
|
+
ivar_list = slots.map {|s| "@#{s.name}" }.join(', ')
|
186
|
+
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
187
|
+
def initialize(#{param_list})
|
188
|
+
#{ivar_list} = #{param_list}
|
189
|
+
end
|
190
|
+
End
|
191
|
+
end
|
192
|
+
|
193
|
+
attr_writer :_dao
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|