MindsDB 25.7.3.0__py3-none-any.whl → 25.7.4.0__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 (61) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/api/a2a/common/server/server.py +16 -6
  3. mindsdb/api/executor/command_executor.py +206 -135
  4. mindsdb/api/executor/datahub/datanodes/project_datanode.py +14 -3
  5. mindsdb/api/executor/planner/plan_join.py +3 -0
  6. mindsdb/api/executor/planner/plan_join_ts.py +117 -100
  7. mindsdb/api/executor/planner/query_planner.py +1 -0
  8. mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +54 -85
  9. mindsdb/api/http/initialize.py +16 -43
  10. mindsdb/api/http/namespaces/agents.py +23 -20
  11. mindsdb/api/http/namespaces/chatbots.py +83 -120
  12. mindsdb/api/http/namespaces/file.py +1 -1
  13. mindsdb/api/http/namespaces/jobs.py +38 -60
  14. mindsdb/api/http/namespaces/tree.py +69 -61
  15. mindsdb/api/mcp/start.py +2 -0
  16. mindsdb/api/mysql/mysql_proxy/utilities/dump.py +3 -2
  17. mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
  18. mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
  19. mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +25 -5
  20. mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +3 -3
  21. mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
  22. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_tables.py +82 -73
  23. mindsdb/integrations/handlers/hubspot_handler/requirements.txt +1 -1
  24. mindsdb/integrations/handlers/langchain_handler/langchain_handler.py +83 -76
  25. mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
  26. mindsdb/integrations/handlers/litellm_handler/litellm_handler.py +5 -2
  27. mindsdb/integrations/handlers/litellm_handler/settings.py +2 -1
  28. mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +106 -90
  29. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +41 -39
  30. mindsdb/integrations/handlers/salesforce_handler/constants.py +208 -0
  31. mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +141 -80
  32. mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +0 -1
  33. mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
  34. mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +32 -17
  35. mindsdb/integrations/handlers/web_handler/web_handler.py +19 -22
  36. mindsdb/integrations/libs/vectordatabase_handler.py +10 -1
  37. mindsdb/integrations/utilities/handler_utils.py +32 -12
  38. mindsdb/interfaces/agents/agents_controller.py +167 -108
  39. mindsdb/interfaces/agents/langchain_agent.py +10 -3
  40. mindsdb/interfaces/data_catalog/data_catalog_loader.py +4 -4
  41. mindsdb/interfaces/database/database.py +38 -13
  42. mindsdb/interfaces/database/integrations.py +20 -5
  43. mindsdb/interfaces/database/projects.py +63 -16
  44. mindsdb/interfaces/database/views.py +86 -60
  45. mindsdb/interfaces/jobs/jobs_controller.py +103 -110
  46. mindsdb/interfaces/knowledge_base/controller.py +26 -5
  47. mindsdb/interfaces/knowledge_base/evaluate.py +2 -1
  48. mindsdb/interfaces/knowledge_base/executor.py +24 -0
  49. mindsdb/interfaces/query_context/context_controller.py +100 -133
  50. mindsdb/interfaces/skills/skills_controller.py +18 -6
  51. mindsdb/interfaces/storage/db.py +40 -6
  52. mindsdb/interfaces/variables/variables_controller.py +8 -15
  53. mindsdb/utilities/config.py +3 -3
  54. mindsdb/utilities/functions.py +72 -60
  55. mindsdb/utilities/log.py +38 -6
  56. mindsdb/utilities/ps.py +7 -7
  57. {mindsdb-25.7.3.0.dist-info → mindsdb-25.7.4.0.dist-info}/METADATA +246 -247
  58. {mindsdb-25.7.3.0.dist-info → mindsdb-25.7.4.0.dist-info}/RECORD +61 -60
  59. {mindsdb-25.7.3.0.dist-info → mindsdb-25.7.4.0.dist-info}/WHEEL +0 -0
  60. {mindsdb-25.7.3.0.dist-info → mindsdb-25.7.4.0.dist-info}/licenses/LICENSE +0 -0
  61. {mindsdb-25.7.3.0.dist-info → mindsdb-25.7.4.0.dist-info}/top_level.txt +0 -0
mindsdb/__about__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  __title__ = "MindsDB"
2
2
  __package_name__ = "mindsdb"
3
- __version__ = "25.7.3.0"
3
+ __version__ = "25.7.4.0"
4
4
  __description__ = "MindsDB's AI SQL Server enables developers to build AI tools that need access to real-time data to perform their tasks"
5
5
  __email__ = "jorge@mindsdb.com"
6
6
  __author__ = "MindsDB Inc"
@@ -1,3 +1,7 @@
1
+ import json
2
+ import time
3
+ from typing import AsyncIterable, Any, Dict
4
+
1
5
  from starlette.applications import Starlette
2
6
  from starlette.middleware.cors import CORSMiddleware
3
7
  from starlette.responses import JSONResponse
@@ -19,14 +23,12 @@ from ...common.types import (
19
23
  SendTaskStreamingRequest,
20
24
  )
21
25
  from pydantic import ValidationError
22
- import json
23
- import time
24
- from typing import AsyncIterable, Any, Dict
25
26
  from ...common.server.task_manager import TaskManager
26
27
 
27
- import logging
28
+ from mindsdb.utilities import log
29
+ from mindsdb.utilities.log import get_uvicorn_logging_config, get_mindsdb_log_level
28
30
 
29
- logger = logging.getLogger(__name__)
31
+ logger = log.getLogger(__name__)
30
32
 
31
33
 
32
34
  class A2AServer:
@@ -68,7 +70,15 @@ class A2AServer:
68
70
  import uvicorn
69
71
 
70
72
  # Configure uvicorn with optimized settings for streaming
71
- uvicorn.run(self.app, host=self.host, port=self.port, http="h11", timeout_keep_alive=65, log_level="info")
73
+ uvicorn.run(
74
+ self.app,
75
+ host=self.host,
76
+ port=self.port,
77
+ http="h11",
78
+ timeout_keep_alive=65,
79
+ log_level=get_mindsdb_log_level(),
80
+ log_config=get_uvicorn_logging_config("uvicorn_a2a"),
81
+ )
72
82
 
73
83
  def _get_agent_card(self, request: Request) -> JSONResponse:
74
84
  return JSONResponse(self.agent_card.model_dump(exclude_none=True))
@@ -154,6 +154,64 @@ def _get_show_where(
154
154
  return None
155
155
 
156
156
 
157
+ def match_one_part_name(identifier: Identifier, ensure_lower_case: bool = False) -> str:
158
+ """Extract a single-part name from an Identifier object, optionally ensuring it is lowercase.
159
+
160
+ Args:
161
+ identifier (Identifier): The identifier to extract the name from. Must contain exactly one part.
162
+ ensure_lower_case (bool, optional): If True, raises ValueError if the name is not lowercase. Defaults to False.
163
+
164
+ Returns:
165
+ str: The extracted name, converted to lowercase if not quoted.
166
+
167
+ Raises:
168
+ ValueError: If the identifier does not contain exactly one part, or if ensure_lower_case is True and the name is not lowercase.
169
+ """
170
+ match identifier.parts, identifier.is_quoted:
171
+ case [name], [is_quoted]:
172
+ ...
173
+ case _:
174
+ raise ValueError(f"Only single-part names are allowed: {identifier}")
175
+ if not is_quoted:
176
+ name = name.lower()
177
+ if ensure_lower_case and not name.islower():
178
+ raise ValueError(f"The name must be in lowercase: {identifier}")
179
+ return name
180
+
181
+
182
+ def match_two_part_name(
183
+ identifier: Identifier, ensure_lower_case: bool = False, default_db_name: str | None = None
184
+ ) -> tuple[str, str]:
185
+ """Extract a (database, name) tuple from an Identifier object that may have one or two parts.
186
+
187
+ Args:
188
+ identifier (Identifier): The identifier to extract names from. Must contain one or two parts.
189
+ ensure_lower_case (bool, optional): If True, raises ValueError if the name part is not lowercase. Defaults to False.
190
+ default_db_name (str | None, optional): The default database name to use if only one part is provided. Defaults to None.
191
+
192
+ Returns:
193
+ tuple[str, str]: A tuple of (database_name, name), where database_name may be None if not provided and no default is given.
194
+
195
+ Raises:
196
+ ValueError: If the identifier does not contain one or two parts, or if ensure_lower_case is True and the name is not lowercase.
197
+ """
198
+ db_name = None
199
+ match identifier.parts, identifier.is_quoted:
200
+ case [name], [is_quoted]:
201
+ ...
202
+ case [db_name, name], [_, is_quoted]:
203
+ ...
204
+ case _:
205
+ raise ValueError(f"Only single-part or two-part names are allowed: {identifier}")
206
+ if not is_quoted:
207
+ name = name.lower()
208
+ if ensure_lower_case and not name.islower():
209
+ raise ValueError(f"The name must be in lowercase: {identifier}")
210
+ if db_name is None:
211
+ db_name = default_db_name
212
+ return db_name, name
213
+
214
+
157
215
  class ExecuteCommands:
158
216
  def __init__(self, session, context=None):
159
217
  if context is None:
@@ -177,19 +235,11 @@ class ExecuteCommands:
177
235
  if statement_type is CreateDatabase:
178
236
  return self.answer_create_database(statement)
179
237
  elif statement_type is CreateMLEngine:
180
- name = statement.name.parts[-1]
181
-
182
- return self.answer_create_ml_engine(
183
- name,
184
- handler=statement.handler,
185
- params=statement.params,
186
- if_not_exists=getattr(statement, "if_not_exists", False),
187
- )
238
+ return self.answer_create_ml_engine(statement)
188
239
  elif statement_type is DropMLEngine:
189
240
  return self.answer_drop_ml_engine(statement)
190
241
  elif statement_type is DropPredictor:
191
242
  return self.answer_drop_model(statement, database_name)
192
-
193
243
  elif statement_type is DropTables:
194
244
  return self.answer_drop_tables(statement, database_name)
195
245
  elif statement_type is DropDatasource or statement_type is DropDatabase:
@@ -660,10 +710,9 @@ class ExecuteCommands:
660
710
 
661
711
  def answer_create_trigger(self, statement, database_name):
662
712
  triggers_controller = TriggersController()
663
-
664
- name = statement.name
665
- trigger_name = statement.name.parts[-1]
666
- project_name = name.parts[-2] if len(name.parts) > 1 else database_name
713
+ project_name, trigger_name = match_two_part_name(
714
+ statement.name, ensure_lower_case=True, default_db_name=database_name
715
+ )
667
716
 
668
717
  triggers_controller.add(
669
718
  trigger_name,
@@ -687,10 +736,9 @@ class ExecuteCommands:
687
736
 
688
737
  def answer_create_job(self, statement: CreateJob, database_name):
689
738
  jobs_controller = JobsController()
690
-
691
- name = statement.name
692
- job_name = name.parts[-1]
693
- project_name = name.parts[-2] if len(name.parts) > 1 else database_name
739
+ project_name, job_name = match_two_part_name(
740
+ statement.name, ensure_lower_case=True, default_db_name=database_name
741
+ )
694
742
 
695
743
  try:
696
744
  jobs_controller.create(job_name, project_name, statement)
@@ -702,10 +750,8 @@ class ExecuteCommands:
702
750
 
703
751
  def answer_drop_job(self, statement, database_name):
704
752
  jobs_controller = JobsController()
753
+ project_name, job_name = match_two_part_name(statement.name, default_db_name=database_name)
705
754
 
706
- name = statement.name
707
- job_name = name.parts[-1]
708
- project_name = name.parts[-2] if len(name.parts) > 1 else database_name
709
755
  try:
710
756
  jobs_controller.delete(job_name, project_name)
711
757
  except EntityNotExistsError:
@@ -718,9 +764,8 @@ class ExecuteCommands:
718
764
 
719
765
  def answer_create_chatbot(self, statement, database_name):
720
766
  chatbot_controller = ChatBotController()
767
+ project_name, name = match_two_part_name(statement.name, ensure_lower_case=True, default_db_name=database_name)
721
768
 
722
- name = statement.name
723
- project_name = name.parts[-2] if len(name.parts) > 1 else database_name
724
769
  is_running = statement.params.pop("is_running", True)
725
770
 
726
771
  database = self.session.integration_controller.get(statement.database.parts[-1])
@@ -738,7 +783,7 @@ class ExecuteCommands:
738
783
  if statement.agent is not None:
739
784
  agent_name = statement.agent.parts[-1]
740
785
  chatbot_controller.add_chatbot(
741
- name.parts[-1],
786
+ name,
742
787
  project_name=project_name,
743
788
  model_name=model_name,
744
789
  agent_name=agent_name,
@@ -912,14 +957,12 @@ class ExecuteCommands:
912
957
  return ExecuteAnswer(data=ResultSet.from_df(df, table_name=""))
913
958
 
914
959
  def answer_create_kb_index(self, statement, database_name):
915
- table_name = statement.name.parts[-1]
916
- project_name = statement.name.parts[0] if len(statement.name.parts) > 1 else database_name
960
+ project_name, table_name = match_two_part_name(statement.name, default_db_name=database_name)
917
961
  self.session.kb_controller.create_index(table_name=table_name, project_name=project_name)
918
962
  return ExecuteAnswer()
919
963
 
920
964
  def answer_evaluate_kb(self, statement: EvaluateKnowledgeBase, database_name):
921
- table_name = statement.name.parts[-1]
922
- project_name = statement.name.parts[0] if len(statement.name.parts) > 1 else database_name
965
+ project_name, table_name = match_two_part_name(statement.name, default_db_name=database_name)
923
966
  scores = self.session.kb_controller.evaluate(
924
967
  table_name=table_name, project_name=project_name, params=statement.params
925
968
  )
@@ -928,6 +971,7 @@ class ExecuteCommands:
928
971
  def _get_model_info(self, identifier, except_absent=True, database_name=None):
929
972
  if len(identifier.parts) == 1:
930
973
  identifier.parts = [database_name, identifier.parts[0]]
974
+ identifier.is_quoted = [False] + identifier.is_quoted
931
975
 
932
976
  database_name, model_name, model_version = resolve_model_identifier(identifier)
933
977
  if database_name is None:
@@ -1105,7 +1149,24 @@ class ExecuteCommands:
1105
1149
  handler = self.session.integration_controller.get_data_handler(name, connect=False)
1106
1150
  handler.handler_storage.import_files(storage)
1107
1151
 
1108
- def answer_create_ml_engine(self, name: str, handler: str, params: dict = None, if_not_exists=False):
1152
+ def answer_create_ml_engine(self, statement: CreateMLEngine) -> ExecuteAnswer:
1153
+ """Handles the `CREATE ML_ENGINE` command, which creates a new ML integration (engine) in the system.
1154
+
1155
+ Args:
1156
+ statement (CreateMLEngine): The AST object representing the CREATE ML_ENGINE command.
1157
+
1158
+ Returns:
1159
+ ExecuteAnswer: The result of the ML engine creation operation.
1160
+
1161
+ Raises:
1162
+ ValueError: If the ml_engine name format is invalid.
1163
+ """
1164
+ name = match_one_part_name(statement.name, ensure_lower_case=True)
1165
+
1166
+ handler = statement.handler
1167
+ params = statement.params
1168
+ if_not_exists = getattr(statement, "if_not_exists", False)
1169
+
1109
1170
  integrations = self.session.integration_controller.get_all()
1110
1171
  if name in integrations:
1111
1172
  if not if_not_exists:
@@ -1147,8 +1208,21 @@ class ExecuteCommands:
1147
1208
 
1148
1209
  return ExecuteAnswer()
1149
1210
 
1150
- def answer_drop_ml_engine(self, statement: ASTNode):
1151
- name = statement.name.parts[-1]
1211
+ def answer_drop_ml_engine(self, statement: DropMLEngine) -> ExecuteAnswer:
1212
+ """Handles the `DROP ML_ENGINE` command, which removes an ML integration (engine) from the system.
1213
+
1214
+ Args:
1215
+ statement (DropMLEngine): The AST object representing the DROP ML_ENGINE command.
1216
+
1217
+ Raises:
1218
+ EntityNotExistsError: If the integration does not exist and IF EXISTS is not specified.
1219
+ ValueError: If the integration name is provided in an invalid format.
1220
+
1221
+ Returns:
1222
+ ExecuteAnswer: The result of the ML engine deletion operation.
1223
+ """
1224
+ name = match_one_part_name(statement.name)
1225
+
1152
1226
  integrations = self.session.integration_controller.get_all()
1153
1227
  if name not in integrations:
1154
1228
  if not statement.if_exists:
@@ -1158,53 +1232,57 @@ class ExecuteCommands:
1158
1232
  self.session.integration_controller.delete(name)
1159
1233
  return ExecuteAnswer()
1160
1234
 
1161
- def answer_create_database(self, statement: ASTNode):
1162
- """create new handler (datasource/integration in old terms)
1235
+ def answer_create_database(self, statement: CreateDatabase) -> ExecuteAnswer:
1236
+ """Create new integration or project
1237
+
1163
1238
  Args:
1164
- statement (ASTNode): data for creating database/project
1239
+ statement (CreateDatabase): data for creating database/project
1240
+
1241
+ Returns:
1242
+ ExecuteAnswer: 'ok' answer
1165
1243
  """
1244
+ database_name = match_one_part_name(statement.name, ensure_lower_case=True)
1166
1245
 
1167
- if len(statement.name.parts) != 1:
1168
- raise Exception("Database name should contain only 1 part.")
1246
+ engine = (statement.engine or "mindsdb").lower()
1169
1247
 
1170
- database_name = statement.name.parts[0]
1171
- engine = statement.engine
1172
- if engine is None:
1173
- engine = "mindsdb"
1174
- engine = engine.lower()
1175
1248
  connection_args = statement.parameters
1176
1249
 
1177
- if engine == "mindsdb":
1178
- try:
1250
+ try:
1251
+ if engine == "mindsdb":
1179
1252
  ProjectController().add(database_name)
1180
- except EntityExistsError:
1181
- if statement.if_not_exists is False:
1182
- raise
1183
- else:
1184
- try:
1253
+ else:
1185
1254
  self._create_integration(database_name, engine, connection_args)
1186
- except EntityExistsError:
1187
- if getattr(statement, "if_not_exists", False) is False:
1188
- raise
1255
+ except EntityExistsError:
1256
+ if statement.if_not_exists is False:
1257
+ raise
1189
1258
 
1190
1259
  return ExecuteAnswer()
1191
1260
 
1192
- def answer_drop_database(self, statement):
1193
- if len(statement.name.parts) != 1:
1194
- raise Exception("Database name should contain only 1 part.")
1195
- db_name = statement.name.parts[0]
1261
+ def answer_drop_database(self, statement: DropDatabase | DropDatasource) -> ExecuteAnswer:
1262
+ """Drop a database (project or integration) by name.
1263
+
1264
+ Args:
1265
+ statement (DropDatabase | DropDatasource): The parsed DROP DATABASE or DROP DATASOURCE statement.
1266
+
1267
+ Raises:
1268
+ Exception: If the database name format is invalid.
1269
+ EntityNotExistsError: If the database does not exist and 'IF EXISTS' is not specified in the statement.
1270
+
1271
+ Returns:
1272
+ ExecuteAnswer: The result of the drop database operation.
1273
+ """
1274
+ db_name = match_one_part_name(statement.name)
1275
+
1196
1276
  try:
1197
- self.session.database_controller.delete(db_name)
1277
+ self.session.database_controller.delete(db_name, strict_case=statement.name.is_quoted[0])
1198
1278
  except EntityNotExistsError:
1199
1279
  if statement.if_exists is not True:
1200
1280
  raise
1201
1281
  return ExecuteAnswer()
1202
1282
 
1203
- def answer_alter_database(self, statement):
1204
- if len(statement.name.parts) != 1:
1205
- raise Exception("Database name should contain only 1 part.")
1206
- db_name = statement.name.parts[0]
1207
- self.session.database_controller.update(db_name, data=statement.params)
1283
+ def answer_alter_database(self, statement: AlterDatabase) -> ExecuteAnswer:
1284
+ db_name = match_one_part_name(statement.name)
1285
+ self.session.database_controller.update(db_name, data=statement.params, strict_case=statement.name.is_quoted[0])
1208
1286
  return ExecuteAnswer()
1209
1287
 
1210
1288
  def answer_drop_tables(self, statement, database_name):
@@ -1242,35 +1320,19 @@ class ExecuteCommands:
1242
1320
 
1243
1321
  return ExecuteAnswer()
1244
1322
 
1245
- def answer_create_or_alter_view(self, statement: ASTNode, database_name: str) -> ExecuteAnswer:
1323
+ def answer_create_or_alter_view(self, statement: CreateView | AlterView, database_name: str) -> ExecuteAnswer:
1246
1324
  """Process CREATE and ALTER VIEW commands
1247
1325
 
1248
1326
  Args:
1249
- statement (ASTNode): data for creating or altering view
1327
+ statement (CreateView | AlterView): data for creating or altering view
1250
1328
  database_name (str): name of the current database
1251
1329
 
1252
1330
  Returns:
1253
1331
  ExecuteAnswer: answer for the command
1254
1332
  """
1255
- project_name = database_name
1256
-
1257
- if isinstance(statement.name, str):
1258
- parts = statement.name.split(".")
1259
- elif isinstance(statement.name, Identifier):
1260
- parts = statement.name.parts
1261
- else:
1262
- raise ValueError(f"Unknown type of view name: {statement.name}")
1263
-
1264
- match parts:
1265
- case [project_name, view_name]:
1266
- pass
1267
- case [view_name]:
1268
- pass
1269
- case _:
1270
- raise ValueError(
1271
- 'View name should be in the form "project_name.view_name" '
1272
- f'or "view_name", got {statement.name.parts}'
1273
- )
1333
+ project_name, view_name = match_two_part_name(
1334
+ statement.name, default_db_name=database_name, ensure_lower_case=isinstance(statement, CreateView)
1335
+ )
1274
1336
 
1275
1337
  query_str = statement.query_str
1276
1338
 
@@ -1303,7 +1365,7 @@ class ExecuteCommands:
1303
1365
  raise
1304
1366
  elif isinstance(statement, AlterView):
1305
1367
  try:
1306
- project.update_view(view_name, query=query_str)
1368
+ project.update_view(view_name, query=query_str, strict_case=(not view_name.islower()))
1307
1369
  except EntityNotExistsError:
1308
1370
  raise ExecutorException(f"View {view_name} does not exist in {project_name}")
1309
1371
  else:
@@ -1311,19 +1373,33 @@ class ExecuteCommands:
1311
1373
 
1312
1374
  return ExecuteAnswer()
1313
1375
 
1314
- def answer_drop_view(self, statement, database_name):
1315
- names = statement.names
1376
+ def answer_drop_view(self, statement: DropView, database_name: str) -> ExecuteAnswer:
1377
+ """Drop one or more views from the specified database/project.
1316
1378
 
1317
- for name in names:
1318
- view_name = name.parts[-1]
1319
- if len(name.parts) > 1:
1320
- db_name = name.parts[0]
1321
- else:
1322
- db_name = database_name
1323
- project = self.session.database_controller.get_project(db_name)
1379
+ Args:
1380
+ statement (DropView): The parsed DROP VIEW statement containing view names and options.
1381
+ database_name (str): The name of the database (project) from which to drop the views.
1382
+
1383
+ Raises:
1384
+ EntityNotExistsError: If a view does not exist and 'IF EXISTS' is not specified in the statement.
1385
+ ValueError: If the view name format is invalid.
1386
+
1387
+ Returns:
1388
+ ExecuteAnswer: The result of the drop view operation.
1389
+ """
1390
+ for name in statement.names:
1391
+ match name.parts, name.is_quoted:
1392
+ case [view_name], [view_name_quoted]:
1393
+ db_name_quoted = False
1394
+ case [database_name, view_name], [db_name_quoted, view_name_quoted]:
1395
+ pass
1396
+ case _:
1397
+ raise ValueError(f"Invalid view name: {name}")
1398
+
1399
+ project = self.session.database_controller.get_project(database_name, db_name_quoted)
1324
1400
 
1325
1401
  try:
1326
- project.drop_view(view_name)
1402
+ project.drop_view(view_name, strict_case=view_name_quoted)
1327
1403
  except EntityNotExistsError:
1328
1404
  if statement.if_exists is not True:
1329
1405
  raise
@@ -1337,7 +1413,9 @@ class ExecuteCommands:
1337
1413
  "Please pass the model parameters as a JSON object in the embedding_model field."
1338
1414
  )
1339
1415
 
1340
- project_name = statement.name.parts[0] if len(statement.name.parts) > 1 else database_name
1416
+ project_name, kb_name = match_two_part_name(
1417
+ statement.name, ensure_lower_case=True, default_db_name=database_name
1418
+ )
1341
1419
 
1342
1420
  if statement.storage is not None:
1343
1421
  if len(statement.storage.parts) != 2:
@@ -1349,8 +1427,6 @@ class ExecuteCommands:
1349
1427
  # TODO: implement this
1350
1428
  raise ExecutorException("Create a knowledge base from a select is not supported yet")
1351
1429
 
1352
- kb_name = statement.name.parts[-1]
1353
-
1354
1430
  # create the knowledge base
1355
1431
  _ = self.session.kb_controller.add(
1356
1432
  name=kb_name,
@@ -1363,13 +1439,12 @@ class ExecuteCommands:
1363
1439
 
1364
1440
  return ExecuteAnswer()
1365
1441
 
1366
- def answer_drop_kb(self, statement: DropKnowledgeBase, database_name: str):
1367
- name = statement.name.parts[-1]
1368
- project_name = statement.name.parts[0] if len(statement.name.parts) > 1 else database_name
1442
+ def answer_drop_kb(self, statement: DropKnowledgeBase, database_name: str) -> ExecuteAnswer:
1443
+ project_name, kb_name = match_two_part_name(statement.name, default_db_name=database_name)
1369
1444
 
1370
1445
  # delete the knowledge base
1371
1446
  self.session.kb_controller.delete(
1372
- name=name,
1447
+ name=kb_name,
1373
1448
  project_name=project_name,
1374
1449
  if_exists=statement.if_exists,
1375
1450
  )
@@ -1377,8 +1452,7 @@ class ExecuteCommands:
1377
1452
  return ExecuteAnswer()
1378
1453
 
1379
1454
  def answer_create_skill(self, statement, database_name):
1380
- name = statement.name.parts[-1]
1381
- project_name = statement.name.parts[0] if len(statement.name.parts) > 1 else database_name
1455
+ project_name, name = match_two_part_name(statement.name, ensure_lower_case=True, default_db_name=database_name)
1382
1456
 
1383
1457
  try:
1384
1458
  _ = self.session.skills_controller.add_skill(name, project_name, statement.type, statement.params)
@@ -1389,11 +1463,10 @@ class ExecuteCommands:
1389
1463
  return ExecuteAnswer()
1390
1464
 
1391
1465
  def answer_drop_skill(self, statement, database_name):
1392
- name = statement.name.parts[-1]
1393
- project_name = statement.name.parts[0] if len(statement.name.parts) > 1 else database_name
1466
+ project_name, name = match_two_part_name(statement.name, default_db_name=database_name)
1394
1467
 
1395
1468
  try:
1396
- self.session.skills_controller.delete_skill(name, project_name)
1469
+ self.session.skills_controller.delete_skill(name, project_name, strict_case=True)
1397
1470
  except ValueError as e:
1398
1471
  # Project does not exist or skill does not exist.
1399
1472
  raise ExecutorException(str(e))
@@ -1401,8 +1474,7 @@ class ExecuteCommands:
1401
1474
  return ExecuteAnswer()
1402
1475
 
1403
1476
  def answer_update_skill(self, statement, database_name):
1404
- name = statement.name.parts[-1]
1405
- project_name = statement.name.parts[0] if len(statement.name.parts) > 1 else database_name
1477
+ project_name, name = match_two_part_name(statement.name, default_db_name=database_name)
1406
1478
 
1407
1479
  type = statement.params.pop("type", None)
1408
1480
  try:
@@ -1416,8 +1488,7 @@ class ExecuteCommands:
1416
1488
  return ExecuteAnswer()
1417
1489
 
1418
1490
  def answer_create_agent(self, statement, database_name):
1419
- name = statement.name.parts[-1]
1420
- project_name = statement.name.parts[0] if len(statement.name.parts) > 1 else database_name
1491
+ project_name, name = match_two_part_name(statement.name, ensure_lower_case=True, default_db_name=database_name)
1421
1492
 
1422
1493
  skills = statement.params.pop("skills", [])
1423
1494
  provider = statement.params.pop("provider", None)
@@ -1439,9 +1510,8 @@ class ExecuteCommands:
1439
1510
 
1440
1511
  return ExecuteAnswer()
1441
1512
 
1442
- def answer_drop_agent(self, statement, database_name):
1443
- name = statement.name.parts[-1]
1444
- project_name = statement.name.parts[0] if len(statement.name.parts) > 1 else database_name
1513
+ def answer_drop_agent(self, statement: DropAgent, database_name: str):
1514
+ project_name, name = match_two_part_name(statement.name, default_db_name=database_name)
1445
1515
 
1446
1516
  try:
1447
1517
  self.session.agents_controller.delete_agent(name, project_name)
@@ -1451,9 +1521,8 @@ class ExecuteCommands:
1451
1521
 
1452
1522
  return ExecuteAnswer()
1453
1523
 
1454
- def answer_update_agent(self, statement, database_name):
1455
- name = statement.name.parts[-1]
1456
- project_name = statement.name.parts[0] if len(statement.name.parts) > 1 else database_name
1524
+ def answer_update_agent(self, statement: UpdateAgent, database_name: str):
1525
+ project_name, name = match_two_part_name(statement.name, default_db_name=database_name)
1457
1526
 
1458
1527
  model = statement.params.pop("model", None)
1459
1528
  skills_to_add = statement.params.pop("skills_to_add", [])
@@ -1474,14 +1543,13 @@ class ExecuteCommands:
1474
1543
  return ExecuteAnswer()
1475
1544
 
1476
1545
  @mark_process("learn")
1477
- def answer_create_predictor(self, statement: CreatePredictor, database_name):
1478
- integration_name = database_name
1546
+ def answer_create_predictor(self, statement: CreatePredictor, database_name: str):
1547
+ integration_name, model_name = match_two_part_name(
1548
+ statement.name, ensure_lower_case=True, default_db_name=database_name
1549
+ )
1479
1550
 
1480
- # allow creation in non-active projects, e.g. 'create mode proj.model' works whether `proj` is active or not
1481
- if len(statement.name.parts) > 1:
1482
- integration_name = statement.name.parts[0]
1483
- model_name = statement.name.parts[-1]
1484
1551
  statement.name.parts = [integration_name.lower(), model_name]
1552
+ statement.name.is_quoted = [False, False]
1485
1553
 
1486
1554
  ml_integration_name = "lightwood" # default
1487
1555
  if statement.using is not None:
@@ -1498,7 +1566,9 @@ class ExecuteCommands:
1498
1566
  ml_handler = self.session.integration_controller.get_ml_handler(ml_integration_name)
1499
1567
  except EntityNotExistsError:
1500
1568
  # not exist, try to create it with same name as handler
1501
- self.answer_create_ml_engine(ml_integration_name, handler=ml_integration_name)
1569
+ self.answer_create_ml_engine(
1570
+ CreateMLEngine(name=Identifier(ml_integration_name), handler=ml_integration_name)
1571
+ )
1502
1572
 
1503
1573
  ml_handler = self.session.integration_controller.get_ml_handler(ml_integration_name)
1504
1574
 
@@ -1511,7 +1581,6 @@ class ExecuteCommands:
1511
1581
 
1512
1582
  try:
1513
1583
  df = self.session.model_controller.create_model(statement, ml_handler)
1514
-
1515
1584
  return ExecuteAnswer(data=ResultSet.from_df(df))
1516
1585
  except EntityExistsError:
1517
1586
  if getattr(statement, "if_not_exists", False) is True:
@@ -1926,22 +1995,24 @@ class ExecuteCommands:
1926
1995
  self.session.model_controller.set_model_active_version(project_name, model_name, version)
1927
1996
  return ExecuteAnswer()
1928
1997
 
1929
- def answer_drop_model(self, statement, database_name):
1930
- model_parts = statement.name.parts
1931
- version = None
1998
+ def answer_drop_model(self, statement: DropPredictor, database_name: str) -> ExecuteAnswer:
1999
+ """Handles the DROP MODEL (or DROP PREDICTOR) command, which removes a model
2000
+ or a specific model version from a project.
1932
2001
 
1933
- # with version?
1934
- if model_parts[-1].isdigit():
1935
- version = int(model_parts[-1])
1936
- model_parts = model_parts[:-1]
2002
+ Args:
2003
+ statement (DropPredictor): The AST object representing the DROP MODEL or DROP PREDICTOR command.
2004
+ database_name (str): The name of the current database/project.
1937
2005
 
1938
- if len(model_parts) == 2:
1939
- project_name, model_name = model_parts
1940
- elif len(model_parts) == 1:
1941
- model_name = model_parts[0]
2006
+ Raises:
2007
+ EntityNotExistsError: If the model or version does not exist and IF EXISTS is not specified.
2008
+ ValueError: If the model name format is invalid.
2009
+
2010
+ Returns:
2011
+ ExecuteAnswer: The result of the model deletion operation.
2012
+ """
2013
+ project_name, model_name, version = resolve_model_identifier(statement.name)
2014
+ if project_name is None:
1942
2015
  project_name = database_name
1943
- else:
1944
- raise ExecutorException(f"Unknown model: {statement.name}")
1945
2016
 
1946
2017
  if version is not None:
1947
2018
  # delete version
@@ -124,8 +124,20 @@ class ProjectDataNode(DataNode):
124
124
  raise NotImplementedError(f"Can't delete object: {query_table}")
125
125
 
126
126
  elif isinstance(query, Select):
127
+ match query.from_table.parts, query.from_table.is_quoted:
128
+ case [query_table], [is_quoted]:
129
+ ...
130
+ case [query_table, int(_)], [is_quoted, _]:
131
+ ...
132
+ case [query_table, str(version)], [is_quoted, _] if version.isdigit():
133
+ ...
134
+ case _:
135
+ raise ValueError("Table name should contain only one part")
136
+
137
+ if not is_quoted:
138
+ query_table = query_table.lower()
139
+
127
140
  # region is it query to 'models'?
128
- query_table = query.from_table.parts[0].lower()
129
141
  if query_table in ("models", "jobs", "mdb_triggers", "chatbots", "skills", "agents"):
130
142
  new_query = deepcopy(query)
131
143
  project_filter = BinaryOperation("=", args=[Identifier("project"), Constant(self.project.name)])
@@ -137,8 +149,7 @@ class ProjectDataNode(DataNode):
137
149
  # endregion
138
150
 
139
151
  # other table from project
140
-
141
- if self.project.get_view(query_table):
152
+ if self.project.get_view(query_table, strict_case=is_quoted):
142
153
  # this is the view
143
154
  df = self.project.query_view(query, session)
144
155