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
2 changes: 2 additions & 0 deletions skills/hotdata/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ hotdata ingest new-datasource --service buckets --bucket-url s3://bucket/prefix
# Files in S3/GCS/Azure buckets (csv, jsonl, parquet); --glob narrows the match.
# Public buckets need no credentials; private ones take --config @creds.json
# ({"aws_access_key_id": …, "aws_secret_access_key": …, "endpoint_url": …}).
# --continuous keeps a bucket datasource synced: it's re-run incrementally on a
# schedule, appending only newly-arrived objects (no re-read of the whole bucket).

hotdata ingest new-datasource --service iceberg --config @catalog.json --table ns.orders
# Iceberg via a REST catalog. --table is REQUIRED (repeatable, namespace.table).
Expand Down
4 changes: 4 additions & 0 deletions src/client/ingest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,10 @@ pub struct IngestRequest {
pub tables: Vec<String>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub validate_only: bool,
/// filesystem only: keep this datasource continuously synced — the scheduler
/// re-runs it incrementally (append only new objects). Ignored otherwise.
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub continuous: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub database_id: Option<String>,
}
Expand Down
33 changes: 33 additions & 0 deletions src/commands/ingest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,11 @@ pub struct CreateArgs {
#[arg(long)]
glob: Option<String>,

/// Keep this datasource continuously synced — refreshed incrementally on a
/// schedule, appending only newly-arrived objects (buckets only)
#[arg(long)]
continuous: bool,

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.

nit: the guided wizard has no matching prompt, so continuous is unreachable interactively (not blocking).

build_filesystem_interactive (src/commands/ingest.rs:527) prompts bucket URL / format / glob and leaves continuous at its Default false. On a terminal with no --service, that's the default path, so a user who wants continuous sync has to know to drop out of the wizard and pass the flag — and any_given() now makes --continuous alone hard-fail with "--service is required" rather than hinting the wizard can't do it. A one-line Confirm/select_optional("Keep continuously synced?") in the filesystem builder would close the gap; up to you whether that belongs in this PR.


/// Catalog type, e.g. rest (iceberg)
#[arg(long = "catalog-type")]
catalog_type: Option<String>,
Expand All @@ -713,6 +718,7 @@ impl CreateArgs {
|| self.bucket_url.is_some()
|| self.format.is_some()
|| self.glob.is_some()
|| self.continuous
|| self.catalog_type.is_some()
|| self.database_id.is_some()
}
Expand Down Expand Up @@ -779,6 +785,7 @@ fn build_create_request(
bucket_url: Some(args.bucket_url.ok_or("buckets connectors need --bucket-url")?),
file_glob: args.glob,
file_format: args.format,
continuous: args.continuous,

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.

super nit: --continuous is silently dropped for every other family (not blocking).

hotdata ingest new-datasource --service postgres --continuous exits 0 and reports success with no continuous sync configured, since only the filesystem arm reads the field. That matches how --glob/--bucket-url already behave for SQL, so it's consistent — but "silently on for nothing" is a worse failure mode than an unused path hint, because the user believes sync is enabled. An early if args.continuous && entry.family != "filesystem" { return Err(...) } would make it explicit and is cheap to unit-test alongside the new test.

..Default::default()
},
"iceberg" => IngestRequest {
Expand Down Expand Up @@ -1837,6 +1844,7 @@ mod tests {
bucket_url: None,
format: None,
glob: None,
continuous: false,
catalog_type: None,
database_id: None,
}
Expand Down Expand Up @@ -1869,6 +1877,31 @@ mod tests {
assert_eq!(req.database_id.as_deref(), Some("db_1"));
}

#[test]
fn create_request_filesystem_carries_continuous_flag() {
let e = entry("buckets", "filesystem");
let mut args = create_args();
args.bucket_url = Some("s3://b/prefix".into());
args.format = Some("jsonl".into());
args.continuous = true;
let req = build_create_request(&e, args, None).unwrap();
assert_eq!(req.family, "filesystem");
assert_eq!(req.bucket_url.as_deref(), Some("s3://b/prefix"));
assert!(req.continuous); // --continuous rides through to the request body

// Default is off, and it serializes only when true (skip_serializing_if).
let mut off = create_args();
off.bucket_url = Some("s3://b".into());
off.format = Some("jsonl".into());
let req_off = build_create_request(&e, off, None).unwrap();
assert!(!req_off.continuous);
assert!(
!serde_json::to_string(&req_off)
.unwrap()
.contains("continuous")
);
}

#[test]
fn create_request_rejects_invalid_names() {
let e = entry("postgres", "sql");
Expand Down
Loading