Skip to content

Commit 4062834

Browse files
adding -c / --command parameter to pgcli w/tests
1 parent 1723391 commit 4062834

File tree

3 files changed

+178
-0
lines changed

3 files changed

+178
-0
lines changed

pgcli/main.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,23 @@ def _check_ongoing_transaction_and_allow_quitting(self):
911911
def run_cli(self):
912912
logger = self.logger
913913

914+
# Handle single command mode (-c flag) - similar to psql behavior
915+
if hasattr(self, 'single_command') and self.single_command:
916+
try:
917+
logger.debug("Running single command: %s", self.single_command)
918+
# Execute the command using the same logic as interactive mode
919+
self.handle_watch_command(self.single_command)
920+
except PgCliQuitError:
921+
# Normal exit from quit command
922+
sys.exit(0)
923+
except Exception as e:
924+
logger.error("Error executing command: %s", e)
925+
logger.error("traceback: %r", traceback.format_exc())
926+
click.secho(str(e), err=True, fg="red")
927+
sys.exit(1)
928+
# Exit successfully after executing the command
929+
sys.exit(0)
930+
914931
history_file = self.config["main"]["history_file"]
915932
if history_file == "default":
916933
history_file = config_location() + "history"
@@ -1426,6 +1443,12 @@ def echo_via_pager(self, text, color=None):
14261443
type=str,
14271444
help="SQL statement to execute after connecting.",
14281445
)
1446+
@click.option(
1447+
"-c",
1448+
"--command",
1449+
default="",
1450+
help="run only single command (SQL or internal) and exit.",
1451+
)
14291452
@click.argument("dbname", default=lambda: None, envvar="PGDATABASE", nargs=1)
14301453
@click.argument("username", default=lambda: None, envvar="PGUSER", nargs=1)
14311454
def cli(
@@ -1454,6 +1477,7 @@ def cli(
14541477
ssh_tunnel: str,
14551478
init_command: str,
14561479
log_file: str,
1480+
command: str,
14571481
):
14581482
if version:
14591483
print("Version:", __version__)
@@ -1514,6 +1538,9 @@ def cli(
15141538
log_file=log_file,
15151539
)
15161540

1541+
# Store single command for -c option
1542+
pgcli.single_command = command if command else None
1543+
15171544
# Choose which ever one has a valid value.
15181545
if dbname_opt and dbname:
15191546
# work as psql: when database is given as option and argument use the argument as user
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Feature: run the cli with -c/--command option,
2+
execute a single command,
3+
and exit
4+
5+
Scenario: run pgcli with -c and a SQL query
6+
When we run pgcli with -c "SELECT 1 as test_column"
7+
then we see the query result
8+
and pgcli exits successfully
9+
10+
Scenario: run pgcli with --command and a SQL query
11+
When we run pgcli with --command "SELECT 'hello' as greeting"
12+
then we see the query result
13+
and pgcli exits successfully
14+
15+
Scenario: run pgcli with -c and a special command
16+
When we run pgcli with -c "\dt"
17+
then we see the command output
18+
and pgcli exits successfully
19+
20+
Scenario: run pgcli with -c and an invalid query
21+
When we run pgcli with -c "SELECT invalid_column FROM nonexistent_table"
22+
then we see an error message
23+
and pgcli exits successfully
24+
25+
Scenario: run pgcli with -c and multiple statements
26+
When we run pgcli with -c "SELECT 1; SELECT 2"
27+
then we see both query results
28+
and pgcli exits successfully
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""
2+
Steps for testing -c/--command option behavioral tests.
3+
"""
4+
5+
import subprocess
6+
from behave import when, then
7+
8+
9+
@when('we run pgcli with -c "{command}"')
10+
def step_run_pgcli_with_c(context, command):
11+
"""Run pgcli with -c flag and a command."""
12+
cmd = [
13+
"pgcli",
14+
"-h", context.conf["host"],
15+
"-p", str(context.conf["port"]),
16+
"-U", context.conf["user"],
17+
"-d", context.conf["dbname"],
18+
"-c", command
19+
]
20+
try:
21+
context.cmd_output = subprocess.check_output(
22+
cmd,
23+
cwd=context.package_root,
24+
stderr=subprocess.STDOUT,
25+
timeout=5
26+
)
27+
context.exit_code = 0
28+
except subprocess.CalledProcessError as e:
29+
context.cmd_output = e.output
30+
context.exit_code = e.returncode
31+
except subprocess.TimeoutExpired as e:
32+
context.cmd_output = b"Command timed out"
33+
context.exit_code = -1
34+
35+
36+
@when('we run pgcli with --command "{command}"')
37+
def step_run_pgcli_with_command(context, command):
38+
"""Run pgcli with --command flag and a command."""
39+
cmd = [
40+
"pgcli",
41+
"-h", context.conf["host"],
42+
"-p", str(context.conf["port"]),
43+
"-U", context.conf["user"],
44+
"-d", context.conf["dbname"],
45+
"--command", command
46+
]
47+
try:
48+
context.cmd_output = subprocess.check_output(
49+
cmd,
50+
cwd=context.package_root,
51+
stderr=subprocess.STDOUT,
52+
timeout=5
53+
)
54+
context.exit_code = 0
55+
except subprocess.CalledProcessError as e:
56+
context.cmd_output = e.output
57+
context.exit_code = e.returncode
58+
except subprocess.TimeoutExpired as e:
59+
context.cmd_output = b"Command timed out"
60+
context.exit_code = -1
61+
62+
63+
@then("we see the query result")
64+
def step_see_query_result(context):
65+
"""Verify that the query result is in the output."""
66+
output = context.cmd_output.decode('utf-8')
67+
# Check for common query result indicators
68+
assert any([
69+
"SELECT" in output,
70+
"test_column" in output,
71+
"greeting" in output,
72+
"hello" in output,
73+
"+-" in output, # table border
74+
"|" in output, # table column separator
75+
]), f"Expected query result in output, but got: {output}"
76+
77+
78+
@then("we see both query results")
79+
def step_see_both_query_results(context):
80+
"""Verify that both query results are in the output."""
81+
output = context.cmd_output.decode('utf-8')
82+
# Should contain output from both SELECT statements
83+
assert "SELECT" in output, f"Expected SELECT in output, but got: {output}"
84+
# The output should have multiple result sets
85+
assert output.count("SELECT") >= 2, f"Expected at least 2 SELECT results, but got: {output}"
86+
87+
88+
@then("we see the command output")
89+
def step_see_command_output(context):
90+
"""Verify that the special command output is present."""
91+
output = context.cmd_output.decode('utf-8')
92+
# For \dt we should see table-related output
93+
# It might be empty if no tables exist, but shouldn't error
94+
assert context.exit_code == 0, f"Expected exit code 0, but got: {context.exit_code}"
95+
96+
97+
@then("we see an error message")
98+
def step_see_error_message(context):
99+
"""Verify that an error message is in the output."""
100+
output = context.cmd_output.decode('utf-8')
101+
assert any([
102+
"does not exist" in output,
103+
"error" in output.lower(),
104+
"ERROR" in output,
105+
]), f"Expected error message in output, but got: {output}"
106+
107+
108+
@then("pgcli exits successfully")
109+
def step_pgcli_exits_successfully(context):
110+
"""Verify that pgcli exited with code 0."""
111+
assert context.exit_code == 0, f"Expected exit code 0, but got: {context.exit_code}"
112+
# Clean up
113+
context.cmd_output = None
114+
context.exit_code = None
115+
116+
117+
@then("pgcli exits with error")
118+
def step_pgcli_exits_with_error(context):
119+
"""Verify that pgcli exited with a non-zero code."""
120+
assert context.exit_code != 0, f"Expected non-zero exit code, but got: {context.exit_code}"
121+
# Clean up
122+
context.cmd_output = None
123+
context.exit_code = None

0 commit comments

Comments
 (0)