Skip to content

Commit 5d5e082

Browse files
authored
Add \ne to edit a named query in the external editor (#1609)
\ne <name> loads a named query's SQL into $EDITOR and writes the edited result back to the [named queries] section on save, creating it if it does not exist. Complements \ns for editing longer queries. Addresses the editor part of issue #1430. Co-authored-by: Diego <DiegoDAF@users.noreply.github.com>
1 parent ea6c30d commit 5d5e082

4 files changed

Lines changed: 82 additions & 0 deletions

File tree

‎AUTHORS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ Contributors:
151151
* Anjanna
152152
* Shayan Golshani (shgol)
153153
* Tommi Kyntölä (kynde)
154+
* Diego
154155

155156
Creator:
156157
--------

‎changelog.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ Bug fixes:
1010

1111
Features:
1212
---------
13+
* Add ``\\ne <name>`` to edit a named query in the external editor. Loads the
14+
named query's SQL into ``$EDITOR``; on save it is written back to the
15+
``[named queries]`` section, creating it if it does not exist. Complements
16+
``\\ns`` (save) by making longer queries easier to edit ([issue 1430](https://github.com/dbcli/pgcli/issues/1430)).
1317
* Enable ``.pgpass`` support for SSH tunnel connections.
1418
* Preserve original hostname for ``.pgpass`` lookup using PostgreSQL's ``hostaddr`` parameter
1519
* SSH tunnel endpoint (``127.0.0.1``) is passed via ``hostaddr``, keeping ``host`` for ``.pgpass``

‎pgcli/main.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,31 @@ def toggle_named_query_quiet(self):
319319
message = f"Named query quiet mode: {status}"
320320
return [(None, None, None, message)]
321321

322+
def edit_named_query(self, pattern, **_):
323+
r"""Edit (or create) a named query in the external editor (\ne name).
324+
325+
Loads the named query's SQL into ``$EDITOR``; on save, persists it back
326+
to the ``[named queries]`` section. If the name does not exist, the
327+
editor opens empty and saving creates it.
328+
"""
329+
name = pattern.strip()
330+
if not name:
331+
return [(None, None, None, "Usage: \\ne <name>")]
332+
333+
existing = NamedQueries.instance.get(name)
334+
sql, message = special.open_external_editor(sql=existing or "")
335+
if message:
336+
return [(None, None, None, message)]
337+
338+
sql = (sql or "").strip()
339+
if not sql:
340+
return [(None, None, None, f"{name}: empty query, not saved.")]
341+
if existing is not None and sql == existing.strip():
342+
return [(None, None, None, f"{name}: no changes.")]
343+
344+
NamedQueries.instance.save(name, sql)
345+
return [(None, None, None, f"{name}: {'Created' if existing is None else 'Saved'}")]
346+
322347
def _is_named_query_execution(self, text):
323348
"""Check if the command is a named query execution (\n <name>)."""
324349
text = text.strip()
@@ -334,6 +359,13 @@ def register_special_commands(self):
334359
case_sensitive=True,
335360
)
336361

362+
self.pgspecial.register(
363+
self.edit_named_query,
364+
"\\ne",
365+
"\\ne name",
366+
"Edit a named query in the external editor.",
367+
)
368+
337369
self.pgspecial.register(
338370
self.change_db,
339371
"\\c",

‎tests/test_main.py‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,3 +636,48 @@ def test_notifications(executor):
636636
with mock.patch("pgcli.main.click.secho") as mock_secho:
637637
run(executor, "notify chan1, 'testing2'")
638638
mock_secho.assert_not_called()
639+
640+
641+
def test_edit_named_query():
642+
"""Test \\ne edits/creates a named query via the external editor."""
643+
from pgspecial.namedqueries import NamedQueries
644+
645+
with tempfile.TemporaryDirectory() as tmpdir:
646+
config_file = os.path.join(tmpdir, "config")
647+
with open(config_file, "w") as f:
648+
f.write("[main]\n")
649+
f.write(f"log_file = {os.path.join(tmpdir, 'pgcli.log')}\n")
650+
651+
cli = PGCli(pgclirc_file=config_file)
652+
653+
# Create a new named query (editor returns SQL, no error).
654+
with mock.patch("pgcli.main.special.open_external_editor", return_value=("select 1", None)):
655+
out = cli.edit_named_query("foo")
656+
assert "Created" in out[0][3]
657+
assert NamedQueries.instance.get("foo") == "select 1"
658+
659+
# Update the existing one.
660+
with mock.patch("pgcli.main.special.open_external_editor", return_value=("select 2", None)):
661+
out = cli.edit_named_query("foo")
662+
assert "Saved" in out[0][3]
663+
assert NamedQueries.instance.get("foo") == "select 2"
664+
665+
# No changes -> not re-saved.
666+
with mock.patch("pgcli.main.special.open_external_editor", return_value=("select 2", None)):
667+
out = cli.edit_named_query("foo")
668+
assert "no changes" in out[0][3]
669+
670+
# Empty editor result -> not saved, previous value kept.
671+
with mock.patch("pgcli.main.special.open_external_editor", return_value=("", None)):
672+
out = cli.edit_named_query("foo")
673+
assert "not saved" in out[0][3]
674+
assert NamedQueries.instance.get("foo") == "select 2"
675+
676+
# Editor reported an error -> surfaced, nothing saved.
677+
with mock.patch("pgcli.main.special.open_external_editor", return_value=(None, "boom")):
678+
out = cli.edit_named_query("foo")
679+
assert out[0][3] == "boom"
680+
681+
# Missing name -> usage message.
682+
out = cli.edit_named_query("")
683+
assert "Usage" in out[0][3]

0 commit comments

Comments
 (0)