mssql 0.0.2

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.
@@ -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