use super::super::ast::*;
use super::super::tokenizer::CypherToken;
use super::CypherParser;
impl CypherParser {
pub(super) fn identifier_opens_load_csv(&self) -> bool {
self.peek_soft_word("LOAD")
&& self
.soft_word_at(1)
.is_some_and(|w| w.eq_ignore_ascii_case("CSV"))
}
pub(super) fn misplaced_load_csv_error() -> String {
"LOAD CSV must be the first clause of the query: it is a row source, and KGLite streams \
the rest of the pipeline over batches of its rows. Move it to the front, or load the \
file separately and pass the rows in as a parameter."
.to_string()
}
pub(super) fn parse_load_csv_clause(&mut self) -> Result<Clause, String> {
self.expect_soft_word("LOAD", "LOAD CSV")?;
self.expect_soft_word("CSV", "LOAD CSV")?;
let with_headers = if self.check(&CypherToken::With) {
self.advance();
self.expect_soft_word("HEADERS", "LOAD CSV WITH HEADERS")?;
true
} else {
false
};
self.expect_soft_word("FROM", "LOAD CSV")?;
let source = self.parse_expression()?;
self.expect(&CypherToken::As)?;
let variable = self.try_consume_alias_name()?;
let field_terminator = if self.eat_soft_word("FIELDTERMINATOR") {
Some(self.parse_field_terminator()?)
} else {
None
};
Ok(Clause::LoadCsv(LoadCsvClause {
with_headers,
source,
variable,
field_terminator,
}))
}
fn parse_field_terminator(&mut self) -> Result<u8, String> {
let literal = match self.advance().cloned() {
Some(CypherToken::StringLit(s)) => s,
Some(token) => {
return Err(format!(
"FIELDTERMINATOR expects a quoted single-character separator, got {token:?}"
))
}
None => {
return Err(
"FIELDTERMINATOR expects a quoted single-character separator, but \
reached end of query"
.to_string(),
)
}
};
let bytes = literal.as_bytes();
match bytes.len() {
1 => Ok(bytes[0]),
0 => Err("FIELDTERMINATOR cannot be the empty string".to_string()),
_ => Err(format!(
"FIELDTERMINATOR must be a single-byte character, got {literal:?} \
({} bytes). Multi-character and non-ASCII separators are not supported.",
bytes.len()
)),
}
}
}