thoth-dbmanager 0.4.0__py3-none-any.whl → 0.4.1__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/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/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.1.dist-info}/METADATA +6 -6
- thoth_dbmanager-0.4.1.dist-info/RECORD +39 -0
- thoth_dbmanager-0.4.1.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.1.dist-info}/WHEEL +0 -0
- {thoth_dbmanager-0.4.0.dist-info → thoth_dbmanager-0.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
"""
|
2
|
+
PostgreSQL plugin implementation.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
from typing import Any, Dict, List
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
from ..core.interfaces import DbPlugin, DbAdapter
|
9
|
+
from ..core.registry import register_plugin
|
10
|
+
from ..adapters.postgresql import PostgreSQLAdapter
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
@register_plugin("postgresql")
|
16
|
+
class PostgreSQLPlugin(DbPlugin):
|
17
|
+
"""
|
18
|
+
PostgreSQL database plugin implementation.
|
19
|
+
"""
|
20
|
+
|
21
|
+
plugin_name = "PostgreSQL Plugin"
|
22
|
+
plugin_version = "1.0.0"
|
23
|
+
supported_db_types = ["postgresql", "postgres"]
|
24
|
+
required_dependencies = ["psycopg2-binary", "SQLAlchemy"]
|
25
|
+
|
26
|
+
def __init__(self, db_root_path: str, db_mode: str = "dev", **kwargs):
|
27
|
+
super().__init__(db_root_path, db_mode, **kwargs)
|
28
|
+
self.db_id = None
|
29
|
+
self.db_directory_path = None
|
30
|
+
|
31
|
+
# LSH manager integration (for backward compatibility)
|
32
|
+
self._lsh_manager = None
|
33
|
+
|
34
|
+
def create_adapter(self, **kwargs) -> DbAdapter:
|
35
|
+
"""Create and return a PostgreSQL adapter instance"""
|
36
|
+
return PostgreSQLAdapter(kwargs)
|
37
|
+
|
38
|
+
def validate_connection_params(self, **kwargs) -> bool:
|
39
|
+
"""Validate connection parameters for PostgreSQL"""
|
40
|
+
required_params = ['host', 'port', 'database', 'user', 'password']
|
41
|
+
|
42
|
+
for param in required_params:
|
43
|
+
if param not in kwargs:
|
44
|
+
logger.error(f"Missing required parameter: {param}")
|
45
|
+
return False
|
46
|
+
|
47
|
+
# Validate types
|
48
|
+
try:
|
49
|
+
port = int(kwargs['port'])
|
50
|
+
if port <= 0 or port > 65535:
|
51
|
+
logger.error(f"Invalid port number: {port}")
|
52
|
+
return False
|
53
|
+
except (ValueError, TypeError):
|
54
|
+
logger.error(f"Port must be a valid integer: {kwargs.get('port')}")
|
55
|
+
return False
|
56
|
+
|
57
|
+
# Validate required string parameters are not empty
|
58
|
+
string_params = ['host', 'database', 'user', 'password']
|
59
|
+
for param in string_params:
|
60
|
+
if not kwargs.get(param) or not isinstance(kwargs[param], str):
|
61
|
+
logger.error(f"Parameter {param} must be a non-empty string")
|
62
|
+
return False
|
63
|
+
|
64
|
+
return True
|
65
|
+
|
66
|
+
def initialize(self, **kwargs) -> None:
|
67
|
+
"""Initialize the PostgreSQL plugin"""
|
68
|
+
super().initialize(**kwargs)
|
69
|
+
|
70
|
+
# Set up database directory path (for LSH and other features)
|
71
|
+
if 'database' in kwargs:
|
72
|
+
self.db_id = kwargs['database']
|
73
|
+
self._setup_directory_path(self.db_id)
|
74
|
+
|
75
|
+
logger.info(f"PostgreSQL plugin initialized for database: {self.db_id}")
|
76
|
+
|
77
|
+
def _setup_directory_path(self, db_id: str) -> None:
|
78
|
+
"""Set up the database directory path"""
|
79
|
+
if isinstance(self.db_root_path, str):
|
80
|
+
self.db_root_path = Path(self.db_root_path)
|
81
|
+
|
82
|
+
self.db_directory_path = Path(self.db_root_path) / f"{self.db_mode}_databases" / db_id
|
83
|
+
self.db_id = db_id
|
84
|
+
|
85
|
+
# Reset LSH manager when directory path changes
|
86
|
+
self._lsh_manager = None
|
87
|
+
|
88
|
+
@property
|
89
|
+
def lsh_manager(self):
|
90
|
+
"""Lazy load LSH manager for backward compatibility"""
|
91
|
+
if self._lsh_manager is None and self.db_directory_path:
|
92
|
+
from ..lsh.manager import LshManager
|
93
|
+
self._lsh_manager = LshManager(self.db_directory_path)
|
94
|
+
return self._lsh_manager
|
95
|
+
|
96
|
+
# LSH integration methods for backward compatibility
|
97
|
+
def set_lsh(self) -> str:
|
98
|
+
"""Set LSH for backward compatibility"""
|
99
|
+
try:
|
100
|
+
if self.lsh_manager and self.lsh_manager.load_lsh():
|
101
|
+
return "success"
|
102
|
+
else:
|
103
|
+
return "error"
|
104
|
+
except Exception as e:
|
105
|
+
logger.error(f"Error loading LSH: {e}")
|
106
|
+
return "error"
|
107
|
+
|
108
|
+
def query_lsh(self, keyword: str, signature_size: int = 30, n_gram: int = 3, top_n: int = 10) -> Dict[str, Dict[str, List[str]]]:
|
109
|
+
"""Query LSH for backward compatibility"""
|
110
|
+
if self.lsh_manager:
|
111
|
+
try:
|
112
|
+
return self.lsh_manager.query(
|
113
|
+
keyword=keyword,
|
114
|
+
signature_size=signature_size,
|
115
|
+
n_gram=n_gram,
|
116
|
+
top_n=top_n
|
117
|
+
)
|
118
|
+
except Exception as e:
|
119
|
+
logger.error(f"LSH query failed: {e}")
|
120
|
+
raise Exception(f"Error querying LSH for {self.db_id}: {e}")
|
121
|
+
else:
|
122
|
+
raise Exception(f"LSH not available for {self.db_id}")
|
123
|
+
|
124
|
+
def get_connection_info(self) -> Dict[str, Any]:
|
125
|
+
"""Get connection information"""
|
126
|
+
base_info = super().get_plugin_info()
|
127
|
+
|
128
|
+
if self.adapter:
|
129
|
+
adapter_info = self.adapter.get_connection_info()
|
130
|
+
base_info.update(adapter_info)
|
131
|
+
|
132
|
+
base_info.update({
|
133
|
+
"db_id": self.db_id,
|
134
|
+
"db_directory_path": str(self.db_directory_path) if self.db_directory_path else None,
|
135
|
+
"lsh_available": self.lsh_manager is not None
|
136
|
+
})
|
137
|
+
|
138
|
+
return base_info
|
139
|
+
|
140
|
+
def get_example_data(self, table_name: str, number_of_rows: int = 30) -> Dict[str, List[Any]]:
|
141
|
+
"""Get example data through adapter"""
|
142
|
+
if self.adapter:
|
143
|
+
return self.adapter.get_example_data(table_name, number_of_rows)
|
144
|
+
else:
|
145
|
+
raise RuntimeError("Plugin not initialized")
|
@@ -0,0 +1,170 @@
|
|
1
|
+
"""
|
2
|
+
SQLite plugin implementation.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
from typing import Any, Dict, List
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
from ..core.interfaces import DbPlugin, DbAdapter
|
9
|
+
from ..core.registry import register_plugin
|
10
|
+
from ..adapters.sqlite import SQLiteAdapter
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
@register_plugin("sqlite")
|
16
|
+
class SQLitePlugin(DbPlugin):
|
17
|
+
"""
|
18
|
+
SQLite database plugin implementation.
|
19
|
+
"""
|
20
|
+
|
21
|
+
plugin_name = "SQLite Plugin"
|
22
|
+
plugin_version = "1.0.0"
|
23
|
+
supported_db_types = ["sqlite", "sqlite3"]
|
24
|
+
required_dependencies = ["SQLAlchemy"]
|
25
|
+
|
26
|
+
def __init__(self, db_root_path: str, db_mode: str = "dev", **kwargs):
|
27
|
+
super().__init__(db_root_path, db_mode, **kwargs)
|
28
|
+
self.db_id = None
|
29
|
+
self.db_directory_path = None
|
30
|
+
self.database_path = None
|
31
|
+
|
32
|
+
# LSH manager integration (for backward compatibility)
|
33
|
+
self._lsh_manager = None
|
34
|
+
|
35
|
+
def create_adapter(self, **kwargs) -> DbAdapter:
|
36
|
+
"""Create and return a SQLite adapter instance"""
|
37
|
+
return SQLiteAdapter(kwargs)
|
38
|
+
|
39
|
+
def validate_connection_params(self, **kwargs) -> bool:
|
40
|
+
"""Validate connection parameters for SQLite"""
|
41
|
+
# For SQLite, we need either database_path or database_name
|
42
|
+
database_path = kwargs.get('database_path')
|
43
|
+
database_name = kwargs.get('database_name')
|
44
|
+
|
45
|
+
if not database_path and not database_name:
|
46
|
+
logger.error("Either 'database_path' or 'database_name' is required for SQLite")
|
47
|
+
return False
|
48
|
+
|
49
|
+
if database_path:
|
50
|
+
# Validate that the path is a string
|
51
|
+
if not isinstance(database_path, str):
|
52
|
+
logger.error("database_path must be a string")
|
53
|
+
return False
|
54
|
+
|
55
|
+
# Check if parent directory exists or can be created
|
56
|
+
try:
|
57
|
+
db_path = Path(database_path)
|
58
|
+
db_path.parent.mkdir(parents=True, exist_ok=True)
|
59
|
+
except Exception as e:
|
60
|
+
logger.error(f"Cannot create directory for database path {database_path}: {e}")
|
61
|
+
return False
|
62
|
+
|
63
|
+
if database_name:
|
64
|
+
if not isinstance(database_name, str) or not database_name.strip():
|
65
|
+
logger.error("database_name must be a non-empty string")
|
66
|
+
return False
|
67
|
+
|
68
|
+
return True
|
69
|
+
|
70
|
+
def initialize(self, **kwargs) -> None:
|
71
|
+
"""Initialize the SQLite plugin"""
|
72
|
+
# Handle database path resolution
|
73
|
+
database_path = kwargs.get('database_path')
|
74
|
+
database_name = kwargs.get('database_name')
|
75
|
+
|
76
|
+
if not database_path and database_name:
|
77
|
+
# Create database path from name and root path
|
78
|
+
db_root = Path(self.db_root_path)
|
79
|
+
db_dir = db_root / f"{self.db_mode}_databases" / database_name
|
80
|
+
db_dir.mkdir(parents=True, exist_ok=True)
|
81
|
+
database_path = str(db_dir / f"{database_name}.db")
|
82
|
+
kwargs['database_path'] = database_path
|
83
|
+
|
84
|
+
# Set database path for adapter
|
85
|
+
self.database_path = database_path
|
86
|
+
|
87
|
+
# Initialize with updated kwargs
|
88
|
+
super().initialize(**kwargs)
|
89
|
+
|
90
|
+
# Set up database directory path and ID
|
91
|
+
if database_name:
|
92
|
+
self.db_id = database_name
|
93
|
+
else:
|
94
|
+
# Extract database name from path
|
95
|
+
self.db_id = Path(database_path).stem
|
96
|
+
|
97
|
+
self._setup_directory_path(self.db_id)
|
98
|
+
|
99
|
+
logger.info(f"SQLite plugin initialized for database: {self.db_id} at {self.database_path}")
|
100
|
+
|
101
|
+
def _setup_directory_path(self, db_id: str) -> None:
|
102
|
+
"""Set up the database directory path"""
|
103
|
+
if isinstance(self.db_root_path, str):
|
104
|
+
self.db_root_path = Path(self.db_root_path)
|
105
|
+
|
106
|
+
self.db_directory_path = Path(self.db_root_path) / f"{self.db_mode}_databases" / db_id
|
107
|
+
self.db_id = db_id
|
108
|
+
|
109
|
+
# Reset LSH manager when directory path changes
|
110
|
+
self._lsh_manager = None
|
111
|
+
|
112
|
+
@property
|
113
|
+
def lsh_manager(self):
|
114
|
+
"""Lazy load LSH manager for backward compatibility"""
|
115
|
+
if self._lsh_manager is None and self.db_directory_path:
|
116
|
+
from ..lsh.manager import LshManager
|
117
|
+
self._lsh_manager = LshManager(self.db_directory_path)
|
118
|
+
return self._lsh_manager
|
119
|
+
|
120
|
+
# LSH integration methods for backward compatibility
|
121
|
+
def set_lsh(self) -> str:
|
122
|
+
"""Set LSH for backward compatibility"""
|
123
|
+
try:
|
124
|
+
if self.lsh_manager and self.lsh_manager.load_lsh():
|
125
|
+
return "success"
|
126
|
+
else:
|
127
|
+
return "error"
|
128
|
+
except Exception as e:
|
129
|
+
logger.error(f"Error loading LSH: {e}")
|
130
|
+
return "error"
|
131
|
+
|
132
|
+
def query_lsh(self, keyword: str, signature_size: int = 30, n_gram: int = 3, top_n: int = 10) -> Dict[str, Dict[str, List[str]]]:
|
133
|
+
"""Query LSH for backward compatibility"""
|
134
|
+
if self.lsh_manager:
|
135
|
+
try:
|
136
|
+
return self.lsh_manager.query(
|
137
|
+
keyword=keyword,
|
138
|
+
signature_size=signature_size,
|
139
|
+
n_gram=n_gram,
|
140
|
+
top_n=top_n
|
141
|
+
)
|
142
|
+
except Exception as e:
|
143
|
+
logger.error(f"LSH query failed: {e}")
|
144
|
+
raise Exception(f"Error querying LSH for {self.db_id}: {e}")
|
145
|
+
else:
|
146
|
+
raise Exception(f"LSH not available for {self.db_id}")
|
147
|
+
|
148
|
+
def get_connection_info(self) -> Dict[str, Any]:
|
149
|
+
"""Get connection information"""
|
150
|
+
base_info = super().get_plugin_info()
|
151
|
+
|
152
|
+
if self.adapter:
|
153
|
+
adapter_info = self.adapter.get_connection_info()
|
154
|
+
base_info.update(adapter_info)
|
155
|
+
|
156
|
+
base_info.update({
|
157
|
+
"db_id": self.db_id,
|
158
|
+
"database_path": self.database_path,
|
159
|
+
"db_directory_path": str(self.db_directory_path) if self.db_directory_path else None,
|
160
|
+
"lsh_available": self.lsh_manager is not None
|
161
|
+
})
|
162
|
+
|
163
|
+
return base_info
|
164
|
+
|
165
|
+
def get_example_data(self, table_name: str, number_of_rows: int = 30) -> Dict[str, List[Any]]:
|
166
|
+
"""Get example data through adapter"""
|
167
|
+
if self.adapter:
|
168
|
+
return self.adapter.get_example_data(table_name, number_of_rows)
|
169
|
+
else:
|
170
|
+
raise RuntimeError("Plugin not initialized")
|
@@ -0,0 +1,149 @@
|
|
1
|
+
"""
|
2
|
+
SQL Server plugin implementation.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
from typing import Any, Dict, List
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
from ..core.interfaces import DbPlugin, DbAdapter
|
9
|
+
from ..core.registry import register_plugin
|
10
|
+
from ..adapters.sqlserver import SQLServerAdapter
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
@register_plugin("sqlserver")
|
16
|
+
class SQLServerPlugin(DbPlugin):
|
17
|
+
"""
|
18
|
+
SQL Server database plugin implementation.
|
19
|
+
"""
|
20
|
+
|
21
|
+
plugin_name = "SQL Server Plugin"
|
22
|
+
plugin_version = "1.0.0"
|
23
|
+
supported_db_types = ["sqlserver", "mssql"]
|
24
|
+
required_dependencies = ["pyodbc", "SQLAlchemy"]
|
25
|
+
|
26
|
+
def __init__(self, db_root_path: str, db_mode: str = "dev", **kwargs):
|
27
|
+
super().__init__(db_root_path, db_mode, **kwargs)
|
28
|
+
self.db_id = None
|
29
|
+
self.db_directory_path = None
|
30
|
+
|
31
|
+
# LSH manager integration (for backward compatibility)
|
32
|
+
self._lsh_manager = None
|
33
|
+
|
34
|
+
def create_adapter(self, **kwargs) -> DbAdapter:
|
35
|
+
"""Create and return a SQL Server adapter instance"""
|
36
|
+
return SQLServerAdapter(kwargs)
|
37
|
+
|
38
|
+
def validate_connection_params(self, **kwargs) -> bool:
|
39
|
+
"""Validate connection parameters for SQL Server"""
|
40
|
+
required_params = ['host', 'port', 'database', 'user', 'password']
|
41
|
+
|
42
|
+
for param in required_params:
|
43
|
+
if param not in kwargs:
|
44
|
+
logger.error(f"Missing required parameter: {param}")
|
45
|
+
return False
|
46
|
+
|
47
|
+
# Validate types
|
48
|
+
try:
|
49
|
+
port = int(kwargs['port'])
|
50
|
+
if port <= 0 or port > 65535:
|
51
|
+
logger.error(f"Invalid port number: {port}")
|
52
|
+
return False
|
53
|
+
except (ValueError, TypeError):
|
54
|
+
logger.error(f"Port must be a valid integer: {kwargs.get('port')}")
|
55
|
+
return False
|
56
|
+
|
57
|
+
# Validate required string parameters are not empty
|
58
|
+
string_params = ['host', 'database', 'user', 'password']
|
59
|
+
for param in string_params:
|
60
|
+
if not kwargs.get(param) or not isinstance(kwargs[param], str):
|
61
|
+
logger.error(f"Parameter {param} must be a non-empty string")
|
62
|
+
return False
|
63
|
+
|
64
|
+
return True
|
65
|
+
|
66
|
+
def initialize(self, **kwargs) -> None:
|
67
|
+
"""Initialize the SQL Server plugin"""
|
68
|
+
super().initialize(**kwargs)
|
69
|
+
|
70
|
+
# Set up database directory path (for LSH and other features)
|
71
|
+
if 'database' in kwargs:
|
72
|
+
self.db_id = kwargs['database']
|
73
|
+
self._setup_directory_path(self.db_id)
|
74
|
+
|
75
|
+
logger.info(f"SQL Server plugin initialized for database: {self.db_id}")
|
76
|
+
|
77
|
+
def _setup_directory_path(self, db_id: str) -> None:
|
78
|
+
"""Set up directory path for database-specific files"""
|
79
|
+
if self.db_root_path:
|
80
|
+
self.db_directory_path = Path(self.db_root_path) / "sqlserver" / db_id
|
81
|
+
self.db_directory_path.mkdir(parents=True, exist_ok=True)
|
82
|
+
|
83
|
+
@property
|
84
|
+
def lsh_manager(self):
|
85
|
+
"""Get LSH manager (for backward compatibility)"""
|
86
|
+
return self._lsh_manager
|
87
|
+
|
88
|
+
def get_connection_info(self) -> Dict[str, Any]:
|
89
|
+
"""Get connection information"""
|
90
|
+
base_info = super().get_plugin_info()
|
91
|
+
|
92
|
+
if self.adapter:
|
93
|
+
adapter_info = self.adapter.get_connection_info()
|
94
|
+
base_info.update(adapter_info)
|
95
|
+
|
96
|
+
base_info.update({
|
97
|
+
"db_id": self.db_id,
|
98
|
+
"db_directory_path": str(self.db_directory_path) if self.db_directory_path else None,
|
99
|
+
"lsh_available": self.lsh_manager is not None
|
100
|
+
})
|
101
|
+
|
102
|
+
return base_info
|
103
|
+
|
104
|
+
def get_example_data(self, table_name: str, number_of_rows: int = 30) -> Dict[str, List[Any]]:
|
105
|
+
"""Get example data through adapter"""
|
106
|
+
if self.adapter:
|
107
|
+
return self.adapter.get_example_data(table_name, number_of_rows)
|
108
|
+
else:
|
109
|
+
raise RuntimeError("Plugin not initialized")
|
110
|
+
|
111
|
+
def validate_connection_string(self, connection_string: str) -> bool:
|
112
|
+
"""Validate SQL Server connection string format."""
|
113
|
+
return "mssql+pyodbc://" in connection_string
|
114
|
+
|
115
|
+
def get_database_info(self) -> Dict[str, Any]:
|
116
|
+
"""Get SQL Server database information."""
|
117
|
+
return {
|
118
|
+
"name": self.name,
|
119
|
+
"display_name": self.display_name,
|
120
|
+
"description": self.description,
|
121
|
+
"version": self.version,
|
122
|
+
"features": [
|
123
|
+
"transactions",
|
124
|
+
"foreign_keys",
|
125
|
+
"indexes",
|
126
|
+
"views",
|
127
|
+
"stored_procedures",
|
128
|
+
"triggers",
|
129
|
+
"computed_columns",
|
130
|
+
"partitioning"
|
131
|
+
],
|
132
|
+
"data_types": [
|
133
|
+
"INT", "BIGINT", "DECIMAL", "NUMERIC", "FLOAT", "REAL",
|
134
|
+
"VARCHAR", "NVARCHAR", "TEXT", "NTEXT", "BINARY", "VARBINARY",
|
135
|
+
"DATE", "TIME", "DATETIME", "DATETIME2", "SMALLDATETIME",
|
136
|
+
"BIT", "UNIQUEIDENTIFIER", "XML"
|
137
|
+
]
|
138
|
+
}
|
139
|
+
|
140
|
+
def get_sample_connection_config(self) -> Dict[str, Any]:
|
141
|
+
"""Get sample connection configuration."""
|
142
|
+
return {
|
143
|
+
"host": "localhost",
|
144
|
+
"port": 1433,
|
145
|
+
"database": "mydatabase",
|
146
|
+
"username": "user",
|
147
|
+
"password": "password",
|
148
|
+
"driver": "ODBC Driver 17 for SQL Server"
|
149
|
+
}
|
@@ -0,0 +1,224 @@
|
|
1
|
+
"""
|
2
|
+
Supabase plugin implementation.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
from typing import Any, Dict, List
|
6
|
+
from pathlib import Path
|
7
|
+
from urllib.parse import urlparse
|
8
|
+
|
9
|
+
from ..core.interfaces import DbPlugin, DbAdapter
|
10
|
+
from ..core.registry import register_plugin
|
11
|
+
from ..adapters.supabase import SupabaseAdapter
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
@register_plugin("supabase")
|
17
|
+
class SupabasePlugin(DbPlugin):
|
18
|
+
"""
|
19
|
+
Supabase database plugin implementation.
|
20
|
+
Extends PostgreSQL functionality with Supabase-specific features.
|
21
|
+
"""
|
22
|
+
|
23
|
+
plugin_name = "Supabase Plugin"
|
24
|
+
plugin_version = "1.0.0"
|
25
|
+
supported_db_types = ["supabase", "supabase-postgresql"]
|
26
|
+
required_dependencies = ["psycopg2-binary", "SQLAlchemy", "supabase", "postgrest-py"]
|
27
|
+
|
28
|
+
def __init__(self, db_root_path: str, db_mode: str = "dev", **kwargs):
|
29
|
+
super().__init__(db_root_path, db_mode, **kwargs)
|
30
|
+
self.db_id = None
|
31
|
+
self.db_directory_path = None
|
32
|
+
self.project_url = None
|
33
|
+
self.api_key = None
|
34
|
+
|
35
|
+
# LSH manager integration (for backward compatibility)
|
36
|
+
self._lsh_manager = None
|
37
|
+
|
38
|
+
def create_adapter(self, **kwargs) -> DbAdapter:
|
39
|
+
"""Create and return a Supabase adapter instance"""
|
40
|
+
return SupabaseAdapter(kwargs)
|
41
|
+
|
42
|
+
def validate_connection_params(self, **kwargs) -> bool:
|
43
|
+
"""Validate connection parameters for Supabase"""
|
44
|
+
# Support both direct database connection and REST API connection
|
45
|
+
use_rest_api = kwargs.get('use_rest_api', False)
|
46
|
+
|
47
|
+
if use_rest_api:
|
48
|
+
return self._validate_rest_params(**kwargs)
|
49
|
+
else:
|
50
|
+
return self._validate_direct_params(**kwargs)
|
51
|
+
|
52
|
+
def _validate_rest_params(self, **kwargs) -> bool:
|
53
|
+
"""Validate REST API connection parameters"""
|
54
|
+
required_params = ['project_url', 'api_key']
|
55
|
+
|
56
|
+
for param in required_params:
|
57
|
+
if param not in kwargs:
|
58
|
+
logger.error(f"Missing required parameter for REST API: {param}")
|
59
|
+
return False
|
60
|
+
|
61
|
+
# Validate project URL format
|
62
|
+
project_url = kwargs.get('project_url')
|
63
|
+
try:
|
64
|
+
parsed = urlparse(project_url)
|
65
|
+
if not parsed.netloc.endswith('.supabase.co'):
|
66
|
+
logger.error("Invalid Supabase project URL format")
|
67
|
+
return False
|
68
|
+
except Exception:
|
69
|
+
logger.error("Invalid project URL format")
|
70
|
+
return False
|
71
|
+
|
72
|
+
# Validate API key format
|
73
|
+
api_key = kwargs.get('api_key')
|
74
|
+
if not api_key or not isinstance(api_key, str):
|
75
|
+
logger.error("API key must be a non-empty string")
|
76
|
+
return False
|
77
|
+
|
78
|
+
return True
|
79
|
+
|
80
|
+
def _validate_direct_params(self, **kwargs) -> bool:
|
81
|
+
"""Validate direct database connection parameters"""
|
82
|
+
required_params = ['host', 'port', 'database', 'user', 'password']
|
83
|
+
|
84
|
+
for param in required_params:
|
85
|
+
if param not in kwargs:
|
86
|
+
logger.error(f"Missing required parameter: {param}")
|
87
|
+
return False
|
88
|
+
|
89
|
+
# Validate types
|
90
|
+
try:
|
91
|
+
port = int(kwargs['port'])
|
92
|
+
if port <= 0 or port > 65535:
|
93
|
+
logger.error(f"Invalid port number: {port}")
|
94
|
+
return False
|
95
|
+
except (ValueError, TypeError):
|
96
|
+
logger.error(f"Port must be a valid integer: {kwargs.get('port')}")
|
97
|
+
return False
|
98
|
+
|
99
|
+
# Validate required string parameters are not empty
|
100
|
+
string_params = ['host', 'database', 'user', 'password']
|
101
|
+
for param in string_params:
|
102
|
+
if not kwargs.get(param) or not isinstance(kwargs[param], str):
|
103
|
+
logger.error(f"Parameter {param} must be a non-empty string")
|
104
|
+
return False
|
105
|
+
|
106
|
+
# Validate SSL parameters if provided
|
107
|
+
ssl_params = ['sslcert', 'sslkey', 'sslrootcert']
|
108
|
+
for param in ssl_params:
|
109
|
+
if param in kwargs and kwargs[param]:
|
110
|
+
if not isinstance(kwargs[param], str):
|
111
|
+
logger.error(f"SSL parameter {param} must be a string")
|
112
|
+
return False
|
113
|
+
|
114
|
+
return True
|
115
|
+
|
116
|
+
def initialize(self, **kwargs) -> None:
|
117
|
+
"""Initialize the Supabase plugin"""
|
118
|
+
super().initialize(**kwargs)
|
119
|
+
|
120
|
+
# Store connection parameters
|
121
|
+
self.project_url = kwargs.get('project_url')
|
122
|
+
self.api_key = kwargs.get('api_key')
|
123
|
+
|
124
|
+
# Set up database directory path (for LSH and other features)
|
125
|
+
if 'database' in kwargs:
|
126
|
+
self.db_id = kwargs['database']
|
127
|
+
elif 'project_url' in kwargs:
|
128
|
+
# Extract database name from project URL
|
129
|
+
parsed = urlparse(kwargs['project_url'])
|
130
|
+
self.db_id = parsed.netloc.split('.')[0]
|
131
|
+
|
132
|
+
if self.db_id:
|
133
|
+
self._setup_directory_path(self.db_id)
|
134
|
+
|
135
|
+
logger.info(f"Supabase plugin initialized for project: {self.db_id}")
|
136
|
+
|
137
|
+
def _setup_directory_path(self, db_id: str) -> None:
|
138
|
+
"""Set up the database directory path"""
|
139
|
+
if isinstance(self.db_root_path, str):
|
140
|
+
self.db_root_path = Path(self.db_root_path)
|
141
|
+
|
142
|
+
self.db_directory_path = Path(self.db_root_path) / f"{self.db_mode}_databases" / db_id
|
143
|
+
self.db_id = db_id
|
144
|
+
|
145
|
+
# Reset LSH manager when directory path changes
|
146
|
+
self._lsh_manager = None
|
147
|
+
|
148
|
+
@property
|
149
|
+
def lsh_manager(self):
|
150
|
+
"""Lazy load LSH manager for backward compatibility"""
|
151
|
+
if self._lsh_manager is None and self.db_directory_path:
|
152
|
+
from ..lsh.manager import LshManager
|
153
|
+
self._lsh_manager = LshManager(self.db_directory_path)
|
154
|
+
return self._lsh_manager
|
155
|
+
|
156
|
+
# LSH integration methods for backward compatibility
|
157
|
+
def set_lsh(self) -> str:
|
158
|
+
"""Set LSH for backward compatibility"""
|
159
|
+
try:
|
160
|
+
if self.lsh_manager and self.lsh_manager.load_lsh():
|
161
|
+
return "success"
|
162
|
+
else:
|
163
|
+
return "error"
|
164
|
+
except Exception as e:
|
165
|
+
logger.error(f"Error loading LSH: {e}")
|
166
|
+
return "error"
|
167
|
+
|
168
|
+
def query_lsh(self, keyword: str, signature_size: int = 30, n_gram: int = 3, top_n: int = 10) -> Dict[str, Dict[str, List[str]]]:
|
169
|
+
"""Query LSH for backward compatibility"""
|
170
|
+
if self.lsh_manager:
|
171
|
+
try:
|
172
|
+
return self.lsh_manager.query(
|
173
|
+
keyword=keyword,
|
174
|
+
signature_size=signature_size,
|
175
|
+
n_gram=n_gram,
|
176
|
+
top_n=top_n
|
177
|
+
)
|
178
|
+
except Exception as e:
|
179
|
+
logger.error(f"LSH query failed: {e}")
|
180
|
+
raise Exception(f"Error querying LSH for {self.db_id}: {e}")
|
181
|
+
else:
|
182
|
+
raise Exception(f"LSH not available for {self.db_id}")
|
183
|
+
|
184
|
+
def get_connection_info(self) -> Dict[str, Any]:
|
185
|
+
"""Get connection information"""
|
186
|
+
base_info = super().get_plugin_info()
|
187
|
+
|
188
|
+
if self.adapter:
|
189
|
+
adapter_info = self.adapter.get_connection_info()
|
190
|
+
base_info.update(adapter_info)
|
191
|
+
|
192
|
+
base_info.update({
|
193
|
+
"db_id": self.db_id,
|
194
|
+
"db_directory_path": str(self.db_directory_path) if self.db_directory_path else None,
|
195
|
+
"lsh_available": self.lsh_manager is not None,
|
196
|
+
"project_url": self.project_url,
|
197
|
+
"connection_mode": "REST API" if self.connection_params.get('use_rest_api') else "Direct Database"
|
198
|
+
})
|
199
|
+
|
200
|
+
return base_info
|
201
|
+
|
202
|
+
def get_example_data(self, table_name: str, number_of_rows: int = 30) -> Dict[str, List[Any]]:
|
203
|
+
"""Get example data through adapter"""
|
204
|
+
if self.adapter:
|
205
|
+
return self.adapter.get_example_data(table_name, number_of_rows)
|
206
|
+
else:
|
207
|
+
raise RuntimeError("Plugin not initialized")
|
208
|
+
|
209
|
+
def get_required_parameters(self) -> Dict[str, Any]:
|
210
|
+
"""Get required connection parameters for Supabase"""
|
211
|
+
use_rest_api = self.connection_params.get('use_rest_api', False)
|
212
|
+
|
213
|
+
if use_rest_api:
|
214
|
+
return {
|
215
|
+
"required": ["project_url", "api_key"],
|
216
|
+
"optional": ["schema", "timeout", "pool_size"],
|
217
|
+
"connection_mode": "REST API"
|
218
|
+
}
|
219
|
+
else:
|
220
|
+
return {
|
221
|
+
"required": ["host", "port", "database", "user", "password"],
|
222
|
+
"optional": ["schema", "sslmode", "sslcert", "sslkey", "sslrootcert", "pool_size", "connect_timeout"],
|
223
|
+
"connection_mode": "Direct Database"
|
224
|
+
}
|