Skip to content
Draft
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
41 changes: 41 additions & 0 deletions packages/server/src/service/package.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,47 @@ describe("service/package", () => {
});
});

it("should probe a data file whose name is outside the bare-path set", async () => {
// DuckDB's table-path check takes a bare path only when every
// character is in a short safe set, so these files used to list
// with `error` and no `info`. A double quote can't appear in a
// Windows file name.
const names = [
"Revenue by month.csv",
"Bob's sales.csv",
"orders (1).csv",
"données.csv",
...(process.platform === "win32" ? [] : ['say "hi".csv']),
];
await fs.mkdir(join(testPackageDirectory, "data"), {
recursive: true,
});
const csv = "Name,Value\nJohn,10\nJane,20\nJim,30";
const conn = new DuckDBConnection("duckdb");

for (const file of names) {
await fs.writeFile(
join(testPackageDirectory, "data", file),
csv,
);
const name = `data/${file}`;
// @ts-expect-error Accessing private static method for testing
const info = await Package.getDatabaseInfo(
testPackageDirectory,
name,
conn,
);
expect(info).toEqual({
name,
columns: [
{ name: "Name", type: "string" },
{ name: "Value", type: "number" },
],
rowCount: 3,
});
}
});

it("should probe an xlsx database file", async () => {
// Real committed fixture: DuckDB's excel extension reads it via the
// same duckdb.table() replacement scan the csv path uses.
Expand Down
9 changes: 8 additions & 1 deletion packages/server/src/service/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3325,8 +3325,15 @@ export class Package {
});
// Forward slashes work on Windows DuckDB and avoid escaping.
const normalizedPath = fullPath.replace(/\\/g, "/");
// DuckDB's table-path check refuses a bare path holding a space or a
// quote, which left such a file listed as unreadable. So the path goes
// in as a single-quoted literal ('' for a quote), which the check
// accepts and canonicalizes to the same SQL a bare path gets. The
// literal sits in a Malloy double-quoted string, so `\` and `"` are
// escaped for that string.
const literal = `'${normalizedPath.replace(/'/g, "''")}'`;
const model = runtime.loadModel(
`source: temp is duckdb.table('${normalizedPath}')`,
`source: temp is duckdb.table("${literal.replace(/[\\"]/g, (c) => `\\${c}`)}")`,
);
const modelDef = await model.getModel();
const fields = (modelDef._modelDef.contents["temp"] as SourceDef).fields;
Expand Down
Loading