You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit e73293a
Browse filesBrowse the repository at this point in the historyBrowse files
### Pre-review checklist
* [x] If my changes add or modify user-facing interface (commands, flags, output formats), I consulted maintainers and got sign-off beforehand ([how](https://github.com/snowflakedb/snowflake-cli/blob/main/docs/contributing/adding-commands.md#design-sign-off-before-writing-code)).
* [x] I've confirmed that instructions included in README.md are still correct after my changes in the codebase.
* [x] I've added or updated unit tests to verify correctness of my new code.
* [x] I've added or updated integration tests to verify correctness of my new code.
* [x] Manually tested on macOS
* [ ] Manually tested on Windows
* [x] I've confirmed my changes are up-to-date with the target branch.
* [x] I've described my changes in `RELEASE-NOTES.md` (see [when and how](https://github.com/snowflakedb/snowflake-cli/blob/main/docs/contributing/lifecycle.md#release-notes-format)).
* [x] I've updated documentation if behavior changed.
### Changes description
Adds SnowSQL-compatible `!spool <file>` / `!spool off` ([SNOW-4138413](https://snowflakecomputing.atlassian.net/browse/SNOW-4138413)).
`!spool` opens the path for append (UTF-8, `~` expanded). Later `print_result` output is written to that file **and** still printed on the terminal until `!spool off`. Format follows `--format` / `!set output_format`. Works in the REPL, `-q`, and `-f`. The whole remainder is one path (spaces and `=` allowed). `off` is a case-insensitive keyword, so a file named `off` cannot be spooled. Success is silent. `!spool off` when already off is a no-op.
The file is the formatted query result only (TABLE / PLAIN / JSON / CSV). Prompts, `!` commands, SQL text, and `Time Elapsed` stay on the terminal.
A new spool file is owner-only (`0600`). Appending to an existing file leaves its mode unchanged and warns if others can read or write it. On Windows the same warning uses `file_permissions_are_strict()`. A missing `whoami`/`icacls` does not abort `!spool`. The path is sanitized before the warning and the unwritable-path error.
Until `!spool off`, TABLE is a static pipe table on both sinks (no Live refresh, no wrapping to the window).
#### SnowSQL 1.5.2 vs `snow sql`
Replicated from SnowSQL `!spool`:
| | SnowSQL 1.5.2 | This PR |
| --- | --- | --- |
| What is in the file | Formatted query result only | Same (`print_result` only) |
| Still printed on the terminal | Yes | Yes |
| Prompts, SQL, `!` commands, timing / row counts in the file | No (default) | No |
| Append, not overwrite | Yes | Yes |
| REPL, `-q`, `-f` | Yes | Yes |
Intentionally different:
| | SnowSQL 1.5.2 | This PR |
| --- | --- | --- |
| New file mode | Usually `0644` (umask) | `0600` |
| Existing file broader than owner-only | Silent | Warn; mode unchanged |
| TABLE on screen while spooling | Always static tabulate | Static pipe table so it matches the file (CLI otherwise uses Live / wrap on a TTY) |
| Format names | Tabulate (`psql`, `csv`, …) | `--format` / `!set` (`table`, `plain`, `json`, …) |
| No-args / unwritable path | `The file is not writeable:` | CLI usage / `Cannot write spool file` |
| `output_file`, `timing_in_output_file`, `!print` | Exists | Out of scope |
#### Errors vs SnowSQL 1.5.2
| Case | SnowSQL 1.5.2 | This PR |
| --- | --- | --- |
| no args | `The file is not writeable:` (empty path) | `No arguments passed to 'spool' command. Usage: !spool <file> or !spool off` |
| unwritable path | `The file is not writeable: <path>` | `Cannot write spool file '<path>': <OS error>` |
| `off` when already off | silent | silent |
#### Why tee through `print_result`, not `sys.stdout =`
Query results are consumed once. Tee at write time via `_result_stream()` to `TeeTextIO` so JSON, CSV, TABLE, and PLAIN all hit both sinks without rendering twice.
Do not assign `sys.stdout`. Rich caches `is_terminal` on the stream it first saw (`_stdout_is_terminal` / GH#2725). While spooling, both sinks use the non-TTY layout so the file does not pick up ANSI / Live.
#### Single-SELECT print order
For one SELECT, Typer prints after `execute_sql` returns. Draining the statement generator first would run `!spool off` before `print_result`, so the file would be empty. The one-SELECT path prints `QueryResult` from `next()`, drains the rest (including `!spool off`), then returns `EmptyResult()`. A trailing `!set` after a single SELECT does not rewrite that result.
#### Why not public [PR #2721](#2721)
REPL-only; truncates; `sys.stdout =`; extra `-- Query:` banner. Wrong contract for this ticket.
Out of scope: SnowSQL `output_file` / `timing_in_output_file`, overwrite flag, spooling prompts or Time Elapsed.
## Test plan
- [x] Unit: spool create `0600`; warn on existing `0644` / `0640` / `0666` / `0622`; no warn on existing `0600`; trailing `!set` after a single SELECT stays TABLE
- [x] Unit: Windows owner-only warning (not skipped); `CliError` from the permission check does not abort; ANSI stripped from the warning and `Cannot write spool file`
- [x] Integration: `tests_integration/sql/test_snowsql_commands.py` spool cases
- [x] Manual, macOS, PR worktree `hatch run snow sql`: `select repeat('x', 400)` wraps; after `!spool` the same query is one long line on screen and in the file; after `!spool off` wrap returns
- [x] Manual, SnowSQL 1.5.2: REPL `!spool /tmp/snowsql-spool-contents.txt` then `select 2 as n;` — file is only the table. `-q` with newlines (`!spool` / `select 4` / `!spool off`) — file is only the table. `!spool path; select …` on one line is one `!spool` and creates no file
[SNOW-4138413]: https://snowflakecomputing.atlassian.net/browse/SNOW-4138413?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
GitOrigin-RevId: 69c2c2fd10802836d5c4d41f1b5e7967e4dd5532
Copy file name to clipboardExpand all lines: RELEASE-NOTES.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -22,6 +22,7 @@
22
22
## New additions
23
23
* Streamlit entities support `include_project_definition: true` to opt in to uploading the project-root `snowflake.yml` with `snow streamlit deploy`. When that file is uploaded, deploy emits a warning because stage readers can retrieve it.
24
24
*`snow upgrade` is now generally available. It upgrades a direct install of Snowflake CLI and is listed in `snow --help` without a feature flag. Other install channels refuse and print the direct-install command.
25
+
*`snow sql` accepts `!spool <file>` and `!spool off`. Subsequent query results are appended to the file and still printed on the terminal. Until `!spool off`, TABLE is a static pipe table on both the terminal and the file: no Live refresh and no wrapping to the window. Format follows `--format` / `!set output_format`.
25
26
26
27
## Fixes and improvements
27
28
*`snow streamlit deploy --replace` now applies `main_file` changes to existing versioned apps in place, preserving the app, grants and version history. Files are uploaded before the entrypoint changes; `--prune` runs only after the change succeeds. Accounts that reject the update receive an actionable error.
0 commit comments