#![forbid(unsafe_code)]
#![warn(missing_docs)]
use std::borrow::Cow;
use std::fmt::Write as _;
mod schema;
pub use schema::TABLES;
pub(crate) const NULL: bool = false;
pub(crate) const NOT_NULL: bool = true;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Column {
pub name: &'static str,
pub sql_type: &'static str,
pub not_null: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Table {
pub name: &'static str,
pub columns: &'static [Column],
pub csv: &'static str,
}
pub fn table_names() -> impl Iterator<Item = &'static str> {
TABLES.iter().map(|t| t.name)
}
pub fn find(name: &str) -> Option<&'static Table> {
TABLES.iter().find(|t| t.name == name)
}
impl Table {
pub fn column_names(&self) -> Vec<&'static str> {
self.columns.iter().map(|c| c.name).collect()
}
pub fn header(&self) -> Vec<Cow<'static, str>> {
parse_record(self.csv.lines().next().unwrap_or(""))
}
pub fn rows(&self) -> Rows {
let mut lines = self.csv.lines();
lines.next(); Rows { lines }
}
pub fn create_table_sql(&self) -> String {
let mut s = format!("CREATE TABLE \"{}\" (", self.name);
for (i, c) in self.columns.iter().enumerate() {
if i > 0 {
s.push_str(", ");
}
let _ = write!(s, "\"{}\" {}", c.name, c.sql_type);
if c.not_null {
s.push_str(" NOT NULL");
}
}
s.push(')');
s
}
pub fn insert_sql(&self, row: &[Cow<'_, str>]) -> String {
assert_eq!(
row.len(),
self.columns.len(),
"{}: got {} values, want {}",
self.name,
row.len(),
self.columns.len()
);
let mut s = format!("INSERT INTO \"{}\" VALUES (", self.name);
for (i, (c, v)) in self.columns.iter().zip(row).enumerate() {
if i > 0 {
s.push_str(", ");
}
if v.is_empty() {
s.push_str("NULL");
} else if c.is_character() {
let _ = write!(s, "'{}'", v.replace('\'', "''"));
} else {
s.push_str(v);
}
}
s.push(')');
s
}
}
impl Column {
#[must_use]
pub const fn new(
name: &'static str,
sql_type: &'static str,
not_null: bool,
) -> Self {
Self {
name,
sql_type,
not_null,
}
}
pub fn base_type(&self) -> &'static str {
match self.sql_type.find('(') {
Some(i) => &self.sql_type[..i],
None => self.sql_type,
}
}
pub fn is_character(&self) -> bool {
matches!(self.base_type(), "VARCHAR" | "CHAR" | "DATE" | "TIMESTAMP")
}
}
#[derive(Clone, Debug)]
pub struct Rows {
lines: std::str::Lines<'static>,
}
impl Iterator for Rows {
type Item = Vec<Cow<'static, str>>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let line = self.lines.next()?;
if !line.is_empty() {
return Some(parse_record(line));
}
}
}
}
fn parse_record(line: &str) -> Vec<Cow<'_, str>> {
let mut fields = Vec::new();
let bytes = line.as_bytes();
let mut i = 0;
loop {
if i < bytes.len() && bytes[i] == b'"' {
let mut value = String::new();
let mut start = i + 1;
let mut j = start;
while j < bytes.len() {
if bytes[j] == b'"' {
if bytes.get(j + 1) == Some(&b'"') {
value.push_str(&line[start..j + 1]);
j += 2;
start = j;
continue;
}
break;
}
j += 1;
}
value.push_str(&line[start..j.min(bytes.len())]);
fields.push(Cow::Owned(value));
i = (j + 1).min(bytes.len());
} else {
let j = bytes[i..]
.iter()
.position(|&b| b == b',')
.map_or(bytes.len(), |k| i + k);
fields.push(Cow::Borrowed(&line[i..j]));
i = j;
}
if i >= bytes.len() {
return fields;
}
debug_assert_eq!(bytes[i], b',');
i += 1;
if i == bytes.len() {
fields.push(Cow::Borrowed(""));
return fields;
}
}
}