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.
- data/MIT-LICENSE +20 -0
- data/README +13 -0
- data/Rakefile +11 -0
- data/bin/rsql +89 -0
- data/lib/rsql/odbc.rb +103 -0
- data/lib/rsql/rsql.rb +203 -0
- data/rsql.gemspec +20 -0
- 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
data/Rakefile
ADDED
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
|
+
|