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.

Files changed (163) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +40 -29
  3. mindsdb/api/a2a/__init__.py +1 -1
  4. mindsdb/api/a2a/agent.py +16 -10
  5. mindsdb/api/a2a/common/server/server.py +7 -3
  6. mindsdb/api/a2a/common/server/task_manager.py +12 -5
  7. mindsdb/api/a2a/common/types.py +66 -0
  8. mindsdb/api/a2a/task_manager.py +65 -17
  9. mindsdb/api/common/middleware.py +10 -12
  10. mindsdb/api/executor/command_executor.py +51 -40
  11. mindsdb/api/executor/datahub/datanodes/datanode.py +2 -2
  12. mindsdb/api/executor/datahub/datanodes/information_schema_datanode.py +7 -13
  13. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +101 -49
  14. mindsdb/api/executor/datahub/datanodes/project_datanode.py +8 -4
  15. mindsdb/api/executor/datahub/datanodes/system_tables.py +3 -2
  16. mindsdb/api/executor/exceptions.py +29 -10
  17. mindsdb/api/executor/planner/plan_join.py +17 -3
  18. mindsdb/api/executor/planner/query_prepare.py +2 -20
  19. mindsdb/api/executor/sql_query/sql_query.py +74 -74
  20. mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +1 -2
  21. mindsdb/api/executor/sql_query/steps/subselect_step.py +0 -1
  22. mindsdb/api/executor/utilities/functions.py +6 -6
  23. mindsdb/api/executor/utilities/sql.py +37 -20
  24. mindsdb/api/http/gui.py +5 -11
  25. mindsdb/api/http/initialize.py +75 -61
  26. mindsdb/api/http/namespaces/agents.py +10 -15
  27. mindsdb/api/http/namespaces/analysis.py +13 -20
  28. mindsdb/api/http/namespaces/auth.py +1 -1
  29. mindsdb/api/http/namespaces/chatbots.py +0 -5
  30. mindsdb/api/http/namespaces/config.py +15 -11
  31. mindsdb/api/http/namespaces/databases.py +140 -201
  32. mindsdb/api/http/namespaces/file.py +17 -4
  33. mindsdb/api/http/namespaces/handlers.py +17 -7
  34. mindsdb/api/http/namespaces/knowledge_bases.py +28 -7
  35. mindsdb/api/http/namespaces/models.py +94 -126
  36. mindsdb/api/http/namespaces/projects.py +13 -22
  37. mindsdb/api/http/namespaces/sql.py +33 -25
  38. mindsdb/api/http/namespaces/tab.py +27 -37
  39. mindsdb/api/http/namespaces/views.py +1 -1
  40. mindsdb/api/http/start.py +16 -10
  41. mindsdb/api/mcp/__init__.py +2 -1
  42. mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +15 -20
  43. mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +26 -50
  44. mindsdb/api/mysql/mysql_proxy/utilities/__init__.py +0 -1
  45. mindsdb/api/mysql/mysql_proxy/utilities/dump.py +8 -2
  46. mindsdb/integrations/handlers/byom_handler/byom_handler.py +165 -190
  47. mindsdb/integrations/handlers/databricks_handler/databricks_handler.py +98 -46
  48. mindsdb/integrations/handlers/druid_handler/druid_handler.py +32 -40
  49. mindsdb/integrations/handlers/file_handler/file_handler.py +7 -0
  50. mindsdb/integrations/handlers/gitlab_handler/gitlab_handler.py +5 -2
  51. mindsdb/integrations/handlers/lightwood_handler/functions.py +45 -79
  52. mindsdb/integrations/handlers/mssql_handler/mssql_handler.py +438 -100
  53. mindsdb/integrations/handlers/mssql_handler/requirements_odbc.txt +3 -0
  54. mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +235 -3
  55. mindsdb/integrations/handlers/oracle_handler/__init__.py +2 -0
  56. mindsdb/integrations/handlers/oracle_handler/connection_args.py +7 -1
  57. mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +321 -16
  58. mindsdb/integrations/handlers/oracle_handler/requirements.txt +1 -1
  59. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +14 -2
  60. mindsdb/integrations/handlers/shopify_handler/shopify_handler.py +25 -12
  61. mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +2 -1
  62. mindsdb/integrations/handlers/statsforecast_handler/requirements.txt +1 -0
  63. mindsdb/integrations/handlers/statsforecast_handler/requirements_extra.txt +1 -0
  64. mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +4 -4
  65. mindsdb/integrations/handlers/zendesk_handler/zendesk_tables.py +144 -111
  66. mindsdb/integrations/libs/api_handler.py +10 -10
  67. mindsdb/integrations/libs/base.py +4 -4
  68. mindsdb/integrations/libs/llm/utils.py +2 -2
  69. mindsdb/integrations/libs/ml_handler_process/create_engine_process.py +4 -7
  70. mindsdb/integrations/libs/ml_handler_process/func_call_process.py +2 -7
  71. mindsdb/integrations/libs/ml_handler_process/learn_process.py +37 -47
  72. mindsdb/integrations/libs/ml_handler_process/update_engine_process.py +4 -7
  73. mindsdb/integrations/libs/ml_handler_process/update_process.py +2 -7
  74. mindsdb/integrations/libs/process_cache.py +132 -140
  75. mindsdb/integrations/libs/response.py +18 -12
  76. mindsdb/integrations/libs/vectordatabase_handler.py +26 -0
  77. mindsdb/integrations/utilities/files/file_reader.py +6 -7
  78. mindsdb/integrations/utilities/handlers/auth_utilities/snowflake/__init__.py +1 -0
  79. mindsdb/integrations/utilities/handlers/auth_utilities/snowflake/snowflake_jwt_gen.py +151 -0
  80. mindsdb/integrations/utilities/rag/config_loader.py +37 -26
  81. mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +83 -30
  82. mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +4 -4
  83. mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +55 -133
  84. mindsdb/integrations/utilities/rag/settings.py +58 -133
  85. mindsdb/integrations/utilities/rag/splitters/file_splitter.py +5 -15
  86. mindsdb/interfaces/agents/agents_controller.py +2 -3
  87. mindsdb/interfaces/agents/constants.py +0 -2
  88. mindsdb/interfaces/agents/litellm_server.py +34 -58
  89. mindsdb/interfaces/agents/mcp_client_agent.py +10 -10
  90. mindsdb/interfaces/agents/mindsdb_database_agent.py +5 -5
  91. mindsdb/interfaces/agents/run_mcp_agent.py +12 -21
  92. mindsdb/interfaces/chatbot/chatbot_task.py +20 -23
  93. mindsdb/interfaces/chatbot/polling.py +30 -18
  94. mindsdb/interfaces/data_catalog/data_catalog_loader.py +16 -17
  95. mindsdb/interfaces/data_catalog/data_catalog_reader.py +15 -4
  96. mindsdb/interfaces/database/data_handlers_cache.py +190 -0
  97. mindsdb/interfaces/database/database.py +3 -3
  98. mindsdb/interfaces/database/integrations.py +7 -110
  99. mindsdb/interfaces/database/projects.py +2 -6
  100. mindsdb/interfaces/database/views.py +1 -4
  101. mindsdb/interfaces/file/file_controller.py +6 -6
  102. mindsdb/interfaces/functions/controller.py +1 -1
  103. mindsdb/interfaces/functions/to_markdown.py +2 -2
  104. mindsdb/interfaces/jobs/jobs_controller.py +5 -9
  105. mindsdb/interfaces/jobs/scheduler.py +3 -9
  106. mindsdb/interfaces/knowledge_base/controller.py +244 -128
  107. mindsdb/interfaces/knowledge_base/evaluate.py +36 -41
  108. mindsdb/interfaces/knowledge_base/executor.py +11 -0
  109. mindsdb/interfaces/knowledge_base/llm_client.py +51 -17
  110. mindsdb/interfaces/knowledge_base/preprocessing/json_chunker.py +40 -61
  111. mindsdb/interfaces/model/model_controller.py +172 -168
  112. mindsdb/interfaces/query_context/context_controller.py +14 -2
  113. mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +10 -14
  114. mindsdb/interfaces/skills/retrieval_tool.py +43 -50
  115. mindsdb/interfaces/skills/skill_tool.py +2 -2
  116. mindsdb/interfaces/skills/skills_controller.py +1 -4
  117. mindsdb/interfaces/skills/sql_agent.py +25 -19
  118. mindsdb/interfaces/storage/db.py +16 -6
  119. mindsdb/interfaces/storage/fs.py +114 -169
  120. mindsdb/interfaces/storage/json.py +19 -18
  121. mindsdb/interfaces/tabs/tabs_controller.py +49 -72
  122. mindsdb/interfaces/tasks/task_monitor.py +3 -9
  123. mindsdb/interfaces/tasks/task_thread.py +7 -9
  124. mindsdb/interfaces/triggers/trigger_task.py +7 -13
  125. mindsdb/interfaces/triggers/triggers_controller.py +47 -52
  126. mindsdb/migrations/migrate.py +16 -16
  127. mindsdb/utilities/api_status.py +58 -0
  128. mindsdb/utilities/config.py +68 -2
  129. mindsdb/utilities/exception.py +40 -1
  130. mindsdb/utilities/fs.py +0 -1
  131. mindsdb/utilities/hooks/profiling.py +17 -14
  132. mindsdb/utilities/json_encoder.py +24 -10
  133. mindsdb/utilities/langfuse.py +40 -45
  134. mindsdb/utilities/log.py +272 -0
  135. mindsdb/utilities/ml_task_queue/consumer.py +52 -58
  136. mindsdb/utilities/ml_task_queue/producer.py +26 -30
  137. mindsdb/utilities/render/sqlalchemy_render.py +22 -20
  138. mindsdb/utilities/starters.py +0 -10
  139. mindsdb/utilities/utils.py +2 -2
  140. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/METADATA +293 -276
  141. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/RECORD +144 -158
  142. mindsdb/api/mysql/mysql_proxy/utilities/exceptions.py +0 -14
  143. mindsdb/api/postgres/__init__.py +0 -0
  144. mindsdb/api/postgres/postgres_proxy/__init__.py +0 -0
  145. mindsdb/api/postgres/postgres_proxy/executor/__init__.py +0 -1
  146. mindsdb/api/postgres/postgres_proxy/executor/executor.py +0 -189
  147. mindsdb/api/postgres/postgres_proxy/postgres_packets/__init__.py +0 -0
  148. mindsdb/api/postgres/postgres_proxy/postgres_packets/errors.py +0 -322
  149. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_fields.py +0 -34
  150. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message.py +0 -31
  151. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_formats.py +0 -1265
  152. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_identifiers.py +0 -31
  153. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_packets.py +0 -253
  154. mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +0 -477
  155. mindsdb/api/postgres/postgres_proxy/utilities/__init__.py +0 -10
  156. mindsdb/api/postgres/start.py +0 -11
  157. mindsdb/integrations/handlers/mssql_handler/tests/__init__.py +0 -0
  158. mindsdb/integrations/handlers/mssql_handler/tests/test_mssql_handler.py +0 -169
  159. mindsdb/integrations/handlers/oracle_handler/tests/__init__.py +0 -0
  160. mindsdb/integrations/handlers/oracle_handler/tests/test_oracle_handler.py +0 -32
  161. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/WHEEL +0 -0
  162. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/licenses/LICENSE +0 -0
  163. {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('POST', '/sql/query')
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
- HTTPStatus.BAD_REQUEST,
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.error(f"Error query processing: \n{traceback.format_exc()}")
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.error(f"Error query processing: \n{traceback.format_exc()}")
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.error(f"Error query processing: \n{traceback.format_exc()}")
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('GET', '/sql/list_databases')
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
- "name": db_row[0],
138
- "tables": [
139
- table_row[0]
140
- for table_row in mysql_proxy.process_query(
141
- "SHOW TABLES FROM `{}`".format(db_row[0])
142
- ).result_set.to_lists()
143
- ]
144
- } for db_row in result.result_set.to_lists()]
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()) - {'index', 'name', 'content'}) != 0
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('get_tabs')
41
- @api_endpoint_metrics('GET', '/tabs')
39
+ @ns_conf.doc("get_tabs")
40
+ @api_endpoint_metrics("GET", "/tabs")
42
41
  def get(self):
43
- mode = request.args.get('mode')
42
+ mode = request.args.get("mode")
44
43
 
45
- if mode == 'new':
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 as e:
55
- logger.warning("unable to get tabs data - %s", e)
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('save_tab')
60
- @api_endpoint_metrics('POST', '/tabs')
58
+ @ns_conf.doc("save_tab")
59
+ @api_endpoint_metrics("POST", "/tabs")
61
60
  def post(self):
62
- mode = request.args.get('mode')
61
+ mode = request.args.get("mode")
63
62
 
64
- if mode == 'new':
63
+ if mode == "new":
65
64
  if _is_request_valid() is False:
66
- return http_error(400, 'Error', 'Invalid parameters')
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 as e:
82
- logger.error("unable to store tabs data - %s", e)
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 '', 200
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('GET', '/tabs/tab')
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, 'Error', 'The tab does not exist')
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('PUT', '/tabs/tab')
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, 'Error', 'Invalid parameters')
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, 'Error', 'The tab does not exist')
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('DELETE', '/tabs/tab')
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, 'Error', 'The tab does not exist')
131
- return '', 200
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, "Not found", f"Project name {project_name} does not exist")
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(Mount("/", app=WSGIMiddleware(app)))
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)
@@ -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.error(f"Error processing query: {str(e)}")
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 mindsdb.api.executor.planner import utils as planner_utils
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.mysql.mysql_proxy.utilities import ErSqlSyntaxError
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 = {'connection_id': self.sqlserver.connection_id}
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 Exception as mdb_error:
90
+ except ParsingException as mdb_error:
94
91
  # not all statements are parsed by parse_sql
95
- logger.warning('Failed to parse SQL query')
96
- logger.debug(f'Query that cannot be parsed: {sql}')
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
- # == a place for workarounds ==
103
- # or run sql in integration without parsing
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.mysql.mysql_proxy.utilities import (
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 ErWrongCharset(f"SQL contains non utf-8 values: {text}")
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.error("Session closed, on packet read error")
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 SqlApiException as e:
720
- # classified error
721
- error_type = "expected"
722
-
723
- response = SQLAnswer(
724
- resp_type=RESPONSE_TYPE.ERROR,
725
- error_code=e.err_code,
726
- error_message=str(e),
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
- error_code = ERR.ER_SYNTAX_ERROR
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.error(f"ERROR while executing query\n{error_traceback}\n{e}")
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 = SocketServer.ThreadingTCPServer((host, port), MysqlProxy)
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
- json_encoder = CustomJSONEncoder()
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 json_encoder.encode(var)
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