mssql 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
1
+ class CommandParser
2
+
3
+ def initialize(line)
4
+ @line = line
5
+ @command = nil
6
+ @params = nil
7
+ parse
8
+ end
9
+
10
+ attr_reader :command, :params
11
+
12
+ def is_command?
13
+ !@command.nil?
14
+ end
15
+
16
+ def parse
17
+ m = @line.match /^\s*\.(\w+) *(.*)$/
18
+ return if m.nil?
19
+ @command = m[1].to_sym
20
+ @params = m[2].split(' ').compact.map{|p| p.split(".")}.flatten
21
+ case @command
22
+ when "find"
23
+ when "explain"
24
+ when "use"
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,58 @@
1
+ class Connection
2
+
3
+ def initialize(configs, after_connect_handler=nil)
4
+ @configs = configs
5
+ @after_connect_handler = after_connect_handler
6
+ use :default_connection
7
+ end
8
+
9
+ attr_reader :results, :error, :name
10
+
11
+ def error?
12
+ !@error.nil?
13
+ end
14
+
15
+ def one_column_one_row?
16
+ return false if error?
17
+ @results.rows.size == 1 && @results.columns.size == 1
18
+ rescue
19
+ false
20
+ end
21
+
22
+ def exec(sql)
23
+ begin
24
+ result = @client.execute sql
25
+ rows = result.each(:symbolize_keys => true, :empty_sets => true, :as => :array)
26
+ @results = Hashie::Mash.new({
27
+ :columns => result.fields,
28
+ :rows => rows,
29
+ :affected => result.do,
30
+ :return_code => @client.return_code
31
+ })
32
+ @error = nil
33
+ @results
34
+ rescue TinyTds::Error => e
35
+ @result = nil
36
+ @error = Hashie::Mash.new(:error => e.to_s, :severity => e.severity, :db_error_number => e.db_error_number)
37
+ end
38
+ end
39
+
40
+ KEYS = [:username, :password, :host, :database]
41
+
42
+ def use(name = :default_connection)
43
+ return false unless @configs.has_key?(name.to_s)
44
+ connect @configs[name.to_s]
45
+ return true
46
+ end
47
+
48
+ private
49
+
50
+ def connect(config)
51
+ new_client = TinyTds::Client.new(config.to_hash.symbolize_keys)
52
+ @client.close if @client
53
+ @client = new_client
54
+ @name = config.name || "#{config.username}@#{config.host}"
55
+ @after_connect_handler.call(@name) if @after_connect_handler
56
+ end
57
+
58
+ end
@@ -0,0 +1,126 @@
1
+ require 'readline'
2
+
3
+ class Controller
4
+
5
+ def initialize
6
+ connect
7
+ trap_int
8
+ @lines = []
9
+ end
10
+
11
+ def run
12
+ if ENV["EMACS"] == "t"
13
+ emacs_run_loop
14
+ elsif @options.input_file
15
+ show File.read(@options.input_file)
16
+ elsif @options.query
17
+ show @options.query
18
+ else
19
+ run_loop
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def emacs_run_loop
26
+ print @prompt
27
+ $stdin.flush
28
+ $stdin.each_line do |line|
29
+ #print "emacs_run_loop #{line}"
30
+ ret = handle_line(line)
31
+ print @prompt unless has_more?
32
+ $stdout.fsync
33
+ break if ret && ret < 0
34
+ end
35
+ end
36
+
37
+ def has_more?
38
+ c = $stdin.read_nonblock(1) rescue nil
39
+ $stdin.ungetbyte(c) if c
40
+ !!c
41
+ end
42
+
43
+ def run_loop
44
+ loop do
45
+ while line = Readline.readline(@prompt, true)
46
+ ret = handle_line(line)
47
+ return if ret && ret < 0
48
+ end
49
+ exec_lines
50
+ end
51
+ end
52
+
53
+ def handle_line(line)
54
+ line = line.strip
55
+ return if line.empty?
56
+ command = Command.new(line, @connection)
57
+ if command.exit?
58
+ exec_lines
59
+ return -1
60
+ end
61
+ if command.go?
62
+ exec_lines
63
+ end
64
+ return 0 if command.exec
65
+ @lines << line
66
+ # if $stdin.eof?
67
+ # exec_lines
68
+ # return -1
69
+ # end
70
+ 0
71
+ end
72
+
73
+ def exec_lines
74
+ show @lines.join("\n")
75
+ @lines = []
76
+ end
77
+
78
+ def trap_int
79
+ #stty_save = `stty -g`.chomp
80
+ trap('INT') do
81
+ #system('stty', stty_save);
82
+ exit
83
+ end
84
+ end
85
+
86
+ def show(query)
87
+ QueryOutput.new(@connection, query).show
88
+ end
89
+
90
+ def connect
91
+ read_configs
92
+ @connection = Connection.new @configs, Proc.new{ |name| @prompt = "#{name}> " }
93
+ rescue TinyTds::Error => e
94
+ print "#{e.to_s}\n"
95
+ exit
96
+ end
97
+
98
+ def read_configs
99
+ file_configs = YAML.load(File.read("#{ENV['HOME']}/.mssql")) rescue {}
100
+ @configs = Hashie::Mash.new(file_configs)
101
+ params = ParamsParser.new
102
+ @options = params.options
103
+ unless params.options.empty?
104
+ if params.options.connection
105
+ key = params.options.connection.to_s
106
+ if @configs.has_key?(key)
107
+ @configs.default_connection = @configs[key]
108
+ else
109
+ print "unkonwn connection #{key}\n"
110
+ exit 1
111
+ end
112
+ else
113
+ if missing = params.missing_key(Connection::KEYS)
114
+ print "missing #{missing} param\n"
115
+ exit 2
116
+ end
117
+ @configs.default_connection = params.options
118
+ end
119
+ end
120
+ unless @configs.default_connection
121
+ params.print_usage
122
+ exit 3
123
+ end
124
+ end
125
+
126
+ end
@@ -0,0 +1,22 @@
1
+ require 'optparse'
2
+ require 'pp'
3
+ require 'yaml'
4
+
5
+ require 'rubygems'
6
+ #require "bundler/setup"
7
+
8
+ require 'tiny_tds'
9
+ require 'hashie'
10
+ require 'active_support/core_ext/hash/keys.rb'
11
+
12
+ $: << File.join(File.dirname(__FILE__))
13
+
14
+ require 'params_parser'
15
+ require 'connection'
16
+ require 'table_output'
17
+ require 'command'
18
+ require 'controller'
19
+ require 'query_output'
20
+ require 'command_parser'
21
+
22
+ STDOUT.sync = true
@@ -0,0 +1,49 @@
1
+ class ParamsParser
2
+
3
+ def initialize
4
+ @options = Hashie::Mash.new
5
+ parse
6
+ end
7
+
8
+ def parse
9
+ @opts = OptionParser.new do |opts|
10
+ opts.banner = ""
11
+ available_options = [
12
+ ['-c', 'connection', 'use connection defined in ~/.mssql'],
13
+ ['-h', 'host', 'server host'],
14
+ ['-u', 'username', 'username'],
15
+ ['-p', 'password', 'password'],
16
+ ['-d', 'database', 'use database name'],
17
+ ['-i', 'input_file', 'input file name'],
18
+ ['-q', 'query', 'run query and exit']
19
+ ]
20
+ available_options.each do |o|
21
+ opts.on(o[0], "--#{o[1]} #{o[1].upcase}", o[2]) do |value|
22
+ @options[o[1]] = value
23
+ end
24
+ end
25
+ opts.on_tail("-?", "--help", "show syntax summary") do
26
+ print_usage
27
+ exit
28
+ end
29
+ end
30
+ @opts
31
+ @opts.parse!(ARGV)
32
+ @opts
33
+ end
34
+
35
+ def print_usage
36
+ puts <<-END
37
+ Usage: #{File.basename($0)} <options>
38
+ #{@opts.to_s}
39
+ END
40
+ end
41
+
42
+ def missing_key(keys)
43
+ keys.find do |key|
44
+ !@options.has_key?(key.to_s)
45
+ end
46
+ end
47
+
48
+ attr_reader :options
49
+ end
@@ -0,0 +1,55 @@
1
+ class QueryOutput
2
+
3
+ def initialize(connection, query)
4
+ @connection = connection
5
+ @query = query
6
+ end
7
+
8
+ def show
9
+ exec
10
+ @connection.error? ? print_error : print_tables
11
+ end
12
+
13
+ def show_text_or_table
14
+ exec
15
+ if @connection.one_column_one_row?
16
+ print_text
17
+ else
18
+ print_tables unless @connection.error?
19
+ end
20
+ print_error if @connection.error?
21
+ end
22
+
23
+ private
24
+
25
+ def print_error
26
+ error = @connection.error
27
+ print "\nmsg: #{error.db_error_number}, severity: #{error.severity}\n#{error.error}\n"
28
+ end
29
+
30
+ def print_text
31
+ print @connection.results.rows[0][0]
32
+ print "\n"
33
+ rescue
34
+ end
35
+
36
+ def print_tables
37
+ results = @connection.results
38
+ if results.columns.first.kind_of?(Array)
39
+ (0..results.columns.size-1).each do |index|
40
+ print TableOutput.new(results.columns[index], results.rows[index]).to_s
41
+ end
42
+ else
43
+ if results.columns.size > 0
44
+ print TableOutput.new(results.columns, results.rows).to_s
45
+ else
46
+ print "#{results.affected} rows affected\n"
47
+ end
48
+ end
49
+ end
50
+
51
+ def exec
52
+ @connection.exec @query
53
+ end
54
+
55
+ end
@@ -0,0 +1,50 @@
1
+ class TableOutput
2
+
3
+ def initialize(cols, rows)
4
+ @cols = cols
5
+ @rows = rows
6
+ calc_sizes
7
+ @ascii_encoding = Encoding.find("ASCII-8BIT")
8
+ end
9
+
10
+ def calc_sizes
11
+ @sizes = []
12
+ @cols.each_with_index do |col, index|
13
+ @sizes[index] = col.to_s.length
14
+ end
15
+ @rows.each do |row|
16
+ data_row = []
17
+ row.each_with_index do |value, index|
18
+ # TODO - fix for binary data (timestams)
19
+ if value.class == String && value.encoding.to_s == "ASCII-8BIT"
20
+ value = "***"
21
+ row[index] = value
22
+ end
23
+ size = value.to_s.length
24
+ if @sizes[index] < size
25
+ @sizes[index] = size
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ def to_s
32
+ format = @sizes.map{|s| " %-#{s}s "}.join("|")
33
+ output = []
34
+
35
+ separator = @sizes.map{|s| "-" * (s+2)}.join("+")
36
+ separator = "+#{separator}+"
37
+ output << ""
38
+ output << separator
39
+ output << "|#{format}|" % @cols
40
+ output << separator
41
+ @rows.each do |row|
42
+ output << "|#{format}|" % row
43
+ end
44
+ output << separator
45
+ output << "#{@rows.size} rows affected"
46
+ output << ""
47
+ output.join("\n")
48
+ end
49
+
50
+ end
@@ -0,0 +1,50 @@
1
+ class TableOutput
2
+
3
+ def initialize(cols, rows)
4
+ @cols = cols
5
+ @rows = rows
6
+ calc_sizes
7
+ @ascii_encoding = Encoding.find("ASCII-8BIT")
8
+ end
9
+
10
+ def calc_sizes
11
+ @sizes = []
12
+ @cols.each_with_index do |col, index|
13
+ @sizes[index] = col.to_s.length
14
+ end
15
+ @rows.each do |row|
16
+ data_row = []
17
+ row.each_with_index do |value, index|
18
+ # TODO - fix for binary data (timestams)
19
+ if value.class == String && value.encoding.to_s == "ASCII-8BIT"
20
+ value = "***"
21
+ row[index] = value
22
+ end
23
+ size = value.to_s.length
24
+ if @sizes[index] < size
25
+ @sizes[index] = size
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ def to_s
32
+ format = @sizes.map{|s| " %-#{s}s "}.join("|")
33
+ output = []
34
+
35
+ separator = @sizes.map{|s| "-" * (s+2)}.join("+")
36
+ separator = "+#{separator}+"
37
+ output << ""
38
+ output << separator
39
+ output << "|#{format}|" % @cols
40
+ output << separator
41
+ @rows.each do |row|
42
+ output << "|#{format}|" % row
43
+ end
44
+ output << separator
45
+ output << "#{@rows.size} rows affected"
46
+ output << ""
47
+ output.join("\n")
48
+ end
49
+
50
+ end