cetusql 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/cetusql.gemspec +34 -0
- data/exe/cetussql +975 -0
- data/lib/cetusql.rb +5 -0
- data/lib/cetusql/cli_sqlite.rb +285 -0
- data/lib/cetusql/cli_utils.rb +412 -0
- data/lib/cetusql/version.rb +3 -0
- metadata +106 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 40c861766945ebcd5aeb412cff9788e0cf0a6292
|
4
|
+
data.tar.gz: 960a09544ab79c76a9718972240b5b470e2e6d91
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 241637ec8ca55d51a70060827a87694550ba44d9e48a5397a807af399e20b9f264a77bbd770f77aceced890ff2f6dfe6994a3ebc404a19cc7b19a03dac48d95b
|
7
|
+
data.tar.gz: c56e7c267813b5a71578dcb7dd16a164cf6825cae3b6253199bc574036de38c1b8959d19eb84cbedcffb053179be5f04b1286ddb1e446347e9f74365b8580063
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 rkumar
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Cetusql
|
2
|
+
|
3
|
+
Command-line based sqlite3 database explorer. The idea is to minimize typing of basic queries/tablenames and column names by allowing for selection of the same.
|
4
|
+
|
5
|
+
|
6
|
+
Some basic features:
|
7
|
+
|
8
|
+
- Lists databases in current folder and allows menu for selection.
|
9
|
+
- selection of tablenames from list, and of columns names and creates formatted output to a local temp file which
|
10
|
+
is then displayed.
|
11
|
+
- bookmark/save common SQL queries and recall later.
|
12
|
+
|
13
|
+
Uses sqlite3 gem, readline for editing. In the case of columns, uses `fzf` for selection.
|
14
|
+
|
15
|
+
Also, uses 'term-table.rb' for formatting of output. This needs to be placed in your path as an executable.
|
16
|
+
|
17
|
+
You may replace that with your own formatter such as `csvlook` or `column -t`. However, these two crash on non-ascii characters on a Mac, so I've written a ruby replacement.
|
18
|
+
You can forgo it entirely and just have comma or tab separated output sent to a file.
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
$ gem install cetusql
|
23
|
+
$ brew install fzf
|
24
|
+
$ brew install readline
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
run `cetusql` on the command line.
|
29
|
+
|
30
|
+
If no database name is passed, will prompt for one from *.db and *.sqlite files in current directory.
|
31
|
+
After selection, one may select one or more tables from a list.
|
32
|
+
Then one may select one ore more columns from a list.
|
33
|
+
Edit the SQL statement if need be, and press ENTER.
|
34
|
+
The output is displayed in vim using a temp file.
|
35
|
+
|
36
|
+
Use the menu to change table or database file, or set other options, save last SQL query, select from favorite queries, etc.
|
37
|
+
|
38
|
+
## Development
|
39
|
+
|
40
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
41
|
+
|
42
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rkumar/cetusql.
|
47
|
+
|
48
|
+
|
49
|
+
## License
|
50
|
+
|
51
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
52
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "cetusql"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/cetusql.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
#require 'cetusql/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cetusql"
|
8
|
+
spec.version = "0.0.1"
|
9
|
+
spec.authors = ["Rahul Kumar"]
|
10
|
+
spec.email = ["sentinel1879@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{command line based sqlite db navigator}
|
13
|
+
spec.description = %q{command line based sqlite db navigator. ruby 1.9.3 .. 2.4.}
|
14
|
+
spec.homepage = "https://github.com/rkumar/cetusql"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
#spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
else
|
22
|
+
#raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
# http://bundler.io/blog/2015/03/20/moving-bins-to-exe.html
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
32
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
33
|
+
spec.add_runtime_dependency 'sqlite3', '~> 1.3', '>= 1.3.11'
|
34
|
+
end
|
data/exe/cetussql
ADDED
@@ -0,0 +1,975 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# ----------------------------------------------------------------------------- #
|
3
|
+
# File: cetusql
|
4
|
+
# Description: Fast database navigator for sqlite
|
5
|
+
# Author: rkumar
|
6
|
+
# Date: 2013-02-17 - 17:48
|
7
|
+
# License: MIT
|
8
|
+
# Last update: 2017-04-04 17:09
|
9
|
+
# ----------------------------------------------------------------------------- #
|
10
|
+
# cetussql Copyright (C) 2012-2017 rahul kumar
|
11
|
+
# == TODO
|
12
|
+
# - when in sql menu keep there, the menu of SQL is visible, it looks like we
|
13
|
+
# are still there.
|
14
|
+
# + toggle_menu - formatting, columns headers, no-history, no-indexinfo etc
|
15
|
+
# - try to get table name or query name in temp file name
|
16
|
+
# - if in db_menu key is unknown then try global menu and remove some options from other menus
|
17
|
+
# - reduce db_menu some options useless unless sql fired. put under SQL menu "s"
|
18
|
+
# - store history in another file, per database, not one file, getting too large.
|
19
|
+
# - the menus and hotkeys suck , need to be fixed.
|
20
|
+
# - define joins, so they can be reused
|
21
|
+
# + view_schema
|
22
|
+
# + make_query
|
23
|
+
# + menu keeps repeating don't show, can press ENTER or ` to view it
|
24
|
+
# -
|
25
|
+
# + order by is stored, so picked up against wrong database
|
26
|
+
# + if headers requested then we have to add them.
|
27
|
+
# last_sql must be for current database not just any. save under this database and retrieve under this database
|
28
|
+
# - up arrow on table menu should call edit_last_sql
|
29
|
+
# ? don't redraw the menu. the loop is outside the menu. keep it inside menu() so that it is not redrawn
|
30
|
+
# but exits upon q. what if user does not want that ?
|
31
|
+
#
|
32
|
+
# options : unformatted with tabs output - huge outputs take time
|
33
|
+
# STDOUT or file. specify output to a file
|
34
|
+
# header off and on
|
35
|
+
#
|
36
|
+
#
|
37
|
+
# == END TODO
|
38
|
+
require 'readline'
|
39
|
+
require 'io/wait' # may not need this
|
40
|
+
# http://www.ruby-doc.org/stdlib-1.9.3/libdoc/shellwords/rdoc/Shellwords.html
|
41
|
+
require 'shellwords'
|
42
|
+
require 'yaml'
|
43
|
+
#require 'fileutils'
|
44
|
+
# in ~/work/projects/common/
|
45
|
+
require 'cetusql/cli_utils'
|
46
|
+
require 'cetusql/cli_sqlite'
|
47
|
+
# -- requires 1.9.3 or higher for io/wait
|
48
|
+
# -- cannot do with Highline since we need a timeout on wait, not sure if HL can do that
|
49
|
+
|
50
|
+
VERSION="0.1.0"
|
51
|
+
O_CONFIG=true
|
52
|
+
CONFIG_FILE=File.expand_path("~/.cetusqlinfo")
|
53
|
+
# Using this to store data that has to be used in actions which do not take parameters or return values
|
54
|
+
# when called from a key
|
55
|
+
$options = {}
|
56
|
+
$options[:headers] = true
|
57
|
+
$options[:output_to] = nil
|
58
|
+
$options[:formatting] = true
|
59
|
+
|
60
|
+
$bindings = {}
|
61
|
+
$bindings[:db] = {
|
62
|
+
:t => :select_table,
|
63
|
+
:s => :sql_menu,
|
64
|
+
:m => :multi_select_table,
|
65
|
+
:l => :list_tables,
|
66
|
+
:q => :quit
|
67
|
+
}
|
68
|
+
$bindings[:table] = {
|
69
|
+
:v => :view_menu,
|
70
|
+
"4".to_sym => :select_columns,
|
71
|
+
"5".to_sym => :select_orderby,
|
72
|
+
"6".to_sym => :select_where,
|
73
|
+
"R".to_sym => :run_query,
|
74
|
+
"0".to_sym => :make_query,
|
75
|
+
:s => :save_sql,
|
76
|
+
:x => :new_query, # DDL no result enter a query and execute
|
77
|
+
:h => :history_menu, # queries for this table
|
78
|
+
:e => :edit_last_sql,
|
79
|
+
:O => :output_to,
|
80
|
+
:d => :change_database,
|
81
|
+
:j => :define_join,
|
82
|
+
"UP" => :edit_last_sql,
|
83
|
+
:q => :quit
|
84
|
+
}
|
85
|
+
$old_bindings = {
|
86
|
+
"`" => "main_menu",
|
87
|
+
"=" => "toggle_menu",
|
88
|
+
"!" => "command_mode",
|
89
|
+
"@" => "selection_mode_toggle",
|
90
|
+
"M-a" => "select_all",
|
91
|
+
"M-A" => "unselect_all",
|
92
|
+
"," => "goto_parent_dir",
|
93
|
+
"+" => "goto_dir",
|
94
|
+
"." => "pop_dir",
|
95
|
+
":" => "subcommand",
|
96
|
+
"'" => "goto_bookmark",
|
97
|
+
"/" => "enter_regex",
|
98
|
+
"M-p" => "prev_page",
|
99
|
+
"M-n" => "next_page",
|
100
|
+
"SPACE" => "next_page",
|
101
|
+
"M-f" => "select_visited_files",
|
102
|
+
"M-d" => "select_used_dirs",
|
103
|
+
"M-b" => "select_bookmarks",
|
104
|
+
"M-m" => "create_bookmark",
|
105
|
+
"M-M" => "show_marks",
|
106
|
+
"C-c" => "escape",
|
107
|
+
"ESCAPE" => "escape",
|
108
|
+
"TAB" => "views",
|
109
|
+
"C-i" => "views",
|
110
|
+
"?" => "dirtree",
|
111
|
+
"ENTER" => "select_current",
|
112
|
+
"D" => "delete_file",
|
113
|
+
"M" => "file_actions most",
|
114
|
+
"Q" => "quit_command",
|
115
|
+
"RIGHT" => "column_next",
|
116
|
+
"LEFT" => "column_next 1",
|
117
|
+
"C-x" => "file_actions",
|
118
|
+
"M--" => "columns_incdec -1",
|
119
|
+
"M-+" => "columns_incdec 1",
|
120
|
+
"S" => "command_file list y ls -lh",
|
121
|
+
"L" => "command_file Page n less",
|
122
|
+
"C-d" => "cursor_scroll_dn",
|
123
|
+
"C-b" => "cursor_scroll_up",
|
124
|
+
"UP" => "cursor_up",
|
125
|
+
"DOWN" => "cursor_dn",
|
126
|
+
"C-SPACE" => "visual_mode_toggle",
|
127
|
+
|
128
|
+
"M-?" => "print_help",
|
129
|
+
"F1" => "print_help",
|
130
|
+
"F2" => "child_dirs",
|
131
|
+
"F3" => "dirtree",
|
132
|
+
"F4" => "tree",
|
133
|
+
"S-F1" => "dirtree",
|
134
|
+
"S-F2" => "tree"
|
135
|
+
|
136
|
+
}
|
137
|
+
|
138
|
+
|
139
|
+
$mode = nil
|
140
|
+
$glines=%x(tput lines).to_i
|
141
|
+
$gcols=%x(tput cols).to_i
|
142
|
+
$grows = $glines - 3
|
143
|
+
$pagesize = 60
|
144
|
+
$gviscols = 3
|
145
|
+
$pagesize = $grows * $gviscols
|
146
|
+
MSCROLL = 10
|
147
|
+
$quitting = false
|
148
|
+
$modified = $writing = false
|
149
|
+
|
150
|
+
#$help = "#{BOLD}M-?#{BOLD_OFF} Help #{BOLD}`#{BOLD_OFF} Menu #{BOLD}!#{BOLD_OFF} Command #{BOLD}=#{BOLD_OFF} Toggle #{BOLD}q#{BOLD_OFF} Quit "
|
151
|
+
$help = "#{BOLD}`#{BOLD_OFF} Menu #{BOLD}=#{BOLD_OFF} Toggle #{BOLD}q#{BOLD_OFF} Quit "
|
152
|
+
|
153
|
+
db = nil
|
154
|
+
|
155
|
+
## main loop which calls all other programs
|
156
|
+
def run()
|
157
|
+
ctr=0
|
158
|
+
filename, db, tables = change_database ARGV[0]
|
159
|
+
#filename = ARGV[0] || getdbname
|
160
|
+
#if filename
|
161
|
+
#db = Database.new filename
|
162
|
+
#tables = db.tables
|
163
|
+
#end
|
164
|
+
exit unless db
|
165
|
+
$mode = :db
|
166
|
+
# TODO populate readline with earlier SQLS for this database
|
167
|
+
prompt = ""
|
168
|
+
ch, text = db_menu
|
169
|
+
process_number ch
|
170
|
+
|
171
|
+
|
172
|
+
while true
|
173
|
+
i = 0
|
174
|
+
# title
|
175
|
+
filename = $g_data[:filename]
|
176
|
+
db = $g_data[:db]
|
177
|
+
#print "#{GREEN}#{$help} #{BLUE}cetusql #{VERSION} :#{$mode}#{CLEAR}\n"
|
178
|
+
#t = "#{$title}"
|
179
|
+
#t = t[t.size-$gcols..-1] if t.size >= $gcols
|
180
|
+
#print "#{BOLD}#{t}#{CLEAR}\n"
|
181
|
+
# another query , change table, save sql, change db, select multiple tables
|
182
|
+
# schema, count, sample
|
183
|
+
# ZZZ XXX
|
184
|
+
#print "\r#{_mm}#{$patt} >"
|
185
|
+
ch = text = nil
|
186
|
+
#print "\r:#{$mode} >"
|
187
|
+
print "\r:%-5s >" % [$mode]
|
188
|
+
ch = get_char
|
189
|
+
print ch
|
190
|
+
# TODO
|
191
|
+
# depending on mode, we display menu if tilde pressed, or we process based on tilde
|
192
|
+
ch, text = process_key ch
|
193
|
+
puts ch if $opt_debug
|
194
|
+
if ch == "`" or ch == "ENTER"
|
195
|
+
if $mode == :table
|
196
|
+
display_menu "Menu for #{$mode} #{current_tablename}", $bindings[$mode]
|
197
|
+
else
|
198
|
+
display_menu "Menu for #{$mode} #{current_dbname}", $bindings[$mode]
|
199
|
+
end
|
200
|
+
elsif ch == '='
|
201
|
+
toggle_menu
|
202
|
+
end
|
203
|
+
if ch == "q" or ch == "ESCAPE" or ch == "C-c"
|
204
|
+
if $mode == :table
|
205
|
+
$mode = :db
|
206
|
+
$g_data[:current_tablename] = nil
|
207
|
+
else
|
208
|
+
break
|
209
|
+
end
|
210
|
+
end
|
211
|
+
#puts
|
212
|
+
#break if ch == "q" or ch == "ESCAPE" or ch == "C-c"
|
213
|
+
end
|
214
|
+
puts "bye"
|
215
|
+
$writing = true
|
216
|
+
config_write if $writing
|
217
|
+
end
|
218
|
+
def process_key ch
|
219
|
+
# get correct action map
|
220
|
+
h = $bindings[$mode] || $bindings[:db]
|
221
|
+
binding = h[ch]
|
222
|
+
binding = h[ch.to_sym] unless binding
|
223
|
+
if binding
|
224
|
+
if respond_to?(binding, true)
|
225
|
+
# 2017-03-19 - we can't send return values from a method ??
|
226
|
+
send(binding)
|
227
|
+
end
|
228
|
+
else
|
229
|
+
if $mode == :db
|
230
|
+
process_number ch
|
231
|
+
end
|
232
|
+
end
|
233
|
+
return ch, binding
|
234
|
+
end
|
235
|
+
def config_write
|
236
|
+
$g_data[:db] = nil
|
237
|
+
$g_data[:tables] = nil
|
238
|
+
writeYML $g_data, CONFIG_FILE
|
239
|
+
end
|
240
|
+
def config_read
|
241
|
+
if File.exist? CONFIG_FILE
|
242
|
+
$g_data = loadYML(CONFIG_FILE)
|
243
|
+
else
|
244
|
+
$g_data = {}
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def loadYML( filename)
|
249
|
+
hash = YAML::load( File.open( filename ) )
|
250
|
+
#puts hash.keys.size
|
251
|
+
return hash
|
252
|
+
end
|
253
|
+
|
254
|
+
require 'fileutils' # for mkdir_p
|
255
|
+
def writeYML obj, filename
|
256
|
+
dir = File.dirname filename
|
257
|
+
unless File.exist? dir
|
258
|
+
FileUtils::mkdir_p dir
|
259
|
+
end
|
260
|
+
File.open(filename, 'w') {|f| f.write obj.to_yaml }
|
261
|
+
#$stderr.puts color("==> written to #{filename}", "green", "bold") unless $opt_quiet
|
262
|
+
$stderr.puts("==> written to #{filename}") unless $opt_quiet
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
def sql_menu
|
267
|
+
# check if there is an sql that has been fired, then save_sql and edit_last
|
268
|
+
# if table selected then select columns, order_by where etc
|
269
|
+
h = {
|
270
|
+
:s => :save_sql,
|
271
|
+
:h => :sql_history, # databases accessed, tables accessed, sqls issued
|
272
|
+
:e => :edit_last_sql,
|
273
|
+
:O => :output_to,
|
274
|
+
:F => :formatting_toggle,
|
275
|
+
:q => :quit
|
276
|
+
}
|
277
|
+
menu "SQL Menu for #{current_dbname}", h
|
278
|
+
end
|
279
|
+
def view_menu
|
280
|
+
h = {
|
281
|
+
:a => :view_all_rows,
|
282
|
+
:s => :view_sample,
|
283
|
+
:r => :view_recent,
|
284
|
+
:c => :view_schema,
|
285
|
+
:q => :quit
|
286
|
+
}
|
287
|
+
menu "View Menu for #{current_tablename}", h
|
288
|
+
end
|
289
|
+
def db_menu
|
290
|
+
h = {
|
291
|
+
:t => :select_table,
|
292
|
+
:m => :multi_select_table,
|
293
|
+
:l => :list_tables,
|
294
|
+
:s => :sql_menu,
|
295
|
+
#:d => :change_database,
|
296
|
+
"UP" => :edit_last_sql,
|
297
|
+
:q => :quit
|
298
|
+
}
|
299
|
+
menu "DB Menu for #{current_dbname}", h
|
300
|
+
end
|
301
|
+
def toggle_menu
|
302
|
+
h = { :f => :formatting_toggle, :h => :toggle_headers, :i => :toggle_index_info , "H" => :toggle_history,
|
303
|
+
:p => :toggle_pager_mode}
|
304
|
+
ch, menu_text = menu "Toggle Menu", h
|
305
|
+
case menu_text
|
306
|
+
when :toggle_headers
|
307
|
+
$options[:headers] = !$options[:headers]
|
308
|
+
pgreen "headers is #{$options[:headers]}"
|
309
|
+
when :toggle_index_info
|
310
|
+
# should index info be printed along with columsn, may take time if many tables
|
311
|
+
$options[:index_info] = !$options[:index_info]
|
312
|
+
pgreen "index_info calculation is #{$options[:index_info]}"
|
313
|
+
when :toggle_history
|
314
|
+
$options[:history_save] = !$options[:history_save]
|
315
|
+
pgreen "Saving SQL history is #{$options[:history_save]}"
|
316
|
+
when :toggle_pager_mode
|
317
|
+
$editor_mode = !$editor_mode
|
318
|
+
if $editor_mode
|
319
|
+
$default_command = nil
|
320
|
+
else
|
321
|
+
$default_command = ENV['MANPAGER'] || ENV['PAGER']
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
def process_number ch
|
326
|
+
# this is done in :db mode, to quick select a table, not in table mode since numbers are used
|
327
|
+
# if number is < 10 then see if there's a table and select that table
|
328
|
+
puts ch if $opt_debug
|
329
|
+
chi = ch.to_i
|
330
|
+
if chi > 0 and chi < 10
|
331
|
+
tables = fetch(:tables)
|
332
|
+
tablename = tables[chi - 1]
|
333
|
+
if tablename
|
334
|
+
pgreen "Selected #{tablename} "
|
335
|
+
select_table(tablename)
|
336
|
+
display_menu "Menu for #{tablename}", $bindings[$mode]
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
def table_menu tablename=nil
|
341
|
+
t = tablename || current_tablename()
|
342
|
+
h = {
|
343
|
+
"2".to_sym => :view_all_rows,
|
344
|
+
"3".to_sym => :view_sample,
|
345
|
+
"9".to_sym => :view_recent,
|
346
|
+
"4".to_sym => :select_columns,
|
347
|
+
"5".to_sym => :select_orderby,
|
348
|
+
"6".to_sym => :select_where,
|
349
|
+
"R".to_sym => :run_query,
|
350
|
+
"0".to_sym => :make_query,
|
351
|
+
:s => :save_sql,
|
352
|
+
:x => :new_query, # DDL no result enter a query and execute
|
353
|
+
:h => :history_menu, # queries for this table
|
354
|
+
:c => :view_schema,
|
355
|
+
:e => :edit_last_sql,
|
356
|
+
:O => :output_to,
|
357
|
+
:d => :change_database,
|
358
|
+
:j => :define_join,
|
359
|
+
"UP" => :edit_last_sql,
|
360
|
+
:q => :quit
|
361
|
+
}
|
362
|
+
menu "Table Menu for #{t}", h
|
363
|
+
end
|
364
|
+
|
365
|
+
# TODO
|
366
|
+
# 2017-03-27 - construct a query and run it.
|
367
|
+
# actually it should give a submenu
|
368
|
+
def make_query
|
369
|
+
# 1. select table if nil
|
370
|
+
# 2 select columns for display
|
371
|
+
# 3. select order
|
372
|
+
# 4. select where
|
373
|
+
# 5. select LIMIT
|
374
|
+
# 6. edit and run
|
375
|
+
tablename = current_tablename()
|
376
|
+
unless tablename
|
377
|
+
tablename = select_table
|
378
|
+
end
|
379
|
+
return unless tablename
|
380
|
+
puts "Select columns"
|
381
|
+
sel_columns = select_columns
|
382
|
+
puts "Select where condition"
|
383
|
+
where_str = select_where
|
384
|
+
puts "Select order by"
|
385
|
+
orderby_cols = select_orderby
|
386
|
+
run_query
|
387
|
+
end
|
388
|
+
def new_query
|
389
|
+
db = current_db()
|
390
|
+
tablename = current_tablename()
|
391
|
+
columns = db.columns(tablename)
|
392
|
+
#sql = "SELECT #{columns.join(",")} FROM #{tablename} ;"
|
393
|
+
sql = "SELECT\n#{columns.join(",\n")} \nFROM #{tablename} \nLIMIT 1000\n ;"
|
394
|
+
puts "created sql" if $opt_debug
|
395
|
+
edit_execute_in_editor sql
|
396
|
+
end
|
397
|
+
def edit_execute_in_editor sql
|
398
|
+
require 'tempfile'
|
399
|
+
tmpfile = Tempfile.new('SQL.XXXXXX')
|
400
|
+
filename = tmpfile.path
|
401
|
+
filename = Shellwords.escape(filename)
|
402
|
+
tmpfile.write(sql)
|
403
|
+
puts "written to #{filename}" if $opt_debug
|
404
|
+
tmpfile.close
|
405
|
+
mtime = File.mtime(filename)
|
406
|
+
puts mtime if $opt_debug
|
407
|
+
command = nil
|
408
|
+
#system "vim #{filename}" and (command = File.open(filename).readlines.join(" ") )
|
409
|
+
system "vim #{filename}"
|
410
|
+
mtime2 = File.mtime(filename)
|
411
|
+
puts mtime2 if $opt_debug
|
412
|
+
# we are comparing modification times of the file to see if user quit or saved.
|
413
|
+
# vim returns a zero in both cases, unless user quits using :cq
|
414
|
+
return if mtime2 == mtime
|
415
|
+
#puts "not returnig"
|
416
|
+
command = File.open(filename).readlines.join(" ")
|
417
|
+
if command
|
418
|
+
command = command.gsub("\n", " ")
|
419
|
+
#puts "got : #{command}" if command
|
420
|
+
puts "..."
|
421
|
+
view_sql command
|
422
|
+
end
|
423
|
+
set_last_sql command
|
424
|
+
end
|
425
|
+
|
426
|
+
def view_schema
|
427
|
+
tablename = current_tablename
|
428
|
+
puts
|
429
|
+
pbold "Schema for #{tablename}"
|
430
|
+
list_metadata tablename
|
431
|
+
indexes_for_table tablename
|
432
|
+
puts
|
433
|
+
end
|
434
|
+
|
435
|
+
def change_database filename=nil
|
436
|
+
# add to g_data as visited_dbs
|
437
|
+
filename = select_database_name unless filename
|
438
|
+
if filename
|
439
|
+
db = Database.new filename
|
440
|
+
tables = db.tables
|
441
|
+
$g_data[:filename] = filename
|
442
|
+
$g_data[:db] = db
|
443
|
+
$mode = :db
|
444
|
+
$g_data[:current_tablename] = nil
|
445
|
+
|
446
|
+
#$g_data[:tables] = tables
|
447
|
+
store(:tables, tables)
|
448
|
+
$g_data[:visited_dbs] ||= []
|
449
|
+
# TODO should be unique. remove if exists, then add
|
450
|
+
$g_data[:visited_dbs].delete(filename)
|
451
|
+
$g_data[:visited_dbs] << filename
|
452
|
+
$g_data[:last_sql] = nil
|
453
|
+
clear_screen
|
454
|
+
puts "#{BLUE}Using #{current_dbname}#{CLEAR}"
|
455
|
+
list_tables
|
456
|
+
list_indexes
|
457
|
+
end
|
458
|
+
return filename, db, tables
|
459
|
+
end
|
460
|
+
# get database names
|
461
|
+
def select_database_name
|
462
|
+
# Add recent ones, but remove directory portion if they belong to current dir
|
463
|
+
choices = Dir['*.db','*.sqlite','*.sqlite3'] | $g_data[:visited_dbs].map {|e| e.sub(Dir.pwd + "/","") }
|
464
|
+
if choices
|
465
|
+
if choices.size == 1
|
466
|
+
return File.expand_path(choices.first)
|
467
|
+
else
|
468
|
+
# select_from is in cli_utils.rb
|
469
|
+
#db = select_from "Select database", choices
|
470
|
+
#db = ctrlp choices
|
471
|
+
db = choose choices
|
472
|
+
return File.expand_path(db) if db
|
473
|
+
end
|
474
|
+
end
|
475
|
+
return nil
|
476
|
+
end
|
477
|
+
#alias :select_database_name :getdbname
|
478
|
+
def multi_select_table
|
479
|
+
db = current_db
|
480
|
+
sel_tables = multi_select "select tables ", db.tables
|
481
|
+
columns = []
|
482
|
+
sel_tables.each do |tablename|
|
483
|
+
c = db.columns tablename
|
484
|
+
c.each {|e| columns << "#{tablename}.#{e}" }
|
485
|
+
end
|
486
|
+
sel_columns = multi_select "select columns ", columns
|
487
|
+
#puts sel_columns.size
|
488
|
+
#puts sel_columns.class
|
489
|
+
sel_columns ||= ['*']
|
490
|
+
sql = "SELECT #{sel_columns.join(",")} FROM #{sel_tables.join(',')} \nWHERE\n LIMIT 1000\n ;"
|
491
|
+
puts sql
|
492
|
+
edit_execute_sql sql
|
493
|
+
# TODO edit it and view result
|
494
|
+
# TODO join based on commond fields, we have that code somewhere
|
495
|
+
|
496
|
+
end
|
497
|
+
def define_join
|
498
|
+
db = current_db
|
499
|
+
tablename = current_tablename()
|
500
|
+
|
501
|
+
sel_table = single_select "select lookup table for join:", db.tables
|
502
|
+
sel_table = sel_table.chomp
|
503
|
+
unless sel_table
|
504
|
+
return
|
505
|
+
end
|
506
|
+
columns = db.columns sel_table
|
507
|
+
sel_columns = multi_select "select columns to join ", columns
|
508
|
+
sel_columns_this = multi_select "select columns from this table ", db.columns( tablename)
|
509
|
+
|
510
|
+
str = tablename + "." + sel_columns_this.first + " = " + sel_table + "." + sel_columns.first
|
511
|
+
puts str
|
512
|
+
ca = sel_columns_this.first
|
513
|
+
cb = sel_columns.first
|
514
|
+
hash_put([:joins, tablename, sel_table, ca, cb])
|
515
|
+
|
516
|
+
#puts sel_columns.size
|
517
|
+
#puts sel_columns.class
|
518
|
+
# TODO edit it and view result
|
519
|
+
# TODO join based on commond fields, we have that code somewhere
|
520
|
+
|
521
|
+
end
|
522
|
+
# for current database create hash entries from a heirarchy, like mkdir -p
|
523
|
+
def hash_put array
|
524
|
+
h = $g_data[:databases][current_dbname()]
|
525
|
+
array.each do |e|
|
526
|
+
h[e] ||= {}
|
527
|
+
h = h[e]
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
# select a table and then puts user into table_menu (that is bad!)
|
532
|
+
def select_table tablename=nil
|
533
|
+
db = $g_data[:db]
|
534
|
+
filename = $g_data[:filename]
|
535
|
+
unless tablename
|
536
|
+
tablename = select_from "Select table from:(#{filename})", db.tables
|
537
|
+
end
|
538
|
+
unless tablename
|
539
|
+
if confirm("Do you wish to quit ? ")
|
540
|
+
$quitting = true
|
541
|
+
return false
|
542
|
+
end
|
543
|
+
return
|
544
|
+
end
|
545
|
+
clear_screen
|
546
|
+
|
547
|
+
$g_data[:current_tablename] = tablename
|
548
|
+
list_metadata tablename
|
549
|
+
indexes_for_table tablename
|
550
|
+
joins_for_table tablename
|
551
|
+
$mode = :table
|
552
|
+
return
|
553
|
+
end
|
554
|
+
# store some values against the current database
|
555
|
+
def store key, value
|
556
|
+
$g_data[:databases][current_dbname()] ||= {}
|
557
|
+
$g_data[:databases][current_dbname()][key] = value
|
558
|
+
#$g_data[key] = value
|
559
|
+
end
|
560
|
+
# retrieve some values against the current database
|
561
|
+
def fetch key
|
562
|
+
$g_data[:databases][current_dbname()] ||= {}
|
563
|
+
$g_data[:databases][current_dbname()][key]
|
564
|
+
#$g_data[key]
|
565
|
+
end
|
566
|
+
def select_columns
|
567
|
+
#puts "inside select_columns"
|
568
|
+
db = current_db()
|
569
|
+
tablename = current_tablename || return
|
570
|
+
columns = db.columns tablename
|
571
|
+
# TODO
|
572
|
+
# display columns with datatypes and indexes on them
|
573
|
+
# count of rows
|
574
|
+
# options for sample, first 100 rows last 100 rows (based on count)
|
575
|
+
# option for selecting column, where, order, RUN, all_cols,
|
576
|
+
sel_columns = multi_select "select column from #{tablename}", columns
|
577
|
+
#puts sel_columns.size
|
578
|
+
#puts sel_columns.class
|
579
|
+
#p sel_columns
|
580
|
+
sel_columns = ['*'] if sel_columns.size == 0
|
581
|
+
# FIXME these should be stored against a specific database and table, otherwise they will cause
|
582
|
+
# errors
|
583
|
+
store ':selected_columns', sel_columns
|
584
|
+
puts fetch(':selected_columns')
|
585
|
+
return sel_columns
|
586
|
+
end
|
587
|
+
def select_orderby
|
588
|
+
db = current_db()
|
589
|
+
tablename = current_tablename || exit(11)
|
590
|
+
columns = db.columns tablename
|
591
|
+
sel_columns = multi_select "select order by from #{tablename}", columns
|
592
|
+
sel_columns = nil if sel_columns.size == 0
|
593
|
+
store ':order_by', sel_columns
|
594
|
+
puts fetch(':order_by')
|
595
|
+
return sel_columns
|
596
|
+
end
|
597
|
+
def select_where
|
598
|
+
db = current_db()
|
599
|
+
tablename = current_tablename || exit(1)
|
600
|
+
columns = db.columns tablename
|
601
|
+
sel_columns = multi_select "select WHERE from #{tablename}", columns
|
602
|
+
sel_columns = nil if sel_columns.size == 0
|
603
|
+
res = nil
|
604
|
+
if sel_columns
|
605
|
+
a = []
|
606
|
+
sel_columns.each do |e|
|
607
|
+
s = input "WHERE #{e} "
|
608
|
+
a << "#{e} #{s}"
|
609
|
+
end
|
610
|
+
res = a.join(" AND ")
|
611
|
+
end
|
612
|
+
store ':where', res
|
613
|
+
puts fetch(':where')
|
614
|
+
return res
|
615
|
+
end
|
616
|
+
def run_query
|
617
|
+
t = current_tablename
|
618
|
+
c = fetch(':selected_columns') || ['*']
|
619
|
+
o = fetch(':order_by')
|
620
|
+
w = fetch(':where')
|
621
|
+
sql = "SELECT #{c.join(', ')} FROM #{t} "
|
622
|
+
if w
|
623
|
+
#sql += " WHERE #{w.join(', ')} "
|
624
|
+
sql += " WHERE #{w} "
|
625
|
+
end
|
626
|
+
if o
|
627
|
+
sql += " ORDER BY #{o.join(', ')} "
|
628
|
+
end
|
629
|
+
edit_execute_in_editor sql
|
630
|
+
#sql = vared sql, "Edit SQL: "
|
631
|
+
#return if sql.nil? or sql.size == 0
|
632
|
+
#set_last_sql sql
|
633
|
+
#view_sql sql
|
634
|
+
end
|
635
|
+
def edit_execute_sql sql
|
636
|
+
command = vared sql, "Edit SQL:"
|
637
|
+
if command.nil? or command.size == 0
|
638
|
+
return false
|
639
|
+
end
|
640
|
+
puts "..."
|
641
|
+
view_sql command
|
642
|
+
set_last_sql command
|
643
|
+
return true
|
644
|
+
end
|
645
|
+
def list_tables
|
646
|
+
db = current_db()
|
647
|
+
tables = current_db.tables
|
648
|
+
puts
|
649
|
+
data = [["#", "Table", "Rows", "Indexed"]]
|
650
|
+
ctr = 1
|
651
|
+
tables.each do |t|
|
652
|
+
#columns, datatypes = db.get_metadata t
|
653
|
+
content = db.get_data "SELECT count(1) from #{t} "
|
654
|
+
rows = content.first.first
|
655
|
+
indexed = '?'
|
656
|
+
if $options[:index_info]
|
657
|
+
indexed = db.indexed_columns(t) || "---"
|
658
|
+
end
|
659
|
+
char = ctr < 10 ? ctr.to_s : ""
|
660
|
+
row = [char, t, rows, indexed]
|
661
|
+
data << row
|
662
|
+
ctr += 1
|
663
|
+
#puts "#{t} #{rows} "
|
664
|
+
end
|
665
|
+
view_array data
|
666
|
+
end
|
667
|
+
# I don't need to loop through tables SILLY !
|
668
|
+
# FIXME just loop without tables
|
669
|
+
def list_indexes
|
670
|
+
db = current_db()
|
671
|
+
data = [["Index", "Table", "Column/s"]]
|
672
|
+
#columns, datatypes = db.get_metadata t
|
673
|
+
sql = %Q{SELECT name, tbl_name, sql FROM sqlite_master WHERE type = "index" ORDER BY tbl_name }
|
674
|
+
content = db.get_data sql
|
675
|
+
return if content.nil? or content == []
|
676
|
+
content.each do |r|
|
677
|
+
if r[-1] != nil
|
678
|
+
m = r[-1].match /\((.*)\)/
|
679
|
+
r[-1] = m[1] if m
|
680
|
+
end
|
681
|
+
row = [ *r ]
|
682
|
+
data << row
|
683
|
+
end
|
684
|
+
view_array data
|
685
|
+
end
|
686
|
+
def indexes_for_table table
|
687
|
+
db = current_db()
|
688
|
+
data = [["Index", "Table", "Column/s"]]
|
689
|
+
sql = %Q{SELECT name, tbl_name, sql FROM sqlite_master WHERE type = "index" and tbl_name = "#{table}" }
|
690
|
+
content = db.get_data sql
|
691
|
+
if content.nil? or content == []
|
692
|
+
pred "No indexes for table #{table}"
|
693
|
+
return
|
694
|
+
end
|
695
|
+
title = "Indexes of table: #{table}"
|
696
|
+
puts "-"*title.size
|
697
|
+
pbold title
|
698
|
+
puts "-"*title.size
|
699
|
+
content.each do |r|
|
700
|
+
if r[-1] != nil
|
701
|
+
m = r[-1].match /\((.*)\)/
|
702
|
+
r[-1] = m[1] if m
|
703
|
+
end
|
704
|
+
row = [ *r ]
|
705
|
+
data << row
|
706
|
+
end
|
707
|
+
view_array data
|
708
|
+
end
|
709
|
+
def joins_for_table tablename
|
710
|
+
arrow = "->"
|
711
|
+
checkmark = "\u2523"
|
712
|
+
checkmark = "\u279C"
|
713
|
+
db = current_db()
|
714
|
+
h = fetch(:joins)
|
715
|
+
return unless h
|
716
|
+
hj = h[tablename]
|
717
|
+
return unless hj
|
718
|
+
#p hj
|
719
|
+
pbold "Joins for table: #{tablename}"
|
720
|
+
hj.each_pair do |k, v|
|
721
|
+
puts " #{tablename} -> #{k} "
|
722
|
+
v.each_pair do |k1, v1|
|
723
|
+
puts " #{checkmark} #{tablename}.#{k1} = #{k}.#{v1.keys.first}"
|
724
|
+
end
|
725
|
+
end
|
726
|
+
end
|
727
|
+
# if too long split and columnate at 10 rows
|
728
|
+
def list_metadata table
|
729
|
+
db = current_db()
|
730
|
+
columns, datatypes = db.get_metadata table
|
731
|
+
array = []
|
732
|
+
title = "Columns of table: #{table}"
|
733
|
+
puts "-"*title.size
|
734
|
+
pbold title
|
735
|
+
puts "-"*title.size
|
736
|
+
|
737
|
+
columns.each_with_index do |e, ix|
|
738
|
+
#print "%-20s %s\n" % [e, datatypes[ix] ]
|
739
|
+
array << " %-20s #{GREEN}%-8s#{CLEAR} " % [e, datatypes[ix] ]
|
740
|
+
end
|
741
|
+
if false
|
742
|
+
array = columnate array, $grows - 7
|
743
|
+
array.each {|line| print line, "\n" }
|
744
|
+
end
|
745
|
+
print_in_cols array
|
746
|
+
#array.to_table
|
747
|
+
|
748
|
+
end
|
749
|
+
def config
|
750
|
+
$g_data[:databases][current_dbname()]
|
751
|
+
end
|
752
|
+
def sql_history
|
753
|
+
dbc = config()
|
754
|
+
hist = dbc[:history]
|
755
|
+
saved = dbc[:saved_sqls]
|
756
|
+
ctr = 1
|
757
|
+
all = []
|
758
|
+
if saved
|
759
|
+
pbold "Saved sqls"
|
760
|
+
saved.each_with_index {|e,i| puts "#{ctr} #{e}"; ctr += 1; }
|
761
|
+
all += saved
|
762
|
+
end
|
763
|
+
if hist
|
764
|
+
pbold "History"
|
765
|
+
hist.each_with_index {|e,i| puts "#{ctr} #{e}"; ctr += 1; }
|
766
|
+
all += hist
|
767
|
+
end
|
768
|
+
print "Select an sql: "
|
769
|
+
choice = $stdin.gets.chomp
|
770
|
+
puts choice
|
771
|
+
return if choice == ""
|
772
|
+
sql = all[choice.to_i - 1]
|
773
|
+
return unless sql
|
774
|
+
sql = vared sql, "Edit sql: "
|
775
|
+
return if sql.nil? or sql.size == 0
|
776
|
+
puts sql
|
777
|
+
view_sql sql
|
778
|
+
end
|
779
|
+
|
780
|
+
# choose from an array. printed vertically no columns, choose number
|
781
|
+
def choose array, title=nil, prompt=': '
|
782
|
+
|
783
|
+
pbold title if title
|
784
|
+
array.each_with_index {|e,i| puts "#{i+1} #{e}" }
|
785
|
+
while true
|
786
|
+
print "> \r (Enter 1 to #{array.size}) "
|
787
|
+
choice = $stdin.gets.chomp
|
788
|
+
if choice == "" or choice == "q"
|
789
|
+
return nil
|
790
|
+
end
|
791
|
+
chi = choice.to_i
|
792
|
+
if chi < 1 or chi > array.size
|
793
|
+
next
|
794
|
+
end
|
795
|
+
break
|
796
|
+
end
|
797
|
+
#puts choice
|
798
|
+
return nil if choice == ""
|
799
|
+
k = array[choice.to_i - 1]
|
800
|
+
return k
|
801
|
+
end
|
802
|
+
CSI = "\e["
|
803
|
+
def OLDchoose array, title=nil, prompt=': '
|
804
|
+
|
805
|
+
# why is save and resutore not working heee
|
806
|
+
$stdout.write "#{CSI}s" # save cursor position
|
807
|
+
while true
|
808
|
+
pbold title if title
|
809
|
+
array.each_with_index {|e,i| puts "#{i+1} #{e}" }
|
810
|
+
print prompt
|
811
|
+
choice = $stdin.gets.chomp
|
812
|
+
chi = choice.to_i
|
813
|
+
puts chi
|
814
|
+
if choice == "" or choice == "q"
|
815
|
+
return nil
|
816
|
+
end
|
817
|
+
if chi < 1 or chi > array.size
|
818
|
+
puts "restore"
|
819
|
+
$stdout.write "#{CSI}u" # restore cursor position
|
820
|
+
next
|
821
|
+
end
|
822
|
+
break
|
823
|
+
end
|
824
|
+
#puts choice
|
825
|
+
return nil if choice == ""
|
826
|
+
k = array[choice.to_i - 1]
|
827
|
+
return k
|
828
|
+
end
|
829
|
+
|
830
|
+
def view_sql sql
|
831
|
+
begin
|
832
|
+
view_data current_db, sql, $options
|
833
|
+
rescue => e
|
834
|
+
puts e.to_s
|
835
|
+
puts e.message
|
836
|
+
#puts e.backtrace.join("\n")
|
837
|
+
end
|
838
|
+
|
839
|
+
end
|
840
|
+
|
841
|
+
|
842
|
+
# send output of following commands to this file
|
843
|
+
def output_to filename=nil
|
844
|
+
unless filename
|
845
|
+
filename = input "Enter output filename: "
|
846
|
+
filename = filename.chomp
|
847
|
+
end
|
848
|
+
filename = nil if filename == ""
|
849
|
+
$options[:output_to] = filename
|
850
|
+
return filename
|
851
|
+
end
|
852
|
+
def formatting_toggle
|
853
|
+
$options[:formatting] = !$options[:formatting]
|
854
|
+
puts "Column formatting is now #{$options[:formatting]} "
|
855
|
+
pause
|
856
|
+
end
|
857
|
+
def current_db
|
858
|
+
$g_data[:db]
|
859
|
+
end
|
860
|
+
def current_dbname
|
861
|
+
$g_data[:filename] || "WARNING NOT SET: :filename"
|
862
|
+
end
|
863
|
+
def current_tablename
|
864
|
+
$g_data[:current_tablename]
|
865
|
+
end
|
866
|
+
# save an sql statement like a bookmark
|
867
|
+
# Would have liked to have a nickname for it, or title.
|
868
|
+
def save_sql sql=nil
|
869
|
+
sql ||= $g_data[:last_sql]
|
870
|
+
$g_data[:databases] ||= {}
|
871
|
+
$g_data[:databases][current_dbname()] ||= {}
|
872
|
+
$g_data[:databases][current_dbname()][:saved_sqls] ||= []
|
873
|
+
$g_data[:databases][current_dbname()][:saved_sqls].delete sql
|
874
|
+
$g_data[:databases][current_dbname()][:saved_sqls] << sql
|
875
|
+
$g_data[:databases][current_dbname()][:last_sql] = sql
|
876
|
+
pgreen "Saved sql statement"
|
877
|
+
$g_data[:last_sql] = nil
|
878
|
+
end
|
879
|
+
def set_last_sql sql
|
880
|
+
$g_data[:last_sql] = sql
|
881
|
+
# set history of sqls issued
|
882
|
+
$g_data[:databases] ||= {}
|
883
|
+
$g_data[:databases][current_dbname()] ||= {}
|
884
|
+
$g_data[:databases][current_dbname()][:last_sql] = sql
|
885
|
+
$g_data[:databases][current_dbname()][:history] ||= []
|
886
|
+
$g_data[:databases][current_dbname()][:history].delete sql
|
887
|
+
$g_data[:databases][current_dbname()][:history] << sql
|
888
|
+
end
|
889
|
+
def edit_last_sql
|
890
|
+
# this can be buggy, if you change database
|
891
|
+
#sql = $g_data[:last_sql]
|
892
|
+
sql = $g_data[:databases][current_dbname()][:last_sql]
|
893
|
+
#edit_execute_sql sql
|
894
|
+
edit_execute_in_editor sql
|
895
|
+
end
|
896
|
+
def view_array data
|
897
|
+
filename = tabulate2 data, $options
|
898
|
+
puts "Got #{filename} " if $opt_verbose
|
899
|
+
File.open(filename).readlines.each {|line| puts line}
|
900
|
+
end
|
901
|
+
# display all rows of current table
|
902
|
+
def view_all_rows
|
903
|
+
table = current_tablename()
|
904
|
+
#set_last_sql sql
|
905
|
+
sql = "SELECT * from #{table}"
|
906
|
+
view_sql sql
|
907
|
+
end
|
908
|
+
def view_sample
|
909
|
+
table = current_tablename()
|
910
|
+
#set_last_sql sql
|
911
|
+
sql = "SELECT * from #{table} LIMIT 100"
|
912
|
+
view_sql sql
|
913
|
+
end
|
914
|
+
# view last 100 inserted rows
|
915
|
+
def view_recent
|
916
|
+
table = current_tablename()
|
917
|
+
sql = current_db().sql_recent_rows(table)
|
918
|
+
view_sql sql
|
919
|
+
end
|
920
|
+
# uses fzf to select an old query
|
921
|
+
# fzf messes with quotes, so don't use
|
922
|
+
def history_menu
|
923
|
+
|
924
|
+
dbc = config()
|
925
|
+
array = []
|
926
|
+
hist = dbc[:history] || []
|
927
|
+
saved = dbc[:saved_sqls] || []
|
928
|
+
array = saved | hist
|
929
|
+
unless array
|
930
|
+
perror "No history for this database!"
|
931
|
+
return false
|
932
|
+
end
|
933
|
+
#sql = single_select "Select query: ", array
|
934
|
+
puts "Use up arrow to navigate queries, ^r to search:"
|
935
|
+
command = editline array
|
936
|
+
#command = vared sql, "Edit SQL:"
|
937
|
+
if command.nil? or command.size == 0
|
938
|
+
$quitting = true
|
939
|
+
return false
|
940
|
+
end
|
941
|
+
view_sql command
|
942
|
+
set_last_sql command
|
943
|
+
end
|
944
|
+
def display_menu title, h
|
945
|
+
puts "inside display_menu with #{title}, mode = #{$mode} " if $opt_debug
|
946
|
+
unless h
|
947
|
+
h = $bindings[:db]
|
948
|
+
end
|
949
|
+
return unless h
|
950
|
+
pbold "#{title}"
|
951
|
+
ctr = 0
|
952
|
+
#h.each_pair { |k, v| puts " #{k}: #{v}" }
|
953
|
+
h.each_pair { |k, v|
|
954
|
+
print " #{k}: %-20s" % [v]
|
955
|
+
if ctr > 1
|
956
|
+
print "\n"
|
957
|
+
ctr = 0
|
958
|
+
else
|
959
|
+
ctr += 1
|
960
|
+
end
|
961
|
+
}
|
962
|
+
print "\n"
|
963
|
+
end
|
964
|
+
def clear_screen
|
965
|
+
system "clear"
|
966
|
+
t = "#{GREEN}#{$help} #{BLUE}cetusql #{VERSION}#{CLEAR}"
|
967
|
+
print "#{BOLD}#{t}#{CLEAR}\n"
|
968
|
+
end
|
969
|
+
|
970
|
+
|
971
|
+
|
972
|
+
config_read
|
973
|
+
$g_data ||= {}
|
974
|
+
|
975
|
+
run
|