Variables: Export Data Sets refuses more data sets or variables than Import Data Sets reads

dataset.export checked only the 1 MiB size, so a document with more than
1000 data sets or 256 variables exported a CSV that dataset.import then
refused. Export now refuses such a document with a message naming the
limit, and the messages have rows in every complete-message catalog.
This commit is contained in:
dotCooCoo
2026-11-02 12:03:37 +07:00
committed by bflatastic
parent adb446049b
commit 1be658c88c
25 changed files with 111 additions and 1 deletions
+1 -1
View File
@@ -740,7 +740,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Export Data Sets…",
["Window", "Variables"],
None,
"{path?} write every dataset as CSV (header: name, then variables); without path → {csv, datasets}",
"{path?} write every dataset as CSV (header: name, then variables); without path → {csv, datasets}. Refused when the document has more than 1000 data sets or 256 variables, the most dataset.import reads",
has_doc,
dataset_export
),
@@ -341,12 +341,20 @@ pub(super) fn dataset_import(s: &mut Session, p: &Value) -> Result<Value> {
}
/// Export every dataset as CSV (`name` then variables). With `path`, write the file; else return `{csv}`.
/// A document with more data sets or variables than [`dataset_import`] reads is refused.
pub(super) fn dataset_export(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "dataset.export";
let st = s.doc()?;
if st.doc.variables.variables.is_empty() {
return Err(bad(C, "define a variable first"));
}
if st.doc.variables.datasets.len() > MAX_CSV_ROWS {
return Err(bad(C, format!("more than {MAX_CSV_ROWS} data sets: Import Data Sets reads at most {MAX_CSV_ROWS}")));
}
// The name column comes first, so the import's column cap leaves room for this many variables.
if st.doc.variables.variables.len() > MAX_CSV_COLS {
return Err(bad(C, format!("more than {MAX_CSV_COLS} variables: Import Data Sets reads at most {MAX_CSV_COLS}")));
}
let csv = datasets_to_csv(&st.doc.variables);
if csv.len() > MAX_CSV_BYTES {
return Err(bad(C, format!("CSV would be larger than {MAX_CSV_BYTES} bytes")));
+27
View File
@@ -1325,6 +1325,33 @@ fn datasets_csv_keeps_spaces_in_quoted_cells() {
assert_eq!(value("C"), text_value(" quoted "));
}
/// Export refuses a document with more data sets or variables than `dataset.import` reads, so
/// every file it writes imports back: 1,000 data sets and 256 variables export and import, and
/// one more of either is refused.
#[test]
fn datasets_export_refuses_more_than_import_reads() {
let mut s = session();
let header: Vec<String> = (1..=256).map(|i| format!("V{i}")).collect();
let rows: String = (1..=1000).map(|i| format!("Set {i},x\n")).collect();
s.execute("dataset.import", &json!({"csv": format!("name,{}\n{rows}", header.join(","))})).unwrap();
let csv = s.execute("dataset.export", &json!({})).unwrap()["csv"].as_str().unwrap().to_string();
s.execute("dataset.import", &json!({"csv": csv})).unwrap();
s.execute("dataset.new", &json!({"name": "Set 1001"})).unwrap();
let err = s.execute("dataset.export", &json!({})).unwrap_err().to_string();
assert!(err.contains("more than 1000 data sets"), "{err}");
s.execute("dataset.delete", &json!({"names": ["Set 1001"]})).unwrap();
s.execute("dataset.export", &json!({})).unwrap();
s.execute("variable.define", &json!({"name": "V257", "kind": "text"})).unwrap();
let err = s.execute("dataset.export", &json!({})).unwrap_err().to_string();
assert!(err.contains("more than 256 variables"), "{err}");
let dir = Folder::new("vars-csv-caps");
let out = dir.file("sets.csv");
assert!(s.execute("dataset.export", &json!({"path": out})).is_err());
assert!(!std::path::Path::new(&out).exists(), "no file is written");
}
/// A header row with a tab and no comma outside quotes makes tabs the separator; a quoted cell
/// may then hold tabs and commas. A header row with a comma outside quotes keeps commas, even
/// when a cell holds a tab. A tab-separated header with an unclosed quote fails with the reason.