mirror of
https://github.com/RaisFast/raisfast.git
synced 2026-09-23 16:02:25 +00:00
203 lines
5.1 KiB
Rust
203 lines
5.1 KiB
Rust
use std::collections::HashMap;
|
|
use std::env;
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
fn main() {
|
|
println!("cargo:rerun-if-changed=migrations");
|
|
|
|
let out_dir = env::var("OUT_DIR").unwrap();
|
|
let dest = Path::new(&out_dir).join("schema_meta.rs");
|
|
|
|
let schema = parse_schema();
|
|
let code = generate_code(&schema);
|
|
fs::write(&dest, code).unwrap();
|
|
}
|
|
|
|
fn parse_schema() -> HashMap<String, Vec<String>> {
|
|
let mut tables: HashMap<String, Vec<String>> = HashMap::new();
|
|
|
|
for path in &[
|
|
"migrations/sqlite/schema.sqlite.sql",
|
|
"migrations/sqlite/tenantable.sqlite.sql",
|
|
] {
|
|
if let Ok(content) = fs::read_to_string(path) {
|
|
parse_sql(&content, &mut tables);
|
|
}
|
|
}
|
|
|
|
tables
|
|
}
|
|
|
|
fn parse_sql(content: &str, tables: &mut HashMap<String, Vec<String>>) {
|
|
let mut in_create = false;
|
|
let mut current_table = String::new();
|
|
let mut columns: Vec<String> = Vec::new();
|
|
|
|
for line in content.lines() {
|
|
let trimmed = line.trim();
|
|
|
|
if !in_create {
|
|
if let Some(rest) = trimmed.strip_prefix("CREATE TABLE")
|
|
&& let Some(name) = extract_table_name(rest)
|
|
{
|
|
current_table = name;
|
|
columns = Vec::new();
|
|
in_create = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if trimmed.starts_with(')')
|
|
&& !trimmed.contains("DEFAULT")
|
|
&& !trimmed.contains("REFERENCES")
|
|
{
|
|
if !current_table.is_empty() {
|
|
tables.insert(current_table.clone(), columns.clone());
|
|
}
|
|
in_create = false;
|
|
continue;
|
|
}
|
|
|
|
if let Some(col) = extract_column_name(trimmed) {
|
|
columns.push(col);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn extract_table_name(s: &str) -> Option<String> {
|
|
let s = s.trim();
|
|
let s = s.strip_prefix("IF NOT EXISTS").unwrap_or(s).trim();
|
|
let s = s.strip_prefix("IF EXISTS").unwrap_or(s).trim();
|
|
|
|
let name = if s.starts_with('"') {
|
|
s.split('"').nth(1)?.to_string()
|
|
} else if s.starts_with('`') {
|
|
s.split('`').nth(1)?.to_string()
|
|
} else {
|
|
s.split(|c: char| c.is_whitespace() || c == '(')
|
|
.next()?
|
|
.to_string()
|
|
};
|
|
|
|
if name.is_empty() { None } else { Some(name) }
|
|
}
|
|
|
|
fn extract_column_name(line: &str) -> Option<String> {
|
|
let line = line.trim_end_matches(',');
|
|
let line = line.trim();
|
|
|
|
if line.is_empty() || line.starts_with("--") {
|
|
return None;
|
|
}
|
|
|
|
let upper = line.to_uppercase();
|
|
if upper.starts_with("PRIMARY KEY")
|
|
|| upper.starts_with("UNIQUE(")
|
|
|| upper.starts_with("UNIQUE (")
|
|
|| upper.starts_with("CHECK(")
|
|
|| upper.starts_with("CHECK (")
|
|
|| upper.starts_with("FOREIGN KEY")
|
|
|| upper.starts_with("CONSTRAINT")
|
|
{
|
|
return None;
|
|
}
|
|
|
|
let name = if line.starts_with('"') {
|
|
line.split('"').nth(1)?.to_string()
|
|
} else if line.starts_with('`') {
|
|
line.split('`').nth(1)?.to_string()
|
|
} else {
|
|
line.split_whitespace().next()?.to_string()
|
|
};
|
|
|
|
if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
|
|
return None;
|
|
}
|
|
|
|
Some(name)
|
|
}
|
|
|
|
fn generate_code(tables: &HashMap<String, Vec<String>>) -> String {
|
|
let mut entries: Vec<String> = Vec::new();
|
|
for (table, columns) in tables {
|
|
for col in columns {
|
|
if col == "id" || col == "rowid" || col == "tenant_id" {
|
|
continue;
|
|
}
|
|
entries.push(format!(" (\"{table}\", \"{col}\"),"));
|
|
}
|
|
}
|
|
|
|
let mut table_list: Vec<String> = Vec::new();
|
|
for (table, columns) in tables {
|
|
let cols: String = columns
|
|
.iter()
|
|
.map(|c| format!("\"{c}\""))
|
|
.collect::<Vec<_>>()
|
|
.join(", ");
|
|
table_list.push(format!(" (\"{table}\", &[{cols}]),"));
|
|
}
|
|
|
|
let e = entries.join("\n");
|
|
let t = table_list.join("\n");
|
|
|
|
format!(
|
|
r#"// Auto-generated by build.rs — DO NOT EDIT
|
|
|
|
const COLUMNS: &[(&str, &str)] = &[
|
|
{e}
|
|
];
|
|
|
|
const fn str_eq(a: &str, b: &str) -> bool {{
|
|
let a = a.as_bytes();
|
|
let b = b.as_bytes();
|
|
if a.len() != b.len() {{
|
|
return false;
|
|
}}
|
|
let mut i = 0;
|
|
while i < a.len() {{
|
|
if a[i] != b[i] {{
|
|
return false;
|
|
}}
|
|
i += 1;
|
|
}}
|
|
true
|
|
}}
|
|
|
|
/// Returns `true` if the given table has the given column.
|
|
/// `tenant_id`, `id`, and `rowid` are always accepted.
|
|
pub const fn column_exists(table: &str, column: &str) -> bool {{
|
|
if str_eq(column, "tenant_id") || str_eq(column, "id") || str_eq(column, "rowid") {{
|
|
return true;
|
|
}}
|
|
let mut i = 0;
|
|
while i < COLUMNS.len() {{
|
|
if str_eq(COLUMNS[i].0, table) && str_eq(COLUMNS[i].1, column) {{
|
|
return true;
|
|
}}
|
|
i += 1;
|
|
}}
|
|
false
|
|
}}
|
|
|
|
/// All tables and their columns.
|
|
pub const ALL_TABLES: &[(&str, &[&str])] = &[
|
|
{t}
|
|
];
|
|
|
|
/// Returns `true` if the given table exists in the schema.
|
|
pub const fn table_exists(table: &str) -> bool {{
|
|
let mut i = 0;
|
|
while i < ALL_TABLES.len() {{
|
|
if str_eq(ALL_TABLES[i].0, table) {{
|
|
return true;
|
|
}}
|
|
i += 1;
|
|
}}
|
|
false
|
|
}}
|
|
"#
|
|
)
|
|
}
|