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 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
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.tgz
11
+ *.bak
12
+ t.t
13
+ *.tmp
14
+ *.t
15
+ *.sqlite
16
+ *.db
17
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cetusql.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
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