Skip to main content

tensorlogic_adapters/database/
sql.rs

1//! SQL query generation and database statistics utilities.
2
3use super::SchemaDatabase;
4use crate::AdapterError;
5
6/// SQL query generator for schema database operations.
7///
8/// This utility generates SQL queries for creating tables and CRUD operations
9/// on schema databases. Can be used with both SQLite and PostgreSQL with
10/// minor dialect adjustments.
11pub struct SchemaDatabaseSQL;
12
13impl SchemaDatabaseSQL {
14    /// Generate CREATE TABLE statements for schema storage.
15    pub fn create_tables_sql() -> Vec<String> {
16        vec![
17            // Schemas table
18            r#"
19            CREATE TABLE IF NOT EXISTS schemas (
20                id INTEGER PRIMARY KEY AUTOINCREMENT,
21                name TEXT NOT NULL,
22                version INTEGER NOT NULL DEFAULT 1,
23                created_at INTEGER NOT NULL,
24                updated_at INTEGER NOT NULL,
25                description TEXT,
26                UNIQUE(name, version)
27            )
28            "#
29            .to_string(),
30            // Domains table
31            r#"
32            CREATE TABLE IF NOT EXISTS domains (
33                id INTEGER PRIMARY KEY AUTOINCREMENT,
34                schema_id INTEGER NOT NULL,
35                name TEXT NOT NULL,
36                cardinality INTEGER NOT NULL,
37                description TEXT,
38                metadata TEXT,
39                FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE,
40                UNIQUE(schema_id, name)
41            )
42            "#
43            .to_string(),
44            // Predicates table
45            r#"
46            CREATE TABLE IF NOT EXISTS predicates (
47                id INTEGER PRIMARY KEY AUTOINCREMENT,
48                schema_id INTEGER NOT NULL,
49                name TEXT NOT NULL,
50                arity INTEGER NOT NULL,
51                description TEXT,
52                constraints TEXT,
53                metadata TEXT,
54                FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE,
55                UNIQUE(schema_id, name)
56            )
57            "#
58            .to_string(),
59            // Predicate arguments table
60            r#"
61            CREATE TABLE IF NOT EXISTS predicate_arguments (
62                id INTEGER PRIMARY KEY AUTOINCREMENT,
63                predicate_id INTEGER NOT NULL,
64                position INTEGER NOT NULL,
65                domain_name TEXT NOT NULL,
66                FOREIGN KEY (predicate_id) REFERENCES predicates(id) ON DELETE CASCADE,
67                UNIQUE(predicate_id, position)
68            )
69            "#
70            .to_string(),
71            // Variables table
72            r#"
73            CREATE TABLE IF NOT EXISTS variables (
74                id INTEGER PRIMARY KEY AUTOINCREMENT,
75                schema_id INTEGER NOT NULL,
76                name TEXT NOT NULL,
77                domain_name TEXT NOT NULL,
78                FOREIGN KEY (schema_id) REFERENCES schemas(id) ON DELETE CASCADE,
79                UNIQUE(schema_id, name)
80            )
81            "#
82            .to_string(),
83            // Indexes for performance
84            "CREATE INDEX IF NOT EXISTS idx_schemas_name ON schemas(name)".to_string(),
85            "CREATE INDEX IF NOT EXISTS idx_domains_schema ON domains(schema_id)".to_string(),
86            "CREATE INDEX IF NOT EXISTS idx_predicates_schema ON predicates(schema_id)".to_string(),
87        ]
88    }
89
90    /// Generate INSERT query for storing a domain.
91    pub fn insert_domain_sql() -> &'static str {
92        r#"
93        INSERT INTO domains (schema_id, name, cardinality, description, metadata)
94        VALUES (?, ?, ?, ?, ?)
95        "#
96    }
97
98    /// Generate INSERT query for storing a predicate.
99    pub fn insert_predicate_sql() -> &'static str {
100        r#"
101        INSERT INTO predicates (schema_id, name, arity, description, constraints, metadata)
102        VALUES (?, ?, ?, ?, ?, ?)
103        "#
104    }
105
106    /// Generate INSERT query for storing a predicate argument.
107    pub fn insert_predicate_arg_sql() -> &'static str {
108        r#"
109        INSERT INTO predicate_arguments (predicate_id, position, domain_name)
110        VALUES (?, ?, ?)
111        "#
112    }
113
114    /// Generate INSERT query for storing a variable.
115    pub fn insert_variable_sql() -> &'static str {
116        r#"
117        INSERT INTO variables (schema_id, name, domain_name)
118        VALUES (?, ?, ?)
119        "#
120    }
121
122    /// Generate SELECT query for loading a schema.
123    pub fn select_schema_sql() -> &'static str {
124        "SELECT id, name, version, created_at, updated_at, description FROM schemas WHERE id = ?"
125    }
126
127    /// Generate SELECT query for loading domains.
128    pub fn select_domains_sql() -> &'static str {
129        "SELECT name, cardinality, description, metadata FROM domains WHERE schema_id = ?"
130    }
131
132    /// Generate SELECT query for loading predicates.
133    pub fn select_predicates_sql() -> &'static str {
134        "SELECT id, name, arity, description, constraints, metadata FROM predicates WHERE schema_id = ?"
135    }
136
137    /// Generate SELECT query for loading predicate arguments.
138    pub fn select_predicate_args_sql() -> &'static str {
139        "SELECT position, domain_name FROM predicate_arguments WHERE predicate_id = ? ORDER BY position"
140    }
141}
142
143/// Statistics about database storage.
144#[derive(Clone, Debug)]
145pub struct DatabaseStats {
146    /// Total number of stored schemas
147    pub total_schemas: usize,
148    /// Total number of domains across all schemas
149    pub total_domains: usize,
150    /// Total number of predicates across all schemas
151    pub total_predicates: usize,
152    /// Total database size in bytes (if applicable)
153    pub size_bytes: Option<usize>,
154}
155
156impl DatabaseStats {
157    /// Create empty statistics.
158    pub fn new() -> Self {
159        Self {
160            total_schemas: 0,
161            total_domains: 0,
162            total_predicates: 0,
163            size_bytes: None,
164        }
165    }
166
167    /// Calculate statistics from a database implementation.
168    pub fn from_database<D: SchemaDatabase>(db: &D) -> Result<Self, AdapterError> {
169        let schemas = db.list_schemas()?;
170        let total_schemas = schemas.len();
171        let total_domains: usize = schemas.iter().map(|s| s.num_domains).sum();
172        let total_predicates: usize = schemas.iter().map(|s| s.num_predicates).sum();
173
174        Ok(Self {
175            total_schemas,
176            total_domains,
177            total_predicates,
178            size_bytes: None,
179        })
180    }
181
182    /// Calculate average domains per schema.
183    pub fn avg_domains_per_schema(&self) -> f64 {
184        if self.total_schemas == 0 {
185            0.0
186        } else {
187            self.total_domains as f64 / self.total_schemas as f64
188        }
189    }
190
191    /// Calculate average predicates per schema.
192    pub fn avg_predicates_per_schema(&self) -> f64 {
193        if self.total_schemas == 0 {
194            0.0
195        } else {
196            self.total_predicates as f64 / self.total_schemas as f64
197        }
198    }
199}
200
201impl Default for DatabaseStats {
202    fn default() -> Self {
203        Self::new()
204    }
205}