tursodatabase/turso · error

Unexpected row for INSERT

Error message

Unexpected row for INSERT

What it means

Batched CSV import builds one multi-row INSERT (CSV_INSERT_BATCH_SIZE rows per statement) and runs it via conn.query with run_with_row_callback. INSERT must not produce rows, so a row callback firing means the executor returned rows for DML - an engine-side invariant break and the CLI panics to surface it.

Source

Thrown at cli/commands/import.rs:165

            };

            if !record.is_empty() {
                let values: Vec<String> = record
                    .iter()
                    .map(|r| format!("'{}'", r.replace("'", "''")))
                    .collect();
                batch.push(values.join(","));

                if batch.len() >= CSV_INSERT_BATCH_SIZE {
                    println!("Inserting batch of {} rows", batch.len());
                    let insert_string =
                        format!("INSERT INTO {} VALUES ({});", args.table, batch.join("),("));

                    match self.conn.query(insert_string) {
                        Ok(rows) => {
                            if let Some(mut rows) = rows {
                                let res = rows.run_with_row_callback(|_| {
                                    panic!("Unexpected row for INSERT");
                                });
                                match res {
                                    Ok(_) => {
                                        success_rows += batch.len() as u64;
                                    }
                                    Err(LimboError::Interrupt) => {
                                        let _ = self.writer.write_all(b"interrupt\n");

                                        failed_rows += batch.len() as u64;
                                    }
                                    Err(LimboError::Busy) => {
                                        let _ = self.writer.write_all(b"database is busy\n");

                                        failed_rows += batch.len() as u64;
                                    }
                                    Err(e) => {
                                        let _ = self.writer.write_all(
                                            format!("Error executing query: {e:?}\n").as_bytes(),

View on GitHub (pinned to 244cde92a7)

Solutions

  1. Update the CLI/engine to a fixed build; report with the failing CSV and generated batch statement if current
  2. Fall back to importing with the sqlite3 CLI (same file format), then reopen the database with tursodb
  3. Import a small sample first to detect a broken import path before loading large files

Example fix

# before
tursodb import big.csv --table t   # panics: Unexpected row for INSERT

# after
sqlite3 t.db ".mode csv" ".import big.csv t"
# then continue using t.db from tursodb
Defensive patterns

Strategy: fallback

Validate before calling

import subprocess
rc = subprocess.run(['tursodb','import','sample.csv','--table','t']).returncode
if rc != 0:
    raise SystemExit('import path broken; fall back to sqlite3 .import')

Prevention

When it happens

Trigger: `tursodb import` over a CSV large enough to fill a batch, on a build where INSERT execution signals rows; also if the constructed multi-row statement string degenerates into something row-returning (e.g. RETURNING-handling regressions).

Common situations: Bulk-loading realistic CSVs on a regression build; CI import tests after executor changes.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20). Data as JSON: /api/errors/eec42a237e8a5d73. Report an issue: GitHub.