MindsDB 25.9.3rc1__py3-none-any.whl → 25.10.0rc1__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.
Potentially problematic release.
This version of MindsDB might be problematic. Click here for more details.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +1 -9
- mindsdb/api/a2a/__init__.py +1 -1
- mindsdb/api/a2a/agent.py +9 -1
- mindsdb/api/a2a/common/server/server.py +4 -0
- mindsdb/api/a2a/common/server/task_manager.py +8 -1
- mindsdb/api/a2a/common/types.py +66 -0
- mindsdb/api/a2a/task_manager.py +50 -0
- mindsdb/api/common/middleware.py +1 -1
- mindsdb/api/executor/command_executor.py +49 -36
- mindsdb/api/executor/datahub/datanodes/information_schema_datanode.py +7 -13
- mindsdb/api/executor/datahub/datanodes/integration_datanode.py +2 -2
- mindsdb/api/executor/datahub/datanodes/system_tables.py +2 -1
- mindsdb/api/executor/planner/query_prepare.py +2 -20
- mindsdb/api/executor/utilities/sql.py +5 -4
- mindsdb/api/http/initialize.py +76 -60
- mindsdb/api/http/namespaces/agents.py +0 -3
- mindsdb/api/http/namespaces/chatbots.py +0 -5
- mindsdb/api/http/namespaces/file.py +2 -0
- mindsdb/api/http/namespaces/handlers.py +10 -5
- mindsdb/api/http/namespaces/knowledge_bases.py +20 -0
- mindsdb/api/http/namespaces/sql.py +2 -2
- mindsdb/api/http/start.py +2 -2
- mindsdb/api/mysql/mysql_proxy/utilities/dump.py +8 -2
- mindsdb/integrations/handlers/byom_handler/byom_handler.py +2 -10
- mindsdb/integrations/handlers/databricks_handler/databricks_handler.py +98 -46
- mindsdb/integrations/handlers/druid_handler/druid_handler.py +32 -40
- mindsdb/integrations/handlers/gitlab_handler/gitlab_handler.py +5 -2
- mindsdb/integrations/handlers/mssql_handler/mssql_handler.py +438 -100
- mindsdb/integrations/handlers/mssql_handler/requirements_odbc.txt +3 -0
- mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +235 -3
- mindsdb/integrations/handlers/oracle_handler/__init__.py +2 -0
- mindsdb/integrations/handlers/oracle_handler/connection_args.py +7 -1
- mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +321 -16
- mindsdb/integrations/handlers/oracle_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +2 -2
- mindsdb/integrations/handlers/zendesk_handler/zendesk_tables.py +144 -111
- mindsdb/integrations/libs/response.py +2 -2
- mindsdb/integrations/utilities/handlers/auth_utilities/snowflake/__init__.py +1 -0
- mindsdb/integrations/utilities/handlers/auth_utilities/snowflake/snowflake_jwt_gen.py +151 -0
- mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +24 -21
- mindsdb/interfaces/agents/agents_controller.py +0 -2
- mindsdb/interfaces/data_catalog/data_catalog_loader.py +6 -7
- mindsdb/interfaces/data_catalog/data_catalog_reader.py +15 -4
- mindsdb/interfaces/database/data_handlers_cache.py +190 -0
- mindsdb/interfaces/database/database.py +3 -3
- mindsdb/interfaces/database/integrations.py +1 -121
- mindsdb/interfaces/database/projects.py +2 -6
- mindsdb/interfaces/database/views.py +1 -4
- mindsdb/interfaces/jobs/jobs_controller.py +0 -4
- mindsdb/interfaces/jobs/scheduler.py +0 -1
- mindsdb/interfaces/knowledge_base/controller.py +197 -108
- mindsdb/interfaces/knowledge_base/evaluate.py +36 -41
- mindsdb/interfaces/knowledge_base/executor.py +11 -0
- mindsdb/interfaces/knowledge_base/llm_client.py +51 -17
- mindsdb/interfaces/model/model_controller.py +4 -4
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +4 -10
- mindsdb/interfaces/skills/skills_controller.py +1 -4
- mindsdb/interfaces/storage/db.py +16 -6
- mindsdb/interfaces/triggers/triggers_controller.py +1 -3
- mindsdb/utilities/config.py +19 -2
- mindsdb/utilities/exception.py +2 -2
- mindsdb/utilities/json_encoder.py +24 -10
- mindsdb/utilities/render/sqlalchemy_render.py +15 -14
- mindsdb/utilities/starters.py +0 -10
- {mindsdb-25.9.3rc1.dist-info → mindsdb-25.10.0rc1.dist-info}/METADATA +276 -264
- {mindsdb-25.9.3rc1.dist-info → mindsdb-25.10.0rc1.dist-info}/RECORD +70 -84
- mindsdb/api/postgres/__init__.py +0 -0
- mindsdb/api/postgres/postgres_proxy/__init__.py +0 -0
- mindsdb/api/postgres/postgres_proxy/executor/__init__.py +0 -1
- mindsdb/api/postgres/postgres_proxy/executor/executor.py +0 -182
- mindsdb/api/postgres/postgres_proxy/postgres_packets/__init__.py +0 -0
- mindsdb/api/postgres/postgres_proxy/postgres_packets/errors.py +0 -322
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_fields.py +0 -34
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message.py +0 -31
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_formats.py +0 -1265
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_identifiers.py +0 -31
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_packets.py +0 -265
- mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +0 -477
- mindsdb/api/postgres/postgres_proxy/utilities/__init__.py +0 -10
- mindsdb/api/postgres/start.py +0 -11
- mindsdb/integrations/handlers/mssql_handler/tests/__init__.py +0 -0
- mindsdb/integrations/handlers/mssql_handler/tests/test_mssql_handler.py +0 -169
- mindsdb/integrations/handlers/oracle_handler/tests/__init__.py +0 -0
- mindsdb/integrations/handlers/oracle_handler/tests/test_oracle_handler.py +0 -32
- {mindsdb-25.9.3rc1.dist-info → mindsdb-25.10.0rc1.dist-info}/WHEEL +0 -0
- {mindsdb-25.9.3rc1.dist-info → mindsdb-25.10.0rc1.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.9.3rc1.dist-info → mindsdb-25.10.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -19,9 +19,7 @@ class DataCatalogLoader(BaseDataCatalog):
|
|
|
19
19
|
if not self.is_data_catalog_supported():
|
|
20
20
|
return
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
tables = self._load_table_metadata(loaded_table_names)
|
|
22
|
+
tables = self._load_table_metadata()
|
|
25
23
|
|
|
26
24
|
if tables:
|
|
27
25
|
columns = self._load_column_metadata(tables)
|
|
@@ -34,7 +32,7 @@ class DataCatalogLoader(BaseDataCatalog):
|
|
|
34
32
|
|
|
35
33
|
self.logger.info(f"Metadata loading completed for {self.database_name}.")
|
|
36
34
|
|
|
37
|
-
def _get_loaded_table_names(self) -> List[str]:
|
|
35
|
+
def _get_loaded_table_names(self, in_table_names) -> List[str]:
|
|
38
36
|
"""
|
|
39
37
|
Retrieve the names of tables that are already present in the data catalog for the current integration.
|
|
40
38
|
If table_names are provided, only those tables will be checked.
|
|
@@ -43,8 +41,8 @@ class DataCatalogLoader(BaseDataCatalog):
|
|
|
43
41
|
List[str]: Names of tables already loaded in the data catalog.
|
|
44
42
|
"""
|
|
45
43
|
query = db.session.query(db.MetaTables).filter_by(integration_id=self.integration_id)
|
|
46
|
-
if
|
|
47
|
-
query = query.filter(db.MetaTables.name.in_(
|
|
44
|
+
if in_table_names:
|
|
45
|
+
query = query.filter(db.MetaTables.name.in_(in_table_names))
|
|
48
46
|
|
|
49
47
|
tables = query.all()
|
|
50
48
|
table_names = [table.name for table in tables]
|
|
@@ -54,7 +52,7 @@ class DataCatalogLoader(BaseDataCatalog):
|
|
|
54
52
|
|
|
55
53
|
return table_names
|
|
56
54
|
|
|
57
|
-
def _load_table_metadata(self
|
|
55
|
+
def _load_table_metadata(self) -> List[Union[db.MetaTables, None]]:
|
|
58
56
|
"""
|
|
59
57
|
Load the table metadata from the handler.
|
|
60
58
|
"""
|
|
@@ -75,6 +73,7 @@ class DataCatalogLoader(BaseDataCatalog):
|
|
|
75
73
|
df.columns = df.columns.str.lower()
|
|
76
74
|
|
|
77
75
|
# Filter out tables that are already loaded in the data catalog
|
|
76
|
+
loaded_table_names = self._get_loaded_table_names(list(df["table_name"]))
|
|
78
77
|
if loaded_table_names:
|
|
79
78
|
df = df[~df["table_name"].isin(loaded_table_names)]
|
|
80
79
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import sqlalchemy as sa
|
|
2
|
+
|
|
1
3
|
from mindsdb.interfaces.data_catalog.base_data_catalog import BaseDataCatalog
|
|
2
4
|
from mindsdb.interfaces.storage import db
|
|
3
5
|
|
|
@@ -13,8 +15,11 @@ class DataCatalogReader(BaseDataCatalog):
|
|
|
13
15
|
"""
|
|
14
16
|
tables = self._read_metadata()
|
|
15
17
|
if not tables:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
# try case independent
|
|
19
|
+
tables = self._read_metadata(strict_case=False)
|
|
20
|
+
if not tables:
|
|
21
|
+
self.logger.warning(f"No metadata found for database '{self.database_name}'")
|
|
22
|
+
return f"No metadata found for database '{self.database_name}'"
|
|
18
23
|
|
|
19
24
|
metadata_str = "Data Catalog: \n"
|
|
20
25
|
if hasattr(self.data_handler, "meta_get_handler_info"):
|
|
@@ -42,7 +47,7 @@ class DataCatalogReader(BaseDataCatalog):
|
|
|
42
47
|
"""
|
|
43
48
|
return self.data_handler.meta_get_handler_info()
|
|
44
49
|
|
|
45
|
-
def _read_metadata(self) -> list:
|
|
50
|
+
def _read_metadata(self, strict_case=True) -> list:
|
|
46
51
|
"""
|
|
47
52
|
Read the metadata from the data catalog and return it in a structured format.
|
|
48
53
|
"""
|
|
@@ -52,6 +57,12 @@ class DataCatalogReader(BaseDataCatalog):
|
|
|
52
57
|
query = db.session.query(db.MetaTables).filter_by(integration_id=self.integration_id)
|
|
53
58
|
if self.table_names:
|
|
54
59
|
cleaned_table_names = [name.strip("`").split(".")[-1] for name in self.table_names]
|
|
55
|
-
|
|
60
|
+
|
|
61
|
+
if strict_case:
|
|
62
|
+
query = query.filter(db.MetaTables.name.in_(cleaned_table_names))
|
|
63
|
+
else:
|
|
64
|
+
cleaned_table_names = [name.lower() for name in cleaned_table_names]
|
|
65
|
+
query = query.filter(sa.func.lower(db.MetaTables.name).in_(cleaned_table_names))
|
|
56
66
|
tables = query.all()
|
|
67
|
+
|
|
57
68
|
return tables
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import time
|
|
3
|
+
import threading
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
|
|
7
|
+
from mindsdb.integrations.libs.base import DatabaseHandler
|
|
8
|
+
from mindsdb.utilities.context import context as ctx
|
|
9
|
+
from mindsdb.utilities import log
|
|
10
|
+
|
|
11
|
+
logger = log.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(kw_only=True, slots=True)
|
|
15
|
+
class HandlersCacheRecord:
|
|
16
|
+
"""Record for a handler in the cache
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
handler (DatabaseHandler): handler instance
|
|
20
|
+
expired_at (float): time when the handler will be expired
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
handler: DatabaseHandler
|
|
24
|
+
expired_at: float
|
|
25
|
+
connect_attempt_done: threading.Event = field(default_factory=threading.Event)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def expired(self) -> bool:
|
|
29
|
+
"""check if the handler is expired
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
bool: True if the handler is expired, False otherwise
|
|
33
|
+
"""
|
|
34
|
+
return self.expired_at < time.time()
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def has_references(self) -> bool:
|
|
38
|
+
"""check if the handler has references
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
bool: True if the handler has references, False otherwise
|
|
42
|
+
"""
|
|
43
|
+
return sys.getrefcount(self.handler) > 2
|
|
44
|
+
|
|
45
|
+
def connect(self) -> None:
|
|
46
|
+
"""connect to the handler"""
|
|
47
|
+
try:
|
|
48
|
+
if not self.handler.is_connected:
|
|
49
|
+
self.handler.connect()
|
|
50
|
+
except Exception:
|
|
51
|
+
logger.warning(f"Error connecting to handler: {self.handler.name}", exc_info=True)
|
|
52
|
+
finally:
|
|
53
|
+
self.connect_attempt_done.set()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class HandlersCache:
|
|
57
|
+
"""Cache for data handlers that keep connections opened during ttl time from handler last use"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, ttl: int = 60, clean_timeout: float = 3):
|
|
60
|
+
"""init cache
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
ttl (int): time to live (in seconds) for record in cache
|
|
64
|
+
clean_timeout (float): interval between cleanups of expired handlers
|
|
65
|
+
"""
|
|
66
|
+
self.ttl: int = ttl
|
|
67
|
+
self._clean_timeout: int = clean_timeout
|
|
68
|
+
self.handlers: dict[str, list[HandlersCacheRecord]] = defaultdict(list)
|
|
69
|
+
self._lock = threading.RLock()
|
|
70
|
+
self._stop_event = threading.Event()
|
|
71
|
+
self.cleaner_thread = None
|
|
72
|
+
|
|
73
|
+
def __del__(self):
|
|
74
|
+
self._stop_clean()
|
|
75
|
+
|
|
76
|
+
def _start_clean(self) -> None:
|
|
77
|
+
"""start worker that close connections after ttl expired"""
|
|
78
|
+
if isinstance(self.cleaner_thread, threading.Thread) and self.cleaner_thread.is_alive():
|
|
79
|
+
return
|
|
80
|
+
with self._lock:
|
|
81
|
+
self._stop_event.clear()
|
|
82
|
+
self.cleaner_thread = threading.Thread(target=self._clean, name="HandlersCache.clean")
|
|
83
|
+
self.cleaner_thread.daemon = True
|
|
84
|
+
self.cleaner_thread.start()
|
|
85
|
+
|
|
86
|
+
def _stop_clean(self) -> None:
|
|
87
|
+
"""stop clean worker"""
|
|
88
|
+
self._stop_event.set()
|
|
89
|
+
|
|
90
|
+
def set(self, handler: DatabaseHandler):
|
|
91
|
+
"""add (or replace) handler in cache
|
|
92
|
+
|
|
93
|
+
NOTE: If the handler is not thread-safe, then use a lock when making connection. Otherwise, make connection in
|
|
94
|
+
the same thread without using a lock to speed up parallel queries. (They don't need to wait for a connection in
|
|
95
|
+
another thread.)
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
handler (DatabaseHandler)
|
|
99
|
+
"""
|
|
100
|
+
thread_safe = getattr(handler, "thread_safe", True)
|
|
101
|
+
with self._lock:
|
|
102
|
+
try:
|
|
103
|
+
# If the handler is defined to be thread safe, set 0 as the last element of the key, otherwise set the thrad ID.
|
|
104
|
+
key = (
|
|
105
|
+
handler.name,
|
|
106
|
+
ctx.company_id,
|
|
107
|
+
0 if thread_safe else threading.get_native_id(),
|
|
108
|
+
)
|
|
109
|
+
record = HandlersCacheRecord(handler=handler, expired_at=time.time() + self.ttl)
|
|
110
|
+
self.handlers[key].append(record)
|
|
111
|
+
except Exception:
|
|
112
|
+
logger.warning("Error setting data handler cache record:", exc_info=True)
|
|
113
|
+
return
|
|
114
|
+
self._start_clean()
|
|
115
|
+
record.connect()
|
|
116
|
+
|
|
117
|
+
def _get_cache_records(self, name: str) -> tuple[list[HandlersCacheRecord] | None, str]:
|
|
118
|
+
"""get cache records by name
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
name (str): handler name
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
tuple[list[HandlersCacheRecord] | None, str]: cache records and key of the handler in cache
|
|
125
|
+
"""
|
|
126
|
+
# If the handler is not thread safe, the thread ID will be assigned to the last element of the key.
|
|
127
|
+
key = (name, ctx.company_id, 0)
|
|
128
|
+
if key not in self.handlers:
|
|
129
|
+
key = (name, ctx.company_id, threading.get_native_id())
|
|
130
|
+
return self.handlers.get(key, []), key
|
|
131
|
+
|
|
132
|
+
def get(self, name: str) -> DatabaseHandler | None:
|
|
133
|
+
"""get handler from cache by name
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
name (str): handler name
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
DatabaseHandler
|
|
140
|
+
"""
|
|
141
|
+
with self._lock:
|
|
142
|
+
records, _ = self._get_cache_records(name)
|
|
143
|
+
for record in records:
|
|
144
|
+
if record.expired is False and record.has_references is False:
|
|
145
|
+
record.expired_at = time.time() + self.ttl
|
|
146
|
+
if record.connect_attempt_done.wait(timeout=10) is False:
|
|
147
|
+
logger.warning(f"Handler's connection attempt has not finished in 10s: {record.handler.name}")
|
|
148
|
+
return record.handler
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
def delete(self, name: str) -> None:
|
|
152
|
+
"""delete handler from cache
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
name (str): handler name
|
|
156
|
+
"""
|
|
157
|
+
with self._lock:
|
|
158
|
+
records, key = self._get_cache_records(name)
|
|
159
|
+
if len(records) > 0:
|
|
160
|
+
del self.handlers[key]
|
|
161
|
+
for record in records:
|
|
162
|
+
try:
|
|
163
|
+
record.handler.disconnect()
|
|
164
|
+
except Exception:
|
|
165
|
+
logger.debug("Error disconnecting data handler:", exc_info=True)
|
|
166
|
+
|
|
167
|
+
if len(self.handlers) == 0:
|
|
168
|
+
self._stop_clean()
|
|
169
|
+
|
|
170
|
+
def _clean(self) -> None:
|
|
171
|
+
"""worker that delete from cache handlers that was not in use for ttl"""
|
|
172
|
+
while self._stop_event.wait(timeout=self._clean_timeout) is False:
|
|
173
|
+
with self._lock:
|
|
174
|
+
for key in list(self.handlers.keys()):
|
|
175
|
+
active_handlers_list = []
|
|
176
|
+
for record in self.handlers[key]:
|
|
177
|
+
if record.expired and record.has_references is False:
|
|
178
|
+
try:
|
|
179
|
+
record.handler.disconnect()
|
|
180
|
+
except Exception:
|
|
181
|
+
logger.debug("Error disconnecting data handler:", exc_info=True)
|
|
182
|
+
else:
|
|
183
|
+
active_handlers_list.append(record)
|
|
184
|
+
if len(active_handlers_list) > 0:
|
|
185
|
+
self.handlers[key] = active_handlers_list
|
|
186
|
+
else:
|
|
187
|
+
del self.handlers[key]
|
|
188
|
+
|
|
189
|
+
if len(self.handlers) == 0:
|
|
190
|
+
self._stop_event.set()
|
|
@@ -32,10 +32,10 @@ class DatabaseController:
|
|
|
32
32
|
Returns:
|
|
33
33
|
None
|
|
34
34
|
"""
|
|
35
|
-
databases = self.get_dict()
|
|
36
|
-
if name
|
|
35
|
+
databases = self.get_dict(lowercase=False)
|
|
36
|
+
if name not in databases:
|
|
37
37
|
raise EntityNotExistsError("Database does not exists", name)
|
|
38
|
-
db_type = databases[name
|
|
38
|
+
db_type = databases[name]["type"]
|
|
39
39
|
if db_type == "project":
|
|
40
40
|
project = self.get_project(name, strict_case)
|
|
41
41
|
project.delete()
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import sys
|
|
3
2
|
import base64
|
|
4
3
|
import shutil
|
|
5
4
|
import ast
|
|
@@ -33,127 +32,11 @@ from mindsdb.integrations.libs.ml_exec_base import BaseMLEngineExec
|
|
|
33
32
|
from mindsdb.integrations.libs.base import BaseHandler
|
|
34
33
|
import mindsdb.utilities.profiler as profiler
|
|
35
34
|
from mindsdb.interfaces.data_catalog.data_catalog_loader import DataCatalogLoader
|
|
35
|
+
from mindsdb.interfaces.database.data_handlers_cache import HandlersCache
|
|
36
36
|
|
|
37
37
|
logger = log.getLogger(__name__)
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class HandlersCache:
|
|
41
|
-
"""Cache for data handlers that keep connections opened during ttl time from handler last use"""
|
|
42
|
-
|
|
43
|
-
def __init__(self, ttl: int = 60):
|
|
44
|
-
"""init cache
|
|
45
|
-
|
|
46
|
-
Args:
|
|
47
|
-
ttl (int): time to live (in seconds) for record in cache
|
|
48
|
-
"""
|
|
49
|
-
self.ttl = ttl
|
|
50
|
-
self.handlers = {}
|
|
51
|
-
self._lock = threading.RLock()
|
|
52
|
-
self._stop_event = threading.Event()
|
|
53
|
-
self.cleaner_thread = None
|
|
54
|
-
|
|
55
|
-
def __del__(self):
|
|
56
|
-
self._stop_clean()
|
|
57
|
-
|
|
58
|
-
def _start_clean(self) -> None:
|
|
59
|
-
"""start worker that close connections after ttl expired"""
|
|
60
|
-
if isinstance(self.cleaner_thread, threading.Thread) and self.cleaner_thread.is_alive():
|
|
61
|
-
return
|
|
62
|
-
self._stop_event.clear()
|
|
63
|
-
self.cleaner_thread = threading.Thread(target=self._clean, name="HandlersCache.clean")
|
|
64
|
-
self.cleaner_thread.daemon = True
|
|
65
|
-
self.cleaner_thread.start()
|
|
66
|
-
|
|
67
|
-
def _stop_clean(self) -> None:
|
|
68
|
-
"""stop clean worker"""
|
|
69
|
-
self._stop_event.set()
|
|
70
|
-
|
|
71
|
-
def set(self, handler: DatabaseHandler):
|
|
72
|
-
"""add (or replace) handler in cache
|
|
73
|
-
|
|
74
|
-
NOTE: If the handler is not thread-safe, then use a lock when making connection. Otherwise, make connection in
|
|
75
|
-
the same thread without using a lock to speed up parallel queries. (They don't need to wait for a connection in
|
|
76
|
-
another thread.)
|
|
77
|
-
|
|
78
|
-
Args:
|
|
79
|
-
handler (DatabaseHandler)
|
|
80
|
-
"""
|
|
81
|
-
thread_safe = getattr(handler, "thread_safe", False)
|
|
82
|
-
with self._lock:
|
|
83
|
-
try:
|
|
84
|
-
# If the handler is defined to be thread safe, set 0 as the last element of the key, otherwise set the thrad ID.
|
|
85
|
-
key = (
|
|
86
|
-
handler.name,
|
|
87
|
-
ctx.company_id,
|
|
88
|
-
0 if thread_safe else threading.get_native_id(),
|
|
89
|
-
)
|
|
90
|
-
self.handlers[key] = {"handler": handler, "expired_at": time.time() + self.ttl}
|
|
91
|
-
if thread_safe:
|
|
92
|
-
handler.connect()
|
|
93
|
-
except Exception:
|
|
94
|
-
pass
|
|
95
|
-
self._start_clean()
|
|
96
|
-
try:
|
|
97
|
-
if not thread_safe:
|
|
98
|
-
handler.connect()
|
|
99
|
-
except Exception:
|
|
100
|
-
pass
|
|
101
|
-
|
|
102
|
-
def get(self, name: str) -> Optional[DatabaseHandler]:
|
|
103
|
-
"""get handler from cache by name
|
|
104
|
-
|
|
105
|
-
Args:
|
|
106
|
-
name (str): handler name
|
|
107
|
-
|
|
108
|
-
Returns:
|
|
109
|
-
DatabaseHandler
|
|
110
|
-
"""
|
|
111
|
-
with self._lock:
|
|
112
|
-
# If the handler is not thread safe, the thread ID will be assigned to the last element of the key.
|
|
113
|
-
key = (name, ctx.company_id, threading.get_native_id())
|
|
114
|
-
if key not in self.handlers:
|
|
115
|
-
# If the handler is thread safe, a 0 will be assigned to the last element of the key.
|
|
116
|
-
key = (name, ctx.company_id, 0)
|
|
117
|
-
if key not in self.handlers or self.handlers[key]["expired_at"] < time.time():
|
|
118
|
-
return None
|
|
119
|
-
self.handlers[key]["expired_at"] = time.time() + self.ttl
|
|
120
|
-
return self.handlers[key]["handler"]
|
|
121
|
-
|
|
122
|
-
def delete(self, name: str) -> None:
|
|
123
|
-
"""delete handler from cache
|
|
124
|
-
|
|
125
|
-
Args:
|
|
126
|
-
name (str): handler name
|
|
127
|
-
"""
|
|
128
|
-
with self._lock:
|
|
129
|
-
key = (name, ctx.company_id, threading.get_native_id())
|
|
130
|
-
if key in self.handlers:
|
|
131
|
-
try:
|
|
132
|
-
self.handlers[key].disconnect()
|
|
133
|
-
except Exception:
|
|
134
|
-
pass
|
|
135
|
-
del self.handlers[key]
|
|
136
|
-
if len(self.handlers) == 0:
|
|
137
|
-
self._stop_clean()
|
|
138
|
-
|
|
139
|
-
def _clean(self) -> None:
|
|
140
|
-
"""worker that delete from cache handlers that was not in use for ttl"""
|
|
141
|
-
while self._stop_event.wait(timeout=3) is False:
|
|
142
|
-
with self._lock:
|
|
143
|
-
for key in list(self.handlers.keys()):
|
|
144
|
-
if (
|
|
145
|
-
self.handlers[key]["expired_at"] < time.time()
|
|
146
|
-
and sys.getrefcount(self.handlers[key]) == 2 # returned ref count is always 1 higher
|
|
147
|
-
):
|
|
148
|
-
try:
|
|
149
|
-
self.handlers[key].disconnect()
|
|
150
|
-
except Exception:
|
|
151
|
-
pass
|
|
152
|
-
del self.handlers[key]
|
|
153
|
-
if len(self.handlers) == 0:
|
|
154
|
-
self._stop_event.set()
|
|
155
|
-
|
|
156
|
-
|
|
157
40
|
class IntegrationController:
|
|
158
41
|
@staticmethod
|
|
159
42
|
def _is_not_empty_str(s):
|
|
@@ -183,9 +66,6 @@ class IntegrationController:
|
|
|
183
66
|
)
|
|
184
67
|
handler_meta = self.get_handler_meta(engine)
|
|
185
68
|
|
|
186
|
-
if not name.islower():
|
|
187
|
-
raise ValueError(f"The name must be in lower case: {name}")
|
|
188
|
-
|
|
189
69
|
accept_connection_args = handler_meta.get("connection_args")
|
|
190
70
|
logger.debug("%s: accept_connection_args - %s", self.__class__.__name__, accept_connection_args)
|
|
191
71
|
|
|
@@ -38,20 +38,16 @@ class Project:
|
|
|
38
38
|
return p
|
|
39
39
|
|
|
40
40
|
def create(self, name: str):
|
|
41
|
-
name = name.lower()
|
|
42
|
-
|
|
43
41
|
company_id = ctx.company_id if ctx.company_id is not None else 0
|
|
44
42
|
|
|
45
43
|
existing_record = db.Integration.query.filter(
|
|
46
|
-
|
|
44
|
+
db.Integration.name == name, db.Integration.company_id == ctx.company_id
|
|
47
45
|
).first()
|
|
48
46
|
if existing_record is not None:
|
|
49
47
|
raise EntityExistsError("Database exists with this name ", name)
|
|
50
48
|
|
|
51
49
|
existing_record = db.Project.query.filter(
|
|
52
|
-
(
|
|
53
|
-
& (db.Project.company_id == company_id)
|
|
54
|
-
& (db.Project.deleted_at == sa.null())
|
|
50
|
+
(db.Project.name == name) & (db.Project.company_id == company_id) & (db.Project.deleted_at == sa.null())
|
|
55
51
|
).first()
|
|
56
52
|
if existing_record is not None:
|
|
57
53
|
raise EntityExistsError("Project already exists", name)
|
|
@@ -8,7 +8,6 @@ from mindsdb.interfaces.model.functions import get_project_record, get_project_r
|
|
|
8
8
|
|
|
9
9
|
class ViewController:
|
|
10
10
|
def add(self, name, query, project_name):
|
|
11
|
-
name = name.lower()
|
|
12
11
|
from mindsdb.interfaces.database.database import DatabaseController
|
|
13
12
|
|
|
14
13
|
database_controller = DatabaseController()
|
|
@@ -20,9 +19,7 @@ class ViewController:
|
|
|
20
19
|
project_id = project_databases_dict[project_name]["id"]
|
|
21
20
|
view_record = (
|
|
22
21
|
db.session.query(db.View.id)
|
|
23
|
-
.filter(
|
|
24
|
-
func.lower(db.View.name) == name, db.View.company_id == ctx.company_id, db.View.project_id == project_id
|
|
25
|
-
)
|
|
22
|
+
.filter(db.View.name == name, db.View.company_id == ctx.company_id, db.View.project_id == project_id)
|
|
26
23
|
.first()
|
|
27
24
|
)
|
|
28
25
|
if view_record is not None:
|
|
@@ -128,8 +128,6 @@ class JobsController:
|
|
|
128
128
|
at the moment supports: 'every <number> <dimension>' or 'every <dimension>'
|
|
129
129
|
:return: name of created job
|
|
130
130
|
"""
|
|
131
|
-
if not name.islower():
|
|
132
|
-
raise ValueError(f"The name must be in lower case: {name}")
|
|
133
131
|
|
|
134
132
|
project_controller = ProjectController()
|
|
135
133
|
project = project_controller.get(name=project_name)
|
|
@@ -174,8 +172,6 @@ class JobsController:
|
|
|
174
172
|
# no schedule for job end_at is meaningless
|
|
175
173
|
end_at = None
|
|
176
174
|
|
|
177
|
-
name = name.lower()
|
|
178
|
-
|
|
179
175
|
# create job record
|
|
180
176
|
record = db.Jobs(
|
|
181
177
|
company_id=ctx.company_id,
|