-
Notifications
You must be signed in to change notification settings - Fork 134
fix: --dir silently read a populated warehouse store as empty #1204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| /** | ||
| * Shared guards for file-backed drivers (DuckDB, SQLite). | ||
| * | ||
| * Both engines create an empty database when asked to open a file that does | ||
| * not exist. For a warehouse connection that is never what the caller wants: | ||
| * a mistyped or mis-resolved path then yields a working connector over an | ||
| * empty database, so every query succeeds and returns nothing. An agent handed | ||
| * that result reports "no tables" instead of an error. | ||
| * | ||
| * Opening a store is therefore read-or-fail by default. Creation is opt-in via | ||
| * `create: true`, which the tools that deliberately materialize a local store | ||
| * (local test scratch databases, schema sync targets) pass explicitly. | ||
| */ | ||
|
|
||
| import * as fs from "fs" | ||
| import type { ConnectionConfig } from "./types" | ||
|
|
||
| /** | ||
| * Whether `dbPath` names a file on the local filesystem, and so can be | ||
| * existence-checked before the driver opens it. | ||
| * | ||
| * Only the exact string `:memory:` is an in-memory database. Both engines | ||
| * treat `:memory:named` — and any other colon-prefixed name — as an ordinary | ||
| * (if oddly named) file: DuckDB really does write a file called | ||
| * `:memory:named`, so those must stay inside the guard. An empty path is | ||
| * DuckDB's in-memory database and SQLite's anonymous temporary one; neither | ||
| * touches disk. | ||
| * | ||
| * A scheme-qualified target is not a local file: MotherDuck (`md:`), object | ||
| * storage (`s3://`), DuckLake, and any other scheme a DuckDB extension | ||
| * provides. Those are left to the driver, which reports an unknown scheme as a | ||
| * missing-extension error rather than silently creating anything. The pattern | ||
| * requires two or more characters before the colon so a Windows drive letter | ||
| * (`C:\data\wh.duckdb`) stays a path. | ||
| */ | ||
| export function isLocalFilePath(dbPath: string): boolean { | ||
| if (dbPath === "" || dbPath === ":memory:") return false | ||
| if (/^[a-zA-Z][a-zA-Z0-9+.-]+:/.test(dbPath)) return false | ||
|
anandgupta42 marked this conversation as resolved.
anandgupta42 marked this conversation as resolved.
|
||
| return true | ||
| } | ||
|
|
||
| /** | ||
| * The store path a file-backed connection names, or a loud failure. | ||
| * | ||
| * Both drivers used to read `(config.path as string) ?? ":memory:"`. That turns | ||
| * ANY failure to carry a path — a config the registry never loaded, a field | ||
| * under the wrong name, a lookup that fell through — into a successful | ||
| * connection over an empty in-memory database. Every query then returns no rows | ||
| * and no error, which reads as a healthy warehouse that happens to be empty. | ||
| * | ||
| * It is a worse failure than creating a store on disk: a stray file can at | ||
| * least be found afterwards, whereas an in-memory database leaves nothing | ||
| * behind to explain the empty answer. `:memory:` remains available, but only | ||
| * when a caller asks for it by name. | ||
| */ | ||
| export function requireStorePath(config: ConnectionConfig, engine: string): string { | ||
| const value = config.path | ||
| if (typeof value === "string" && value !== "") return value | ||
| throw new Error( | ||
|
anandgupta42 marked this conversation as resolved.
|
||
| `${engine} connection is missing its "path". A file-backed warehouse must name its database explicitly — ` + | ||
| `falling back to an in-memory database would answer every query with no rows and no error, ` + | ||
| `which is indistinguishable from a healthy but empty warehouse. ` + | ||
| `Set "path" to the database file, or to ":memory:" if a throwaway empty database is genuinely what you want.`, | ||
| ) | ||
| } | ||
|
|
||
| /** Whether the caller explicitly opted in to creating the store. */ | ||
| export function allowsCreate(config: ConnectionConfig): boolean { | ||
| return config.create === true | ||
| } | ||
|
|
||
| /** | ||
| * Throw unless the store is safe to open: it already exists, the caller opted | ||
| * in to creating it, or the path is not a local file at all. | ||
| * | ||
| * @param engine Human-readable engine name used in the error message. | ||
| * @param allowCreate Whether this open will actually create the store. Defaults | ||
| * to the config's `create` flag; a driver passes it explicitly when its own | ||
| * options can veto creation — SQLite never creates a read-only connection. | ||
| */ | ||
| export function assertStoreExists( | ||
| config: ConnectionConfig, | ||
| dbPath: string, | ||
| engine: string, | ||
| allowCreate: boolean = allowsCreate(config), | ||
| ): void { | ||
| if (allowCreate) return | ||
| if (!isLocalFilePath(dbPath)) return | ||
| if (fs.existsSync(dbPath)) return | ||
|
anandgupta42 marked this conversation as resolved.
anandgupta42 marked this conversation as resolved.
|
||
| throw new Error( | ||
| `${engine} database file not found: "${dbPath}". ` + | ||
| `Opening a warehouse connection never creates the database — an empty store would answer every query with no rows. ` + | ||
| `Check the "path" in your connection config (relative paths resolve against the config file's directory, not the current directory), ` + | ||
| `or pass "create": true if this store is meant to be created.`, | ||
| ) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.