sbfaulkner-rsql 0.9.4

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