MindsDB 25.7.3.0__py3-none-any.whl → 25.8.2.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 (102) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +11 -1
  3. mindsdb/api/a2a/common/server/server.py +16 -6
  4. mindsdb/api/executor/command_executor.py +215 -150
  5. mindsdb/api/executor/datahub/datanodes/project_datanode.py +14 -3
  6. mindsdb/api/executor/planner/plan_join.py +3 -0
  7. mindsdb/api/executor/planner/plan_join_ts.py +117 -100
  8. mindsdb/api/executor/planner/query_planner.py +1 -0
  9. mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +54 -85
  10. mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +21 -24
  11. mindsdb/api/executor/sql_query/steps/fetch_dataframe_partition.py +9 -3
  12. mindsdb/api/executor/sql_query/steps/subselect_step.py +11 -8
  13. mindsdb/api/executor/utilities/mysql_to_duckdb_functions.py +264 -0
  14. mindsdb/api/executor/utilities/sql.py +30 -0
  15. mindsdb/api/http/initialize.py +18 -44
  16. mindsdb/api/http/namespaces/agents.py +23 -20
  17. mindsdb/api/http/namespaces/chatbots.py +83 -120
  18. mindsdb/api/http/namespaces/file.py +1 -1
  19. mindsdb/api/http/namespaces/jobs.py +38 -60
  20. mindsdb/api/http/namespaces/tree.py +69 -61
  21. mindsdb/api/http/namespaces/views.py +56 -72
  22. mindsdb/api/mcp/start.py +2 -0
  23. mindsdb/api/mysql/mysql_proxy/utilities/dump.py +3 -2
  24. mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
  25. mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
  26. mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +25 -5
  27. mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +3 -3
  28. mindsdb/integrations/handlers/db2_handler/db2_handler.py +19 -23
  29. mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
  30. mindsdb/integrations/handlers/gong_handler/__about__.py +2 -0
  31. mindsdb/integrations/handlers/gong_handler/__init__.py +30 -0
  32. mindsdb/integrations/handlers/gong_handler/connection_args.py +37 -0
  33. mindsdb/integrations/handlers/gong_handler/gong_handler.py +164 -0
  34. mindsdb/integrations/handlers/gong_handler/gong_tables.py +508 -0
  35. mindsdb/integrations/handlers/gong_handler/icon.svg +25 -0
  36. mindsdb/integrations/handlers/gong_handler/test_gong_handler.py +125 -0
  37. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_tables.py +82 -73
  38. mindsdb/integrations/handlers/hubspot_handler/requirements.txt +1 -1
  39. mindsdb/integrations/handlers/huggingface_handler/__init__.py +8 -12
  40. mindsdb/integrations/handlers/huggingface_handler/finetune.py +203 -223
  41. mindsdb/integrations/handlers/huggingface_handler/huggingface_handler.py +360 -383
  42. mindsdb/integrations/handlers/huggingface_handler/requirements.txt +7 -7
  43. mindsdb/integrations/handlers/huggingface_handler/requirements_cpu.txt +7 -7
  44. mindsdb/integrations/handlers/huggingface_handler/settings.py +25 -25
  45. mindsdb/integrations/handlers/langchain_handler/langchain_handler.py +83 -77
  46. mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
  47. mindsdb/integrations/handlers/litellm_handler/litellm_handler.py +5 -2
  48. mindsdb/integrations/handlers/litellm_handler/settings.py +2 -1
  49. mindsdb/integrations/handlers/openai_handler/constants.py +11 -30
  50. mindsdb/integrations/handlers/openai_handler/helpers.py +27 -34
  51. mindsdb/integrations/handlers/openai_handler/openai_handler.py +14 -12
  52. mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +106 -90
  53. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +41 -39
  54. mindsdb/integrations/handlers/salesforce_handler/constants.py +215 -0
  55. mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +141 -80
  56. mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +0 -1
  57. mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
  58. mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +32 -17
  59. mindsdb/integrations/handlers/web_handler/web_handler.py +19 -22
  60. mindsdb/integrations/libs/llm/config.py +0 -14
  61. mindsdb/integrations/libs/llm/utils.py +0 -15
  62. mindsdb/integrations/libs/vectordatabase_handler.py +10 -1
  63. mindsdb/integrations/utilities/files/file_reader.py +5 -19
  64. mindsdb/integrations/utilities/handler_utils.py +32 -12
  65. mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +1 -1
  66. mindsdb/interfaces/agents/agents_controller.py +246 -149
  67. mindsdb/interfaces/agents/constants.py +0 -1
  68. mindsdb/interfaces/agents/langchain_agent.py +11 -6
  69. mindsdb/interfaces/data_catalog/data_catalog_loader.py +4 -4
  70. mindsdb/interfaces/database/database.py +38 -13
  71. mindsdb/interfaces/database/integrations.py +20 -5
  72. mindsdb/interfaces/database/projects.py +174 -23
  73. mindsdb/interfaces/database/views.py +86 -60
  74. mindsdb/interfaces/jobs/jobs_controller.py +103 -110
  75. mindsdb/interfaces/knowledge_base/controller.py +33 -6
  76. mindsdb/interfaces/knowledge_base/evaluate.py +2 -1
  77. mindsdb/interfaces/knowledge_base/executor.py +24 -0
  78. mindsdb/interfaces/knowledge_base/preprocessing/document_preprocessor.py +6 -10
  79. mindsdb/interfaces/knowledge_base/preprocessing/text_splitter.py +73 -0
  80. mindsdb/interfaces/query_context/context_controller.py +111 -145
  81. mindsdb/interfaces/skills/skills_controller.py +18 -6
  82. mindsdb/interfaces/storage/db.py +40 -6
  83. mindsdb/interfaces/variables/variables_controller.py +8 -15
  84. mindsdb/utilities/config.py +5 -3
  85. mindsdb/utilities/fs.py +54 -17
  86. mindsdb/utilities/functions.py +72 -60
  87. mindsdb/utilities/log.py +38 -6
  88. mindsdb/utilities/ps.py +7 -7
  89. {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/METADATA +282 -268
  90. {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/RECORD +94 -92
  91. mindsdb/integrations/handlers/anyscale_endpoints_handler/__about__.py +0 -9
  92. mindsdb/integrations/handlers/anyscale_endpoints_handler/__init__.py +0 -20
  93. mindsdb/integrations/handlers/anyscale_endpoints_handler/anyscale_endpoints_handler.py +0 -290
  94. mindsdb/integrations/handlers/anyscale_endpoints_handler/creation_args.py +0 -14
  95. mindsdb/integrations/handlers/anyscale_endpoints_handler/icon.svg +0 -4
  96. mindsdb/integrations/handlers/anyscale_endpoints_handler/requirements.txt +0 -2
  97. mindsdb/integrations/handlers/anyscale_endpoints_handler/settings.py +0 -51
  98. mindsdb/integrations/handlers/anyscale_endpoints_handler/tests/test_anyscale_endpoints_handler.py +0 -212
  99. /mindsdb/integrations/handlers/{anyscale_endpoints_handler/tests/__init__.py → gong_handler/requirements.txt} +0 -0
  100. {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/WHEEL +0 -0
  101. {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/licenses/LICENSE +0 -0
  102. {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/top_level.txt +0 -0
@@ -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:
@@ -1134,11 +1195,17 @@ class ExecuteCommands:
1134
1195
  msg = dedent(
1135
1196
  f"""\
1136
1197
  The '{handler_module_meta["name"]}' handler cannot be used. Reason is:
1137
- {handler_module_meta["import"]["error_message"]}
1198
+ {handler_module_meta["import"]["error_message"] or msg}
1138
1199
  """
1139
1200
  )
1140
1201
  is_cloud = self.session.config.get("cloud", False)
1141
- if is_cloud is False and "No module named" in handler_module_meta["import"]["error_message"]:
1202
+ if (
1203
+ is_cloud is False
1204
+ # NOTE: BYOM may raise these errors if there is an error in the user's code,
1205
+ # therefore error_message will be None
1206
+ and handler_module_meta["name"] != "byom"
1207
+ and "No module named" in handler_module_meta["import"]["error_message"]
1208
+ ):
1142
1209
  logger.info(get_handler_install_message(handler_module_meta["name"]))
1143
1210
  ast_drop = DropMLEngine(name=Identifier(name))
1144
1211
  self.answer_drop_ml_engine(ast_drop)
@@ -1147,8 +1214,21 @@ class ExecuteCommands:
1147
1214
 
1148
1215
  return ExecuteAnswer()
1149
1216
 
1150
- def answer_drop_ml_engine(self, statement: ASTNode):
1151
- name = statement.name.parts[-1]
1217
+ def answer_drop_ml_engine(self, statement: DropMLEngine) -> ExecuteAnswer:
1218
+ """Handles the `DROP ML_ENGINE` command, which removes an ML integration (engine) from the system.
1219
+
1220
+ Args:
1221
+ statement (DropMLEngine): The AST object representing the DROP ML_ENGINE command.
1222
+
1223
+ Raises:
1224
+ EntityNotExistsError: If the integration does not exist and IF EXISTS is not specified.
1225
+ ValueError: If the integration name is provided in an invalid format.
1226
+
1227
+ Returns:
1228
+ ExecuteAnswer: The result of the ML engine deletion operation.
1229
+ """
1230
+ name = match_one_part_name(statement.name)
1231
+
1152
1232
  integrations = self.session.integration_controller.get_all()
1153
1233
  if name not in integrations:
1154
1234
  if not statement.if_exists:
@@ -1158,53 +1238,57 @@ class ExecuteCommands:
1158
1238
  self.session.integration_controller.delete(name)
1159
1239
  return ExecuteAnswer()
1160
1240
 
1161
- def answer_create_database(self, statement: ASTNode):
1162
- """create new handler (datasource/integration in old terms)
1241
+ def answer_create_database(self, statement: CreateDatabase) -> ExecuteAnswer:
1242
+ """Create new integration or project
1243
+
1163
1244
  Args:
1164
- statement (ASTNode): data for creating database/project
1245
+ statement (CreateDatabase): data for creating database/project
1246
+
1247
+ Returns:
1248
+ ExecuteAnswer: 'ok' answer
1165
1249
  """
1250
+ database_name = match_one_part_name(statement.name, ensure_lower_case=True)
1166
1251
 
1167
- if len(statement.name.parts) != 1:
1168
- raise Exception("Database name should contain only 1 part.")
1252
+ engine = (statement.engine or "mindsdb").lower()
1169
1253
 
1170
- database_name = statement.name.parts[0]
1171
- engine = statement.engine
1172
- if engine is None:
1173
- engine = "mindsdb"
1174
- engine = engine.lower()
1175
1254
  connection_args = statement.parameters
1176
1255
 
1177
- if engine == "mindsdb":
1178
- try:
1256
+ try:
1257
+ if engine == "mindsdb":
1179
1258
  ProjectController().add(database_name)
1180
- except EntityExistsError:
1181
- if statement.if_not_exists is False:
1182
- raise
1183
- else:
1184
- try:
1259
+ else:
1185
1260
  self._create_integration(database_name, engine, connection_args)
1186
- except EntityExistsError:
1187
- if getattr(statement, "if_not_exists", False) is False:
1188
- raise
1261
+ except EntityExistsError:
1262
+ if statement.if_not_exists is False:
1263
+ raise
1189
1264
 
1190
1265
  return ExecuteAnswer()
1191
1266
 
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]
1267
+ def answer_drop_database(self, statement: DropDatabase | DropDatasource) -> ExecuteAnswer:
1268
+ """Drop a database (project or integration) by name.
1269
+
1270
+ Args:
1271
+ statement (DropDatabase | DropDatasource): The parsed DROP DATABASE or DROP DATASOURCE statement.
1272
+
1273
+ Raises:
1274
+ Exception: If the database name format is invalid.
1275
+ EntityNotExistsError: If the database does not exist and 'IF EXISTS' is not specified in the statement.
1276
+
1277
+ Returns:
1278
+ ExecuteAnswer: The result of the drop database operation.
1279
+ """
1280
+ db_name = match_one_part_name(statement.name)
1281
+
1196
1282
  try:
1197
- self.session.database_controller.delete(db_name)
1283
+ self.session.database_controller.delete(db_name, strict_case=statement.name.is_quoted[0])
1198
1284
  except EntityNotExistsError:
1199
1285
  if statement.if_exists is not True:
1200
1286
  raise
1201
1287
  return ExecuteAnswer()
1202
1288
 
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)
1289
+ def answer_alter_database(self, statement: AlterDatabase) -> ExecuteAnswer:
1290
+ db_name = match_one_part_name(statement.name)
1291
+ self.session.database_controller.update(db_name, data=statement.params, strict_case=statement.name.is_quoted[0])
1208
1292
  return ExecuteAnswer()
1209
1293
 
1210
1294
  def answer_drop_tables(self, statement, database_name):
@@ -1242,35 +1326,19 @@ class ExecuteCommands:
1242
1326
 
1243
1327
  return ExecuteAnswer()
1244
1328
 
1245
- def answer_create_or_alter_view(self, statement: ASTNode, database_name: str) -> ExecuteAnswer:
1329
+ def answer_create_or_alter_view(self, statement: CreateView | AlterView, database_name: str) -> ExecuteAnswer:
1246
1330
  """Process CREATE and ALTER VIEW commands
1247
1331
 
1248
1332
  Args:
1249
- statement (ASTNode): data for creating or altering view
1333
+ statement (CreateView | AlterView): data for creating or altering view
1250
1334
  database_name (str): name of the current database
1251
1335
 
1252
1336
  Returns:
1253
1337
  ExecuteAnswer: answer for the command
1254
1338
  """
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
- )
1339
+ project_name, view_name = match_two_part_name(
1340
+ statement.name, default_db_name=database_name, ensure_lower_case=isinstance(statement, CreateView)
1341
+ )
1274
1342
 
1275
1343
  query_str = statement.query_str
1276
1344
 
@@ -1280,30 +1348,18 @@ class ExecuteCommands:
1280
1348
  from_table=NativeQuery(integration=statement.from_table, query=statement.query_str),
1281
1349
  )
1282
1350
  query_str = query.to_string()
1283
- else:
1284
- query = parse_sql(query_str)
1285
-
1286
- if isinstance(query, Select):
1287
- # check create view sql
1288
- query.limit = Constant(1)
1289
-
1290
- query_context_controller.set_context(query_context_controller.IGNORE_CONTEXT)
1291
- try:
1292
- SQLQuery(query, session=self.session, database=database_name)
1293
- finally:
1294
- query_context_controller.release_context(query_context_controller.IGNORE_CONTEXT)
1295
1351
 
1296
1352
  project = self.session.database_controller.get_project(project_name)
1297
1353
 
1298
1354
  if isinstance(statement, CreateView):
1299
1355
  try:
1300
- project.create_view(view_name, query=query_str)
1356
+ project.create_view(view_name, query=query_str, session=self.session)
1301
1357
  except EntityExistsError:
1302
1358
  if getattr(statement, "if_not_exists", False) is False:
1303
1359
  raise
1304
1360
  elif isinstance(statement, AlterView):
1305
1361
  try:
1306
- project.update_view(view_name, query=query_str)
1362
+ project.update_view(view_name, query=query_str, strict_case=(not view_name.islower()))
1307
1363
  except EntityNotExistsError:
1308
1364
  raise ExecutorException(f"View {view_name} does not exist in {project_name}")
1309
1365
  else:
@@ -1311,19 +1367,33 @@ class ExecuteCommands:
1311
1367
 
1312
1368
  return ExecuteAnswer()
1313
1369
 
1314
- def answer_drop_view(self, statement, database_name):
1315
- names = statement.names
1370
+ def answer_drop_view(self, statement: DropView, database_name: str) -> ExecuteAnswer:
1371
+ """Drop one or more views from the specified database/project.
1316
1372
 
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)
1373
+ Args:
1374
+ statement (DropView): The parsed DROP VIEW statement containing view names and options.
1375
+ database_name (str): The name of the database (project) from which to drop the views.
1376
+
1377
+ Raises:
1378
+ EntityNotExistsError: If a view does not exist and 'IF EXISTS' is not specified in the statement.
1379
+ ValueError: If the view name format is invalid.
1380
+
1381
+ Returns:
1382
+ ExecuteAnswer: The result of the drop view operation.
1383
+ """
1384
+ for name in statement.names:
1385
+ match name.parts, name.is_quoted:
1386
+ case [view_name], [view_name_quoted]:
1387
+ db_name_quoted = False
1388
+ case [database_name, view_name], [db_name_quoted, view_name_quoted]:
1389
+ pass
1390
+ case _:
1391
+ raise ValueError(f"Invalid view name: {name}")
1392
+
1393
+ project = self.session.database_controller.get_project(database_name, db_name_quoted)
1324
1394
 
1325
1395
  try:
1326
- project.drop_view(view_name)
1396
+ project.drop_view(view_name, strict_case=view_name_quoted)
1327
1397
  except EntityNotExistsError:
1328
1398
  if statement.if_exists is not True:
1329
1399
  raise
@@ -1337,7 +1407,9 @@ class ExecuteCommands:
1337
1407
  "Please pass the model parameters as a JSON object in the embedding_model field."
1338
1408
  )
1339
1409
 
1340
- project_name = statement.name.parts[0] if len(statement.name.parts) > 1 else database_name
1410
+ project_name, kb_name = match_two_part_name(
1411
+ statement.name, ensure_lower_case=True, default_db_name=database_name
1412
+ )
1341
1413
 
1342
1414
  if statement.storage is not None:
1343
1415
  if len(statement.storage.parts) != 2:
@@ -1349,8 +1421,6 @@ class ExecuteCommands:
1349
1421
  # TODO: implement this
1350
1422
  raise ExecutorException("Create a knowledge base from a select is not supported yet")
1351
1423
 
1352
- kb_name = statement.name.parts[-1]
1353
-
1354
1424
  # create the knowledge base
1355
1425
  _ = self.session.kb_controller.add(
1356
1426
  name=kb_name,
@@ -1363,13 +1433,12 @@ class ExecuteCommands:
1363
1433
 
1364
1434
  return ExecuteAnswer()
1365
1435
 
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
1436
+ def answer_drop_kb(self, statement: DropKnowledgeBase, database_name: str) -> ExecuteAnswer:
1437
+ project_name, kb_name = match_two_part_name(statement.name, default_db_name=database_name)
1369
1438
 
1370
1439
  # delete the knowledge base
1371
1440
  self.session.kb_controller.delete(
1372
- name=name,
1441
+ name=kb_name,
1373
1442
  project_name=project_name,
1374
1443
  if_exists=statement.if_exists,
1375
1444
  )
@@ -1377,8 +1446,7 @@ class ExecuteCommands:
1377
1446
  return ExecuteAnswer()
1378
1447
 
1379
1448
  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
1449
+ project_name, name = match_two_part_name(statement.name, ensure_lower_case=True, default_db_name=database_name)
1382
1450
 
1383
1451
  try:
1384
1452
  _ = self.session.skills_controller.add_skill(name, project_name, statement.type, statement.params)
@@ -1389,11 +1457,10 @@ class ExecuteCommands:
1389
1457
  return ExecuteAnswer()
1390
1458
 
1391
1459
  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
1460
+ project_name, name = match_two_part_name(statement.name, default_db_name=database_name)
1394
1461
 
1395
1462
  try:
1396
- self.session.skills_controller.delete_skill(name, project_name)
1463
+ self.session.skills_controller.delete_skill(name, project_name, strict_case=True)
1397
1464
  except ValueError as e:
1398
1465
  # Project does not exist or skill does not exist.
1399
1466
  raise ExecutorException(str(e))
@@ -1401,8 +1468,7 @@ class ExecuteCommands:
1401
1468
  return ExecuteAnswer()
1402
1469
 
1403
1470
  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
1471
+ project_name, name = match_two_part_name(statement.name, default_db_name=database_name)
1406
1472
 
1407
1473
  type = statement.params.pop("type", None)
1408
1474
  try:
@@ -1416,8 +1482,7 @@ class ExecuteCommands:
1416
1482
  return ExecuteAnswer()
1417
1483
 
1418
1484
  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
1485
+ project_name, name = match_two_part_name(statement.name, ensure_lower_case=True, default_db_name=database_name)
1421
1486
 
1422
1487
  skills = statement.params.pop("skills", [])
1423
1488
  provider = statement.params.pop("provider", None)
@@ -1439,9 +1504,8 @@ class ExecuteCommands:
1439
1504
 
1440
1505
  return ExecuteAnswer()
1441
1506
 
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
1507
+ def answer_drop_agent(self, statement: DropAgent, database_name: str):
1508
+ project_name, name = match_two_part_name(statement.name, default_db_name=database_name)
1445
1509
 
1446
1510
  try:
1447
1511
  self.session.agents_controller.delete_agent(name, project_name)
@@ -1451,9 +1515,8 @@ class ExecuteCommands:
1451
1515
 
1452
1516
  return ExecuteAnswer()
1453
1517
 
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
1518
+ def answer_update_agent(self, statement: UpdateAgent, database_name: str):
1519
+ project_name, name = match_two_part_name(statement.name, default_db_name=database_name)
1457
1520
 
1458
1521
  model = statement.params.pop("model", None)
1459
1522
  skills_to_add = statement.params.pop("skills_to_add", [])
@@ -1474,14 +1537,13 @@ class ExecuteCommands:
1474
1537
  return ExecuteAnswer()
1475
1538
 
1476
1539
  @mark_process("learn")
1477
- def answer_create_predictor(self, statement: CreatePredictor, database_name):
1478
- integration_name = database_name
1540
+ def answer_create_predictor(self, statement: CreatePredictor, database_name: str):
1541
+ integration_name, model_name = match_two_part_name(
1542
+ statement.name, ensure_lower_case=True, default_db_name=database_name
1543
+ )
1479
1544
 
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
1545
  statement.name.parts = [integration_name.lower(), model_name]
1546
+ statement.name.is_quoted = [False, False]
1485
1547
 
1486
1548
  ml_integration_name = "lightwood" # default
1487
1549
  if statement.using is not None:
@@ -1498,7 +1560,9 @@ class ExecuteCommands:
1498
1560
  ml_handler = self.session.integration_controller.get_ml_handler(ml_integration_name)
1499
1561
  except EntityNotExistsError:
1500
1562
  # not exist, try to create it with same name as handler
1501
- self.answer_create_ml_engine(ml_integration_name, handler=ml_integration_name)
1563
+ self.answer_create_ml_engine(
1564
+ CreateMLEngine(name=Identifier(ml_integration_name), handler=ml_integration_name)
1565
+ )
1502
1566
 
1503
1567
  ml_handler = self.session.integration_controller.get_ml_handler(ml_integration_name)
1504
1568
 
@@ -1511,7 +1575,6 @@ class ExecuteCommands:
1511
1575
 
1512
1576
  try:
1513
1577
  df = self.session.model_controller.create_model(statement, ml_handler)
1514
-
1515
1578
  return ExecuteAnswer(data=ResultSet.from_df(df))
1516
1579
  except EntityExistsError:
1517
1580
  if getattr(statement, "if_not_exists", False) is True:
@@ -1926,22 +1989,24 @@ class ExecuteCommands:
1926
1989
  self.session.model_controller.set_model_active_version(project_name, model_name, version)
1927
1990
  return ExecuteAnswer()
1928
1991
 
1929
- def answer_drop_model(self, statement, database_name):
1930
- model_parts = statement.name.parts
1931
- version = None
1992
+ def answer_drop_model(self, statement: DropPredictor, database_name: str) -> ExecuteAnswer:
1993
+ """Handles the DROP MODEL (or DROP PREDICTOR) command, which removes a model
1994
+ or a specific model version from a project.
1932
1995
 
1933
- # with version?
1934
- if model_parts[-1].isdigit():
1935
- version = int(model_parts[-1])
1936
- model_parts = model_parts[:-1]
1996
+ Args:
1997
+ statement (DropPredictor): The AST object representing the DROP MODEL or DROP PREDICTOR command.
1998
+ database_name (str): The name of the current database/project.
1937
1999
 
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]
2000
+ Raises:
2001
+ EntityNotExistsError: If the model or version does not exist and IF EXISTS is not specified.
2002
+ ValueError: If the model name format is invalid.
2003
+
2004
+ Returns:
2005
+ ExecuteAnswer: The result of the model deletion operation.
2006
+ """
2007
+ project_name, model_name, version = resolve_model_identifier(statement.name)
2008
+ if project_name is None:
1942
2009
  project_name = database_name
1943
- else:
1944
- raise ExecutorException(f"Unknown model: {statement.name}")
1945
2010
 
1946
2011
  if version is not None:
1947
2012
  # 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
 
@@ -154,6 +154,7 @@ class PlanJoinTablesQuery:
154
154
  if len(table.parts) > 0:
155
155
  if table.parts[0] in self.planner.databases:
156
156
  integration = table.parts.pop(0)
157
+ table.is_quoted.pop(0)
157
158
  else:
158
159
  integration = self.planner.default_namespace
159
160
 
@@ -389,6 +390,7 @@ class PlanJoinTablesQuery:
389
390
  def process_table(self, item, query_in):
390
391
  table = copy.deepcopy(item.table)
391
392
  table.parts.insert(0, item.integration)
393
+ table.is_quoted.insert(0, False)
392
394
  query2 = Select(from_table=table, targets=[Star()])
393
395
  # parts = tuple(map(str.lower, table_name.parts))
394
396
  conditions = item.conditions
@@ -410,6 +412,7 @@ class PlanJoinTablesQuery:
410
412
  break
411
413
  col = copy.deepcopy(col)
412
414
  col.field.parts = [col.field.parts[-1]]
415
+ col.field.is_quoted = [col.field.is_quoted[-1]]
413
416
  order_by.append(col)
414
417
 
415
418
  if order_by is not False: