1use parking_lot::RwLock;
36use serde::{Deserialize, Serialize};
37use std::collections::HashMap;
38use std::sync::Arc;
39
40pub struct LazySchema {
49 name: String,
51 schema: Option<String>,
53 tables: RwLock<HashMap<String, LazyTableEntry>>,
55 table_names: Vec<String>,
57 enums: Vec<LazyEnum>,
59}
60
61enum LazyTableEntry {
63 Raw(serde_json::Value),
65 Parsed(LazyTable),
67}
68
69impl LazySchema {
70 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
72 let raw: RawSchema = serde_json::from_str(json)?;
73 Ok(Self::from_raw(raw))
74 }
75
76 pub fn from_raw(raw: RawSchema) -> Self {
78 let table_names: Vec<String> = raw.tables.iter().map(|t| t.name.clone()).collect();
79
80 let mut tables = HashMap::with_capacity(raw.tables.len());
81 for table in raw.tables {
82 let name = table.name.clone();
83 tables.insert(name, LazyTableEntry::Raw(serde_json::to_value(table).unwrap()));
84 }
85
86 Self {
87 name: raw.name,
88 schema: raw.schema,
89 tables: RwLock::new(tables),
90 table_names,
91 enums: raw.enums.into_iter().map(LazyEnum::from).collect(),
92 }
93 }
94
95 pub fn name(&self) -> &str {
97 &self.name
98 }
99
100 pub fn schema(&self) -> Option<&str> {
102 self.schema.as_deref()
103 }
104
105 pub fn table_names(&self) -> &[String] {
107 &self.table_names
108 }
109
110 pub fn table_count(&self) -> usize {
112 self.table_names.len()
113 }
114
115 pub fn get_table(&self, name: &str) -> Option<LazyTable> {
117 let tables = self.tables.read();
118
119 if let Some(entry) = tables.get(name) {
121 match entry {
122 LazyTableEntry::Parsed(table) => return Some(table.clone()),
123 LazyTableEntry::Raw(_) => {
124 }
126 }
127 } else {
128 return None;
129 }
130
131 drop(tables);
132
133 let mut tables = self.tables.write();
135
136 if let Some(entry) = tables.get(name) {
138 if let LazyTableEntry::Parsed(table) = entry {
139 return Some(table.clone());
140 }
141 }
142
143 if let Some(entry) = tables.remove(name) {
145 if let LazyTableEntry::Raw(raw) = entry {
146 match serde_json::from_value::<RawTable>(raw) {
147 Ok(raw_table) => {
148 let table = LazyTable::from_raw(raw_table);
149 tables.insert(name.to_string(), LazyTableEntry::Parsed(table.clone()));
150 return Some(table);
151 }
152 Err(_) => return None,
153 }
154 }
155 }
156
157 None
158 }
159
160 pub fn has_table(&self, name: &str) -> bool {
162 self.table_names.iter().any(|n| n == name)
163 }
164
165 pub fn enums(&self) -> &[LazyEnum] {
167 &self.enums
168 }
169
170 pub fn get_enum(&self, name: &str) -> Option<&LazyEnum> {
172 self.enums.iter().find(|e| e.name == name)
173 }
174
175 pub fn memory_stats(&self) -> LazySchemaStats {
177 let tables = self.tables.read();
178 let parsed = tables
179 .values()
180 .filter(|e| matches!(e, LazyTableEntry::Parsed(_)))
181 .count();
182 let raw = tables.len() - parsed;
183
184 LazySchemaStats {
185 total_tables: self.table_names.len(),
186 parsed_tables: parsed,
187 unparsed_tables: raw,
188 enum_count: self.enums.len(),
189 }
190 }
191}
192
193#[derive(Clone)]
199pub struct LazyTable {
200 inner: Arc<LazyTableInner>,
201}
202
203struct LazyTableInner {
204 name: String,
206 schema: Option<String>,
208 comment: Option<String>,
210 primary_key: Vec<String>,
212 columns: RwLock<LazyColumns>,
214 foreign_keys: RwLock<LazyForeignKeys>,
216 indexes: RwLock<LazyIndexes>,
218}
219
220enum LazyColumns {
221 Raw(Vec<serde_json::Value>),
222 Parsed(Vec<LazyColumn>),
223}
224
225enum LazyForeignKeys {
226 Raw(Vec<serde_json::Value>),
227 Parsed(Vec<LazyForeignKey>),
228}
229
230enum LazyIndexes {
231 Raw(Vec<serde_json::Value>),
232 Parsed(Vec<LazyIndex>),
233}
234
235impl LazyTable {
236 fn from_raw(raw: RawTable) -> Self {
237 Self {
238 inner: Arc::new(LazyTableInner {
239 name: raw.name,
240 schema: raw.schema,
241 comment: raw.comment,
242 primary_key: raw.primary_key,
243 columns: RwLock::new(LazyColumns::Raw(
244 raw.columns
245 .into_iter()
246 .map(|c| serde_json::to_value(c).unwrap())
247 .collect(),
248 )),
249 foreign_keys: RwLock::new(LazyForeignKeys::Raw(
250 raw.foreign_keys
251 .into_iter()
252 .map(|f| serde_json::to_value(f).unwrap())
253 .collect(),
254 )),
255 indexes: RwLock::new(LazyIndexes::Raw(
256 raw.indexes
257 .into_iter()
258 .map(|i| serde_json::to_value(i).unwrap())
259 .collect(),
260 )),
261 }),
262 }
263 }
264
265 pub fn name(&self) -> &str {
267 &self.inner.name
268 }
269
270 pub fn schema(&self) -> Option<&str> {
272 self.inner.schema.as_deref()
273 }
274
275 pub fn comment(&self) -> Option<&str> {
277 self.inner.comment.as_deref()
278 }
279
280 pub fn primary_key(&self) -> &[String] {
282 &self.inner.primary_key
283 }
284
285 pub fn columns(&self) -> Vec<LazyColumn> {
287 {
289 let columns = self.inner.columns.read();
290 if let LazyColumns::Parsed(cols) = &*columns {
291 return cols.clone();
292 }
293 }
294
295 let mut columns = self.inner.columns.write();
297
298 if let LazyColumns::Parsed(cols) = &*columns {
300 return cols.clone();
301 }
302
303 if let LazyColumns::Raw(raw) = &*columns {
305 let parsed: Vec<LazyColumn> = raw
306 .iter()
307 .filter_map(|v| serde_json::from_value::<RawColumn>(v.clone()).ok())
308 .map(LazyColumn::from)
309 .collect();
310 let result = parsed.clone();
311 *columns = LazyColumns::Parsed(parsed);
312 return result;
313 }
314
315 vec![]
316 }
317
318 pub fn get_column(&self, name: &str) -> Option<LazyColumn> {
320 self.columns().into_iter().find(|c| c.name() == name)
321 }
322
323 pub fn column_count(&self) -> usize {
325 let columns = self.inner.columns.read();
326 match &*columns {
327 LazyColumns::Raw(raw) => raw.len(),
328 LazyColumns::Parsed(parsed) => parsed.len(),
329 }
330 }
331
332 pub fn foreign_keys(&self) -> Vec<LazyForeignKey> {
334 {
336 let fks = self.inner.foreign_keys.read();
337 if let LazyForeignKeys::Parsed(fks) = &*fks {
338 return fks.clone();
339 }
340 }
341
342 let mut fks = self.inner.foreign_keys.write();
344
345 if let LazyForeignKeys::Parsed(fks) = &*fks {
346 return fks.clone();
347 }
348
349 if let LazyForeignKeys::Raw(raw) = &*fks {
350 let parsed: Vec<LazyForeignKey> = raw
351 .iter()
352 .filter_map(|v| serde_json::from_value::<RawForeignKey>(v.clone()).ok())
353 .map(LazyForeignKey::from)
354 .collect();
355 let result = parsed.clone();
356 *fks = LazyForeignKeys::Parsed(parsed);
357 return result;
358 }
359
360 vec![]
361 }
362
363 pub fn indexes(&self) -> Vec<LazyIndex> {
365 {
367 let idxs = self.inner.indexes.read();
368 if let LazyIndexes::Parsed(idxs) = &*idxs {
369 return idxs.clone();
370 }
371 }
372
373 let mut idxs = self.inner.indexes.write();
375
376 if let LazyIndexes::Parsed(idxs) = &*idxs {
377 return idxs.clone();
378 }
379
380 if let LazyIndexes::Raw(raw) = &*idxs {
381 let parsed: Vec<LazyIndex> = raw
382 .iter()
383 .filter_map(|v| serde_json::from_value::<RawIndex>(v.clone()).ok())
384 .map(LazyIndex::from)
385 .collect();
386 let result = parsed.clone();
387 *idxs = LazyIndexes::Parsed(parsed);
388 return result;
389 }
390
391 vec![]
392 }
393}
394
395#[derive(Clone)]
401pub struct LazyColumn {
402 name: String,
403 db_type: String,
404 nullable: bool,
405 default: Option<String>,
406 auto_increment: bool,
407 is_primary_key: bool,
408 is_unique: bool,
409 comment: Option<String>,
410}
411
412impl LazyColumn {
413 pub fn name(&self) -> &str {
415 &self.name
416 }
417
418 pub fn db_type(&self) -> &str {
420 &self.db_type
421 }
422
423 pub fn is_nullable(&self) -> bool {
425 self.nullable
426 }
427
428 pub fn default(&self) -> Option<&str> {
430 self.default.as_deref()
431 }
432
433 pub fn is_auto_increment(&self) -> bool {
435 self.auto_increment
436 }
437
438 pub fn is_primary_key(&self) -> bool {
440 self.is_primary_key
441 }
442
443 pub fn is_unique(&self) -> bool {
445 self.is_unique
446 }
447
448 pub fn comment(&self) -> Option<&str> {
450 self.comment.as_deref()
451 }
452}
453
454impl From<RawColumn> for LazyColumn {
455 fn from(raw: RawColumn) -> Self {
456 Self {
457 name: raw.name,
458 db_type: raw.db_type,
459 nullable: raw.nullable,
460 default: raw.default,
461 auto_increment: raw.auto_increment,
462 is_primary_key: raw.is_primary_key,
463 is_unique: raw.is_unique,
464 comment: raw.comment,
465 }
466 }
467}
468
469#[derive(Clone)]
475pub struct LazyForeignKey {
476 name: String,
477 columns: Vec<String>,
478 referenced_table: String,
479 referenced_columns: Vec<String>,
480 on_delete: String,
481 on_update: String,
482}
483
484impl LazyForeignKey {
485 pub fn name(&self) -> &str {
487 &self.name
488 }
489
490 pub fn columns(&self) -> &[String] {
492 &self.columns
493 }
494
495 pub fn referenced_table(&self) -> &str {
497 &self.referenced_table
498 }
499
500 pub fn referenced_columns(&self) -> &[String] {
502 &self.referenced_columns
503 }
504
505 pub fn on_delete(&self) -> &str {
507 &self.on_delete
508 }
509
510 pub fn on_update(&self) -> &str {
512 &self.on_update
513 }
514}
515
516impl From<RawForeignKey> for LazyForeignKey {
517 fn from(raw: RawForeignKey) -> Self {
518 Self {
519 name: raw.name,
520 columns: raw.columns,
521 referenced_table: raw.referenced_table,
522 referenced_columns: raw.referenced_columns,
523 on_delete: raw.on_delete,
524 on_update: raw.on_update,
525 }
526 }
527}
528
529#[derive(Clone)]
535pub struct LazyIndex {
536 name: String,
537 columns: Vec<String>,
538 is_unique: bool,
539 is_primary: bool,
540 index_type: Option<String>,
541}
542
543impl LazyIndex {
544 pub fn name(&self) -> &str {
546 &self.name
547 }
548
549 pub fn columns(&self) -> &[String] {
551 &self.columns
552 }
553
554 pub fn is_unique(&self) -> bool {
556 self.is_unique
557 }
558
559 pub fn is_primary(&self) -> bool {
561 self.is_primary
562 }
563
564 pub fn index_type(&self) -> Option<&str> {
566 self.index_type.as_deref()
567 }
568}
569
570impl From<RawIndex> for LazyIndex {
571 fn from(raw: RawIndex) -> Self {
572 Self {
573 name: raw.name,
574 columns: raw.columns,
575 is_unique: raw.is_unique,
576 is_primary: raw.is_primary,
577 index_type: raw.index_type,
578 }
579 }
580}
581
582#[derive(Clone)]
588pub struct LazyEnum {
589 name: String,
590 schema: Option<String>,
591 values: Vec<String>,
592}
593
594impl LazyEnum {
595 pub fn name(&self) -> &str {
597 &self.name
598 }
599
600 pub fn schema(&self) -> Option<&str> {
602 self.schema.as_deref()
603 }
604
605 pub fn values(&self) -> &[String] {
607 &self.values
608 }
609}
610
611impl From<RawEnum> for LazyEnum {
612 fn from(raw: RawEnum) -> Self {
613 Self {
614 name: raw.name,
615 schema: raw.schema,
616 values: raw.values,
617 }
618 }
619}
620
621#[derive(Debug, Clone, Default)]
627pub struct LazySchemaStats {
628 pub total_tables: usize,
630 pub parsed_tables: usize,
632 pub unparsed_tables: usize,
634 pub enum_count: usize,
636}
637
638impl LazySchemaStats {
639 pub fn parse_ratio(&self) -> f64 {
641 if self.total_tables == 0 {
642 0.0
643 } else {
644 self.parsed_tables as f64 / self.total_tables as f64
645 }
646 }
647}
648
649pub trait ParseOnDemand {
655 type Output;
657
658 fn is_parsed(&self) -> bool;
660
661 fn parse(&self) -> Self::Output;
663}
664
665#[derive(Debug, Deserialize, Serialize)]
670struct RawSchema {
671 #[serde(default)]
672 name: String,
673 schema: Option<String>,
674 #[serde(default)]
675 tables: Vec<RawTable>,
676 #[serde(default)]
677 enums: Vec<RawEnum>,
678}
679
680#[derive(Debug, Deserialize, Serialize)]
681struct RawTable {
682 name: String,
683 schema: Option<String>,
684 comment: Option<String>,
685 #[serde(default)]
686 columns: Vec<RawColumn>,
687 #[serde(default)]
688 primary_key: Vec<String>,
689 #[serde(default)]
690 foreign_keys: Vec<RawForeignKey>,
691 #[serde(default)]
692 indexes: Vec<RawIndex>,
693}
694
695#[derive(Debug, Deserialize, Serialize)]
696struct RawColumn {
697 name: String,
698 db_type: String,
699 #[serde(default)]
700 nullable: bool,
701 default: Option<String>,
702 #[serde(default)]
703 auto_increment: bool,
704 #[serde(default)]
705 is_primary_key: bool,
706 #[serde(default)]
707 is_unique: bool,
708 comment: Option<String>,
709}
710
711#[derive(Debug, Deserialize, Serialize)]
712struct RawForeignKey {
713 name: String,
714 #[serde(default)]
715 columns: Vec<String>,
716 referenced_table: String,
717 #[serde(default)]
718 referenced_columns: Vec<String>,
719 #[serde(default = "default_action")]
720 on_delete: String,
721 #[serde(default = "default_action")]
722 on_update: String,
723}
724
725fn default_action() -> String {
726 "NO ACTION".to_string()
727}
728
729#[derive(Debug, Deserialize, Serialize)]
730struct RawIndex {
731 name: String,
732 #[serde(default)]
733 columns: Vec<String>,
734 #[serde(default)]
735 is_unique: bool,
736 #[serde(default)]
737 is_primary: bool,
738 index_type: Option<String>,
739}
740
741#[derive(Debug, Deserialize, Serialize)]
742struct RawEnum {
743 name: String,
744 schema: Option<String>,
745 #[serde(default)]
746 values: Vec<String>,
747}
748
749#[cfg(test)]
750mod tests {
751 use super::*;
752
753 fn sample_schema_json() -> &'static str {
754 r#"{
755 "name": "test_db",
756 "schema": "public",
757 "tables": [
758 {
759 "name": "users",
760 "columns": [
761 {"name": "id", "db_type": "integer", "is_primary_key": true},
762 {"name": "email", "db_type": "varchar(255)", "nullable": false},
763 {"name": "name", "db_type": "varchar(100)", "nullable": true}
764 ],
765 "primary_key": ["id"],
766 "indexes": [
767 {"name": "users_email_idx", "columns": ["email"], "is_unique": true}
768 ]
769 },
770 {
771 "name": "posts",
772 "columns": [
773 {"name": "id", "db_type": "integer", "is_primary_key": true},
774 {"name": "user_id", "db_type": "integer"},
775 {"name": "title", "db_type": "varchar(255)"}
776 ],
777 "primary_key": ["id"],
778 "foreign_keys": [
779 {
780 "name": "posts_user_fk",
781 "columns": ["user_id"],
782 "referenced_table": "users",
783 "referenced_columns": ["id"]
784 }
785 ]
786 }
787 ],
788 "enums": [
789 {"name": "status", "values": ["pending", "active", "archived"]}
790 ]
791 }"#
792 }
793
794 #[test]
795 fn test_lazy_schema_from_json() {
796 let schema = LazySchema::from_json(sample_schema_json()).unwrap();
797
798 assert_eq!(schema.name(), "test_db");
799 assert_eq!(schema.table_count(), 2);
800 assert!(schema.has_table("users"));
801 assert!(schema.has_table("posts"));
802 }
803
804 #[test]
805 fn test_lazy_table_names_no_parse() {
806 let schema = LazySchema::from_json(sample_schema_json()).unwrap();
807
808 let names = schema.table_names();
810 assert_eq!(names.len(), 2);
811
812 let stats = schema.memory_stats();
814 assert_eq!(stats.parsed_tables, 0);
815 assert_eq!(stats.unparsed_tables, 2);
816 }
817
818 #[test]
819 fn test_lazy_table_parsing() {
820 let schema = LazySchema::from_json(sample_schema_json()).unwrap();
821
822 let users = schema.get_table("users").unwrap();
824 assert_eq!(users.name(), "users");
825
826 let stats = schema.memory_stats();
828 assert_eq!(stats.parsed_tables, 1);
829 assert_eq!(stats.unparsed_tables, 1);
830 }
831
832 #[test]
833 fn test_lazy_columns() {
834 let schema = LazySchema::from_json(sample_schema_json()).unwrap();
835 let users = schema.get_table("users").unwrap();
836
837 assert_eq!(users.column_count(), 3);
839
840 let columns = users.columns();
842 assert_eq!(columns.len(), 3);
843
844 let email = users.get_column("email").unwrap();
846 assert_eq!(email.db_type(), "varchar(255)");
847 assert!(!email.is_nullable());
848 }
849
850 #[test]
851 fn test_lazy_foreign_keys() {
852 let schema = LazySchema::from_json(sample_schema_json()).unwrap();
853 let posts = schema.get_table("posts").unwrap();
854
855 let fks = posts.foreign_keys();
856 assert_eq!(fks.len(), 1);
857
858 let fk = &fks[0];
859 assert_eq!(fk.name(), "posts_user_fk");
860 assert_eq!(fk.referenced_table(), "users");
861 }
862
863 #[test]
864 fn test_lazy_indexes() {
865 let schema = LazySchema::from_json(sample_schema_json()).unwrap();
866 let users = schema.get_table("users").unwrap();
867
868 let indexes = users.indexes();
869 assert_eq!(indexes.len(), 1);
870
871 let idx = &indexes[0];
872 assert_eq!(idx.name(), "users_email_idx");
873 assert!(idx.is_unique());
874 }
875
876 #[test]
877 fn test_lazy_enums() {
878 let schema = LazySchema::from_json(sample_schema_json()).unwrap();
879
880 let enums = schema.enums();
881 assert_eq!(enums.len(), 1);
882
883 let status = schema.get_enum("status").unwrap();
884 assert_eq!(status.values(), &["pending", "active", "archived"]);
885 }
886
887 #[test]
888 fn test_cached_access() {
889 let schema = LazySchema::from_json(sample_schema_json()).unwrap();
890
891 let users1 = schema.get_table("users").unwrap();
893 let users2 = schema.get_table("users").unwrap();
894
895 assert_eq!(users1.name(), users2.name());
897 assert_eq!(users1.column_count(), users2.column_count());
898 }
899}
900