//! CSV Virtual Table. //! //! Port of [csv](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/csv.c) C //! extension: `https://www.sqlite.org/csv.html` //! //! # Example //! //! ```rust,no_run //! # use rusqlite::{Connection, Result}; //! fn example() -> Result<()> { //! // Note: This should be done once (usually when opening the DB). //! let db = Connection::open_in_memory()?; //! rusqlite::vtab::csvtab::load_module(&db)?; //! // Assume my_csv.csv //! let schema = " //! CREATE VIRTUAL TABLE my_csv_data //! USING csv(filename = 'my_csv.csv') //! "; //! db.execute_batch(schema)?; //! // Now the `my_csv_data` (virtual) table can be queried as normal... //! Ok(()) //! } //! ``` use std::fs::File; use std::marker::PhantomData; use std::os::raw::c_int; use std::path::Path; use std::str;
/// Register the "csv" module. /// ```sql /// CREATE VIRTUAL TABLE vtab USING csv( /// filename=FILENAME -- Name of file containing CSV content /// [, schema=SCHEMA] -- Alternative CSV schema. 'CREATE TABLE x(col1 TEXT NOT NULL, col2 INT, ...);' /// [, header=YES|NO] -- First row of CSV defines the names of columns if "yes". Default "no". /// [, columns=N] -- Assume the CSV file contains N columns. /// [, delimiter=C] -- CSV delimiter. Default ','. /// [, quote=C] -- CSV quote. Default '"'. 0 means no quote. /// ); /// ``` pubfn load_module(conn: &Connection) -> Result<()> { let aux: Option<()> = None;
conn.create_module("csv", read_only_module::<CsvTab>(), aux)
}
/// An instance of the CSV virtual table #[repr(C)] struct CsvTab { /// Base class. Must be first
base: ffi::sqlite3_vtab, /// Name of the CSV file
filename: String,
has_headers: bool,
delimiter: u8,
quote: u8, /// Offset to start of data
offset_first_row: csv::Position,
}
let args = &args[3..]; for c_slice in args { let (param, value) = super::parameter(c_slice)?; match param { "filename" => { if !Path::new(value).exists() { return Err(Error::ModuleError(format!("file '{value}' does not exist")));
}
vtab.filename = value.to_owned();
} "schema" => {
schema = Some(value.to_owned());
} "columns" => { iflet Ok(n) = value.parse::<u16>() { if n_col.is_some() { return Err(Error::ModuleError( "more than one 'columns' parameter".to_owned(),
));
} elseif n == 0 { return Err(Error::ModuleError( "must have at least one column".to_owned(),
));
}
n_col = Some(n);
} else { return Err(Error::ModuleError(format!( "unrecognized argument to 'columns': {value}"
)));
}
} "header" => { iflet Some(b) = parse_boolean(value) {
vtab.has_headers = b;
} else { return Err(Error::ModuleError(format!( "unrecognized argument to 'header': {value}"
)));
}
} "delimiter" => { iflet Some(b) = CsvTab::parse_byte(value) {
vtab.delimiter = b;
} else { return Err(Error::ModuleError(format!( "unrecognized argument to 'delimiter': {value}"
)));
}
} "quote" => { iflet Some(b) = CsvTab::parse_byte(value) { if b == b'0' {
vtab.quote = 0;
} else {
vtab.quote = b;
}
} else { return Err(Error::ModuleError(format!( "unrecognized argument to 'quote': {value}"
)));
}
}
_ => { return Err(Error::ModuleError(format!( "unrecognized parameter '{param}'"
)));
}
}
}
if vtab.filename.is_empty() { return Err(Error::ModuleError("no CSV file specified".to_owned()));
}
letmut cols: Vec<String> = Vec::new(); if vtab.has_headers || (n_col.is_none() && schema.is_none()) { letmut reader = vtab.reader()?; if vtab.has_headers {
{ let headers = reader.headers()?; // headers ignored if cols is not empty if n_col.is_none() && schema.is_none() {
cols = headers
.into_iter()
.map(|header| escape_double_quote(header).into_owned())
.collect();
}
}
vtab.offset_first_row = reader.position().clone();
} else { letmut record = csv::ByteRecord::new(); if reader.read_byte_record(&mut record)? { for (i, _) in record.iter().enumerate() {
cols.push(format!("c{i}"));
}
}
}
} elseiflet Some(n_col) = n_col { for i in0..n_col {
cols.push(format!("c{i}"));
}
}
if cols.is_empty() && schema.is_none() { return Err(Error::ModuleError("no column specified".to_owned()));
}
if schema.is_none() { letmut sql = String::from("CREATE TABLE x("); for (i, col) in cols.iter().enumerate() {
sql.push('"');
sql.push_str(col);
sql.push_str("\" TEXT"); if i == cols.len() - 1 {
sql.push_str(");");
} else {
sql.push_str(", ");
}
}
schema = Some(sql);
}
db.config(VTabConfig::DirectOnly)?;
Ok((schema.unwrap(), vtab))
}
// Only a forward full table scan is supported. fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
info.set_estimated_cost(1_000_000.);
Ok(())
}
/// A cursor for the CSV virtual table #[repr(C)] struct CsvTabCursor<'vtab> { /// Base class. Must be first
base: ffi::sqlite3_vtab_cursor, /// The CSV reader object
reader: csv::Reader<File>, /// Current cursor position used as rowid
row_number: usize, /// Values of the current row
cols: csv::StringRecord,
eof: bool,
phantom: PhantomData<&'vtab CsvTab>,
}
fn column(&self, ctx: &mut Context, col: c_int) -> Result<()> { if col < 0 || col as usize >= self.cols.len() { return Err(Error::ModuleError(format!( "column index out of bounds: {col}"
)));
} ifself.cols.is_empty() { return ctx.set_result(&Null);
} // TODO Affinity
ctx.set_result(&self.cols[col as usize].to_owned())
}
fn rowid(&self) -> Result<i64> {
Ok(self.row_number as i64)
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.