Skip to content

Add support for -f/--file option to execute SQL from files#1543

Open
DiegoDAF wants to merge 3 commits into
dbcli:mainfrom
DiegoDAF:feature/file-option
Open

Add support for -f/--file option to execute SQL from files#1543
DiegoDAF wants to merge 3 commits into
dbcli:mainfrom
DiegoDAF:feature/file-option

Conversation

@DiegoDAF

@DiegoDAF DiegoDAF commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

Summary

This PR adds support for the -f/--file option to pgcli, implementing psql-compatible behavior for executing SQL commands from files.

Features

  • Single file execution: pgcli -f file.sql
  • Multiple files: pgcli -f file1.sql -f file2.sql
  • Long form support: pgcli --file file.sql
  • Files are executed sequentially
  • Pager is automatically disabled in file mode
  • Proper error handling and exit codes

Implementation Details

  • Added new click option -f/--file that accepts multiple file paths
  • File paths are validated to ensure they exist and are readable
  • Modified run_cli() to check for file mode and execute file contents before entering interactive mode
  • Updated echo_via_pager() to disable pager when in file mode
  • File contents are read and executed using the existing handle_watch_command() method

Testing

Comprehensive BDD tests included covering:

  • Single file with -f flag
  • Single file with --file flag
  • Multiple statements in one file
  • Multiple -f options for different files
  • Files with special commands
  • Error handling for invalid SQL

Compatibility

This implementation follows psql's behavior and maintains backward compatibility with existing functionality.

Made with ❤️ and 🤖 Claude Code

This commit adds support for the -f/--file option to pgcli, similar to
psql's behavior. Users can now execute SQL commands from files and exit
immediately after execution.

Features:
- Single file execution: pgcli -f file.sql
- Multiple files: pgcli -f file1.sql -f file2.sql
- Long form: pgcli --file file.sql
- Files are executed sequentially
- Pager is automatically disabled in file mode
- Proper error handling and exit codes

Tests included for all scenarios.

Made with ❤️ and 🤖 Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
@DiegoDAF
DiegoDAF marked this pull request as ready for review December 5, 2025 18:38

@j-bennet j-bennet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks ready to merge, however, I'm wondering, what will happen if the file contains a destructive command (DROP table_name) and destructive_warning is enabled?

Comment thread tests/features/steps/file_option.py Fixed
@DiegoDAF

Copy link
Copy Markdown
Contributor Author

This looks ready to merge, however, I'm wondering, what will happen if the file contains a destructive command (DROP table_name) and destructive_warning is enabled?

Thanks for the review!

Short answer: -f doesn't change the destructive-command behavior at all. The file's contents go through the same execute_command path as interactive input, so destructive_warning applies exactly as it does today.

Concretely, with destructive_warning enabled and a file containing DROP table_name:

  • Interactive terminal: the user gets the usual "You're about to run a destructive command. Do you want to proceed?" prompt. Answering no stops execution ("Wise choice!") and nothing from the file runs.
  • Non-interactive stdin (piped, redirected, CI): confirm_destructive_query already returns None because of its existing sys.stdin.isatty() guard, so no prompt is shown and the statement runs. That is pre-existing behaviour, not something -f
    introduces: the existing \i command (execute_from_file) behaves the same way.
  • If destructive_statements_require_transaction is enabled, the statement is still refused outside a transaction, with Destructive statements must be run within a transaction."

One detail worth calling out: -f executes the file as a single unit (the whole content is passed to execute_command), so the destructive check runs once over the entire file rather than per statement. If any statement in the file matches destructive_warning, the single confirmation covers the whole file.

Happy to add a behave scenario covering the destructive-file case if you'd like it
pinned down in tests.

The style gate started failing with ruff 0.15.x: the TimeoutExpired handlers
bound `as e` without using it (F841), and step_see_command_output decoded
cmd_output into `output` but never used it.

The command-output step now asserts on the \dt column headers, which are
rendered whether or not any tables exist, so it verifies the special command
actually produced its listing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants