Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@
codegen's "needs backticks" rule accepted `$` because it was copied from the
SQL side, where Postgres does allow it in an identifier. (@prql-bot, #6322)

- `prqlc watch` no longer exits when a `.prql` file in the watched tree fails to
compile. The initial pass aborted on the first error, so the command never
reached the watch loop — precisely the state watch mode exists to iterate out
of. Both the initial pass and the watch loop now print the failing path
alongside the error and carry on. The loop previously discarded every error it
hit, so a failure that produces no compiler diagnostic — an unwritable `.sql`
output path, say — left the watcher running silently and writing nothing.
(@prql-bot, #6313)

**Documentation**:

- The `prql-java` README now documents the actual API. It advertised a
Expand Down
47 changes: 45 additions & 2 deletions prqlc/prqlc/src/cli/watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,26 @@ pub fn run(command: &mut WatchArgs) -> Result<()> {

fn find_and_compile(path: &Path, opt: &prqlc::Options) -> Result<()> {
for entry in WalkDir::new(path) {
compile_path(entry?.path(), opt)?;
compile_path_reporting_errors(entry?.path(), opt);
}

Ok(())
}

/// Compile `path`, printing any error rather than propagating it.
///
/// A file that doesn't compile is the ordinary starting state for `watch`, and
/// the one it exists to iterate out of, so neither the initial pass nor the
/// watch loop stops for one. `compile_path` prints the compiler's own
/// diagnostics, but it also returns errors it hasn't printed — an unwritable
/// `.sql` path, say — which would otherwise leave the watcher silently
/// producing nothing.
fn compile_path_reporting_errors(path: &Path, opt: &prqlc::Options) {
if let Err(error) = compile_path(path, opt) {
println!("{}: {error}", path.display());
}
}

fn watch_and_compile(path: &Path, opt: &prqlc::Options) -> Result<()> {
let cwd = std::env::current_dir().ok();

Expand Down Expand Up @@ -77,7 +91,7 @@ fn watch_and_compile(path: &Path, opt: &prqlc::Options) -> Result<()> {
&path
};

let _ignore = compile_path(relative_path, opt);
compile_path_reporting_errors(relative_path, opt);
}
}

Expand Down Expand Up @@ -134,3 +148,32 @@ fn compile_path(path: &Path, opt: &prqlc::Options) -> Result<()> {

Ok(())
}

#[cfg(test)]
mod tests {
use std::fs;

use tempfile::TempDir;

use super::*;

/// A file that fails to compile used to abort the initial pass, so
/// `prqlc watch` exited before printing "Watching path" — the one state
/// watch mode exists to iterate out of. The other files must still compile,
/// and the walk must reach the end.
#[test]
fn initial_compile_continues_past_a_failing_file() {
let dir = TempDir::new().unwrap();
// Two good files, since `WalkDir` doesn't order entries: whichever
// side of the failing file the walk puts them on, both must compile.
fs::write(dir.path().join("a_good.prql"), "from tracks\n").unwrap();
fs::write(dir.path().join("b_bad.prql"), "from tracks | filter\n").unwrap();
fs::write(dir.path().join("c_good.prql"), "from albums\n").unwrap();

find_and_compile(dir.path(), &prqlc::Options::default()).unwrap();

assert!(dir.path().join("a_good.sql").is_file());
assert!(dir.path().join("c_good.sql").is_file());
assert!(!dir.path().join("b_bad.sql").exists());
}
}
Loading