MindsDB 25.9.2.0a1__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 +40 -29
- mindsdb/api/a2a/__init__.py +1 -1
- mindsdb/api/a2a/agent.py +16 -10
- mindsdb/api/a2a/common/server/server.py +7 -3
- mindsdb/api/a2a/common/server/task_manager.py +12 -5
- mindsdb/api/a2a/common/types.py +66 -0
- mindsdb/api/a2a/task_manager.py +65 -17
- mindsdb/api/common/middleware.py +10 -12
- mindsdb/api/executor/command_executor.py +51 -40
- mindsdb/api/executor/datahub/datanodes/datanode.py +2 -2
- mindsdb/api/executor/datahub/datanodes/information_schema_datanode.py +7 -13
- mindsdb/api/executor/datahub/datanodes/integration_datanode.py +101 -49
- mindsdb/api/executor/datahub/datanodes/project_datanode.py +8 -4
- mindsdb/api/executor/datahub/datanodes/system_tables.py +3 -2
- mindsdb/api/executor/exceptions.py +29 -10
- mindsdb/api/executor/planner/plan_join.py +17 -3
- mindsdb/api/executor/planner/query_prepare.py +2 -20
- mindsdb/api/executor/sql_query/sql_query.py +74 -74
- mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +1 -2
- mindsdb/api/executor/sql_query/steps/subselect_step.py +0 -1
- mindsdb/api/executor/utilities/functions.py +6 -6
- mindsdb/api/executor/utilities/sql.py +37 -20
- mindsdb/api/http/gui.py +5 -11
- mindsdb/api/http/initialize.py +75 -61
- mindsdb/api/http/namespaces/agents.py +10 -15
- mindsdb/api/http/namespaces/analysis.py +13 -20
- mindsdb/api/http/namespaces/auth.py +1 -1
- mindsdb/api/http/namespaces/chatbots.py +0 -5
- mindsdb/api/http/namespaces/config.py +15 -11
- mindsdb/api/http/namespaces/databases.py +140 -201
- mindsdb/api/http/namespaces/file.py +17 -4
- mindsdb/api/http/namespaces/handlers.py +17 -7
- mindsdb/api/http/namespaces/knowledge_bases.py +28 -7
- mindsdb/api/http/namespaces/models.py +94 -126
- mindsdb/api/http/namespaces/projects.py +13 -22
- mindsdb/api/http/namespaces/sql.py +33 -25
- mindsdb/api/http/namespaces/tab.py +27 -37
- mindsdb/api/http/namespaces/views.py +1 -1
- mindsdb/api/http/start.py +16 -10
- mindsdb/api/mcp/__init__.py +2 -1
- mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +15 -20
- mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +26 -50
- mindsdb/api/mysql/mysql_proxy/utilities/__init__.py +0 -1
- mindsdb/api/mysql/mysql_proxy/utilities/dump.py +8 -2
- mindsdb/integrations/handlers/byom_handler/byom_handler.py +165 -190
- mindsdb/integrations/handlers/databricks_handler/databricks_handler.py +98 -46
- mindsdb/integrations/handlers/druid_handler/druid_handler.py +32 -40
- mindsdb/integrations/handlers/file_handler/file_handler.py +7 -0
- mindsdb/integrations/handlers/gitlab_handler/gitlab_handler.py +5 -2
- mindsdb/integrations/handlers/lightwood_handler/functions.py +45 -79
- 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 +14 -2
- mindsdb/integrations/handlers/shopify_handler/shopify_handler.py +25 -12
- mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +2 -1
- mindsdb/integrations/handlers/statsforecast_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/statsforecast_handler/requirements_extra.txt +1 -0
- mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +4 -4
- mindsdb/integrations/handlers/zendesk_handler/zendesk_tables.py +144 -111
- mindsdb/integrations/libs/api_handler.py +10 -10
- mindsdb/integrations/libs/base.py +4 -4
- mindsdb/integrations/libs/llm/utils.py +2 -2
- mindsdb/integrations/libs/ml_handler_process/create_engine_process.py +4 -7
- mindsdb/integrations/libs/ml_handler_process/func_call_process.py +2 -7
- mindsdb/integrations/libs/ml_handler_process/learn_process.py +37 -47
- mindsdb/integrations/libs/ml_handler_process/update_engine_process.py +4 -7
- mindsdb/integrations/libs/ml_handler_process/update_process.py +2 -7
- mindsdb/integrations/libs/process_cache.py +132 -140
- mindsdb/integrations/libs/response.py +18 -12
- mindsdb/integrations/libs/vectordatabase_handler.py +26 -0
- mindsdb/integrations/utilities/files/file_reader.py +6 -7
- 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/config_loader.py +37 -26
- mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +83 -30
- mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +4 -4
- mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +55 -133
- mindsdb/integrations/utilities/rag/settings.py +58 -133
- mindsdb/integrations/utilities/rag/splitters/file_splitter.py +5 -15
- mindsdb/interfaces/agents/agents_controller.py +2 -3
- mindsdb/interfaces/agents/constants.py +0 -2
- mindsdb/interfaces/agents/litellm_server.py +34 -58
- mindsdb/interfaces/agents/mcp_client_agent.py +10 -10
- mindsdb/interfaces/agents/mindsdb_database_agent.py +5 -5
- mindsdb/interfaces/agents/run_mcp_agent.py +12 -21
- mindsdb/interfaces/chatbot/chatbot_task.py +20 -23
- mindsdb/interfaces/chatbot/polling.py +30 -18
- mindsdb/interfaces/data_catalog/data_catalog_loader.py +16 -17
- 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 +7 -110
- mindsdb/interfaces/database/projects.py +2 -6
- mindsdb/interfaces/database/views.py +1 -4
- mindsdb/interfaces/file/file_controller.py +6 -6
- mindsdb/interfaces/functions/controller.py +1 -1
- mindsdb/interfaces/functions/to_markdown.py +2 -2
- mindsdb/interfaces/jobs/jobs_controller.py +5 -9
- mindsdb/interfaces/jobs/scheduler.py +3 -9
- mindsdb/interfaces/knowledge_base/controller.py +244 -128
- 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/knowledge_base/preprocessing/json_chunker.py +40 -61
- mindsdb/interfaces/model/model_controller.py +172 -168
- mindsdb/interfaces/query_context/context_controller.py +14 -2
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +10 -14
- mindsdb/interfaces/skills/retrieval_tool.py +43 -50
- mindsdb/interfaces/skills/skill_tool.py +2 -2
- mindsdb/interfaces/skills/skills_controller.py +1 -4
- mindsdb/interfaces/skills/sql_agent.py +25 -19
- mindsdb/interfaces/storage/db.py +16 -6
- mindsdb/interfaces/storage/fs.py +114 -169
- mindsdb/interfaces/storage/json.py +19 -18
- mindsdb/interfaces/tabs/tabs_controller.py +49 -72
- mindsdb/interfaces/tasks/task_monitor.py +3 -9
- mindsdb/interfaces/tasks/task_thread.py +7 -9
- mindsdb/interfaces/triggers/trigger_task.py +7 -13
- mindsdb/interfaces/triggers/triggers_controller.py +47 -52
- mindsdb/migrations/migrate.py +16 -16
- mindsdb/utilities/api_status.py +58 -0
- mindsdb/utilities/config.py +68 -2
- mindsdb/utilities/exception.py +40 -1
- mindsdb/utilities/fs.py +0 -1
- mindsdb/utilities/hooks/profiling.py +17 -14
- mindsdb/utilities/json_encoder.py +24 -10
- mindsdb/utilities/langfuse.py +40 -45
- mindsdb/utilities/log.py +272 -0
- mindsdb/utilities/ml_task_queue/consumer.py +52 -58
- mindsdb/utilities/ml_task_queue/producer.py +26 -30
- mindsdb/utilities/render/sqlalchemy_render.py +22 -20
- mindsdb/utilities/starters.py +0 -10
- mindsdb/utilities/utils.py +2 -2
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/METADATA +293 -276
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/RECORD +144 -158
- mindsdb/api/mysql/mysql_proxy/utilities/exceptions.py +0 -14
- 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 -189
- 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 -253
- 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.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/WHEEL +0 -0
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from http import HTTPStatus
|
|
2
|
-
import traceback
|
|
3
2
|
|
|
4
3
|
from flask import request
|
|
5
4
|
from flask_restx import Resource
|
|
@@ -18,6 +17,7 @@ from mindsdb.metrics.metrics import api_endpoint_metrics
|
|
|
18
17
|
from mindsdb.utilities import log
|
|
19
18
|
from mindsdb.utilities.config import Config
|
|
20
19
|
from mindsdb.utilities.context import context as ctx
|
|
20
|
+
from mindsdb.utilities.exception import QueryError
|
|
21
21
|
|
|
22
22
|
logger = log.getLogger(__name__)
|
|
23
23
|
|
|
@@ -29,18 +29,14 @@ class Query(Resource):
|
|
|
29
29
|
super().__init__(*args, **kwargs)
|
|
30
30
|
|
|
31
31
|
@ns_conf.doc("query")
|
|
32
|
-
@api_endpoint_metrics(
|
|
32
|
+
@api_endpoint_metrics("POST", "/sql/query")
|
|
33
33
|
def post(self):
|
|
34
34
|
query = request.json["query"]
|
|
35
35
|
context = request.json.get("context", {})
|
|
36
36
|
|
|
37
37
|
if isinstance(query, str) is False or isinstance(context, dict) is False:
|
|
38
|
-
return http_error(
|
|
39
|
-
|
|
40
|
-
'Wrong arguments',
|
|
41
|
-
'Please provide "query" with the request.'
|
|
42
|
-
)
|
|
43
|
-
logger.debug(f'Incoming query: {query}')
|
|
38
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Wrong arguments", 'Please provide "query" with the request.')
|
|
39
|
+
logger.debug(f"Incoming query: {query}")
|
|
44
40
|
|
|
45
41
|
if context.get("profiling") is True:
|
|
46
42
|
profiler.enable()
|
|
@@ -50,9 +46,7 @@ class Query(Resource):
|
|
|
50
46
|
error_text = None
|
|
51
47
|
error_traceback = None
|
|
52
48
|
|
|
53
|
-
profiler.set_meta(
|
|
54
|
-
query=query, api="http", environment=Config().get("environment")
|
|
55
|
-
)
|
|
49
|
+
profiler.set_meta(query=query, api="http", environment=Config().get("environment"))
|
|
56
50
|
with profiler.Context("http_query_processing"):
|
|
57
51
|
mysql_proxy = FakeMysqlProxy()
|
|
58
52
|
mysql_proxy.set_context(context)
|
|
@@ -67,8 +61,18 @@ class Query(Resource):
|
|
|
67
61
|
"error_code": 0,
|
|
68
62
|
"error_message": str(e),
|
|
69
63
|
}
|
|
70
|
-
logger.
|
|
71
|
-
|
|
64
|
+
logger.warning(f"Error query processing: {e}")
|
|
65
|
+
except QueryError as e:
|
|
66
|
+
error_type = "expected" if e.is_expected else "unexpected"
|
|
67
|
+
query_response = {
|
|
68
|
+
"type": SQL_RESPONSE_TYPE.ERROR,
|
|
69
|
+
"error_code": 0,
|
|
70
|
+
"error_message": str(e),
|
|
71
|
+
}
|
|
72
|
+
if e.is_expected:
|
|
73
|
+
logger.warning(f"Query failed due to expected reason: {e}")
|
|
74
|
+
else:
|
|
75
|
+
logger.exception("Error query processing:")
|
|
72
76
|
except UnknownError as e:
|
|
73
77
|
# unclassified
|
|
74
78
|
error_type = "unexpected"
|
|
@@ -77,7 +81,7 @@ class Query(Resource):
|
|
|
77
81
|
"error_code": 0,
|
|
78
82
|
"error_message": str(e),
|
|
79
83
|
}
|
|
80
|
-
logger.
|
|
84
|
+
logger.exception("Error query processing:")
|
|
81
85
|
|
|
82
86
|
except Exception as e:
|
|
83
87
|
error_type = "unexpected"
|
|
@@ -86,7 +90,7 @@ class Query(Resource):
|
|
|
86
90
|
"error_code": 0,
|
|
87
91
|
"error_message": str(e),
|
|
88
92
|
}
|
|
89
|
-
logger.
|
|
93
|
+
logger.exception("Error query processing:")
|
|
90
94
|
|
|
91
95
|
if query_response.get("type") == SQL_RESPONSE_TYPE.ERROR:
|
|
92
96
|
error_type = "expected"
|
|
@@ -115,7 +119,7 @@ class Query(Resource):
|
|
|
115
119
|
@ns_conf.param("list_databases", "lists databases of mindsdb")
|
|
116
120
|
class ListDatabases(Resource):
|
|
117
121
|
@ns_conf.doc("list_databases")
|
|
118
|
-
@api_endpoint_metrics(
|
|
122
|
+
@api_endpoint_metrics("GET", "/sql/list_databases")
|
|
119
123
|
def get(self):
|
|
120
124
|
listing_query = "SHOW DATABASES"
|
|
121
125
|
mysql_proxy = FakeMysqlProxy()
|
|
@@ -133,17 +137,21 @@ class ListDatabases(Resource):
|
|
|
133
137
|
listing_query_response = {"type": "ok"}
|
|
134
138
|
elif result.type == SQL_RESPONSE_TYPE.TABLE:
|
|
135
139
|
listing_query_response = {
|
|
136
|
-
"data": [
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
"data": [
|
|
141
|
+
{
|
|
142
|
+
"name": db_row[0],
|
|
143
|
+
"tables": [
|
|
144
|
+
table_row[0]
|
|
145
|
+
for table_row in mysql_proxy.process_query(
|
|
146
|
+
"SHOW TABLES FROM `{}`".format(db_row[0])
|
|
147
|
+
).result_set.to_lists()
|
|
148
|
+
],
|
|
149
|
+
}
|
|
150
|
+
for db_row in result.result_set.to_lists()
|
|
151
|
+
]
|
|
145
152
|
}
|
|
146
153
|
except Exception as e:
|
|
154
|
+
logger.exception("Error while retrieving list of databases")
|
|
147
155
|
listing_query_response = {
|
|
148
156
|
"type": "error",
|
|
149
157
|
"error_code": 0,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import traceback
|
|
3
2
|
from http import HTTPStatus
|
|
4
3
|
|
|
5
4
|
from flask import request
|
|
@@ -29,20 +28,20 @@ def _is_request_valid() -> bool:
|
|
|
29
28
|
if (
|
|
30
29
|
isinstance(data, dict) is False
|
|
31
30
|
or len(data.keys()) == 0
|
|
32
|
-
or len(set(data.keys()) - {
|
|
31
|
+
or len(set(data.keys()) - {"index", "name", "content"}) != 0
|
|
33
32
|
):
|
|
34
33
|
return False
|
|
35
34
|
return True
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
@ns_conf.route(
|
|
37
|
+
@ns_conf.route("/")
|
|
39
38
|
class Tabs(Resource):
|
|
40
|
-
@ns_conf.doc(
|
|
41
|
-
@api_endpoint_metrics(
|
|
39
|
+
@ns_conf.doc("get_tabs")
|
|
40
|
+
@api_endpoint_metrics("GET", "/tabs")
|
|
42
41
|
def get(self):
|
|
43
|
-
mode = request.args.get(
|
|
42
|
+
mode = request.args.get("mode")
|
|
44
43
|
|
|
45
|
-
if mode ==
|
|
44
|
+
if mode == "new":
|
|
46
45
|
return tabs_controller.get_all(), 200
|
|
47
46
|
else:
|
|
48
47
|
# deprecated
|
|
@@ -51,26 +50,23 @@ class Tabs(Resource):
|
|
|
51
50
|
try:
|
|
52
51
|
raw_data = storage.file_get(TABS_FILENAME)
|
|
53
52
|
tabs = json.loads(raw_data)
|
|
54
|
-
except Exception
|
|
55
|
-
logger.warning("unable to get tabs data - %s",
|
|
53
|
+
except Exception:
|
|
54
|
+
logger.warning("unable to get tabs data - %s", exc_info=True)
|
|
56
55
|
return {}, 200
|
|
57
56
|
return tabs, 200
|
|
58
57
|
|
|
59
|
-
@ns_conf.doc(
|
|
60
|
-
@api_endpoint_metrics(
|
|
58
|
+
@ns_conf.doc("save_tab")
|
|
59
|
+
@api_endpoint_metrics("POST", "/tabs")
|
|
61
60
|
def post(self):
|
|
62
|
-
mode = request.args.get(
|
|
61
|
+
mode = request.args.get("mode")
|
|
63
62
|
|
|
64
|
-
if mode ==
|
|
63
|
+
if mode == "new":
|
|
65
64
|
if _is_request_valid() is False:
|
|
66
|
-
return http_error(400,
|
|
65
|
+
return http_error(400, "Error", "Invalid parameters")
|
|
67
66
|
data = request.json
|
|
68
67
|
tab_meta = tabs_controller.add(**data)
|
|
69
68
|
tabs_meta = tabs_controller._get_tabs_meta()
|
|
70
|
-
return {
|
|
71
|
-
'tab_meta': tab_meta,
|
|
72
|
-
'tabs_meta': tabs_meta
|
|
73
|
-
}, 200
|
|
69
|
+
return {"tab_meta": tab_meta, "tabs_meta": tabs_meta}, 200
|
|
74
70
|
else:
|
|
75
71
|
# deprecated
|
|
76
72
|
storage = get_storage()
|
|
@@ -78,54 +74,48 @@ class Tabs(Resource):
|
|
|
78
74
|
tabs = request.json
|
|
79
75
|
b_types = json.dumps(tabs).encode("utf-8")
|
|
80
76
|
storage.file_set(TABS_FILENAME, b_types)
|
|
81
|
-
except Exception
|
|
82
|
-
logger.
|
|
83
|
-
logger.error(traceback.format_exc())
|
|
77
|
+
except Exception:
|
|
78
|
+
logger.exception("Unable to store tabs data:")
|
|
84
79
|
return http_error(
|
|
85
|
-
HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
86
|
-
"Can't save tabs",
|
|
87
|
-
'something went wrong during tabs saving'
|
|
80
|
+
HTTPStatus.INTERNAL_SERVER_ERROR, "Can't save tabs", "something went wrong during tabs saving"
|
|
88
81
|
)
|
|
89
82
|
|
|
90
|
-
return
|
|
83
|
+
return "", 200
|
|
91
84
|
|
|
92
85
|
|
|
93
86
|
@ns_conf.route("/<tab_id>")
|
|
94
87
|
@ns_conf.param("tab_id", "id of tab")
|
|
95
88
|
class Tab(Resource):
|
|
96
89
|
@ns_conf.doc("get_tab")
|
|
97
|
-
@api_endpoint_metrics(
|
|
90
|
+
@api_endpoint_metrics("GET", "/tabs/tab")
|
|
98
91
|
def get(self, tab_id: int):
|
|
99
92
|
try:
|
|
100
93
|
tab_data = tabs_controller.get(int(tab_id))
|
|
101
94
|
except EntityNotExistsError:
|
|
102
|
-
return http_error(404,
|
|
95
|
+
return http_error(404, "Error", "The tab does not exist")
|
|
103
96
|
|
|
104
97
|
return tab_data, 200
|
|
105
98
|
|
|
106
99
|
@ns_conf.doc("put_tab")
|
|
107
|
-
@api_endpoint_metrics(
|
|
100
|
+
@api_endpoint_metrics("PUT", "/tabs/tab")
|
|
108
101
|
def put(self, tab_id: int):
|
|
109
102
|
if _is_request_valid() is False:
|
|
110
|
-
return http_error(400,
|
|
103
|
+
return http_error(400, "Error", "Invalid parameters")
|
|
111
104
|
data = request.json
|
|
112
105
|
try:
|
|
113
106
|
tab_meta = tabs_controller.modify(int(tab_id), **data)
|
|
114
107
|
except EntityNotExistsError:
|
|
115
|
-
return http_error(404,
|
|
108
|
+
return http_error(404, "Error", "The tab does not exist")
|
|
116
109
|
|
|
117
110
|
tabs_meta = tabs_controller._get_tabs_meta()
|
|
118
111
|
|
|
119
|
-
return {
|
|
120
|
-
'tab_meta': tab_meta,
|
|
121
|
-
'tabs_meta': tabs_meta
|
|
122
|
-
}, 200
|
|
112
|
+
return {"tab_meta": tab_meta, "tabs_meta": tabs_meta}, 200
|
|
123
113
|
|
|
124
114
|
@ns_conf.doc("delete_tab")
|
|
125
|
-
@api_endpoint_metrics(
|
|
115
|
+
@api_endpoint_metrics("DELETE", "/tabs/tab")
|
|
126
116
|
def delete(self, tab_id: int):
|
|
127
117
|
try:
|
|
128
118
|
tabs_controller.delete(int(tab_id))
|
|
129
119
|
except EntityNotExistsError:
|
|
130
|
-
return http_error(404,
|
|
131
|
-
return
|
|
120
|
+
return http_error(404, "Error", "The tab does not exist")
|
|
121
|
+
return "", 200
|
|
@@ -47,7 +47,7 @@ class ViewsList(Resource):
|
|
|
47
47
|
try:
|
|
48
48
|
project = session.database_controller.get_project(project_name)
|
|
49
49
|
except EntityNotExistsError:
|
|
50
|
-
return http_error(HTTPStatus.NOT_FOUND, "
|
|
50
|
+
return http_error(HTTPStatus.NOT_FOUND, "Project not found", f"Project name {project_name} does not exist")
|
|
51
51
|
|
|
52
52
|
if project.get_view(name) is not None:
|
|
53
53
|
return http_error(HTTPStatus.CONFLICT, "Name conflict", f"View with name {name} already exists.")
|
mindsdb/api/http/start.py
CHANGED
|
@@ -3,6 +3,10 @@ import gc
|
|
|
3
3
|
gc.disable()
|
|
4
4
|
|
|
5
5
|
from flask import Flask
|
|
6
|
+
from starlette.applications import Starlette
|
|
7
|
+
from starlette.routing import Mount
|
|
8
|
+
from a2wsgi import WSGIMiddleware
|
|
9
|
+
import uvicorn
|
|
6
10
|
|
|
7
11
|
from mindsdb.api.http.initialize import initialize_app
|
|
8
12
|
from mindsdb.interfaces.storage import db
|
|
@@ -11,13 +15,6 @@ from mindsdb.utilities.config import config
|
|
|
11
15
|
from mindsdb.utilities.functions import init_lexer_parsers
|
|
12
16
|
from mindsdb.integrations.libs.ml_exec_base import process_cache
|
|
13
17
|
from mindsdb.api.common.middleware import PATAuthMiddleware
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
from starlette.applications import Starlette
|
|
17
|
-
from starlette.routing import Mount
|
|
18
|
-
from starlette.middleware.wsgi import WSGIMiddleware
|
|
19
|
-
import uvicorn
|
|
20
|
-
|
|
21
18
|
from mindsdb.api.a2a import get_a2a_app
|
|
22
19
|
from mindsdb.api.mcp import get_mcp_app
|
|
23
20
|
|
|
@@ -26,12 +23,12 @@ gc.enable()
|
|
|
26
23
|
logger = log.getLogger(__name__)
|
|
27
24
|
|
|
28
25
|
|
|
29
|
-
def start(verbose, app: Flask = None):
|
|
26
|
+
def start(verbose, app: Flask = None, is_restart: bool = False):
|
|
30
27
|
db.init()
|
|
31
28
|
init_lexer_parsers()
|
|
32
29
|
|
|
33
30
|
if app is None:
|
|
34
|
-
app = initialize_app()
|
|
31
|
+
app = initialize_app(is_restart)
|
|
35
32
|
|
|
36
33
|
port = config["api"]["http"]["port"]
|
|
37
34
|
host = config["api"]["http"]["host"]
|
|
@@ -48,7 +45,16 @@ def start(verbose, app: Flask = None):
|
|
|
48
45
|
routes.append(Mount("/mcp", app=mcp))
|
|
49
46
|
|
|
50
47
|
# Root app LAST so it won't shadow the others
|
|
51
|
-
routes.append(
|
|
48
|
+
routes.append(
|
|
49
|
+
Mount(
|
|
50
|
+
"/",
|
|
51
|
+
app=WSGIMiddleware(
|
|
52
|
+
app,
|
|
53
|
+
workers=config["api"]["http"]["a2wsgi"]["workers"],
|
|
54
|
+
send_queue_size=config["api"]["http"]["a2wsgi"]["send_queue_size"],
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
)
|
|
52
58
|
|
|
53
59
|
# Setting logging to None makes uvicorn use the existing logging configuration
|
|
54
60
|
uvicorn.run(Starlette(routes=routes, debug=verbose), host=host, port=int(port), log_level=None, log_config=None)
|
mindsdb/api/mcp/__init__.py
CHANGED
|
@@ -98,7 +98,7 @@ def query(query: str, context: dict | None = None) -> dict[str, Any]:
|
|
|
98
98
|
return {"type": SQL_RESPONSE_TYPE.ERROR, "error_code": 0, "error_message": "Unknown response type"}
|
|
99
99
|
|
|
100
100
|
except Exception as e:
|
|
101
|
-
logger.
|
|
101
|
+
logger.exception("Error processing query:")
|
|
102
102
|
return {"type": SQL_RESPONSE_TYPE.ERROR, "error_code": 0, "error_message": str(e)}
|
|
103
103
|
|
|
104
104
|
|
|
@@ -138,6 +138,7 @@ def list_databases() -> list[str]:
|
|
|
138
138
|
return data
|
|
139
139
|
|
|
140
140
|
except Exception as e:
|
|
141
|
+
logger.exception("Error while retrieving list of databases")
|
|
141
142
|
return {
|
|
142
143
|
"type": "error",
|
|
143
144
|
"error_code": 0,
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from mindsdb_sql_parser import parse_sql
|
|
2
|
-
from
|
|
2
|
+
from mindsdb_sql_parser.exceptions import ParsingException
|
|
3
|
+
from mindsdb_sql_parser.ast.base import ASTNode
|
|
3
4
|
|
|
4
5
|
import mindsdb.utilities.profiler as profiler
|
|
5
|
-
from mindsdb.api.executor.sql_query.result_set import Column
|
|
6
6
|
from mindsdb.api.executor.sql_query import SQLQuery
|
|
7
|
+
from mindsdb.api.executor.sql_query.result_set import Column
|
|
8
|
+
from mindsdb.api.executor.planner import utils as planner_utils
|
|
7
9
|
from mindsdb.api.executor.data_types.answer import ExecuteAnswer
|
|
8
10
|
from mindsdb.api.executor.command_executor import ExecuteCommands
|
|
9
|
-
from mindsdb.api.
|
|
11
|
+
from mindsdb.api.executor.exceptions import SqlSyntaxError
|
|
10
12
|
from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import MYSQL_DATA_TYPE
|
|
11
13
|
from mindsdb.utilities import log
|
|
12
14
|
|
|
@@ -18,7 +20,7 @@ class Executor:
|
|
|
18
20
|
self.session = session
|
|
19
21
|
self.sqlserver = sqlserver
|
|
20
22
|
|
|
21
|
-
self.query = None
|
|
23
|
+
self.query: ASTNode = None
|
|
22
24
|
|
|
23
25
|
self.columns: list[Column] = []
|
|
24
26
|
self.params: list[Column] = []
|
|
@@ -32,14 +34,13 @@ class Executor:
|
|
|
32
34
|
self.sql = ""
|
|
33
35
|
self.sql_lower = ""
|
|
34
36
|
|
|
35
|
-
context = {
|
|
37
|
+
context = {"connection_id": self.sqlserver.connection_id}
|
|
36
38
|
self.command_executor = ExecuteCommands(self.session, context)
|
|
37
39
|
|
|
38
40
|
def change_default_db(self, new_db):
|
|
39
41
|
self.command_executor.change_default_db(new_db)
|
|
40
42
|
|
|
41
43
|
def stmt_prepare(self, sql):
|
|
42
|
-
|
|
43
44
|
self.parse(sql)
|
|
44
45
|
|
|
45
46
|
# if not params
|
|
@@ -57,11 +58,7 @@ class Executor:
|
|
|
57
58
|
|
|
58
59
|
sqlquery.prepare_query()
|
|
59
60
|
|
|
60
|
-
self.params = [Column(
|
|
61
|
-
name=p.value,
|
|
62
|
-
alias=p.value,
|
|
63
|
-
type=MYSQL_DATA_TYPE.TEXT
|
|
64
|
-
) for p in params]
|
|
61
|
+
self.params = [Column(name=p.value, alias=p.value, type=MYSQL_DATA_TYPE.TEXT) for p in params]
|
|
65
62
|
|
|
66
63
|
# TODO:
|
|
67
64
|
# select * from mindsdb.models doesn't invoke prepare_steps and columns_list is empty
|
|
@@ -90,17 +87,15 @@ class Executor:
|
|
|
90
87
|
|
|
91
88
|
try:
|
|
92
89
|
self.query = parse_sql(sql)
|
|
93
|
-
except
|
|
90
|
+
except ParsingException as mdb_error:
|
|
94
91
|
# not all statements are parsed by parse_sql
|
|
95
|
-
logger.warning(
|
|
96
|
-
logger.debug(f
|
|
97
|
-
|
|
98
|
-
raise ErSqlSyntaxError(
|
|
99
|
-
f"The SQL statement cannot be parsed - {sql}: {mdb_error}"
|
|
100
|
-
) from mdb_error
|
|
92
|
+
logger.warning("Failed to parse SQL query")
|
|
93
|
+
logger.debug(f"Query that cannot be parsed: {sql}")
|
|
101
94
|
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
raise SqlSyntaxError(f"The SQL statement cannot be parsed - {sql}: {mdb_error}") from mdb_error
|
|
96
|
+
except Exception:
|
|
97
|
+
logger.exception(f"Unexpected error while parsing SQL query: {sql}")
|
|
98
|
+
raise
|
|
104
99
|
|
|
105
100
|
@profiler.profile()
|
|
106
101
|
def do_execute(self):
|
|
@@ -20,6 +20,7 @@ import struct
|
|
|
20
20
|
import sys
|
|
21
21
|
import tempfile
|
|
22
22
|
import traceback
|
|
23
|
+
import logging
|
|
23
24
|
from functools import partial
|
|
24
25
|
from typing import List
|
|
25
26
|
from dataclasses import dataclass
|
|
@@ -67,11 +68,7 @@ from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import (
|
|
|
67
68
|
)
|
|
68
69
|
from mindsdb.api.executor.data_types.answer import ExecuteAnswer
|
|
69
70
|
from mindsdb.api.executor.data_types.response_type import RESPONSE_TYPE
|
|
70
|
-
from mindsdb.api.
|
|
71
|
-
ErWrongCharset,
|
|
72
|
-
SqlApiException,
|
|
73
|
-
)
|
|
74
|
-
from mindsdb.api.executor import exceptions as exec_exc
|
|
71
|
+
from mindsdb.api.executor import exceptions as executor_exceptions
|
|
75
72
|
|
|
76
73
|
from mindsdb.api.common.middleware import check_auth
|
|
77
74
|
from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import MYSQL_DATA_TYPE
|
|
@@ -81,7 +78,9 @@ from mindsdb.utilities.config import config
|
|
|
81
78
|
from mindsdb.utilities.context import context as ctx
|
|
82
79
|
from mindsdb.utilities.otel import increment_otel_query_request_counter
|
|
83
80
|
from mindsdb.utilities.wizards import make_ssl_cert
|
|
81
|
+
from mindsdb.utilities.exception import QueryError
|
|
84
82
|
from mindsdb.api.mysql.mysql_proxy.utilities.dump import dump_result_set_to_mysql, column_to_mysql_column_dict
|
|
83
|
+
from mindsdb.api.executor.exceptions import WrongCharsetError
|
|
85
84
|
|
|
86
85
|
logger = log.getLogger(__name__)
|
|
87
86
|
|
|
@@ -128,6 +127,14 @@ class SQLAnswer:
|
|
|
128
127
|
raise ValueError(f"Unsupported response type for dump HTTP response: {self.resp_type}")
|
|
129
128
|
|
|
130
129
|
|
|
130
|
+
class MysqlTCPServer(SocketServer.ThreadingTCPServer):
|
|
131
|
+
"""
|
|
132
|
+
Custom TCP Server with increased request queue size
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
request_queue_size = 30
|
|
136
|
+
|
|
137
|
+
|
|
131
138
|
class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
132
139
|
"""
|
|
133
140
|
The Main Server controller class
|
|
@@ -428,7 +435,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
428
435
|
try:
|
|
429
436
|
return text.decode("utf-8")
|
|
430
437
|
except Exception:
|
|
431
|
-
raise
|
|
438
|
+
raise WrongCharsetError(f"SQL contains non utf-8 values: {text}")
|
|
432
439
|
|
|
433
440
|
def is_cloud_connection(self):
|
|
434
441
|
"""Determine source of connection. Must be call before handshake.
|
|
@@ -489,6 +496,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
489
496
|
|
|
490
497
|
@profiler.profile()
|
|
491
498
|
def process_query(self, sql) -> SQLAnswer:
|
|
499
|
+
log.log_ram_info(logger)
|
|
492
500
|
executor = Executor(session=self.session, sqlserver=self)
|
|
493
501
|
executor.query_execute(sql)
|
|
494
502
|
executor_answer = executor.executor_answer
|
|
@@ -647,8 +655,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
647
655
|
try:
|
|
648
656
|
success = p.get()
|
|
649
657
|
except Exception:
|
|
650
|
-
logger.
|
|
651
|
-
logger.error(traceback.format_exc())
|
|
658
|
+
logger.exception("Session closed, on packet read error:")
|
|
652
659
|
return
|
|
653
660
|
|
|
654
661
|
if success is False:
|
|
@@ -712,59 +719,28 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
712
719
|
else:
|
|
713
720
|
logger.warning("Command has no specific handler, return OK msg")
|
|
714
721
|
logger.debug(str(p))
|
|
715
|
-
# p.pprintPacket() TODO: Make a version of print packet
|
|
716
|
-
# that sends it to debug instead
|
|
717
722
|
response = SQLAnswer(RESPONSE_TYPE.OK)
|
|
718
723
|
|
|
719
|
-
except
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
)
|
|
728
|
-
|
|
729
|
-
except exec_exc.ExecutorException as e:
|
|
730
|
-
# unclassified
|
|
731
|
-
error_type = "expected"
|
|
732
|
-
|
|
733
|
-
if isinstance(e, exec_exc.NotSupportedYet):
|
|
734
|
-
error_code = ERR.ER_NOT_SUPPORTED_YET
|
|
735
|
-
elif isinstance(e, exec_exc.KeyColumnDoesNotExist):
|
|
736
|
-
error_code = ERR.ER_KEY_COLUMN_DOES_NOT_EXIST
|
|
737
|
-
elif isinstance(e, exec_exc.TableNotExistError):
|
|
738
|
-
error_code = ERR.ER_TABLE_EXISTS_ERROR
|
|
739
|
-
elif isinstance(e, exec_exc.WrongArgumentError):
|
|
740
|
-
error_code = ERR.ER_WRONG_ARGUMENTS
|
|
741
|
-
elif isinstance(e, exec_exc.LogicError):
|
|
742
|
-
error_code = ERR.ER_WRONG_USAGE
|
|
743
|
-
elif isinstance(e, (exec_exc.BadDbError, exec_exc.BadTableError)):
|
|
744
|
-
error_code = ERR.ER_BAD_DB_ERROR
|
|
724
|
+
except (QueryError, executor_exceptions.ExecutorException, executor_exceptions.UnknownError) as e:
|
|
725
|
+
error_type = "expected" if e.is_expected else "unexpected"
|
|
726
|
+
error_code = e.mysql_error_code
|
|
727
|
+
if e.is_expected:
|
|
728
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
729
|
+
logger.info("Query execution failed with expected error:", exc_info=True)
|
|
730
|
+
else:
|
|
731
|
+
logger.info(f"Query execution failed with expected error: {e}")
|
|
745
732
|
else:
|
|
746
|
-
|
|
747
|
-
|
|
733
|
+
logger.exception("Query execution failed with error")
|
|
748
734
|
response = SQLAnswer(
|
|
749
735
|
resp_type=RESPONSE_TYPE.ERROR,
|
|
750
736
|
error_code=error_code,
|
|
751
737
|
error_message=str(e),
|
|
752
738
|
)
|
|
753
|
-
except exec_exc.UnknownError as e:
|
|
754
|
-
# unclassified
|
|
755
|
-
error_type = "unexpected"
|
|
756
|
-
|
|
757
|
-
response = SQLAnswer(
|
|
758
|
-
resp_type=RESPONSE_TYPE.ERROR,
|
|
759
|
-
error_code=ERR.ER_UNKNOWN_ERROR,
|
|
760
|
-
error_message=str(e),
|
|
761
|
-
)
|
|
762
739
|
|
|
763
740
|
except Exception as e:
|
|
764
|
-
# any other exception
|
|
765
741
|
error_type = "unexpected"
|
|
766
742
|
error_traceback = traceback.format_exc()
|
|
767
|
-
logger.
|
|
743
|
+
logger.exception("ERROR while executing query:")
|
|
768
744
|
error_code = ERR.ER_SYNTAX_ERROR
|
|
769
745
|
response = SQLAnswer(
|
|
770
746
|
resp_type=RESPONSE_TYPE.ERROR,
|
|
@@ -856,7 +832,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
856
832
|
logger.info(f"Starting MindsDB Mysql proxy server on tcp://{host}:{port}")
|
|
857
833
|
|
|
858
834
|
SocketServer.TCPServer.allow_reuse_address = True
|
|
859
|
-
server =
|
|
835
|
+
server = MysqlTCPServer((host, port), MysqlProxy)
|
|
860
836
|
server.mindsdb_config = config
|
|
861
837
|
server.check_auth = partial(check_auth, config=config)
|
|
862
838
|
server.cert_path = cert_path
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .exceptions import *
|
|
@@ -4,6 +4,7 @@ from typing import Any
|
|
|
4
4
|
from array import array
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
|
+
import orjson
|
|
7
8
|
from numpy import dtype as np_dtype
|
|
8
9
|
import pandas as pd
|
|
9
10
|
from pandas.api import types as pd_types
|
|
@@ -21,7 +22,8 @@ from mindsdb.utilities.json_encoder import CustomJSONEncoder
|
|
|
21
22
|
|
|
22
23
|
logger = log.getLogger(__name__)
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
# Pre-bind default encoder for custom types so we can serialize JSON consistently
|
|
26
|
+
_default_json = CustomJSONEncoder().default
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
def column_to_mysql_column_dict(column: Column, database_name: str | None = None) -> dict[str, str | int]:
|
|
@@ -115,7 +117,11 @@ def _dump_str(var: Any) -> str | None:
|
|
|
115
117
|
return str(var)[2:-1]
|
|
116
118
|
if isinstance(var, (dict, list)):
|
|
117
119
|
try:
|
|
118
|
-
return
|
|
120
|
+
return orjson.dumps(
|
|
121
|
+
var,
|
|
122
|
+
default=_default_json,
|
|
123
|
+
option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_PASSTHROUGH_DATETIME,
|
|
124
|
+
).decode("utf-8")
|
|
119
125
|
except Exception:
|
|
120
126
|
return str(var)
|
|
121
127
|
# pd.isna returns array of bools for list
|