sbfaulkner-rsql 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README +13 -0
  3. data/Rakefile +11 -0
  4. data/bin/rsql +89 -0
  5. data/lib/rsql/odbc.rb +103 -0
  6. data/lib/rsql/rsql.rb +203 -0
  7. data/rsql.gemspec +20 -0
  8. metadata +59 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 unwwwired.net
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,13 @@
1
+ rsql
2
+ ===========
3
+
4
+ Command-line access to ODBC datasources.
5
+
6
+
7
+ Example
8
+ =======
9
+
10
+ $ rsql mydsn -u user -p password
11
+
12
+
13
+ Copyright (c) 2008 unwwwired.net, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ Gem::manage_gems
3
+ require 'rake/gempackagetask'
4
+
5
+ load File.join(File.dirname(__FILE__),'rsql.gemspec')
6
+
7
+ Rake::GemPackageTask.new(SPEC) do |pkg|
8
+ pkg.need_tar = true
9
+ end
10
+
11
+ task :default => :package
data/bin/rsql ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # RSQL Copyright © 2007-2008 unwwwired.net
4
+ # Created by: S. Brent Faulkner (brentf@unwwwired.net) 2007-08-29
5
+ #
6
+
7
+ require 'getoptlong'
8
+ require 'readline'
9
+ include Readline
10
+ require 'shellwords'
11
+ include Shellwords
12
+ require 'singleton'
13
+
14
+ require 'odbc'
15
+
16
+ require 'rsql/odbc'
17
+ require 'rsql/rsql'
18
+
19
+
20
+ module RSQL
21
+ COMMAND = File.basename($0)
22
+
23
+ opts = GetoptLong.new(
24
+ [ "--execute", "-e", GetoptLong::REQUIRED_ARGUMENT ],
25
+ [ "--mode", "-m", GetoptLong::REQUIRED_ARGUMENT ],
26
+ [ "--password", "-p", GetoptLong::REQUIRED_ARGUMENT ],
27
+ [ "--quiet", "-q", GetoptLong::NO_ARGUMENT ],
28
+ [ "--user", "-u", GetoptLong::REQUIRED_ARGUMENT ],
29
+ [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ]
30
+ )
31
+
32
+ opts.each do |opt, arg|
33
+ OPTIONS[opt[/--(.*)/,1].to_sym] = arg
34
+ end
35
+
36
+ # to hide the noise from the ODBC driver
37
+ STDERR.reopen("/dev/null") unless OPTIONS[:verbose]
38
+
39
+ if ARGV.length > 1
40
+ puts "#{COMMAND}: too many arguments"
41
+ exit
42
+ end
43
+
44
+ if OPTIONS[:execute]
45
+ # a command requires a database
46
+ if ARGV.length < 1
47
+ puts "#{COMMAND}: no database specified"
48
+ exit
49
+ end
50
+ # force quiet-mode if command provided
51
+ OPTIONS[:quiet] = true
52
+ end
53
+
54
+ # set format for output
55
+ begin
56
+ ODBC::Statement.mode = OPTIONS[:mode]
57
+ rescue => e
58
+ puts "#{COMMAND}: #{e}"
59
+ exit
60
+ end
61
+
62
+ # quiet-mode wins over verbose-mode
63
+ OPTIONS.delete :verbose if OPTIONS[:quiet]
64
+
65
+ puts "#{COMMAND} v0.9.4 - Copyright (c) 2007-2008 unwwwired.net" unless OPTIONS[:quiet]
66
+
67
+ begin
68
+ # use dsn if provided
69
+ begin
70
+ RSQL.instance.use(ARGV[0]) if ARGV.length > 0
71
+ rescue ODBC::Error => error
72
+ puts "ERROR: #{error}"
73
+ end
74
+
75
+ if OPTIONS[:execute]
76
+ RSQL.execute OPTIONS[:execute]
77
+ else
78
+ # get (and process) each command line in turn
79
+ while command = readline("#{COMMAND}> ", true)
80
+ RSQL.execute command
81
+ end
82
+ end
83
+ rescue SystemExit
84
+ puts "Bye" unless OPTIONS[:quiet]
85
+ rescue => exception
86
+ puts %Q(INTERNAL ERROR: #{exception}\n#{exception.backtrace.join("\n")})
87
+ end
88
+
89
+ end
data/lib/rsql/odbc.rb ADDED
@@ -0,0 +1,103 @@
1
+ # extend ruby-odbc for printing result sets
2
+
3
+ module ODBC
4
+
5
+ class Column
6
+ def alias
7
+ @alias ||= name
8
+ end
9
+ def alias=(value)
10
+ @alias = value
11
+ end
12
+ def index
13
+ @index ||= -1
14
+ end
15
+ def index=(value)
16
+ @index = value
17
+ end
18
+ def width
19
+ @width ||= self.alias.length
20
+ end
21
+ def width=(value)
22
+ @width = value
23
+ end
24
+ end
25
+
26
+ class Statement
27
+ class << self
28
+ def mode
29
+ @mode ||= 'column'
30
+ end
31
+
32
+ def mode=(value)
33
+ value = value.to_s
34
+ Kernel.raise ArgumentError, "mode should be either 'column' or 'csv'" unless %w(column csv).include?(value)
35
+ @mode = value
36
+ end
37
+ end
38
+
39
+ def print(aliases = nil)
40
+
41
+ # get the column names, etc.
42
+ displayed_columns = columns(true)
43
+
44
+ # set column indices
45
+ displayed_columns.each_with_index do |c,i|
46
+ c.index = i
47
+ end
48
+
49
+ # set column aliases (if provided)
50
+ if aliases
51
+ displayed_columns.collect! do |c|
52
+ c if c.alias = aliases[c.name.to_sym]
53
+ end.compact!
54
+ end
55
+
56
+ # fetch the data
57
+ # TODO: handle huge result sets better... maybe paginate or base column width on initial n-record set
58
+ resultset = fetch_all
59
+
60
+ case self.class.mode
61
+ when 'column'
62
+ # determine the column widths (might be realy slow with large result sets)
63
+ resultset.each do |r|
64
+ displayed_columns.each do |c|
65
+ if value = r[c.index]
66
+ value = value.to_s
67
+ c.width = value.length if value.length > c.width
68
+ end
69
+ end
70
+ end unless resultset.nil?
71
+
72
+ # prepare the horizontal rule for header and footer
73
+ rule = "+-" + displayed_columns.collect { |c| '-' * c.width }.join("-+-") + "-+"
74
+ # output header
75
+ puts rule
76
+ puts "| " + displayed_columns.collect { |c| c.alias.ljust(c.width,' ') }.join(" | ") + " |"
77
+ puts rule
78
+
79
+ # output each row
80
+ resultset.each do |r|
81
+ puts "| " + displayed_columns.collect { |c| r[c.index].to_s.ljust(c.width, ' ') }.join(" | ") + " |"
82
+ end unless resultset.nil?
83
+
84
+ # output footer
85
+ puts rule
86
+ when 'csv'
87
+ # output header
88
+ puts displayed_columns.collect { |c| c.alias.inspect }.join(",")
89
+ # output each row
90
+ resultset.each do |r|
91
+ # TODO: use FasterCSV to emit the data, since this does escape the text, but not in a manner that can be consumed by FasterCSV
92
+ puts displayed_columns.collect { |c| r[c.index].to_s.inspect }.join(",")
93
+ end unless resultset.nil?
94
+ end
95
+ end
96
+ end
97
+
98
+ class TimeStamp
99
+ def to_s
100
+ "%04d-%02d-%02d %02d:%02d:%02d.%03u" % [ year, month, day, hour, minute, second, fraction ]
101
+ end
102
+ end
103
+ end
data/lib/rsql/rsql.rb ADDED
@@ -0,0 +1,203 @@
1
+ module RSQL
2
+ OPTIONS = { :mode => 'column', :password => '', :user => ENV['USER'] }
3
+
4
+ class RSQL < Object
5
+ include Singleton
6
+
7
+ class << self
8
+ def execute(command)
9
+ # split command into POSIX tokens
10
+ if args = shellwords(command)
11
+ # extract the actual command name
12
+ method_name = args.shift
13
+ # make sure there was a command
14
+ unless method_name.nil?
15
+ # if the command name corresponds to a method
16
+ if instance.respond_to?(method_name)
17
+ # invoke it
18
+ instance.send(method_name, *args)
19
+ else
20
+ # otherwise, hand it off to ODBC
21
+ instance.send :execute, command
22
+ end
23
+ end
24
+ end
25
+ rescue => error
26
+ puts "ERROR: #{error}"
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def initialize
33
+ @dsn = nil
34
+ @database = nil
35
+ end
36
+
37
+ def execute(command)
38
+ unless @database.nil?
39
+ begin
40
+ @database.run(command) do |result|
41
+ begin
42
+ result.print
43
+ rescue
44
+ raise
45
+ ensure
46
+ result.drop
47
+ end
48
+ end
49
+ rescue
50
+ raise
51
+ end
52
+ else
53
+ puts "No dsn selected"
54
+ end
55
+ end
56
+
57
+ public
58
+
59
+ def commit
60
+ unless @database.nil?
61
+ begin
62
+ @database.commit
63
+ rescue
64
+ puts "ERROR: Commit failed"
65
+ raise
66
+ end
67
+ else
68
+ puts "No dsn selected"
69
+ end
70
+ end
71
+
72
+ def describe(table, column = nil)
73
+ unless @database.nil?
74
+ begin
75
+ if column
76
+ statement = @database.columns(table, column)
77
+ else
78
+ statement = @database.columns(table)
79
+ end
80
+ rescue
81
+ raise
82
+ else
83
+ begin
84
+ statement.print :COLUMN_NAME => 'Field', :TYPE_NAME => 'Type', :COLUMN_SIZE => 'Size', :IS_NULLABLE => 'Null', :COLUMN_DEF => 'Default'
85
+ rescue
86
+ raise
87
+ ensure
88
+ statement.drop
89
+ end
90
+ end
91
+ else
92
+ puts "No dsn selected"
93
+ end
94
+ end
95
+
96
+ def help
97
+ puts "COMMIT"
98
+ puts "DESCRIBE table"
99
+ puts "HELP"
100
+ puts "QUIT"
101
+ puts "SET AUTOCOMMIT=value"
102
+ puts "SHOW TABLES [LIKE pattern]"
103
+ puts "USE dsn"
104
+ end
105
+
106
+ def quit
107
+ exit
108
+ end
109
+
110
+ def set(expression)
111
+ unless @database.nil?
112
+ matches = /([a-z]*)=(.*)/.match(expression)
113
+ if matches
114
+ variable,value = matches[1,2]
115
+ case variable.downcase
116
+ when "autocommit"
117
+ begin
118
+ @database.autocommit = (1.coerce(value)[0] != 0)
119
+ rescue ArgumentError
120
+ raise(StandardError, "Variable '#{variable}' can't be set to the value of '#{value}'")
121
+ else
122
+ puts "autocommit set to #{@database.autocommit}"
123
+ end
124
+ else
125
+ raise(StandardError, "Unknown system variable '#{variable}'")
126
+ end
127
+ else
128
+ raise(StandardError, "Syntax error at '#{expression}'")
129
+ end
130
+ else
131
+ puts "No dsn selected"
132
+ end
133
+ end
134
+
135
+ def show(what, *args)
136
+ unless @database.nil?
137
+ case what.downcase
138
+ when 'tables'
139
+ source = @dsn
140
+ begin
141
+ if opt = args.shift
142
+ if opt.downcase == 'like'
143
+ if pattern = args.shift
144
+ statement = @database.tables(pattern)
145
+ source += " (#{pattern})"
146
+ else
147
+ raise(StandardError, "Missing pattern after '#{opt}'")
148
+ end
149
+ else
150
+ raise(StandardError, "Syntax error at '#{opt}'")
151
+ end
152
+ else
153
+ statement = @database.tables
154
+ end
155
+ rescue
156
+ raise
157
+ else
158
+ unless statement.nil?
159
+ begin
160
+ statement.print :TABLE_NAME => "Tables_in_#{source}"
161
+ rescue
162
+ raise
163
+ ensure
164
+ statement.drop
165
+ end
166
+ else
167
+ puts "No tables to show"
168
+ end
169
+ end
170
+ else
171
+ raise(StandardError, "Syntax error at '#{what}'")
172
+ end
173
+ else
174
+ puts "No dsn selected"
175
+ end
176
+ end
177
+
178
+ def use(dsn)
179
+ begin
180
+ unless @database.nil?
181
+ begin
182
+ if @database.connected?
183
+ # in case of pending transaction
184
+ @database.rollback unless @database.autocommit
185
+ @database.disconnect
186
+ end
187
+ rescue
188
+ end
189
+ end
190
+ @dsn = dsn
191
+ @database = ODBC.connect(@dsn, OPTIONS[:user], OPTIONS[:password])
192
+ rescue
193
+ puts "ERROR: Unable to connect to DSN '#{@dsn}' as user '#{OPTIONS[:user]}'"
194
+ @database = nil
195
+ @dsn = nil
196
+ raise
197
+ else
198
+ puts "Database changed" unless OPTIONS[:quiet]
199
+ end
200
+ end
201
+
202
+ end
203
+ end
data/rsql.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ SPEC = Gem::Specification.new do |s|
2
+ # identify the gem
3
+ s.name = "rsql"
4
+ s.version = "0.9.4"
5
+ s.author = "S. Brent Faulkner"
6
+ s.email = "brentf@unwwwired.net"
7
+ s.homepage = "http://www.unwwwired.net"
8
+ # platform of choice
9
+ s.platform = Gem::Platform::RUBY
10
+ # description of gem
11
+ s.summary = "A ruby implementation of an interactive SQL command-line for ODBC"
12
+ s.files = %w(bin/rsql lib/rsql/odbc.rb lib/rsql/rsql.rb MIT-LICENSE Rakefile README rsql.gemspec)
13
+ s.require_path = "lib"
14
+ # s.autorequire = "rsql"
15
+ # s.test_file = "test/rsql.rb"
16
+ s.has_rdoc = true
17
+ s.extra_rdoc_files = ["README"]
18
+ # s.add_dependency("BlueCloth", ">= 0.0.4")
19
+ s.executables = ["rsql"]
20
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sbfaulkner-rsql
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.4
5
+ platform: ruby
6
+ authors:
7
+ - S. Brent Faulkner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-25 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: brentf@unwwwired.net
18
+ executables:
19
+ - rsql
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - bin/rsql
26
+ - lib/rsql/odbc.rb
27
+ - lib/rsql/rsql.rb
28
+ - MIT-LICENSE
29
+ - Rakefile
30
+ - README
31
+ - rsql.gemspec
32
+ has_rdoc: true
33
+ homepage: http://www.unwwwired.net
34
+ post_install_message:
35
+ rdoc_options: []
36
+
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.2.0
55
+ signing_key:
56
+ specification_version: 2
57
+ summary: A ruby implementation of an interactive SQL command-line for ODBC
58
+ test_files: []
59
+