MindsDB 25.7.2.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 (69) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +1 -1
  3. mindsdb/api/a2a/common/server/server.py +16 -6
  4. mindsdb/api/executor/command_executor.py +213 -137
  5. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +5 -1
  6. mindsdb/api/executor/datahub/datanodes/project_datanode.py +14 -3
  7. mindsdb/api/executor/planner/plan_join.py +3 -0
  8. mindsdb/api/executor/planner/plan_join_ts.py +117 -100
  9. mindsdb/api/executor/planner/query_planner.py +1 -0
  10. mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +54 -85
  11. mindsdb/api/http/initialize.py +16 -43
  12. mindsdb/api/http/namespaces/agents.py +24 -21
  13. mindsdb/api/http/namespaces/chatbots.py +83 -120
  14. mindsdb/api/http/namespaces/file.py +1 -1
  15. mindsdb/api/http/namespaces/jobs.py +38 -60
  16. mindsdb/api/http/namespaces/tree.py +69 -61
  17. mindsdb/api/mcp/start.py +2 -0
  18. mindsdb/api/mysql/mysql_proxy/utilities/dump.py +3 -2
  19. mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
  20. mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
  21. mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +25 -5
  22. mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +3 -3
  23. mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
  24. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_tables.py +82 -73
  25. mindsdb/integrations/handlers/hubspot_handler/requirements.txt +1 -1
  26. mindsdb/integrations/handlers/langchain_handler/langchain_handler.py +83 -76
  27. mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
  28. mindsdb/integrations/handlers/litellm_handler/litellm_handler.py +16 -3
  29. mindsdb/integrations/handlers/litellm_handler/settings.py +2 -1
  30. mindsdb/integrations/handlers/llama_index_handler/requirements.txt +1 -1
  31. mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +106 -90
  32. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +41 -39
  33. mindsdb/integrations/handlers/s3_handler/s3_handler.py +72 -70
  34. mindsdb/integrations/handlers/salesforce_handler/constants.py +208 -0
  35. mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +142 -81
  36. mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +12 -4
  37. mindsdb/integrations/handlers/slack_handler/slack_tables.py +141 -161
  38. mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
  39. mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +32 -17
  40. mindsdb/integrations/handlers/web_handler/web_handler.py +19 -22
  41. mindsdb/integrations/handlers/youtube_handler/youtube_tables.py +183 -55
  42. mindsdb/integrations/libs/vectordatabase_handler.py +10 -1
  43. mindsdb/integrations/utilities/handler_utils.py +32 -12
  44. mindsdb/interfaces/agents/agents_controller.py +169 -110
  45. mindsdb/interfaces/agents/langchain_agent.py +10 -3
  46. mindsdb/interfaces/data_catalog/data_catalog_loader.py +22 -8
  47. mindsdb/interfaces/database/database.py +38 -13
  48. mindsdb/interfaces/database/integrations.py +20 -5
  49. mindsdb/interfaces/database/projects.py +63 -16
  50. mindsdb/interfaces/database/views.py +86 -60
  51. mindsdb/interfaces/jobs/jobs_controller.py +103 -110
  52. mindsdb/interfaces/knowledge_base/controller.py +33 -5
  53. mindsdb/interfaces/knowledge_base/evaluate.py +53 -9
  54. mindsdb/interfaces/knowledge_base/executor.py +24 -0
  55. mindsdb/interfaces/knowledge_base/llm_client.py +3 -3
  56. mindsdb/interfaces/knowledge_base/preprocessing/document_preprocessor.py +21 -13
  57. mindsdb/interfaces/query_context/context_controller.py +100 -133
  58. mindsdb/interfaces/skills/skills_controller.py +18 -6
  59. mindsdb/interfaces/storage/db.py +40 -6
  60. mindsdb/interfaces/variables/variables_controller.py +8 -15
  61. mindsdb/utilities/config.py +3 -3
  62. mindsdb/utilities/functions.py +72 -60
  63. mindsdb/utilities/log.py +38 -6
  64. mindsdb/utilities/ps.py +7 -7
  65. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/METADATA +262 -263
  66. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/RECORD +69 -68
  67. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/WHEEL +0 -0
  68. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/licenses/LICENSE +0 -0
  69. {mindsdb-25.7.2.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.2.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"
mindsdb/__main__.py CHANGED
@@ -375,7 +375,7 @@ if __name__ == "__main__":
375
375
  apis = os.getenv("MINDSDB_APIS") or config.cmd_args.api
376
376
 
377
377
  if apis is None: # If "--api" option is not specified, start the default APIs
378
- api_arr = [TrunkProcessEnum.HTTP, TrunkProcessEnum.MYSQL]
378
+ api_arr = [TrunkProcessEnum.HTTP, TrunkProcessEnum.MYSQL, TrunkProcessEnum.MCP, TrunkProcessEnum.A2A]
379
379
  elif apis == "": # If "--api=" (blank) is specified, don't start any APIs
380
380
  api_arr = []
381
381
  else: # The user has provided a list of APIs to start
@@ -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))
@@ -84,7 +84,7 @@ from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import (
84
84
  TYPES,
85
85
  )
86
86
 
87
- from .exceptions import (
87
+ from mindsdb.api.executor.exceptions import (
88
88
  ExecutorException,
89
89
  BadDbError,
90
90
  NotSupportedYet,
@@ -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):
@@ -1221,9 +1299,11 @@ class ExecuteCommands:
1221
1299
  db_name = database_name
1222
1300
 
1223
1301
  dn = self.session.datahub[db_name]
1302
+ if dn is None:
1303
+ raise ExecutorException(f"Cannot delete a table from database '{db_name}': the database does not exist")
1304
+
1224
1305
  if db_name is not None:
1225
1306
  dn.drop_table(table, if_exists=statement.if_exists)
1226
-
1227
1307
  elif db_name in self.session.database_controller.get_dict(filter_type="project"):
1228
1308
  # TODO do we need feature: delete object from project via drop table?
1229
1309
 
@@ -1240,35 +1320,19 @@ class ExecuteCommands:
1240
1320
 
1241
1321
  return ExecuteAnswer()
1242
1322
 
1243
- 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:
1244
1324
  """Process CREATE and ALTER VIEW commands
1245
1325
 
1246
1326
  Args:
1247
- statement (ASTNode): data for creating or altering view
1327
+ statement (CreateView | AlterView): data for creating or altering view
1248
1328
  database_name (str): name of the current database
1249
1329
 
1250
1330
  Returns:
1251
1331
  ExecuteAnswer: answer for the command
1252
1332
  """
1253
- project_name = database_name
1254
-
1255
- if isinstance(statement.name, str):
1256
- parts = statement.name.split(".")
1257
- elif isinstance(statement.name, Identifier):
1258
- parts = statement.name.parts
1259
- else:
1260
- raise ValueError(f"Unknown type of view name: {statement.name}")
1261
-
1262
- match parts:
1263
- case [project_name, view_name]:
1264
- pass
1265
- case [view_name]:
1266
- pass
1267
- case _:
1268
- raise ValueError(
1269
- 'View name should be in the form "project_name.view_name" '
1270
- f'or "view_name", got {statement.name.parts}'
1271
- )
1333
+ project_name, view_name = match_two_part_name(
1334
+ statement.name, default_db_name=database_name, ensure_lower_case=isinstance(statement, CreateView)
1335
+ )
1272
1336
 
1273
1337
  query_str = statement.query_str
1274
1338
 
@@ -1301,7 +1365,7 @@ class ExecuteCommands:
1301
1365
  raise
1302
1366
  elif isinstance(statement, AlterView):
1303
1367
  try:
1304
- project.update_view(view_name, query=query_str)
1368
+ project.update_view(view_name, query=query_str, strict_case=(not view_name.islower()))
1305
1369
  except EntityNotExistsError:
1306
1370
  raise ExecutorException(f"View {view_name} does not exist in {project_name}")
1307
1371
  else:
@@ -1309,19 +1373,33 @@ class ExecuteCommands:
1309
1373
 
1310
1374
  return ExecuteAnswer()
1311
1375
 
1312
- def answer_drop_view(self, statement, database_name):
1313
- 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.
1314
1378
 
1315
- for name in names:
1316
- view_name = name.parts[-1]
1317
- if len(name.parts) > 1:
1318
- db_name = name.parts[0]
1319
- else:
1320
- db_name = database_name
1321
- 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)
1322
1400
 
1323
1401
  try:
1324
- project.drop_view(view_name)
1402
+ project.drop_view(view_name, strict_case=view_name_quoted)
1325
1403
  except EntityNotExistsError:
1326
1404
  if statement.if_exists is not True:
1327
1405
  raise
@@ -1335,7 +1413,9 @@ class ExecuteCommands:
1335
1413
  "Please pass the model parameters as a JSON object in the embedding_model field."
1336
1414
  )
1337
1415
 
1338
- 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
+ )
1339
1419
 
1340
1420
  if statement.storage is not None:
1341
1421
  if len(statement.storage.parts) != 2:
@@ -1347,8 +1427,6 @@ class ExecuteCommands:
1347
1427
  # TODO: implement this
1348
1428
  raise ExecutorException("Create a knowledge base from a select is not supported yet")
1349
1429
 
1350
- kb_name = statement.name.parts[-1]
1351
-
1352
1430
  # create the knowledge base
1353
1431
  _ = self.session.kb_controller.add(
1354
1432
  name=kb_name,
@@ -1361,13 +1439,12 @@ class ExecuteCommands:
1361
1439
 
1362
1440
  return ExecuteAnswer()
1363
1441
 
1364
- def answer_drop_kb(self, statement: DropKnowledgeBase, database_name: str):
1365
- name = statement.name.parts[-1]
1366
- 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)
1367
1444
 
1368
1445
  # delete the knowledge base
1369
1446
  self.session.kb_controller.delete(
1370
- name=name,
1447
+ name=kb_name,
1371
1448
  project_name=project_name,
1372
1449
  if_exists=statement.if_exists,
1373
1450
  )
@@ -1375,8 +1452,7 @@ class ExecuteCommands:
1375
1452
  return ExecuteAnswer()
1376
1453
 
1377
1454
  def answer_create_skill(self, statement, database_name):
1378
- name = statement.name.parts[-1]
1379
- 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)
1380
1456
 
1381
1457
  try:
1382
1458
  _ = self.session.skills_controller.add_skill(name, project_name, statement.type, statement.params)
@@ -1387,11 +1463,10 @@ class ExecuteCommands:
1387
1463
  return ExecuteAnswer()
1388
1464
 
1389
1465
  def answer_drop_skill(self, statement, database_name):
1390
- name = statement.name.parts[-1]
1391
- 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)
1392
1467
 
1393
1468
  try:
1394
- self.session.skills_controller.delete_skill(name, project_name)
1469
+ self.session.skills_controller.delete_skill(name, project_name, strict_case=True)
1395
1470
  except ValueError as e:
1396
1471
  # Project does not exist or skill does not exist.
1397
1472
  raise ExecutorException(str(e))
@@ -1399,8 +1474,7 @@ class ExecuteCommands:
1399
1474
  return ExecuteAnswer()
1400
1475
 
1401
1476
  def answer_update_skill(self, statement, database_name):
1402
- name = statement.name.parts[-1]
1403
- 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)
1404
1478
 
1405
1479
  type = statement.params.pop("type", None)
1406
1480
  try:
@@ -1414,8 +1488,7 @@ class ExecuteCommands:
1414
1488
  return ExecuteAnswer()
1415
1489
 
1416
1490
  def answer_create_agent(self, statement, database_name):
1417
- name = statement.name.parts[-1]
1418
- 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)
1419
1492
 
1420
1493
  skills = statement.params.pop("skills", [])
1421
1494
  provider = statement.params.pop("provider", None)
@@ -1428,15 +1501,17 @@ class ExecuteCommands:
1428
1501
  provider=provider,
1429
1502
  params=statement.params,
1430
1503
  )
1504
+ except EntityExistsError as e:
1505
+ if statement.if_not_exists is not True:
1506
+ raise ExecutorException(str(e))
1431
1507
  except ValueError as e:
1432
1508
  # Project does not exist or agent already exists.
1433
1509
  raise ExecutorException(str(e))
1434
1510
 
1435
1511
  return ExecuteAnswer()
1436
1512
 
1437
- def answer_drop_agent(self, statement, database_name):
1438
- name = statement.name.parts[-1]
1439
- 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)
1440
1515
 
1441
1516
  try:
1442
1517
  self.session.agents_controller.delete_agent(name, project_name)
@@ -1446,9 +1521,8 @@ class ExecuteCommands:
1446
1521
 
1447
1522
  return ExecuteAnswer()
1448
1523
 
1449
- def answer_update_agent(self, statement, database_name):
1450
- name = statement.name.parts[-1]
1451
- 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)
1452
1526
 
1453
1527
  model = statement.params.pop("model", None)
1454
1528
  skills_to_add = statement.params.pop("skills_to_add", [])
@@ -1469,14 +1543,13 @@ class ExecuteCommands:
1469
1543
  return ExecuteAnswer()
1470
1544
 
1471
1545
  @mark_process("learn")
1472
- def answer_create_predictor(self, statement: CreatePredictor, database_name):
1473
- 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
+ )
1474
1550
 
1475
- # allow creation in non-active projects, e.g. 'create mode proj.model' works whether `proj` is active or not
1476
- if len(statement.name.parts) > 1:
1477
- integration_name = statement.name.parts[0]
1478
- model_name = statement.name.parts[-1]
1479
1551
  statement.name.parts = [integration_name.lower(), model_name]
1552
+ statement.name.is_quoted = [False, False]
1480
1553
 
1481
1554
  ml_integration_name = "lightwood" # default
1482
1555
  if statement.using is not None:
@@ -1493,7 +1566,9 @@ class ExecuteCommands:
1493
1566
  ml_handler = self.session.integration_controller.get_ml_handler(ml_integration_name)
1494
1567
  except EntityNotExistsError:
1495
1568
  # not exist, try to create it with same name as handler
1496
- 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
+ )
1497
1572
 
1498
1573
  ml_handler = self.session.integration_controller.get_ml_handler(ml_integration_name)
1499
1574
 
@@ -1506,7 +1581,6 @@ class ExecuteCommands:
1506
1581
 
1507
1582
  try:
1508
1583
  df = self.session.model_controller.create_model(statement, ml_handler)
1509
-
1510
1584
  return ExecuteAnswer(data=ResultSet.from_df(df))
1511
1585
  except EntityExistsError:
1512
1586
  if getattr(statement, "if_not_exists", False) is True:
@@ -1921,22 +1995,24 @@ class ExecuteCommands:
1921
1995
  self.session.model_controller.set_model_active_version(project_name, model_name, version)
1922
1996
  return ExecuteAnswer()
1923
1997
 
1924
- def answer_drop_model(self, statement, database_name):
1925
- model_parts = statement.name.parts
1926
- 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.
1927
2001
 
1928
- # with version?
1929
- if model_parts[-1].isdigit():
1930
- version = int(model_parts[-1])
1931
- 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.
1932
2005
 
1933
- if len(model_parts) == 2:
1934
- project_name, model_name = model_parts
1935
- elif len(model_parts) == 1:
1936
- 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:
1937
2015
  project_name = database_name
1938
- else:
1939
- raise ExecutorException(f"Unknown model: {statement.name}")
1940
2016
 
1941
2017
  if version is not None:
1942
2018
  # delete version