Wrap Rust's open and sanitize-filename crates for SQL — open a path, a URL or the output of a query with your desktop's own program, and turn a string into a usable file name
Compatibility Matrix
This table covers all DuckDB releases that have not reached end-of-life, including LTS releases, and shows the 2 most recent patch releases of each. Older patch releases are not listed, but the extension may be available for them as well.
| Platform | 1.4.4 | 1.4.5 | 1.5.5 | 1.5.6 | 2.0.0 |
|---|---|---|---|---|---|
| Linux AMD64 | |||||
| Linux ARM64 | |||||
| macOS AMD64 | |||||
| macOS ARM64 | |||||
| Windows AMD64 | |||||
| Windows ARM64 | |||||
| WebAssembly |
Installing and Loading
INSTALL duckfn_open FROM community;
LOAD duckfn_open;
Example
-- What would open this on the machine running the query, without opening anything
SELECT op_open_commands('report.html');
-- Hand an existing path to the desktop's default program
SELECT op_open_path('./report.html');
-- Most query output is content, not a path: write it out and get the path back
SELECT op_write_file('{"a":1}', 'json');
About duckfn_open
duckfn_open wraps two mature Rust crates for SQL: open, which hands a
path or a URL to whatever the desktop is configured to open it with, and
sanitize-filename, whose file-name rules it puts on SQL.
Nine scalar function names, in three groups:
- Paths and URLs —
op_open_path(path[, app])andop_open_path_detached(path[, app])hand an existing file or link to the desktop, waiting for it or not;op_open_commands(path)lists the commands a launch would try, running none of them. - Content —
op_write_file(content|blob, ext[, opts])writes text or bytes to a file and returns the path, starting nothing;op_open_content(…)writes it and opens it in the same call;op_open_html(content)is the zero-configuration version of that;op_print(text[, tail])writes to stdout, past the result set's column formatting, and hands the text back. The optional third argument is the registeredop_write_file_optionsSTRUCT (dir,name,part). - File names —
op_sanitize_filename(value[, replacement[, truncate, windows]])rewrites a value into a usable name, andop_sanitize_filename_check(…)answers whether it already is one. This pair starts no process and touches no file, so a name taken from the data can be checked before anything is built on it.
It is aimed at interactive work: a person is at the machine, running DuckDB, and wants the result in front of them. A launcher is desktop software, so DuckDB embedded in an application is outside what this is for — on a machine with no desktop only the half that starts no process is of any use. The extension starts a process and writes the file you ask for (into the directory you name, or the system temp directory); it reads no configuration, opens no network connection of its own, and does not inspect the content it passes along. Requires DuckDB 1.3 or newer.
Documentation: user guide · function reference · source and development notes.
Added Functions
| function_name | function_type | description | comment | examples |
|---|---|---|---|---|
| op_open_commands | scalar | Lists, without running them, the launcher commands op_open_path would try in order | The read-only pair of op_open_path: nothing is started, so the result is stable enough to assert in CI on a headless runner | [SELECT op_open_commands('report.html')] |
| op_open_content | scalar | Writes a piece of text or binary content out to a file and opens it with the program the desktop is configured to use | op_write_file plus op_open_path in one call: it returns the path it wrote, so a launcher that cannot start is reported as an error carrying that path instead of a lost false; it does not wait for the program to exit, and the BLOB signatures write the bytes exactly as they are | [SELECT op_open_content(logo_blob, 'png'); SELECT op_open_content(read_text('report.html'), 'html'); SELECT op_open_content(svg, 'svg', {'dir':'./out', 'part':'sales'})] |
| op_open_html | scalar | Writes an HTML string to a temporary file and opens it in the system default browser, returning the file path | The zero-configuration shortcut: op_open_content(content, 'html') with every option left at its default (system temp directory, generated name); needs no configuration because SQL renders HTML | [SELECT op_open_html('<h1>hello</h1>'); SELECT op_open_html(read_text('report.html'))] |
| op_open_path | scalar | Opens a local path or URL with the program the desktop is configured to use | true once a launcher has been started, false when none could be started; NULL input short-circuits to NULL and starts nothing | [SELECT op_open_path('report.html'); SELECT op_open_path('https://duckdb.org', 'firefox')] |
| op_open_path_detached | scalar | Opens a local path or URL and returns as soon as the launcher is started | The non-blocking pair of op_open_path: true means a process was started, not that the target opened | [SELECT op_open_path_detached('https://duckdb.org')] |
| op_print | scalar | Prints a piece of text straight to stdout, bypassing the result set's column formatting | For character art and other line-aligned output the CLI would otherwise wrap: the text is written to stdout as it is and handed back unchanged, so the call can stay in a projection; the optional second argument is what follows the text instead of the default newline | [SELECT op_print(report, chr(10) || chr(10)); SELECT op_print(tp_qr('https://query.farm')); SELECT op_print('rows: ' || count(*)) FROM t] |
| op_sanitize_filename | scalar | Rewrites a string into something usable as a file name, following sanitize-filename | The crate's own defaults: the illegal characters are deleted (pass a replacement to see where they were), the 255-byte cut is applied, and the Windows rules are on because a query should answer the same way on every machine | [SELECT op_sanitize_filename('quarterly/report:v2.csv'); SELECT op_sanitize_filename(report_title, '_')] |
| op_sanitize_filename_check | scalar | Tells whether a string is already usable as a file name, rewriting nothing | The predicate half of sanitize-filename: true means op_sanitize_filename with the same options would answer with the value it was given | [SELECT count(*) FROM files WHERE NOT op_sanitize_filename_check(name)] |
| op_write_file | scalar | Writes a piece of text or binary content out to a file and returns its path, with the extension required and the directory, file name and name segment optional | Starts nothing: hand the returned path to op_open_path to open it. dir defaults to the system temp directory (created when missing), name defaults to a generated open- |
[SELECT op_write_file(read_blob('logo.png'), 'png'); SELECT op_write_file('{"a":1}', 'json'); SELECT op_write_file(svg, 'svg', {'dir':'./out', 'part':'sales'})] |
Overloaded Functions
This extension does not add any function overloads.
Added Types
| type_name | type_size | logical_type | type_category | internal |
|---|---|---|---|---|
| op_write_file_options | 0 | STRUCT | COMPOSITE | false |
Added Settings
| name | description | input_type | scope | aliases | typed_value |
|---|---|---|---|---|---|
| temp_directory | Set the directory to which temporary files are written. | VARCHAR | GLOBAL | [] | post.db.tmp |