thoth-dbmanager 0.4.0__py3-none-any.whl → 0.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. thoth_dbmanager/ThothDbManager.py +459 -0
  2. thoth_dbmanager/__init__.py +136 -0
  3. thoth_dbmanager/adapters/__init__.py +21 -0
  4. thoth_dbmanager/adapters/mariadb.py +165 -0
  5. thoth_dbmanager/adapters/mysql.py +165 -0
  6. thoth_dbmanager/adapters/oracle.py +554 -0
  7. thoth_dbmanager/adapters/postgresql.py +444 -0
  8. thoth_dbmanager/adapters/qdrant.py +189 -0
  9. thoth_dbmanager/adapters/sqlite.py +385 -0
  10. thoth_dbmanager/adapters/sqlserver.py +583 -0
  11. thoth_dbmanager/adapters/supabase.py +249 -0
  12. thoth_dbmanager/core/__init__.py +13 -0
  13. thoth_dbmanager/core/factory.py +272 -0
  14. thoth_dbmanager/core/interfaces.py +271 -0
  15. thoth_dbmanager/core/registry.py +220 -0
  16. thoth_dbmanager/documents.py +155 -0
  17. thoth_dbmanager/dynamic_imports.py +250 -0
  18. thoth_dbmanager/helpers/__init__.py +0 -0
  19. thoth_dbmanager/helpers/multi_db_generator.py +508 -0
  20. thoth_dbmanager/helpers/preprocess_values.py +159 -0
  21. thoth_dbmanager/helpers/schema.py +376 -0
  22. thoth_dbmanager/helpers/search.py +117 -0
  23. thoth_dbmanager/lsh/__init__.py +21 -0
  24. thoth_dbmanager/lsh/core.py +182 -0
  25. thoth_dbmanager/lsh/factory.py +76 -0
  26. thoth_dbmanager/lsh/manager.py +170 -0
  27. thoth_dbmanager/lsh/storage.py +96 -0
  28. thoth_dbmanager/plugins/__init__.py +23 -0
  29. thoth_dbmanager/plugins/mariadb.py +436 -0
  30. thoth_dbmanager/plugins/mysql.py +408 -0
  31. thoth_dbmanager/plugins/oracle.py +150 -0
  32. thoth_dbmanager/plugins/postgresql.py +145 -0
  33. thoth_dbmanager/plugins/qdrant.py +41 -0
  34. thoth_dbmanager/plugins/sqlite.py +170 -0
  35. thoth_dbmanager/plugins/sqlserver.py +149 -0
  36. thoth_dbmanager/plugins/supabase.py +224 -0
  37. {thoth_dbmanager-0.4.0.dist-info → thoth_dbmanager-0.4.2.dist-info}/METADATA +9 -6
  38. thoth_dbmanager-0.4.2.dist-info/RECORD +41 -0
  39. thoth_dbmanager-0.4.2.dist-info/top_level.txt +1 -0
  40. thoth_dbmanager-0.4.0.dist-info/RECORD +0 -5
  41. thoth_dbmanager-0.4.0.dist-info/top_level.txt +0 -1
  42. {thoth_dbmanager-0.4.0.dist-info → thoth_dbmanager-0.4.2.dist-info}/WHEEL +0 -0
  43. {thoth_dbmanager-0.4.0.dist-info → thoth_dbmanager-0.4.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,271 @@
1
+ """
2
+ Abstract interfaces for database plugins and adapters.
3
+ """
4
+ from abc import ABC, abstractmethod
5
+ from typing import Any, Dict, List, Optional, Union
6
+ from ..documents import (
7
+ BaseThothDbDocument,
8
+ TableDocument,
9
+ ColumnDocument,
10
+ QueryDocument,
11
+ SchemaDocument,
12
+ ForeignKeyDocument,
13
+ IndexDocument,
14
+ ThothDbType
15
+ )
16
+
17
+
18
+ class DbAdapter(ABC):
19
+ """
20
+ Abstract adapter interface for database operations.
21
+ Similar to ThothHaystackVectorStore adapter pattern.
22
+ """
23
+
24
+ def __init__(self, connection_params: Dict[str, Any]):
25
+ """
26
+ Initialize the database adapter.
27
+
28
+ Args:
29
+ connection_params: Database connection parameters
30
+ """
31
+ self.connection_params = connection_params
32
+ self.connection = None
33
+ self._initialized = False
34
+
35
+ @abstractmethod
36
+ def connect(self) -> None:
37
+ """Establish database connection"""
38
+ pass
39
+
40
+ @abstractmethod
41
+ def disconnect(self) -> None:
42
+ """Close database connection"""
43
+ pass
44
+
45
+ @abstractmethod
46
+ def execute_query(self, query: str, params: Optional[Dict] = None, fetch: Union[str, int] = "all", timeout: int = 60) -> Any:
47
+ """
48
+ Execute SQL query through adapter.
49
+
50
+ Args:
51
+ query: SQL query string
52
+ params: Query parameters
53
+ fetch: How to fetch results ('all', 'one', or number)
54
+ timeout: Query timeout in seconds
55
+
56
+ Returns:
57
+ Query results
58
+ """
59
+ pass
60
+
61
+ @abstractmethod
62
+ def get_tables_as_documents(self) -> List[TableDocument]:
63
+ """Return tables as document objects"""
64
+ pass
65
+
66
+ @abstractmethod
67
+ def get_columns_as_documents(self, table_name: str) -> List[ColumnDocument]:
68
+ """Return columns as document objects"""
69
+ pass
70
+
71
+ @abstractmethod
72
+ def get_foreign_keys_as_documents(self) -> List[ForeignKeyDocument]:
73
+ """Return foreign keys as document objects"""
74
+ pass
75
+
76
+ @abstractmethod
77
+ def get_schemas_as_documents(self) -> List[SchemaDocument]:
78
+ """Return schemas as document objects"""
79
+ pass
80
+
81
+ @abstractmethod
82
+ def get_indexes_as_documents(self, table_name: Optional[str] = None) -> List[IndexDocument]:
83
+ """Return indexes as document objects"""
84
+ pass
85
+
86
+ @abstractmethod
87
+ def get_unique_values(self) -> Dict[str, Dict[str, List[str]]]:
88
+ """
89
+ Get unique values from the database.
90
+
91
+ Returns:
92
+ Dict[str, Dict[str, List[str]]]: Dictionary where:
93
+ - outer key is table name
94
+ - inner key is column name
95
+ - value is list of unique values
96
+ """
97
+ pass
98
+
99
+ @abstractmethod
100
+ def get_example_data(self, table_name: str, number_of_rows: int = 30) -> Dict[str, List[Any]]:
101
+ """
102
+ Get example data (most frequent values) for each column in a table.
103
+
104
+ Args:
105
+ table_name (str): The name of the table.
106
+ number_of_rows (int, optional): Maximum number of example values to return per column. Defaults to 30.
107
+
108
+ Returns:
109
+ Dict[str, List[Any]]: A dictionary mapping column names to lists of example values.
110
+ """
111
+ pass
112
+
113
+ def health_check(self) -> bool:
114
+ """Check if database connection is healthy"""
115
+ try:
116
+ self.execute_query("SELECT 1", fetch="one")
117
+ return True
118
+ except Exception:
119
+ return False
120
+
121
+ def get_connection_info(self) -> Dict[str, Any]:
122
+ """Get connection information"""
123
+ return {
124
+ "adapter_type": self.__class__.__name__,
125
+ "connection_params": {k: v for k, v in self.connection_params.items() if k != "password"},
126
+ "connected": self.connection is not None,
127
+ "healthy": self.health_check() if self.connection else False
128
+ }
129
+
130
+
131
+ class DbPlugin(ABC):
132
+ """
133
+ Abstract plugin interface for database implementations.
134
+ Each database type should implement this interface.
135
+ """
136
+
137
+ # Plugin metadata
138
+ plugin_name: str = ""
139
+ plugin_version: str = "1.0.0"
140
+ supported_db_types: List[str] = []
141
+ required_dependencies: List[str] = []
142
+
143
+ def __init__(self, db_root_path: str, db_mode: str = "dev", **kwargs):
144
+ """
145
+ Initialize the database plugin.
146
+
147
+ Args:
148
+ db_root_path: Path to the database root directory
149
+ db_mode: Database mode (dev, prod, etc.)
150
+ **kwargs: Additional plugin-specific parameters
151
+ """
152
+ self.db_root_path = db_root_path
153
+ self.db_mode = db_mode
154
+ self.adapter: Optional[DbAdapter] = None
155
+ self._initialized = False
156
+
157
+ @abstractmethod
158
+ def create_adapter(self, **kwargs) -> DbAdapter:
159
+ """Create and return a database adapter instance"""
160
+ pass
161
+
162
+ @abstractmethod
163
+ def validate_connection_params(self, **kwargs) -> bool:
164
+ """Validate connection parameters for this plugin"""
165
+ pass
166
+
167
+ def initialize(self, **kwargs) -> None:
168
+ """Initialize the plugin with connection parameters"""
169
+ if not self.validate_connection_params(**kwargs):
170
+ raise ValueError(f"Invalid connection parameters for {self.plugin_name}")
171
+
172
+ self.adapter = self.create_adapter(**kwargs)
173
+ self.adapter.connect()
174
+ self._initialized = True
175
+
176
+ def get_plugin_info(self) -> Dict[str, Any]:
177
+ """Get plugin metadata"""
178
+ return {
179
+ "name": self.plugin_name,
180
+ "version": self.plugin_version,
181
+ "supported_db_types": self.supported_db_types,
182
+ "required_dependencies": self.required_dependencies,
183
+ "initialized": self._initialized
184
+ }
185
+
186
+ # Document-based operations
187
+ def add_table_document(self, doc: TableDocument) -> str:
188
+ """Add a table document (for metadata storage)"""
189
+ return doc.id
190
+
191
+ def add_column_document(self, doc: ColumnDocument) -> str:
192
+ """Add a column document (for metadata storage)"""
193
+ return doc.id
194
+
195
+ def add_query_document(self, doc: QueryDocument) -> str:
196
+ """Add a query document (for query history/templates)"""
197
+ return doc.id
198
+
199
+ def search_documents(self, query: str, doc_type: ThothDbType, top_k: int = 10) -> List[BaseThothDbDocument]:
200
+ """Search for documents similar to query"""
201
+ # Default implementation - can be overridden by plugins
202
+ return []
203
+
204
+ def get_document(self, doc_id: str) -> Optional[BaseThothDbDocument]:
205
+ """Get document by ID"""
206
+ # Default implementation - can be overridden by plugins
207
+ return None
208
+
209
+ def get_documents_by_type(self, doc_type: ThothDbType) -> List[BaseThothDbDocument]:
210
+ """Get all documents of a specific type"""
211
+ # Default implementation - can be overridden by plugins
212
+ return []
213
+
214
+ # Backward compatibility methods - delegate to adapter
215
+ def execute_sql(self, sql: str, params: Optional[Dict] = None, fetch: Union[str, int] = "all", timeout: int = 60) -> Any:
216
+ """Execute SQL query (backward compatibility)"""
217
+ if not self.adapter:
218
+ raise RuntimeError("Plugin not initialized")
219
+ return self.adapter.execute_query(sql, params, fetch, timeout)
220
+
221
+ def get_tables(self) -> List[Dict[str, str]]:
222
+ """Get tables in old format (backward compatibility)"""
223
+ if not self.adapter:
224
+ raise RuntimeError("Plugin not initialized")
225
+
226
+ table_docs = self.adapter.get_tables_as_documents()
227
+ return [
228
+ {
229
+ "name": doc.table_name,
230
+ "comment": doc.comment
231
+ }
232
+ for doc in table_docs
233
+ ]
234
+
235
+ def get_columns(self, table_name: str) -> List[Dict[str, Any]]:
236
+ """Get columns in old format (backward compatibility)"""
237
+ if not self.adapter:
238
+ raise RuntimeError("Plugin not initialized")
239
+
240
+ column_docs = self.adapter.get_columns_as_documents(table_name)
241
+ return [
242
+ {
243
+ "name": doc.column_name,
244
+ "data_type": doc.data_type,
245
+ "comment": doc.comment,
246
+ "is_pk": doc.is_pk
247
+ }
248
+ for doc in column_docs
249
+ ]
250
+
251
+ def get_foreign_keys(self) -> List[Dict[str, str]]:
252
+ """Get foreign keys in old format (backward compatibility)"""
253
+ if not self.adapter:
254
+ raise RuntimeError("Plugin not initialized")
255
+
256
+ fk_docs = self.adapter.get_foreign_keys_as_documents()
257
+ return [
258
+ {
259
+ "source_table_name": doc.source_table_name,
260
+ "source_column_name": doc.source_column_name,
261
+ "target_table_name": doc.target_table_name,
262
+ "target_column_name": doc.target_column_name
263
+ }
264
+ for doc in fk_docs
265
+ ]
266
+
267
+ def get_unique_values(self) -> Dict[str, Dict[str, List[str]]]:
268
+ """Get unique values (backward compatibility)"""
269
+ if not self.adapter:
270
+ raise RuntimeError("Plugin not initialized")
271
+ return self.adapter.get_unique_values()
@@ -0,0 +1,220 @@
1
+ """
2
+ Plugin registry system for database plugins.
3
+ """
4
+ import logging
5
+ from typing import Dict, List, Type, Optional, Any
6
+ from .interfaces import DbPlugin
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class DbPluginRegistry:
12
+ """
13
+ Registry for database plugins.
14
+ Manages plugin registration, discovery, and instantiation.
15
+ """
16
+
17
+ _plugins: Dict[str, Type[DbPlugin]] = {}
18
+ _instances: Dict[tuple, DbPlugin] = {} # Singleton instances
19
+
20
+ @classmethod
21
+ def register(cls, db_type: str, plugin_class: Type[DbPlugin]) -> None:
22
+ """
23
+ Register a plugin for a specific database type.
24
+
25
+ Args:
26
+ db_type: Database type identifier (e.g., 'postgresql', 'sqlite')
27
+ plugin_class: Plugin class implementing DbPlugin interface
28
+ """
29
+ if not issubclass(plugin_class, DbPlugin):
30
+ raise TypeError(f"Plugin class {plugin_class.__name__} must inherit from DbPlugin")
31
+
32
+ cls._plugins[db_type] = plugin_class
33
+ logger.info(f"Registered plugin {plugin_class.__name__} for database type '{db_type}'")
34
+
35
+ @classmethod
36
+ def unregister(cls, db_type: str) -> None:
37
+ """
38
+ Unregister a plugin for a specific database type.
39
+
40
+ Args:
41
+ db_type: Database type identifier
42
+ """
43
+ if db_type in cls._plugins:
44
+ plugin_class = cls._plugins.pop(db_type)
45
+ logger.info(f"Unregistered plugin {plugin_class.__name__} for database type '{db_type}'")
46
+
47
+ # Remove any cached instances
48
+ keys_to_remove = [key for key in cls._instances.keys() if key[0] == db_type]
49
+ for key in keys_to_remove:
50
+ del cls._instances[key]
51
+
52
+ @classmethod
53
+ def get_plugin_class(cls, db_type: str) -> Type[DbPlugin]:
54
+ """
55
+ Get plugin class for a specific database type.
56
+
57
+ Args:
58
+ db_type: Database type identifier
59
+
60
+ Returns:
61
+ Plugin class
62
+
63
+ Raises:
64
+ ValueError: If no plugin is registered for the database type
65
+ """
66
+ if db_type not in cls._plugins:
67
+ available_types = list(cls._plugins.keys())
68
+ raise ValueError(f"No plugin registered for database type '{db_type}'. Available types: {available_types}")
69
+
70
+ return cls._plugins[db_type]
71
+
72
+ @classmethod
73
+ def create_plugin(cls, db_type: str, db_root_path: str, db_mode: str = "dev", use_singleton: bool = True, **kwargs) -> DbPlugin:
74
+ """
75
+ Create a plugin instance for a specific database type.
76
+
77
+ Args:
78
+ db_type: Database type identifier
79
+ db_root_path: Path to database root directory
80
+ db_mode: Database mode
81
+ use_singleton: Whether to use singleton pattern
82
+ **kwargs: Additional plugin-specific parameters
83
+
84
+ Returns:
85
+ Plugin instance
86
+ """
87
+ plugin_class = cls.get_plugin_class(db_type)
88
+
89
+ if use_singleton:
90
+ # Create instance key for singleton pattern
91
+ instance_key = (db_type, db_root_path, db_mode, tuple(sorted(kwargs.items())))
92
+
93
+ if instance_key in cls._instances:
94
+ return cls._instances[instance_key]
95
+
96
+ # Create new instance
97
+ plugin_instance = plugin_class(db_root_path=db_root_path, db_mode=db_mode, **kwargs)
98
+ cls._instances[instance_key] = plugin_instance
99
+ return plugin_instance
100
+ else:
101
+ # Create new instance without caching
102
+ return plugin_class(db_root_path=db_root_path, db_mode=db_mode, **kwargs)
103
+
104
+ @classmethod
105
+ def list_plugins(cls) -> List[str]:
106
+ """
107
+ List all registered database types.
108
+
109
+ Returns:
110
+ List of registered database type identifiers
111
+ """
112
+ return list(cls._plugins.keys())
113
+
114
+ @classmethod
115
+ def get_plugin_info(cls, db_type: Optional[str] = None) -> Dict[str, Any]:
116
+ """
117
+ Get information about registered plugins.
118
+
119
+ Args:
120
+ db_type: Specific database type, or None for all plugins
121
+
122
+ Returns:
123
+ Plugin information dictionary
124
+ """
125
+ if db_type:
126
+ if db_type not in cls._plugins:
127
+ raise ValueError(f"No plugin registered for database type '{db_type}'")
128
+
129
+ plugin_class = cls._plugins[db_type]
130
+ return {
131
+ "db_type": db_type,
132
+ "plugin_name": plugin_class.plugin_name,
133
+ "plugin_version": plugin_class.plugin_version,
134
+ "supported_db_types": plugin_class.supported_db_types,
135
+ "required_dependencies": plugin_class.required_dependencies,
136
+ "class_name": plugin_class.__name__,
137
+ "module": plugin_class.__module__
138
+ }
139
+ else:
140
+ # Return info for all plugins
141
+ return {
142
+ db_type: {
143
+ "plugin_name": plugin_class.plugin_name,
144
+ "plugin_version": plugin_class.plugin_version,
145
+ "supported_db_types": plugin_class.supported_db_types,
146
+ "required_dependencies": plugin_class.required_dependencies,
147
+ "class_name": plugin_class.__name__,
148
+ "module": plugin_class.__module__
149
+ }
150
+ for db_type, plugin_class in cls._plugins.items()
151
+ }
152
+
153
+ @classmethod
154
+ def validate_plugin(cls, plugin_class: Type[DbPlugin]) -> bool:
155
+ """
156
+ Validate that a plugin class implements the required interface.
157
+
158
+ Args:
159
+ plugin_class: Plugin class to validate
160
+
161
+ Returns:
162
+ True if valid, False otherwise
163
+ """
164
+ try:
165
+ # Check if it's a subclass of DbPlugin
166
+ if not issubclass(plugin_class, DbPlugin):
167
+ logger.error(f"Plugin {plugin_class.__name__} does not inherit from DbPlugin")
168
+ return False
169
+
170
+ # Check required class attributes
171
+ required_attrs = ['plugin_name', 'plugin_version', 'supported_db_types']
172
+ for attr in required_attrs:
173
+ if not hasattr(plugin_class, attr):
174
+ logger.error(f"Plugin {plugin_class.__name__} missing required attribute: {attr}")
175
+ return False
176
+
177
+ # Check required methods
178
+ required_methods = ['create_adapter', 'validate_connection_params']
179
+ for method in required_methods:
180
+ if not hasattr(plugin_class, method) or not callable(getattr(plugin_class, method)):
181
+ logger.error(f"Plugin {plugin_class.__name__} missing required method: {method}")
182
+ return False
183
+
184
+ return True
185
+
186
+ except Exception as e:
187
+ logger.error(f"Error validating plugin {plugin_class.__name__}: {e}")
188
+ return False
189
+
190
+ @classmethod
191
+ def clear_instances(cls) -> None:
192
+ """Clear all cached plugin instances."""
193
+ cls._instances.clear()
194
+ logger.info("Cleared all cached plugin instances")
195
+
196
+ @classmethod
197
+ def clear_registry(cls) -> None:
198
+ """Clear the entire plugin registry."""
199
+ cls._plugins.clear()
200
+ cls._instances.clear()
201
+ logger.info("Cleared plugin registry")
202
+
203
+
204
+ # Auto-discovery decorator
205
+ def register_plugin(db_type: str):
206
+ """
207
+ Decorator to automatically register a plugin.
208
+
209
+ Args:
210
+ db_type: Database type identifier
211
+
212
+ Example:
213
+ @register_plugin("postgresql")
214
+ class PostgreSQLPlugin(DbPlugin):
215
+ pass
216
+ """
217
+ def decorator(plugin_class: Type[DbPlugin]):
218
+ DbPluginRegistry.register(db_type, plugin_class)
219
+ return plugin_class
220
+ return decorator
@@ -0,0 +1,155 @@
1
+ """
2
+ Document models for Thoth SQL Database Manager.
3
+ Provides type-safe document structures similar to thoth_vdb architecture.
4
+ """
5
+ from enum import Enum
6
+ from pydantic import BaseModel, Field
7
+ from typing import Any, Dict, List, Optional, Union
8
+ from uuid import uuid4
9
+
10
+
11
+ class ThothDbType(Enum):
12
+ """Types of documents supported by Thoth SQL Database Manager"""
13
+ TABLE = "table"
14
+ COLUMN = "column"
15
+ QUERY = "query"
16
+ SCHEMA = "schema"
17
+ FOREIGN_KEY = "foreign_key"
18
+ INDEX = "index"
19
+
20
+
21
+ class BaseThothDbDocument(BaseModel):
22
+ """Base class for all Thoth database documents"""
23
+ id: str = Field(default_factory=lambda: str(uuid4()))
24
+ thoth_type: ThothDbType
25
+ text: str = ""
26
+ metadata: Dict[str, Any] = Field(default_factory=dict)
27
+
28
+
29
+ class TableDocument(BaseThothDbDocument):
30
+ """Document representing a database table"""
31
+ table_name: str
32
+ comment: str = ""
33
+ schema_name: str = "public"
34
+ row_count: Optional[int] = None
35
+ thoth_type: ThothDbType = ThothDbType.TABLE
36
+
37
+ def __init__(self, **data):
38
+ super().__init__(**data)
39
+ if not self.text:
40
+ self.text = f"Table: {self.table_name} in schema {self.schema_name}. {self.comment}"
41
+
42
+
43
+ class ColumnDocument(BaseThothDbDocument):
44
+ """Document representing a database column"""
45
+ table_name: str
46
+ column_name: str
47
+ data_type: str
48
+ comment: str = ""
49
+ is_pk: bool = False
50
+ is_nullable: bool = True
51
+ default_value: Optional[str] = None
52
+ max_length: Optional[int] = None
53
+ schema_name: str = "public"
54
+ thoth_type: ThothDbType = ThothDbType.COLUMN
55
+
56
+ def __init__(self, **data):
57
+ super().__init__(**data)
58
+ if not self.text:
59
+ pk_text = " (Primary Key)" if self.is_pk else ""
60
+ nullable_text = " NOT NULL" if not self.is_nullable else ""
61
+ self.text = f"Column: {self.table_name}.{self.column_name} ({self.data_type}{nullable_text}){pk_text}. {self.comment}"
62
+
63
+
64
+ class QueryDocument(BaseThothDbDocument):
65
+ """Document representing a SQL query with metadata"""
66
+ query: str
67
+ query_type: str = "SELECT" # SELECT, INSERT, UPDATE, DELETE, etc.
68
+ description: str = ""
69
+ parameters: List[str] = Field(default_factory=list)
70
+ result_columns: List[str] = Field(default_factory=list)
71
+ execution_time_ms: Optional[float] = None
72
+ thoth_type: ThothDbType = ThothDbType.QUERY
73
+
74
+ def __init__(self, **data):
75
+ super().__init__(**data)
76
+ if not self.text:
77
+ self.text = f"{self.query_type} query: {self.description}. SQL: {self.query[:100]}..."
78
+
79
+
80
+ class SchemaDocument(BaseThothDbDocument):
81
+ """Document representing a database schema"""
82
+ schema_name: str
83
+ description: str = ""
84
+ table_count: Optional[int] = None
85
+ owner: Optional[str] = None
86
+ thoth_type: ThothDbType = ThothDbType.SCHEMA
87
+
88
+ def __init__(self, **data):
89
+ super().__init__(**data)
90
+ if not self.text:
91
+ self.text = f"Schema: {self.schema_name}. {self.description}"
92
+
93
+
94
+ class ForeignKeyDocument(BaseThothDbDocument):
95
+ """Document representing a foreign key relationship"""
96
+ source_table_name: str
97
+ source_column_name: str
98
+ target_table_name: str
99
+ target_column_name: str
100
+ constraint_name: str = ""
101
+ schema_name: str = "public"
102
+ thoth_type: ThothDbType = ThothDbType.FOREIGN_KEY
103
+
104
+ def __init__(self, **data):
105
+ super().__init__(**data)
106
+ if not self.text:
107
+ self.text = f"Foreign Key: {self.source_table_name}.{self.source_column_name} -> {self.target_table_name}.{self.target_column_name}"
108
+
109
+
110
+ class IndexDocument(BaseThothDbDocument):
111
+ """Document representing a database index"""
112
+ index_name: str
113
+ table_name: str
114
+ columns: List[str]
115
+ is_unique: bool = False
116
+ is_primary: bool = False
117
+ index_type: str = "btree"
118
+ schema_name: str = "public"
119
+ thoth_type: ThothDbType = ThothDbType.INDEX
120
+
121
+ def __init__(self, **data):
122
+ super().__init__(**data)
123
+ if not self.text:
124
+ unique_text = "Unique " if self.is_unique else ""
125
+ primary_text = "Primary " if self.is_primary else ""
126
+ self.text = f"{unique_text}{primary_text}Index: {self.index_name} on {self.table_name}({', '.join(self.columns)})"
127
+
128
+
129
+ # Type aliases for convenience
130
+ ThothDocument = Union[
131
+ TableDocument,
132
+ ColumnDocument,
133
+ QueryDocument,
134
+ SchemaDocument,
135
+ ForeignKeyDocument,
136
+ IndexDocument
137
+ ]
138
+
139
+ # Document type mapping for factory methods
140
+ DOCUMENT_TYPE_MAP = {
141
+ ThothDbType.TABLE: TableDocument,
142
+ ThothDbType.COLUMN: ColumnDocument,
143
+ ThothDbType.QUERY: QueryDocument,
144
+ ThothDbType.SCHEMA: SchemaDocument,
145
+ ThothDbType.FOREIGN_KEY: ForeignKeyDocument,
146
+ ThothDbType.INDEX: IndexDocument,
147
+ }
148
+
149
+
150
+ def create_document(doc_type: ThothDbType, **kwargs) -> BaseThothDbDocument:
151
+ """Factory function to create documents by type"""
152
+ document_class = DOCUMENT_TYPE_MAP.get(doc_type)
153
+ if not document_class:
154
+ raise ValueError(f"Unsupported document type: {doc_type}")
155
+ return document_class(**kwargs)