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.
- thoth_dbmanager/ThothDbManager.py +459 -0
- thoth_dbmanager/__init__.py +136 -0
- thoth_dbmanager/adapters/__init__.py +21 -0
- thoth_dbmanager/adapters/mariadb.py +165 -0
- thoth_dbmanager/adapters/mysql.py +165 -0
- thoth_dbmanager/adapters/oracle.py +554 -0
- thoth_dbmanager/adapters/postgresql.py +444 -0
- thoth_dbmanager/adapters/qdrant.py +189 -0
- thoth_dbmanager/adapters/sqlite.py +385 -0
- thoth_dbmanager/adapters/sqlserver.py +583 -0
- thoth_dbmanager/adapters/supabase.py +249 -0
- thoth_dbmanager/core/__init__.py +13 -0
- thoth_dbmanager/core/factory.py +272 -0
- thoth_dbmanager/core/interfaces.py +271 -0
- thoth_dbmanager/core/registry.py +220 -0
- thoth_dbmanager/documents.py +155 -0
- thoth_dbmanager/dynamic_imports.py +250 -0
- thoth_dbmanager/helpers/__init__.py +0 -0
- thoth_dbmanager/helpers/multi_db_generator.py +508 -0
- thoth_dbmanager/helpers/preprocess_values.py +159 -0
- thoth_dbmanager/helpers/schema.py +376 -0
- thoth_dbmanager/helpers/search.py +117 -0
- thoth_dbmanager/lsh/__init__.py +21 -0
- thoth_dbmanager/lsh/core.py +182 -0
- thoth_dbmanager/lsh/factory.py +76 -0
- thoth_dbmanager/lsh/manager.py +170 -0
- thoth_dbmanager/lsh/storage.py +96 -0
- thoth_dbmanager/plugins/__init__.py +23 -0
- thoth_dbmanager/plugins/mariadb.py +436 -0
- thoth_dbmanager/plugins/mysql.py +408 -0
- thoth_dbmanager/plugins/oracle.py +150 -0
- thoth_dbmanager/plugins/postgresql.py +145 -0
- thoth_dbmanager/plugins/qdrant.py +41 -0
- thoth_dbmanager/plugins/sqlite.py +170 -0
- thoth_dbmanager/plugins/sqlserver.py +149 -0
- thoth_dbmanager/plugins/supabase.py +224 -0
- {thoth_dbmanager-0.4.0.dist-info → thoth_dbmanager-0.4.2.dist-info}/METADATA +9 -6
- thoth_dbmanager-0.4.2.dist-info/RECORD +41 -0
- thoth_dbmanager-0.4.2.dist-info/top_level.txt +1 -0
- thoth_dbmanager-0.4.0.dist-info/RECORD +0 -5
- thoth_dbmanager-0.4.0.dist-info/top_level.txt +0 -1
- {thoth_dbmanager-0.4.0.dist-info → thoth_dbmanager-0.4.2.dist-info}/WHEEL +0 -0
- {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)
|