Skip to main content

datasources_sqlite/
sql_generate.rs

1use crate::default_value_converter::DefaultValueConverter;
2use crate::default_value_options::DefaultValueOptions;
3use crate::type_map::TypeMap;
4use datasources::{Result, SpecField};
5use regex::Regex;
6use std::collections::HashMap;
7use std::sync::LazyLock;
8
9pub const QUOTE_LEFT: &str = "\"";
10pub const QUOTE_RIGHT: &str = "\"";
11pub const PROCEDURE_PAYLOAD_TYPE: &str = "JSON";
12
13pub static TYPES: LazyLock<TypeMap> = LazyLock::new(|| {
14    TypeMap::new(HashMap::from([
15        ("string".into(), "TEXT".into()),
16        ("character".into(), "TEXT".into()),
17        ("integer".into(), "INTEGER".into()),
18        ("unsignedinteger".into(), "INTEGER".into()),
19        ("smallinteger".into(), "INTEGER".into()),
20        ("unsignedsmallinteger".into(), "INTEGER".into()),
21        ("biginteger".into(), "INTEGER".into()),
22        ("unsignedbiginteger".into(), "INTEGER".into()),
23        ("float".into(), "REAL".into()),
24        ("decimal".into(), "NUMERIC".into()),
25        ("boolean".into(), "BOOLEAN".into()),
26        ("datetime".into(), "TEXT".into()),
27        ("binary".into(), "BLOB".into()),
28        ("uuid".into(), "TEXT".into()),
29        ("reference".into(), "INTEGER".into()),
30    ]))
31});
32
33pub fn convert_type(field: &SpecField) -> Result<String> {
34    TYPES.convert_field(field)
35}
36
37pub fn convert_default(spec_type: &str, text: &str) -> Result<Option<String>> {
38    DEFAULTS.convert(spec_type, text)
39}
40
41pub static DEFAULTS: LazyLock<DefaultValueConverter> = LazyLock::new(|| {
42    DefaultValueConverter::new(DefaultValueOptions {
43        new_id: || "(lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-4' || substr(lower(hex(randomblob(2))), 2) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(lower(hex(randomblob(2))), 2) || '-' || lower(hex(randomblob(6))))".into(),
44        now: || "(strftime('%Y-%m-%dT%H:%M:%f', 'now', 'localtime'))".into(),
45        utc_now: || "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))".into(),
46        boolean_on: "1".into(),
47        boolean_off: "0".into(),
48        hex: |arg| format!("X'{arg}'"),
49        hex_reverse: Regex::new(r"^X'(.*)'$").expect("hex"),
50        quote: None,
51        quote_reverse: None,
52    })
53});
54
55const KEYWORDS: &[&str] = &[
56    "AFTER", "ALTER", "AND", "AS", "AT", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BLOB",
57    "BOOLEAN", "BY", "CASCADE", "CAST", "CONSTRAINT", "CREATE", "DECIMAL", "DEFAULT", "DELETE",
58    "DROP", "EACH", "ELSE", "END", "EXISTS", "FALSE", "FLOAT", "FOR", "FOREIGN", "FROM",
59    "GENERATED", "IF", "IN", "INDEX", "INNER", "INSERT", "INT", "INTEGER", "INTO", "JOIN", "KEY",
60    "NOT", "NULL", "NUMERIC", "ON", "OR", "ORDER", "PRIMARY", "REAL", "REFERENCES", "RETURN",
61    "RETURNING", "ROW", "SELECT", "SET", "TABLE", "TEXT", "THEN", "TRIGGER", "TRUE", "UNIQUE",
62    "UPDATE", "VALUES", "VARCHAR", "WHEN", "WHERE",
63];
64
65static TOKEN: LazyLock<Regex> = LazyLock::new(|| {
66    Regex::new(r#"('(?:''|[^'])*')|("(?:""|[^"])*")|(--[^\n]*)|(/\*[\s\S]*?\*/)|(?P<word>[A-Za-z_][A-Za-z0-9_]*)"#)
67        .expect("token")
68});
69
70pub fn apply_keywords(sql: &str, keyword: &dyn Fn(&str) -> String) -> String {
71    TOKEN
72        .replace_all(sql, |caps: &regex::Captures| {
73            if let Some(ident) = caps.name("word") {
74                if KEYWORDS.iter().any(|word| word.eq_ignore_ascii_case(ident.as_str())) {
75                    return keyword(ident.as_str());
76                }
77            }
78            caps.get(0).map(|m| m.as_str().to_string()).unwrap_or_default()
79        })
80        .into_owned()
81}