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