summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAmjith Ramanujam <amjith@newrelic.com>2014-12-08 00:43:21 -0800
committerAmjith Ramanujam <amjith@newrelic.com>2014-12-08 00:43:21 -0800
commit60cbbdcb28a76daa8d6bff380ed44c29b79b3360 (patch)
tree7d24dddabf67080beffb0b6e38f09b248fa27c09
parenta88192bc0cd3c5d70ed253e1c71eed63ea66681c (diff)
Autorefresh for \c, update readme with thanks.
-rw-r--r--README.rst17
-rw-r--r--TODO13
-rwxr-xr-xpgcli/main.py13
-rw-r--r--pgcli/packages/pgspecial.py2
-rw-r--r--pgcli/pgcompleter.py5
-rw-r--r--pgcli/pgexecute.py20
6 files changed, 48 insertions, 22 deletions
diff --git a/README.rst b/README.rst
index 306ec260..994b73c0 100644
--- a/README.rst
+++ b/README.rst
@@ -124,3 +124,20 @@ Then you can install pgcli:
$ sudo pip install pgcli
+Gratitude:
+==========
+
+A special thanks to Jonathan Slenders for creating Python Prompt Toolkit, which
+is quite literally the backbone library that made this app possible. Jonathan
+has also provided valuable feedback and support during the development of this
+app.
+
+This app also includes tabulate (https://pypi.python.org/pypi/tabulate) library
+for printing the output of the tables. The reason for copying it directly and
+not listing it as a dependency is because I had to make a change to the table
+format which is merged back into the original repo, but not yet released in
+PyPI.
+
+Click is used for command line option parsing and printing error messages.
+
+Thanks to psycopg2 for providing a rock solid interface to Postgres dataabase.
diff --git a/TODO b/TODO
index 74ec3a0d..15557e66 100644
--- a/TODO
+++ b/TODO
@@ -1,9 +1,10 @@
-* [] Vendor in tabulate.
-* [] Create a separate pgspecial package.
-* [] Vendor in pgspecial package.
-* [] Add \c command.
-* [] Add MySQL commands (use, describe etc)
-* [X] Add exit keyword.
+* [X] Vendor in tabulate.
+* [X] Create a separate pgspecial package.
+* [X] Vendor in pgspecial package.
+* [X] Auto-refresh for \c and 'use' statements.
+* [X] Add \c command.
+* [O] Add MySQL commands (use, describe etc)
+* [X] Add exit, quit and \q.
* [] Show only table sensitive columns.
* [] Add logging.
* [] Add some tests. Sanity, Unit, Completion, Config.
diff --git a/pgcli/main.py b/pgcli/main.py
index cec004a2..d60b34e8 100755
--- a/pgcli/main.py
+++ b/pgcli/main.py
@@ -12,9 +12,10 @@ from prompt_toolkit.layout.prompt import DefaultPrompt
from prompt_toolkit.layout.menus import CompletionsMenu
from prompt_toolkit.history import FileHistory
from pygments.lexers.sql import SqlLexer
-from tabulate import tabulate
import sqlparse
+from .packages.tabulate import tabulate
+from .packages.pgspecial import COMMANDS
from .pgcompleter import PGCompleter
from .pgstyle import PGStyle
from .pgexecute import PGExecute
@@ -50,7 +51,7 @@ def cli(database, user, password, host, port):
menus=[CompletionsMenu()],
lexer=SqlLexer)
completer = PGCompleter(config.getboolean('main', 'smart_completion'))
- completer.extend_special_commands(pgexecute.special_commands.keys())
+ completer.extend_special_commands(COMMANDS.keys())
completer.extend_table_names(pgexecute.tables())
completer.extend_column_names(pgexecute.all_columns())
line = PGLine(completer=completer,
@@ -66,7 +67,8 @@ def cli(database, user, password, host, port):
# because we want to raise the Exit exception which will be caught
# by the try/except block that wraps the pgexecute.run() statement.
if (document.text.strip().lower() == 'exit'
- or document.text.strip().lower() == 'quit'):
+ or document.text.strip().lower() == 'quit'
+ or document.text.strip() == '\q'):
raise Exit
try:
rows, headers, status = pgexecute.run(document.text)
@@ -78,11 +80,12 @@ def cli(database, user, password, host, port):
# Refresh the table names and column names if necessary.
if need_completion_refresh(document.text):
+ completer.reset_completions()
completer.extend_table_names(pgexecute.tables())
completer.extend_column_names(pgexecute.all_columns())
except Exit:
print ('GoodBye!')
def need_completion_refresh(sql):
- parsed = sqlparse.parse(sql)
- return parsed and parsed[0].token_first().value in ('alter', 'create')
+ first_token = sql.split()[0]
+ return first_token in ('alter', 'create', 'use', '\c', 'drop')
diff --git a/pgcli/packages/pgspecial.py b/pgcli/packages/pgspecial.py
index dde728a8..2d704f47 100644
--- a/pgcli/packages/pgspecial.py
+++ b/pgcli/packages/pgspecial.py
@@ -201,7 +201,6 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose):
cell.append(modifier)
- #import pdb; pdb.set_trace()
# Sequence
if tableinfo.relkind == 'S':
cell.append(seq_values[i])
@@ -693,7 +692,6 @@ def execute(cur, command, verbose, arg):
global COMMANDS
command_executor = COMMANDS[command]
- import pdb; pdb.set_trace()
if callable(command_executor):
return command_executor(cur, arg, verbose)
elif isinstance(command_executor, str):
diff --git a/pgcli/pgcompleter.py b/pgcli/pgcompleter.py
index ccaf6086..267f196f 100644
--- a/pgcli/pgcompleter.py
+++ b/pgcli/pgcompleter.py
@@ -46,6 +46,11 @@ class PGCompleter(Completer):
self.column_names.extend(column_names)
self.all_completions.update(column_names)
+ def reset_completions(self):
+ self.table_names = []
+ self.column_names = ['*']
+ self.all_completions = set(self.keywords)
+
@staticmethod
def find_matches(text, collection):
for item in collection:
diff --git a/pgcli/pgexecute.py b/pgcli/pgexecute.py
index b0e8654d..10a1e1ba 100644
--- a/pgcli/pgexecute.py
+++ b/pgcli/pgexecute.py
@@ -1,14 +1,16 @@
import psycopg2
+from .packages import pgspecial
class PGExecute(object):
- special_commands = {
- '\d': '''SELECT n.nspname as "Schema", c.relname as "Name", CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' END as "Type", pg_catalog.pg_get_userbyid(c.relowner) as "Owner" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r','v','m','S','f','') AND n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' AND n.nspname !~ '^pg_toast' AND pg_catalog.pg_table_is_visible(c.oid) ORDER BY 1,2;''',
- '\dt': '''SELECT n.nspname as "Schema", c.relname as "Name", CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' END as "Type", pg_catalog.pg_get_userbyid(c.relowner) as "Owner" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r','') AND n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' AND n.nspname !~ '^pg_toast' AND pg_catalog.pg_table_is_visible(c.oid) ORDER BY 1,2;'''
- }
+ tables_query = '''SELECT c.relname as "Name" FROM pg_catalog.pg_class c
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE
+ c.relkind IN ('r','') AND n.nspname <> 'pg_catalog' AND n.nspname <>
+ 'information_schema' AND n.nspname !~ '^pg_toast' AND
+ pg_catalog.pg_table_is_visible(c.oid) ORDER BY 1;'''
- tables_query = '''SELECT c.relname as "Name" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r','') AND n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' AND n.nspname !~ '^pg_toast' AND pg_catalog.pg_table_is_visible(c.oid) ORDER BY 1;'''
- columns_query = '''SELECT column_name FROM information_schema.columns WHERE table_name =%s;'''
+ columns_query = '''SELECT column_name FROM information_schema.columns WHERE
+ table_name =%s;'''
def __init__(self, database, user, password, host, port):
self.conn = psycopg2.connect(database=database, user=user,
@@ -45,9 +47,9 @@ class PGExecute(object):
'user "%s"' % (self.dbname, self.user))
with self.conn.cursor() as cur:
- if sql in self.special_commands:
- cur.execute(self.special_commands[sql])
- else:
+ try:
+ return pgspecial.execute(cur, *self.parse_pattern(sql))
+ except KeyError:
cur.execute(sql)
# cur.description will be None for operations that do not return