databendlabs/databend · error · InvalidData

Expected to have terminated string literal after quota '…

Error message

Expected to have terminated string literal after quota ', while consumed buf: {:?}

What it means

Raised by `CursorReadStringExt::fast_read_quoted_text` when it scans a single-quoted literal but never encounters the terminating `'`. It reports the whole buffer consumed so far so the developer can locate where the quote should have appeared.

Solutions

  1. Examine the consumed buffer in the error and add the missing closing `'` at the end.
  2. Ensure quote characters inside the literal are escaped (`\'`) so they do not prematurely interact with the scanner.
  3. Fix the generator/serializer emitting the literal to always close quotes.
  4. Add a pre-parse sanity check that the literal contains an even number of unescaped quotes.

Example fix

// before
let input = "'hello world";
// after
let input = "'hello world'";
Defensive patterns

Strategy: validation

Validate before calling

fn balanced_quotes(input: &str, quote: char) -> bool {
    let mut escaped = false;
    let mut count = 0;
    for c in input.chars() {
        if escaped { escaped = false; continue; }
        match c { '\\' => escaped = true, _ if c == quote => count += 1, _ => {} }
    }
    count % 2 == 0
}

Type guard

fn is_closed_single_quoted(s: &str) -> bool {
    s.starts_with('\'') && s.len() >= 2 && s.ends_with('\'')
}

Try / catch

match fast_read_quoted_text(&mut buf) {
    Ok(s) => s,
    Err(e) => {
        let consumed = e.to_string(); // includes consumed buf
        return Err(anyhow!("missing closing quote; consumed: {consumed}"));
    }
}

Prevention

When it happens

Trigger: Calling `fast_read_quoted_text` on input like `'hello world` — quote opens the literal but EOF is reached before a matching `'` is consumed by the fast-path scanner.

Common situations: Hand-written query/text literals missing the closing quote, template strings where a variable value contained content that consumed the closing quote, or line-oriented input that split a literal across records.

Related errors


AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11). Data as JSON: /api/errors/034f8c3b28a66262. Report an issue: GitHub.

Appendix: source

Thrown at src/common/io/src/cursor_ext/cursor_read_string_ext.rs:181

                    b'\'' => {
                        check_pos(pos + 1, positions)?;
                        buf.push(b'\'');
                    }
                    b'\\' => {
                        check_pos(pos + 1, positions)?;
                        buf.push(b'\\');
                    }
                    _ => {
                        buf.push(b'\\');
                        buf.push(c);
                    }
                }
            } else {
                break;
            }
            start = self.position() as usize;
        }
        Err(std::io::Error::new(
            ErrorKind::InvalidData,
            format!(
                "Expected to have terminated string literal after quota \', while consumed buf: {:?}",
                buf
            ),
        ))
    }
}

// Check that the pre-calculated position is correct.
fn check_pos(curr_pos: usize, positions: &mut VecDeque<usize>) -> Result<()> {
    if let Some(pos) = positions.pop_front()
        && curr_pos == pos
    {
        return Ok(());
    }
    Err(std::io::Error::new(
        ErrorKind::InvalidData,

View on GitHub (pinned to 288d84d76e)