Separate producing a result from printing it: the executor hands over column names, rows and counts, and a printer chosen by .mode writes them as a table or as CSV. INSERT reports how many rows it added.
So far the executor prints. The code that pulls rows from the plan also writes 1 | ada | ada@example.com and (3 rows). That was fine while there was one way to show a result. There will be more: a second output mode in this stage, and later a network protocol that sends rows to a client instead of printing them. If every one of them lives inside the executor, the executor is mostly about formatting.
So this stage separates the two jobs. The executor produces a result: the column names, the rows one at a time, how many there were. Or, for an INSERT, how many rows it added. Or an error. A printer turns a result into lines. The table format you already print becomes one printer, and CSV becomes a second, chosen with .mode csv.
The executor no longer writes lines. It tells a printer what it has, in order, and the printer writes:
The rows still travel one at a time, as the Volcano executor produces them. A printer that needed every row before it could print the first one, to line up columns for instance, would have to keep the whole result in memory. Neither printer here needs to, so neither does.
A SELECT's result is rows. An INSERT's result is a number: how many rows it added. The printer now says it: Insert statement executed successfully (1 rows).
An INSERT always adds exactly one row, so the count is always 1 here, and still rows, like the SELECT footer. It matters more in delete-update, where one statement can change any number of rows and the count is how you know. A statement that fails prints its error and no success line, as before.
CSV is comma-separated values: the header and each row on one line, the values separated by commas, and no footer. It is what a spreadsheet opens and what most tools import.
The problem is a value that holds a comma. Smith, Bob written as it is would read back as two values. So a value that contains a comma, or a double quote, is written between double quotes, and every double quote inside it is written twice:
| Value | Field |
|---|---|
ada | ada |
Smith, Bob | "Smith, Bob" |
Cy "the kid" | "Cy ""the kid""" |
a b | a b |
| (empty) | (empty) |
Nothing else is quoted: not spaces, not numbers, not empty strings. A row with only an id is 2,,. These are the common CSV rules, the ones spreadsheets follow when they read a file.
Only SELECT's rows change with the mode. Error lines, the INSERT success line, [PLAN], [BTREE] and [PAGER] lines, and .help print exactly the same in both modes. They are not rows, so no printer formats them.
The mode lasts until the session ends. The next session, and every -c run, starts in table mode again.
The executor stops printing. It tells a printer what it has, in order.
The format of every earlier stage.
One line per row, values separated by commas, and no footer.
.mode is a meta command, like .help.
These are the exact lines the tests check. The full definition is on the output contract page, sections 10 and 11.
| Input | Output |
|---|---|
| a successful INSERT | Insert statement executed successfully (1 rows) |
.mode csv, .mode table (any case) | nothing |
.mode alone, .mode <anything else>, .mode csv <more> | [ERROR:01001] <message>, and the mode does not change |
| SELECT in CSV mode | the column names joined by ,, then one line per row, values joined by ,; no footer |
a CSV value containing , or " | ", the value with each " doubled, " |
| any other line in CSV mode | exactly as in table mode |
$droid --db users.dbWelcome to droid!>insert into users (id, name, email) values (1, 'ada', 'ada@example.com');Insert statement executed successfully (1 rows)>insert into users (id, name, email) values (2, 'Smith, Bob', 'bob@example.com');Insert statement executed successfully (1 rows)>insert into users (id, name, email) values (3, 'Cy "the kid"', 'cy@example.com');Insert statement executed successfully (1 rows)>insert into users (id, name, email) values (1, 'eve', 'eve@example.com');[ERROR:00701] Key 1 already exists>select * from users;id | name | email1 | ada | ada@example.com2 | Smith, Bob | bob@example.com3 | Cy "the kid" | cy@example.com(3 rows)>.mode csv>select * from users;id,name,email1,ada,ada@example.com2,"Smith, Bob",bob@example.com3,"Cy ""the kid""",cy@example.com>select name from users where id > 5;name>btree find 2[BTREE] Find key=2: FOUND (page=0 cell=1)[BTREE] Row: id=2 name='Smith, Bob' email='bob@example.com'>.mode json[ERROR:01001] Unknown output mode>.mode table>select id, name from users where id = 2;id | name2 | Smith, Bob(1 rows)>.exitexiting.. good bye!
The CSV SELECT that matched nothing printed its header line alone. btree find printed the same lines it prints in table mode, comma and all. After the refused .mode json the session was still in CSV mode, until .mode table.
(3 rows) at the end of a CSV result is a fourth row to anything that reads the file. CSV has no footer."Cy "the kid"" ends the field at the second quote. Every " inside a quoted field is written "".[PLAN] line with a comma in it gets quoted, or loses its brackets. Only rows go through the CSV printer..mode json prints 01001 and then leaves the session in some third state. An error changes nothing.-c run, makes the next session start in CSV. Every session starts in table mode.(1 rows), or (0 rows), after its error. A failed statement prints its error and nothing else.The page above is the same for every language. These are the only language-specific hints.
Printer class with virtual columns, row and end, and two subclasses.Box<dyn Printer>, or a two-variant enum with a match in each method.switch in each operation is enough for two modes.