Skip to content

bug: PUT/GET commands silently succeed with no output when no files are matched or stage path is wrong #776

@wubx

Description

@wubx

Summary

Two related issues with the PUT and GET commands that cause silent failures — no error, no output, no indication that anything went wrong.


Issue 1: GET does not support single file stage paths

Steps to reproduce

GET @mystage/oracle/somefile.log.gz fs:///tmp/download/

Expected behavior

The file is downloaded, or an error is shown if the path is invalid.

Actual behavior

No output, no error, no file downloaded.

Root cause

In driver/src/conn.rs, get_files() unconditionally appends / to the stage path:

if !location.path.ends_with('/') {
    location.path.push('/');  // turns file path into non-existent directory path
}
let list_sql = format!("LIST {location}");

When the user passes a specific file path like @mystage/oracle/file.gz, the code transforms it to @mystage/oracle/file.gz/, which matches nothing in LIST. The while loop never executes and an empty RowStatsIterator is returned with no error.

The silent failure is compounded in display.rs:186:

if rows.is_empty() {
    return Ok(());  // empty result treated as success, no output to user
}

Notes

  • GET @mystage/oracle/ (directory path with trailing /) works correctly
  • Snowflake's GET supports both single file and directory paths
  • This behavior has existed since the original implementation in feat: support get/put stmt #198

Issue 2: PUT silently succeeds when local file does not exist

Steps to reproduce

PUT file:///tmp/nonexistent-file.csv @mystage/

Expected behavior

An error is shown: file not found.

Actual behavior

No output, no error, no file uploaded.

Root cause

In driver/src/conn.rs, put_files() uses glob::glob() to match local files. When the path matches nothing (file does not exist, wrong path, etc.), the for loop never executes and an empty RowStatsIterator is returned — same silent failure path as above.


Proposed fixes

Short term — add empty result checks:

// put_files: after the for loop
if results.is_empty() {
    return Err(Error::BadArgument(
        format!("no local files matched: {}", local_dsn.path())
    ));
}

// get_files: after the while loop  
if results.is_empty() {
    return Err(Error::BadArgument(
        format!("no files found at stage path: {}", stage)
    ));
}

Longer term — support single file paths in GET:

Remove the unconditional / append and handle both cases:

  • If stage path ends with / or is empty → directory mode (current behavior)
  • If stage path does not end with / → single file mode: skip prefix stripping, use basename as local filename

This requires fixing three places in get_files(): the LIST query, the prefix stripping logic, and the stage_file construction.


Environment

  • bendsql version: 0.33.4
  • Affected files: driver/src/conn.rs, cli/src/display.rs

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions