Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More compact bottom bar #1355

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ Contributors:
* Daniele Varrazzo
* Daniel Kukula (dkuku)
* Kian-Meng Ang (kianmeng)
* Frank van Viegen (vanviegen)

Creator:
--------
Expand Down
14 changes: 8 additions & 6 deletions DEVELOP.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,22 @@ It is highly recommended to use virtualenv for development. If you don't know
what a virtualenv is, `this guide <http://docs.python-guide.org/en/latest/dev/virtualenvs/#virtual-environments>`_
will help you get started.

Create a virtualenv (let's call it pgcli-dev). Activate it:
Create a virtualenv (let's call it pgcli-dev) and activate it:

::

source ./pgcli-dev/bin/activate
$ virtualenv pgcli-dev
$ source pgcli-dev/bin/activate

Once the virtualenv is activated, `cd` into the local clone of pgcli folder
and install pgcli using pip as follows:
Once the virtualenv is activated, you may first need to update a few of its
packages to their latest versions:

::
$ pip install --upgrade pip setuptools wheel

$ pip install --editable .
Next, `cd` into your local clone of pgcli and run:

or
::

$ pip install -e .

Expand Down
7 changes: 7 additions & 0 deletions changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Upcoming:
=========

Features:
---------

* Toggle autocommit mode using F6 or a config setting.
* When in a transaction, rollback erring queries without aborting the transaction. ('On error rollback'.)
* Less verbose bottom bar.

Bug fixes:
----------

Expand Down
6 changes: 6 additions & 0 deletions pgcli/key_bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ def _(event):
_logger.debug("Detected F5 key.")
pgcli.explain_mode = not pgcli.explain_mode

@kb.add("f6")
def _(event):
"""Toggle autocommit mode."""
_logger.debug("Detected F6 key.")
pgcli.autocommit = not pgcli.autocommit

@kb.add("tab")
def _(event):
"""Force autocompletion at cursor on non-empty lines."""
Expand Down
3 changes: 3 additions & 0 deletions pgcli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ def __init__(
self.multi_line = c["main"].as_bool("multi_line")
self.multiline_mode = c["main"].get("multi_line_mode", "psql")
self.vi_mode = c["main"].as_bool("vi")
self.autocommit = c["main"].as_bool("autocommit")
self.auto_expand = auto_vertical_output or c["main"].as_bool("auto_expand")
self.expanded_output = c["main"].as_bool("expand")
self.pgspecial.timing_enabled = c["main"].as_bool("timing")
Expand Down Expand Up @@ -431,6 +432,7 @@ def execute_from_file(self, pattern, **_):
self.pgspecial,
on_error_resume=on_error_resume,
explain_mode=self.explain_mode,
autocommit=self.autocommit,
)

def write_to_file(self, pattern, **_):
Expand Down Expand Up @@ -958,6 +960,7 @@ def _evaluate_command(self, text):
exception_formatter,
on_error_resume,
explain_mode=self.explain_mode,
autocommit=self.autocommit,
)

is_special = None
Expand Down
4 changes: 4 additions & 0 deletions pgcli/pgclirc
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ syntax_style = default
# for end are available in the REPL.
vi = False

# Enable automatic commits after each query. When disabled, queries outside of a
# transaction will start a new transaction, but the user will need to COMMIT manually.
autocommit = True

# Error handling
# When one of multiple SQL statements causes an error, choose to either
# continue executing the remaining statements, or stopping
Expand Down
45 changes: 44 additions & 1 deletion pgcli/pgexecute.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ def run(
exception_formatter=None,
on_error_resume=False,
explain_mode=False,
autocommit=True,
):
"""Execute the sql in the database and return the results.

Expand All @@ -296,11 +297,18 @@ def run(
:param on_error_resume: Bool. If true, queries following an exception
(assuming exception_formatter has been supplied) continue to
execute.
:param autocommit: Bool. Set PostgreSQL's autocommit mode to the given
value before executing the query.

:return: Generator yielding tuples containing
(title, rows, headers, status, query, success, is_special)
"""

# Set autocommit mode before running the query, but we cannot do so while
# in a transaction.
if self.conn.info.transaction_status == psycopg.pq.TransactionStatus.IDLE:
self.conn.autocommit = autocommit

# Remove spaces and EOL
statement = statement.strip()
if not statement: # Empty string
Expand Down Expand Up @@ -401,7 +409,42 @@ def handle_notices(n):
return title, None, None, res.command_status.decode()

cur = self.conn.cursor()
cur.execute(split_sql)
if self.conn.info.transaction_status != psycopg.pq.TransactionStatus.IDLE:

# Create a savepoint that we'll revert to in case the user query fails
cur.execute("SAVEPOINT pgcli_error_rollback")
# Execute the user query
try:
cur.execute(split_sql)
except psycopg.DatabaseError:
# The user query failed. Let's rollback to our savepoint, and throw.
with self.conn.cursor() as tmp_cur:
tmp_cur.execute("ROLLBACK TO pgcli_error_rollback")
raise
# The user query succeeded. Release our savepoint.
try:
with self.conn.cursor() as tmp_cur:
tmp_cur.execute("RELEASE pgcli_error_rollback")
except (
psycopg.errors.InvalidSavepointSpecification,
psycopg.errors.NoActiveSqlTransaction,
):
# Our savepoint was deleted by the user executing ROLLBACK or COMMIT.
# Our previous (RELEASE savepoint) query started a new transaction that
# is now in an 'aborted' state. Let's ROLLBACK that transaction.
with self.conn.cursor() as tmp_cur:
tmp_cur.execute("ROLLBACK")
elif not self.conn.autocommit:
# Execute the user query, possibly starting a transaction
try:
cur.execute(split_sql)
except psycopg.DatabaseError:
# The user query failed. Let's rollback the transaction that was just started.
with self.conn.cursor() as tmp_cur:
tmp_cur.execute("ROLLBACK")
raise
else:
cur.execute(split_sql)

# cur.description will be None for operations that do not return
# rows.
Expand Down
34 changes: 15 additions & 19 deletions pgcli/pgtoolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,15 @@ def get_toolbar_tokens():
else:
result.append(("class:bottom-toolbar.off", "[F2] Smart Completion: OFF "))

if pgcli.multi_line:
result.append(("class:bottom-toolbar.on", "[F3] Multiline: ON "))
else:
result.append(("class:bottom-toolbar.off", "[F3] Multiline: OFF "))

if pgcli.multi_line:
if pgcli.multiline_mode == "safe":
result.append(("class:bottom-toolbar", " ([Esc] [Enter] to execute]) "))
result.append(("class:bottom-toolbar.on", "[F3] Multiline: ON (;) "))
else:
result.append(
("class:bottom-toolbar", " (Semi-colon [;] will end the line) ")
("class:bottom-toolbar.on", "[F3] Multiline: ON (alt ⏎) ")
)
else:
result.append(("class:bottom-toolbar.off", "[F3] Multiline: OFF "))

if pgcli.vi_mode:
result.append(
Expand All @@ -53,22 +50,21 @@ def get_toolbar_tokens():
result.append(("class:bottom-toolbar", "[F4] Emacs-mode "))

if pgcli.explain_mode:
result.append(("class:bottom-toolbar", "[F5] Explain: ON "))
result.append(("class:bottom-toolbar.on", "[F5] Explain: ON "))
else:
result.append(("class:bottom-toolbar", "[F5] Explain: OFF "))
result.append(("class:bottom-toolbar.off", "[F5] Explain: OFF "))

if pgcli.pgexecute.failed_transaction():
result.append(
("class:bottom-toolbar.transaction.failed", " Failed transaction")
)

if pgcli.pgexecute.valid_transaction():
result.append(
("class:bottom-toolbar.transaction.valid", " Transaction")
)
if pgcli.autocommit:
result.append(("class:bottom-toolbar.on", "[F6] Autocommit: ON "))
else:
result.append(("class:bottom-toolbar.off", "[F6] Autocommit: OFF "))

if pgcli.completion_refresher.is_refreshing():
result.append(("class:bottom-toolbar", " Refreshing completions..."))
result.append(("class:bottom-toolbar", "<REFRESHING>"))
elif pgcli.pgexecute.failed_transaction():
result.append(("class:bottom-toolbar.transaction.failed", "<TRANSACTION>"))
elif pgcli.pgexecute.valid_transaction():
result.append(("class:bottom-toolbar.transaction.valid", "<TRANSACTION>"))

return result

Expand Down