1use std::fmt;
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
93pub enum SqlDialect {
94 #[default]
96 Standard,
97 PostgreSQL,
99 MySQL,
101 SQLite,
103}
104
105impl SqlDialect {
106 pub fn detect(sql: &str) -> Self {
108 let upper = sql.to_uppercase();
109
110 if upper.contains("INSERT IGNORE") || upper.contains("ON DUPLICATE KEY") {
112 return SqlDialect::MySQL;
113 }
114
115 if upper.contains("INSERT OR IGNORE")
117 || upper.contains("INSERT OR REPLACE")
118 || upper.contains("INSERT OR ABORT")
119 {
120 return SqlDialect::SQLite;
121 }
122
123 if upper.contains("ON CONFLICT") {
125 return SqlDialect::PostgreSQL;
126 }
127
128 SqlDialect::Standard
129 }
130}
131
132impl fmt::Display for SqlDialect {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 match self {
135 SqlDialect::Standard => write!(f, "Standard SQL"),
136 SqlDialect::PostgreSQL => write!(f, "PostgreSQL"),
137 SqlDialect::MySQL => write!(f, "MySQL"),
138 SqlDialect::SQLite => write!(f, "SQLite"),
139 }
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub enum FeatureSupport {
146 Full,
148 Partial,
150 Planned,
152 NotSupported,
154}
155
156impl fmt::Display for FeatureSupport {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 match self {
159 FeatureSupport::Full => write!(f, "✅ Full"),
160 FeatureSupport::Partial => write!(f, "🔄 Partial"),
161 FeatureSupport::Planned => write!(f, "📋 Planned"),
162 FeatureSupport::NotSupported => write!(f, "❌ Not Supported"),
163 }
164 }
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
169pub enum SqlFeature {
170 Select,
172 Insert,
173 Update,
174 Delete,
175
176 CreateTable,
178 DropTable,
179 AlterTable,
180 CreateIndex,
181 DropIndex,
182
183 CreateTableIfNotExists,
185 DropTableIfExists,
186 CreateIndexIfNotExists,
187 DropIndexIfExists,
188
189 OnConflictDoNothing,
191 OnConflictDoUpdate,
192 InsertIgnore,
193 InsertOrIgnore,
194 InsertOrReplace,
195 OnDuplicateKeyUpdate,
196
197 Begin,
199 Commit,
200 Rollback,
201 Savepoint,
202
203 Where,
205 OrderBy,
206 Limit,
207 Offset,
208 GroupBy,
209 Having,
210 Distinct,
211
212 InnerJoin,
214 LeftJoin,
215 RightJoin,
216 CrossJoin,
217
218 SubqueryInFrom,
220 SubqueryInWhere,
221 SubqueryInSelect,
222
223 Union,
225 Intersect,
226 Except,
227
228 ParameterizedQueries,
230 CaseWhen,
231 Cast,
232 NullHandling,
233 InList,
234 Between,
235 Like,
236
237 VectorSearch,
239 EmbeddingType,
240 ContextWindow,
241}
242
243pub fn get_feature_support(feature: SqlFeature) -> FeatureSupport {
245 use SqlFeature::*;
246
247 match feature {
248 Select | Insert | Update | Delete => FeatureSupport::Full,
250 CreateTable | DropTable | CreateIndex | DropIndex => FeatureSupport::Full,
251 CreateTableIfNotExists | DropTableIfExists => FeatureSupport::Full,
252 CreateIndexIfNotExists | DropIndexIfExists => FeatureSupport::Full,
253 Begin | Commit | Rollback => FeatureSupport::Full,
254 Where | OrderBy | Limit | Offset | Distinct => FeatureSupport::Full,
255 ParameterizedQueries | NullHandling | InList | Like => FeatureSupport::Full,
256 OnConflictDoNothing | InsertIgnore | InsertOrIgnore => FeatureSupport::Full,
257 VectorSearch | EmbeddingType => FeatureSupport::Full,
258
259 AlterTable => FeatureSupport::Partial, GroupBy | Having => FeatureSupport::Partial, InnerJoin => FeatureSupport::Partial, OnConflictDoUpdate | InsertOrReplace | OnDuplicateKeyUpdate => FeatureSupport::Partial,
264 CaseWhen | Cast | Between => FeatureSupport::Partial,
265 Union => FeatureSupport::Partial,
266 SubqueryInFrom => FeatureSupport::Partial,
267 Savepoint => FeatureSupport::Partial,
268 ContextWindow => FeatureSupport::Partial,
269
270 LeftJoin | RightJoin | CrossJoin => FeatureSupport::Planned,
272 SubqueryInWhere | SubqueryInSelect => FeatureSupport::Planned,
273 Intersect | Except => FeatureSupport::Planned,
274 }
275}
276
277pub struct CompatibilityMatrix;
279
280impl CompatibilityMatrix {
281 pub fn is_supported(feature: SqlFeature) -> bool {
283 matches!(
284 get_feature_support(feature),
285 FeatureSupport::Full | FeatureSupport::Partial
286 )
287 }
288
289 pub fn fully_supported() -> Vec<SqlFeature> {
291 use SqlFeature::*;
292 vec![
293 Select,
294 Insert,
295 Update,
296 Delete,
297 CreateTable,
298 DropTable,
299 CreateIndex,
300 DropIndex,
301 CreateTableIfNotExists,
302 DropTableIfExists,
303 CreateIndexIfNotExists,
304 DropIndexIfExists,
305 Begin,
306 Commit,
307 Rollback,
308 Where,
309 OrderBy,
310 Limit,
311 Offset,
312 Distinct,
313 ParameterizedQueries,
314 NullHandling,
315 InList,
316 Like,
317 OnConflictDoNothing,
318 InsertIgnore,
319 InsertOrIgnore,
320 VectorSearch,
321 EmbeddingType,
322 ]
323 }
324
325 pub fn print_matrix() -> String {
327 let mut output = String::new();
328 output.push_str("# SochDB SQL Compatibility Matrix\n\n");
329
330 output.push_str("## Core DML\n\n");
331 output.push_str("| Feature | Support |\n");
332 output.push_str("|---------|--------|\n");
333 for feature in &[
334 SqlFeature::Select,
335 SqlFeature::Insert,
336 SqlFeature::Update,
337 SqlFeature::Delete,
338 ] {
339 output.push_str(&format!(
340 "| {:?} | {} |\n",
341 feature,
342 get_feature_support(*feature)
343 ));
344 }
345
346 output.push_str("\n## DDL\n\n");
347 output.push_str("| Feature | Support |\n");
348 output.push_str("|---------|--------|\n");
349 for feature in &[
350 SqlFeature::CreateTable,
351 SqlFeature::DropTable,
352 SqlFeature::AlterTable,
353 SqlFeature::CreateIndex,
354 SqlFeature::DropIndex,
355 SqlFeature::CreateTableIfNotExists,
356 SqlFeature::DropTableIfExists,
357 ] {
358 output.push_str(&format!(
359 "| {:?} | {} |\n",
360 feature,
361 get_feature_support(*feature)
362 ));
363 }
364
365 output.push_str("\n## Conflict/Upsert\n\n");
366 output.push_str("| Feature | Support |\n");
367 output.push_str("|---------|--------|\n");
368 for feature in &[
369 SqlFeature::OnConflictDoNothing,
370 SqlFeature::OnConflictDoUpdate,
371 SqlFeature::InsertIgnore,
372 SqlFeature::InsertOrIgnore,
373 SqlFeature::InsertOrReplace,
374 SqlFeature::OnDuplicateKeyUpdate,
375 ] {
376 output.push_str(&format!(
377 "| {:?} | {} |\n",
378 feature,
379 get_feature_support(*feature)
380 ));
381 }
382
383 output
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 #[test]
392 fn test_dialect_detection() {
393 assert_eq!(SqlDialect::detect("SELECT * FROM users"), SqlDialect::Standard);
394 assert_eq!(
395 SqlDialect::detect("INSERT IGNORE INTO users VALUES (1)"),
396 SqlDialect::MySQL
397 );
398 assert_eq!(
399 SqlDialect::detect("INSERT OR IGNORE INTO users VALUES (1)"),
400 SqlDialect::SQLite
401 );
402 assert_eq!(
403 SqlDialect::detect("INSERT INTO users VALUES (1) ON CONFLICT DO NOTHING"),
404 SqlDialect::PostgreSQL
405 );
406 }
407
408 #[test]
409 fn test_feature_support() {
410 assert_eq!(get_feature_support(SqlFeature::Select), FeatureSupport::Full);
411 assert_eq!(
412 get_feature_support(SqlFeature::AlterTable),
413 FeatureSupport::Partial
414 );
415 assert_eq!(
416 get_feature_support(SqlFeature::LeftJoin),
417 FeatureSupport::Planned
418 );
419 }
420
421 #[test]
422 fn test_compatibility_matrix() {
423 assert!(CompatibilityMatrix::is_supported(SqlFeature::Select));
424 assert!(CompatibilityMatrix::is_supported(SqlFeature::AlterTable));
425 assert!(!CompatibilityMatrix::is_supported(SqlFeature::LeftJoin));
426 }
427}