Printing to stdout
op_print
op_print(text VARCHAR) -> VARCHAR
op_print(text VARCHAR, tail VARCHAR) -> VARCHAR
Writes text straight to stdout, and returns that same text. The console gets it as it is: the DuckDB CLI lays out its results — column widths, borders, wrapping — and character art is read line by line, so one VARCHAR cell destroys it. A QR code makes that concrete:
INSTALL textplot FROM community;
LOAD textplot;
SELECT op_print(tp_qr('https://query.farm'));
-- the code, one row of blocks per line, as it was rendered
The console copy is the point; that is the output this function exists for. The text also comes back, so the same call puts a second copy in the result set, where the CLI lays it out like any other VARCHAR — a QR code there is wrapped across lines and no longer scans. That copy is easy to be rid of, because the call is an ordinary VARCHAR expression and anything that consumes a string consumes it:
-- the code on the console, one number in the result set
SELECT length(op_print(tp_qr('https://query.farm')));
-- keep the value instead, when the note is the column you wanted
SELECT op_print('rows: ' || count(*)) AS note FROM t;
-- or keep the call out of the SELECT list altogether
SELECT count(*) FROM (VALUES ('https://query.farm')) t(url) WHERE op_print(tp_qr(url)) IS NOT NULL;
The two signatures split on what follows the text: with no tail a newline is added, so the next line of
output does not run into this one; with a tail exactly that is written — '' means "keep going on this
line", chr(10) || chr(10) means "leave a blank line". The tail is not part of the return value. Each call
flushes, because Rust buffers stdout by line and a tail without a newline would otherwise leave the text in
the buffer.
Being a scalar function, it is evaluated per row, so a multi-row query prints a running log in execution
order rather than a laid-out report — the report is what the result set is for. Nothing is folded or cached
either: op_print is registered volatile, so SELECT op_print('x') FROM t prints one line per row, constant
argument or not. The same flag is why a call inside a larger expression is evaluated exactly once —
length(op_print(…)) prints one copy, not two — and why EXPLAIN prints nothing at all: a query that is only
being planned never runs it.
NULL in, NULL out: a row with no input prints nothing and answers NULL.