-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Allow DuckDB to read tables from S3 in cudf_polars benchmark runner #24139
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -554,7 +554,7 @@ class RunConfig: | |
| # Query selection & dataset | ||
| queries: list[int] | ||
| query_set: str | ||
| dataset_path: Path | ||
| dataset_path: str | Path | ||
| scale_factor: int | float | ||
| suffix: str | ||
| qualification: bool = False | ||
|
|
@@ -898,8 +898,18 @@ def print_query_plan( | |
| return logical_plan, plan | ||
|
|
||
|
|
||
| def is_remote_path(path: os.PathLike | str) -> bool: | ||
| """Return True if `path` is an object-storage URL rather than a local path.""" | ||
| return "://" in str(path) | ||
|
|
||
|
|
||
| def drop_file_page_cache_recursively(path: os.PathLike | str) -> None: | ||
| """Drop the Linux page cache for all files under `path`.""" | ||
| if is_remote_path(path): | ||
| raise ValueError( | ||
| f"--io-mode cold cannot drop the page cache for the remote dataset {path!r}; " | ||
| "use --io-mode lukewarm or point --path at a local copy." | ||
| ) | ||
| try: | ||
| import kvikio | ||
| except ImportError as err: | ||
|
|
@@ -1959,10 +1969,32 @@ def _make_duckdb_config(run_config: RunConfig | None) -> dict[str, Any]: | |
| return config | ||
|
|
||
|
|
||
| def _duckdb_register_views( | ||
| conn: duckdb.DuckDBPyConnection, | ||
| dataset_path: str | Path, | ||
| suffix: str, | ||
| query_set: str, | ||
| ) -> None: | ||
| """Register one view per table in the query set over `dataset_path`.""" | ||
| if is_remote_path(dataset_path): | ||
| # Object-storage reads go through httpfs, and each caller opens its own | ||
| # connection, so the extension and credentials are set up per connection. | ||
| conn.execute("INSTALL httpfs") | ||
| conn.execute("LOAD httpfs") | ||
| conn.execute("CREATE OR REPLACE SECRET (TYPE s3, PROVIDER credential_chain)") | ||
|
|
||
| tbl_names = PDSDS_TABLE_NAMES if query_set == "pdsds" else PDSH_TABLE_NAMES | ||
| for name in tbl_names: | ||
| pattern = str(dataset_path).removesuffix("/") + f"/{name}{suffix}" | ||
| conn.execute( | ||
| f"CREATE OR REPLACE VIEW {name} AS SELECT * FROM parquet_scan('{pattern}');" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Escape single quotes in
Proposed fix for name in tbl_names:
pattern = str(dataset_path).removesuffix("/") + f"/{name}{suffix}"
+ escaped_pattern = pattern.replace("'", "''")
conn.execute(
- f"CREATE OR REPLACE VIEW {name} AS SELECT * FROM parquet_scan('{pattern}');"
+ f"CREATE OR REPLACE VIEW {name} AS SELECT * FROM parquet_scan('{escaped_pattern}');"
)🧰 Tools🪛 OpenGrep (1.28.0)[ERROR] 1989-1991: SQL query built via f-string passed to execute()/executemany(). Use parameterized queries with placeholders instead. (coderabbit.sql-injection.python-fstring-execute) 🤖 Prompt for AI Agents |
||
| ) | ||
|
|
||
|
|
||
| def print_duckdb_plan( | ||
| q_id: int, | ||
| sql: str, | ||
| dataset_path: Path, | ||
| dataset_path: str | Path, | ||
| suffix: str, | ||
| query_set: str, | ||
| args: argparse.Namespace, | ||
|
|
@@ -1972,18 +2004,8 @@ def print_duckdb_plan( | |
| if duckdb is None: | ||
| raise ImportError(duckdb_err) | ||
|
|
||
| if query_set == "pdsds": | ||
| tbl_names = PDSDS_TABLE_NAMES | ||
| else: | ||
| tbl_names = PDSH_TABLE_NAMES | ||
|
|
||
| with duckdb.connect(config=_make_duckdb_config(run_config)) as conn: | ||
| for name in tbl_names: | ||
| pattern = (Path(dataset_path) / name).as_posix() + suffix | ||
| conn.execute( | ||
| f"CREATE OR REPLACE VIEW {name} AS " | ||
| f"SELECT * FROM parquet_scan('{pattern}');" | ||
| ) | ||
| _duckdb_register_views(conn, dataset_path, suffix, query_set) | ||
|
|
||
| if args.explain_logical and args.explain: | ||
| conn.execute("PRAGMA explain_output = 'all';") | ||
|
|
@@ -2001,7 +2023,7 @@ def print_duckdb_plan( | |
|
|
||
| def execute_duckdb_query( | ||
| query: str, | ||
| dataset_path: Path, | ||
| dataset_path: str | Path, | ||
| *, | ||
| suffix: str = ".parquet", | ||
| query_set: str = "pdsh", | ||
|
|
@@ -2010,17 +2032,8 @@ def execute_duckdb_query( | |
| """Execute a query with DuckDB.""" | ||
| if duckdb is None: | ||
| raise ImportError(duckdb_err) | ||
| if query_set == "pdsds": | ||
| tbl_names = PDSDS_TABLE_NAMES | ||
| else: | ||
| tbl_names = PDSH_TABLE_NAMES | ||
| with duckdb.connect(config=_make_duckdb_config(run_config)) as conn: | ||
| for name in tbl_names: | ||
| pattern = (Path(dataset_path) / name).as_posix() + suffix | ||
| conn.execute( | ||
| f"CREATE OR REPLACE VIEW {name} AS " | ||
| f"SELECT * FROM parquet_scan('{pattern}');" | ||
| ) | ||
| _duckdb_register_views(conn, dataset_path, suffix, query_set) | ||
| return conn.execute(query).pl() | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Add tests and a benchmark for the remote-path branch.
Add unit tests for local registration, remote
httpfssetup, and cold-mode rejection. Add a unit benchmark that exercises remote DuckDB view registration. The repository guideline requires unit tests and unit benchmarks for this change.🤖 Prompt for AI Agents
Source: Coding guidelines