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.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +11 -1
- mindsdb/api/a2a/common/server/server.py +16 -6
- mindsdb/api/executor/command_executor.py +215 -150
- mindsdb/api/executor/datahub/datanodes/project_datanode.py +14 -3
- mindsdb/api/executor/planner/plan_join.py +3 -0
- mindsdb/api/executor/planner/plan_join_ts.py +117 -100
- mindsdb/api/executor/planner/query_planner.py +1 -0
- mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +54 -85
- mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +21 -24
- mindsdb/api/executor/sql_query/steps/fetch_dataframe_partition.py +9 -3
- mindsdb/api/executor/sql_query/steps/subselect_step.py +11 -8
- mindsdb/api/executor/utilities/mysql_to_duckdb_functions.py +264 -0
- mindsdb/api/executor/utilities/sql.py +30 -0
- mindsdb/api/http/initialize.py +18 -44
- mindsdb/api/http/namespaces/agents.py +23 -20
- mindsdb/api/http/namespaces/chatbots.py +83 -120
- mindsdb/api/http/namespaces/file.py +1 -1
- mindsdb/api/http/namespaces/jobs.py +38 -60
- mindsdb/api/http/namespaces/tree.py +69 -61
- mindsdb/api/http/namespaces/views.py +56 -72
- mindsdb/api/mcp/start.py +2 -0
- mindsdb/api/mysql/mysql_proxy/utilities/dump.py +3 -2
- mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +25 -5
- mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +3 -3
- mindsdb/integrations/handlers/db2_handler/db2_handler.py +19 -23
- mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/gong_handler/__about__.py +2 -0
- mindsdb/integrations/handlers/gong_handler/__init__.py +30 -0
- mindsdb/integrations/handlers/gong_handler/connection_args.py +37 -0
- mindsdb/integrations/handlers/gong_handler/gong_handler.py +164 -0
- mindsdb/integrations/handlers/gong_handler/gong_tables.py +508 -0
- mindsdb/integrations/handlers/gong_handler/icon.svg +25 -0
- mindsdb/integrations/handlers/gong_handler/test_gong_handler.py +125 -0
- mindsdb/integrations/handlers/google_calendar_handler/google_calendar_tables.py +82 -73
- mindsdb/integrations/handlers/hubspot_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/huggingface_handler/__init__.py +8 -12
- mindsdb/integrations/handlers/huggingface_handler/finetune.py +203 -223
- mindsdb/integrations/handlers/huggingface_handler/huggingface_handler.py +360 -383
- mindsdb/integrations/handlers/huggingface_handler/requirements.txt +7 -7
- mindsdb/integrations/handlers/huggingface_handler/requirements_cpu.txt +7 -7
- mindsdb/integrations/handlers/huggingface_handler/settings.py +25 -25
- mindsdb/integrations/handlers/langchain_handler/langchain_handler.py +83 -77
- mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
- mindsdb/integrations/handlers/litellm_handler/litellm_handler.py +5 -2
- mindsdb/integrations/handlers/litellm_handler/settings.py +2 -1
- mindsdb/integrations/handlers/openai_handler/constants.py +11 -30
- mindsdb/integrations/handlers/openai_handler/helpers.py +27 -34
- mindsdb/integrations/handlers/openai_handler/openai_handler.py +14 -12
- mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +106 -90
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +41 -39
- mindsdb/integrations/handlers/salesforce_handler/constants.py +215 -0
- mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +141 -80
- mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +0 -1
- mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +32 -17
- mindsdb/integrations/handlers/web_handler/web_handler.py +19 -22
- mindsdb/integrations/libs/llm/config.py +0 -14
- mindsdb/integrations/libs/llm/utils.py +0 -15
- mindsdb/integrations/libs/vectordatabase_handler.py +10 -1
- mindsdb/integrations/utilities/files/file_reader.py +5 -19
- mindsdb/integrations/utilities/handler_utils.py +32 -12
- mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +1 -1
- mindsdb/interfaces/agents/agents_controller.py +246 -149
- mindsdb/interfaces/agents/constants.py +0 -1
- mindsdb/interfaces/agents/langchain_agent.py +11 -6
- mindsdb/interfaces/data_catalog/data_catalog_loader.py +4 -4
- mindsdb/interfaces/database/database.py +38 -13
- mindsdb/interfaces/database/integrations.py +20 -5
- mindsdb/interfaces/database/projects.py +174 -23
- mindsdb/interfaces/database/views.py +86 -60
- mindsdb/interfaces/jobs/jobs_controller.py +103 -110
- mindsdb/interfaces/knowledge_base/controller.py +33 -6
- mindsdb/interfaces/knowledge_base/evaluate.py +2 -1
- mindsdb/interfaces/knowledge_base/executor.py +24 -0
- mindsdb/interfaces/knowledge_base/preprocessing/document_preprocessor.py +6 -10
- mindsdb/interfaces/knowledge_base/preprocessing/text_splitter.py +73 -0
- mindsdb/interfaces/query_context/context_controller.py +111 -145
- mindsdb/interfaces/skills/skills_controller.py +18 -6
- mindsdb/interfaces/storage/db.py +40 -6
- mindsdb/interfaces/variables/variables_controller.py +8 -15
- mindsdb/utilities/config.py +5 -3
- mindsdb/utilities/fs.py +54 -17
- mindsdb/utilities/functions.py +72 -60
- mindsdb/utilities/log.py +38 -6
- mindsdb/utilities/ps.py +7 -7
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/METADATA +282 -268
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/RECORD +94 -92
- mindsdb/integrations/handlers/anyscale_endpoints_handler/__about__.py +0 -9
- mindsdb/integrations/handlers/anyscale_endpoints_handler/__init__.py +0 -20
- mindsdb/integrations/handlers/anyscale_endpoints_handler/anyscale_endpoints_handler.py +0 -290
- mindsdb/integrations/handlers/anyscale_endpoints_handler/creation_args.py +0 -14
- mindsdb/integrations/handlers/anyscale_endpoints_handler/icon.svg +0 -4
- mindsdb/integrations/handlers/anyscale_endpoints_handler/requirements.txt +0 -2
- mindsdb/integrations/handlers/anyscale_endpoints_handler/settings.py +0 -51
- mindsdb/integrations/handlers/anyscale_endpoints_handler/tests/test_anyscale_endpoints_handler.py +0 -212
- /mindsdb/integrations/handlers/{anyscale_endpoints_handler/tests/__init__.py → gong_handler/requirements.txt} +0 -0
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/top_level.txt +0 -0
|
@@ -120,7 +120,7 @@ class DataCatalogLoader(BaseDataCatalog):
|
|
|
120
120
|
Load the column metadata from the handler.
|
|
121
121
|
"""
|
|
122
122
|
self.logger.info(f"Loading columns for {self.database_name}")
|
|
123
|
-
response = self.data_handler.meta_get_columns(
|
|
123
|
+
response = self.data_handler.meta_get_columns([table.name for table in tables])
|
|
124
124
|
if response.resp_type == RESPONSE_TYPE.ERROR:
|
|
125
125
|
self.logger.error(f"Failed to load columns for {self.database_name}: {response.error_message}")
|
|
126
126
|
return []
|
|
@@ -168,7 +168,7 @@ class DataCatalogLoader(BaseDataCatalog):
|
|
|
168
168
|
Load the column statistics metadata from the handler.
|
|
169
169
|
"""
|
|
170
170
|
self.logger.info(f"Loading column statistics for {self.database_name}")
|
|
171
|
-
response = self.data_handler.meta_get_column_statistics(
|
|
171
|
+
response = self.data_handler.meta_get_column_statistics([table.name for table in tables])
|
|
172
172
|
if response.resp_type == RESPONSE_TYPE.ERROR:
|
|
173
173
|
self.logger.error(f"Failed to load column statistics for {self.database_name}: {response.error_message}")
|
|
174
174
|
return
|
|
@@ -233,7 +233,7 @@ class DataCatalogLoader(BaseDataCatalog):
|
|
|
233
233
|
Load the primary keys metadata from the handler.
|
|
234
234
|
"""
|
|
235
235
|
self.logger.info(f"Loading primary keys for {self.database_name}")
|
|
236
|
-
response = self.data_handler.meta_get_primary_keys(
|
|
236
|
+
response = self.data_handler.meta_get_primary_keys([table.name for table in tables])
|
|
237
237
|
if response.resp_type == RESPONSE_TYPE.ERROR:
|
|
238
238
|
self.logger.error(f"Failed to load primary keys for {self.database_name}: {response.error_message}")
|
|
239
239
|
return
|
|
@@ -285,7 +285,7 @@ class DataCatalogLoader(BaseDataCatalog):
|
|
|
285
285
|
Load the foreign keys metadata from the handler.
|
|
286
286
|
"""
|
|
287
287
|
self.logger.info(f"Loading foreign keys for {self.database_name}")
|
|
288
|
-
response = self.data_handler.meta_get_foreign_keys(
|
|
288
|
+
response = self.data_handler.meta_get_foreign_keys([table.name for table in tables])
|
|
289
289
|
if response.resp_type == RESPONSE_TYPE.ERROR:
|
|
290
290
|
self.logger.error(f"Failed to foreign keys for {self.database_name}: {response.error_message}")
|
|
291
291
|
return
|
|
@@ -18,18 +18,30 @@ class DatabaseController:
|
|
|
18
18
|
self.logs_db_controller = LogDBController()
|
|
19
19
|
self.information_schema_controller = None
|
|
20
20
|
|
|
21
|
-
def delete(self, name: str):
|
|
21
|
+
def delete(self, name: str, strict_case: bool = False) -> None:
|
|
22
|
+
"""Delete a database (project or integration) by name.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
name (str): The name of the database to delete.
|
|
26
|
+
strict_case (bool, optional): If True, the database name is case-sensitive. Defaults to False.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
EntityNotExistsError: If the database does not exist.
|
|
30
|
+
Exception: If the database cannot be deleted.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
None
|
|
34
|
+
"""
|
|
22
35
|
databases = self.get_dict()
|
|
23
|
-
|
|
24
|
-
if name not in databases:
|
|
36
|
+
if name.lower() not in databases:
|
|
25
37
|
raise EntityNotExistsError("Database does not exists", name)
|
|
26
|
-
db_type = databases[name]["type"]
|
|
38
|
+
db_type = databases[name.lower()]["type"]
|
|
27
39
|
if db_type == "project":
|
|
28
|
-
project = self.get_project(name)
|
|
40
|
+
project = self.get_project(name, strict_case)
|
|
29
41
|
project.delete()
|
|
30
42
|
return
|
|
31
43
|
elif db_type == "data":
|
|
32
|
-
self.integration_controller.delete(name)
|
|
44
|
+
self.integration_controller.delete(name, strict_case)
|
|
33
45
|
return
|
|
34
46
|
else:
|
|
35
47
|
raise Exception(f"Database with type '{db_type}' cannot be deleted")
|
|
@@ -81,9 +93,9 @@ class DatabaseController:
|
|
|
81
93
|
|
|
82
94
|
return result
|
|
83
95
|
|
|
84
|
-
def get_dict(self, filter_type: Optional[str] = None):
|
|
96
|
+
def get_dict(self, filter_type: Optional[str] = None, lowercase: bool = True):
|
|
85
97
|
return OrderedDict(
|
|
86
|
-
(x["name"].lower(), {"type": x["type"], "engine": x["engine"], "id": x["id"]})
|
|
98
|
+
(x["name"].lower() if lowercase else x["name"], {"type": x["type"], "engine": x["engine"], "id": x["id"]})
|
|
87
99
|
for x in self.get_list(filter_type=filter_type)
|
|
88
100
|
)
|
|
89
101
|
|
|
@@ -98,8 +110,17 @@ class DatabaseController:
|
|
|
98
110
|
def exists(self, db_name: str) -> bool:
|
|
99
111
|
return db_name.lower() in self.get_dict()
|
|
100
112
|
|
|
101
|
-
def get_project(self, name: str):
|
|
102
|
-
|
|
113
|
+
def get_project(self, name: str, strict_case: bool = False):
|
|
114
|
+
"""Get a project by name.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
name (str): The name of the project to retrieve.
|
|
118
|
+
strict_case (bool, optional): If True, the project name is case-sensitive. Defaults to False.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Project: The project instance matching the given name.
|
|
122
|
+
"""
|
|
123
|
+
return self.project_controller.get(name=name, strict_case=strict_case)
|
|
103
124
|
|
|
104
125
|
def get_system_db(self, name: str):
|
|
105
126
|
if name == "log":
|
|
@@ -112,19 +133,21 @@ class DatabaseController:
|
|
|
112
133
|
else:
|
|
113
134
|
raise Exception(f"Database '{name}' does not exists")
|
|
114
135
|
|
|
115
|
-
def update(self, name: str, data: dict):
|
|
136
|
+
def update(self, name: str, data: dict, strict_case: bool = False):
|
|
116
137
|
"""
|
|
117
138
|
Updates the database with the given name using the provided data.
|
|
118
139
|
|
|
119
140
|
Parameters:
|
|
120
141
|
name (str): The name of the database to update.
|
|
121
142
|
data (dict): The data to update the database with.
|
|
143
|
+
strict_case (bool): if True, then name is case-sesitive
|
|
122
144
|
|
|
123
145
|
Raises:
|
|
124
146
|
EntityNotExistsError: If the database does not exist.
|
|
125
147
|
"""
|
|
126
|
-
databases = self.get_dict()
|
|
127
|
-
|
|
148
|
+
databases = self.get_dict(lowercase=(not strict_case))
|
|
149
|
+
if not strict_case:
|
|
150
|
+
name = name.lower()
|
|
128
151
|
if name not in databases:
|
|
129
152
|
raise EntityNotExistsError("Database does not exist.", name)
|
|
130
153
|
|
|
@@ -133,6 +156,8 @@ class DatabaseController:
|
|
|
133
156
|
# Only the name of the project can be updated.
|
|
134
157
|
if {"name"} != set(data):
|
|
135
158
|
raise ValueError("Only the 'name' field can be updated for projects.")
|
|
159
|
+
if not data["name"].islower():
|
|
160
|
+
raise ValueError("New name must be in lower case.")
|
|
136
161
|
self.project_controller.update(name=name, new_name=str(data["name"]))
|
|
137
162
|
return
|
|
138
163
|
|
|
@@ -161,7 +161,7 @@ class IntegrationController:
|
|
|
161
161
|
db.session.commit()
|
|
162
162
|
return integration_record.id
|
|
163
163
|
|
|
164
|
-
def add(self, name, engine, connection_args):
|
|
164
|
+
def add(self, name: str, engine, connection_args):
|
|
165
165
|
logger.debug(
|
|
166
166
|
"%s: add method calling name=%s, engine=%s, connection_args=%s, company_id=%s",
|
|
167
167
|
self.__class__.__name__,
|
|
@@ -172,6 +172,9 @@ class IntegrationController:
|
|
|
172
172
|
)
|
|
173
173
|
handler_meta = self.get_handler_meta(engine)
|
|
174
174
|
|
|
175
|
+
if not name.islower():
|
|
176
|
+
raise ValueError(f"The name must be in lower case: {name}")
|
|
177
|
+
|
|
175
178
|
accept_connection_args = handler_meta.get("connection_args")
|
|
176
179
|
logger.debug("%s: accept_connection_args - %s", self.__class__.__name__, accept_connection_args)
|
|
177
180
|
|
|
@@ -210,20 +213,32 @@ class IntegrationController:
|
|
|
210
213
|
integration_record.data = data
|
|
211
214
|
db.session.commit()
|
|
212
215
|
|
|
213
|
-
def delete(self, name):
|
|
214
|
-
|
|
216
|
+
def delete(self, name: str, strict_case: bool = False) -> None:
|
|
217
|
+
"""Delete an integration by name.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
name (str): The name of the integration to delete.
|
|
221
|
+
strict_case (bool, optional): If True, the integration name is case-sensitive. Defaults to False.
|
|
222
|
+
|
|
223
|
+
Raises:
|
|
224
|
+
Exception: If the integration cannot be deleted (system, permanent, demo, in use, or has active models).
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
None
|
|
228
|
+
"""
|
|
229
|
+
if name == "files":
|
|
215
230
|
raise Exception("Unable to drop: is system database")
|
|
216
231
|
|
|
217
232
|
self.handlers_cache.delete(name)
|
|
218
233
|
|
|
219
234
|
# check permanent integration
|
|
220
|
-
if name in self.handler_modules:
|
|
235
|
+
if name.lower() in self.handler_modules:
|
|
221
236
|
handler = self.handler_modules[name]
|
|
222
237
|
|
|
223
238
|
if getattr(handler, "permanent", False) is True:
|
|
224
239
|
raise Exception("Unable to drop permanent integration")
|
|
225
240
|
|
|
226
|
-
integration_record = self._get_integration_record(name)
|
|
241
|
+
integration_record = self._get_integration_record(name, case_sensitive=strict_case)
|
|
227
242
|
if isinstance(integration_record.data, dict) and integration_record.data.get("is_demo") is True:
|
|
228
243
|
raise Exception("Unable to drop demo object")
|
|
229
244
|
|
|
@@ -3,11 +3,12 @@ from copy import deepcopy
|
|
|
3
3
|
from typing import List, Optional
|
|
4
4
|
from collections import OrderedDict
|
|
5
5
|
|
|
6
|
+
import pandas as pd
|
|
6
7
|
import sqlalchemy as sa
|
|
7
8
|
import numpy as np
|
|
8
9
|
|
|
9
10
|
from mindsdb_sql_parser.ast.base import ASTNode
|
|
10
|
-
from mindsdb_sql_parser.ast import Select, Star, Constant, Identifier
|
|
11
|
+
from mindsdb_sql_parser.ast import Select, Star, Constant, Identifier, BinaryOperation
|
|
11
12
|
from mindsdb_sql_parser import parse_sql
|
|
12
13
|
|
|
13
14
|
from mindsdb.interfaces.storage import db
|
|
@@ -94,14 +95,38 @@ class Project:
|
|
|
94
95
|
def drop_model(self, name: str):
|
|
95
96
|
ModelController().delete_model(name, project_name=self.name)
|
|
96
97
|
|
|
97
|
-
def drop_view(self, name: str):
|
|
98
|
-
|
|
98
|
+
def drop_view(self, name: str, strict_case: bool = False) -> None:
|
|
99
|
+
"""Remove a view with the specified name from the current project.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
name (str): The name of the view to remove.
|
|
103
|
+
strict_case (bool, optional): If True, the view name is case-sensitive. Defaults to False.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
EntityNotExistsError: If the view does not exist.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
None
|
|
110
|
+
"""
|
|
111
|
+
ViewController().delete(name, project_name=self.name, strict_case=strict_case)
|
|
112
|
+
|
|
113
|
+
def create_view(self, name: str, query: str, session):
|
|
114
|
+
ast_query = parse_sql(query)
|
|
115
|
+
|
|
116
|
+
if isinstance(ast_query, Select):
|
|
117
|
+
# check create view sql
|
|
118
|
+
ast_query.limit = Constant(1)
|
|
119
|
+
|
|
120
|
+
query_context_controller.set_context(query_context_controller.IGNORE_CONTEXT)
|
|
121
|
+
try:
|
|
122
|
+
SQLQuery(ast_query, session=session, database=self.name)
|
|
123
|
+
finally:
|
|
124
|
+
query_context_controller.release_context(query_context_controller.IGNORE_CONTEXT)
|
|
99
125
|
|
|
100
|
-
def create_view(self, name: str, query: str):
|
|
101
126
|
ViewController().add(name, query=query, project_name=self.name)
|
|
102
127
|
|
|
103
|
-
def update_view(self, name: str, query: str):
|
|
104
|
-
ViewController().update(name, query=query, project_name=self.name)
|
|
128
|
+
def update_view(self, name: str, query: str, strict_case: bool = False):
|
|
129
|
+
ViewController().update(name, query=query, project_name=self.name, strict_case=strict_case)
|
|
105
130
|
|
|
106
131
|
def delete_view(self, name: str):
|
|
107
132
|
ViewController().delete(name, project_name=self.name)
|
|
@@ -112,21 +137,112 @@ class Project:
|
|
|
112
137
|
view_meta["query_ast"] = parse_sql(view_meta["query"])
|
|
113
138
|
return view_meta
|
|
114
139
|
|
|
115
|
-
|
|
140
|
+
@staticmethod
|
|
141
|
+
def combine_view_select(view_query: Select, query: Select) -> Select:
|
|
142
|
+
"""
|
|
143
|
+
Create a combined query from view's query and outer query.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
# apply optimizations
|
|
147
|
+
if query.where is not None:
|
|
148
|
+
# Get conditions that can be duplicated into view's query
|
|
149
|
+
# It has to be simple condition with identifier and constant
|
|
150
|
+
# Also it shouldn't be under the OR condition
|
|
151
|
+
|
|
152
|
+
def get_conditions_to_move(node):
|
|
153
|
+
if not isinstance(node, BinaryOperation):
|
|
154
|
+
return []
|
|
155
|
+
op = node.op.upper()
|
|
156
|
+
if op == "AND":
|
|
157
|
+
conditions = []
|
|
158
|
+
conditions.extend(get_conditions_to_move(node.args[0]))
|
|
159
|
+
conditions.extend(get_conditions_to_move(node.args[1]))
|
|
160
|
+
return conditions
|
|
161
|
+
|
|
162
|
+
if op == "OR":
|
|
163
|
+
return []
|
|
164
|
+
if isinstance(node.args[0], (Identifier, Constant)) and isinstance(
|
|
165
|
+
node.args[1], (Identifier, Constant)
|
|
166
|
+
):
|
|
167
|
+
return [node]
|
|
168
|
+
|
|
169
|
+
conditions = get_conditions_to_move(query.where)
|
|
170
|
+
|
|
171
|
+
if conditions:
|
|
172
|
+
# analyse targets
|
|
173
|
+
# if target element has alias
|
|
174
|
+
# if element is not identifier or the name is not equal to alias:
|
|
175
|
+
# add alias to black list
|
|
176
|
+
# white list:
|
|
177
|
+
# all targets that are identifiers with no alias or equal to its alias
|
|
178
|
+
# condition can be moved if
|
|
179
|
+
# column is not in black list AND (query has star(*) OR column in white list)
|
|
180
|
+
|
|
181
|
+
has_star = False
|
|
182
|
+
white_list, black_list = [], []
|
|
183
|
+
for target in view_query.targets:
|
|
184
|
+
if isinstance(target, Star):
|
|
185
|
+
has_star = True
|
|
186
|
+
if isinstance(target, Identifier):
|
|
187
|
+
name = target.parts[-1].lower()
|
|
188
|
+
if target.alias is None or target.alias.parts[-1].lower() == name:
|
|
189
|
+
white_list.append(name)
|
|
190
|
+
elif target.alias is not None:
|
|
191
|
+
black_list.append(target.alias.parts[-1].lower())
|
|
192
|
+
|
|
193
|
+
view_where = view_query.where
|
|
194
|
+
for condition in conditions:
|
|
195
|
+
arg1, arg2 = condition.args
|
|
196
|
+
|
|
197
|
+
if isinstance(arg1, Identifier):
|
|
198
|
+
name = arg1.parts[-1].lower()
|
|
199
|
+
if name in black_list or not (has_star or name in white_list):
|
|
200
|
+
continue
|
|
201
|
+
if isinstance(arg2, Identifier):
|
|
202
|
+
name = arg2.parts[-1].lower()
|
|
203
|
+
if name in black_list or not (has_star or name in white_list):
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
# condition can be moved into view
|
|
207
|
+
condition2 = BinaryOperation(condition.op, [arg1, arg2])
|
|
208
|
+
if view_where is None:
|
|
209
|
+
view_where = condition2
|
|
210
|
+
else:
|
|
211
|
+
view_where = BinaryOperation("AND", args=[view_where, condition2])
|
|
212
|
+
|
|
213
|
+
# disable outer condition
|
|
214
|
+
condition.op = "="
|
|
215
|
+
condition.args = [Constant(0), Constant(0)]
|
|
216
|
+
|
|
217
|
+
view_query.where = view_where
|
|
218
|
+
|
|
219
|
+
# combine outer query with view's query
|
|
220
|
+
view_query.parentheses = True
|
|
221
|
+
query.from_table = view_query
|
|
222
|
+
return query
|
|
223
|
+
|
|
224
|
+
def query_view(self, query: Select, session) -> pd.DataFrame:
|
|
116
225
|
view_meta = self.get_view_meta(query)
|
|
117
226
|
|
|
118
227
|
query_context_controller.set_context("view", view_meta["id"])
|
|
119
|
-
|
|
228
|
+
query_applied = False
|
|
120
229
|
try:
|
|
121
|
-
|
|
230
|
+
view_query = view_meta["query_ast"]
|
|
231
|
+
if isinstance(view_query, Select):
|
|
232
|
+
view_query = self.combine_view_select(view_query, query)
|
|
233
|
+
query_applied = True
|
|
234
|
+
|
|
235
|
+
sqlquery = SQLQuery(view_query, session=session)
|
|
122
236
|
df = sqlquery.fetched_data.to_df()
|
|
123
237
|
finally:
|
|
124
238
|
query_context_controller.release_context("view", view_meta["id"])
|
|
125
239
|
|
|
126
240
|
# remove duplicated columns
|
|
127
241
|
df = df.loc[:, ~df.columns.duplicated()]
|
|
128
|
-
|
|
129
|
-
|
|
242
|
+
if query_applied:
|
|
243
|
+
return df
|
|
244
|
+
else:
|
|
245
|
+
return query_df(df, query, session=session)
|
|
130
246
|
|
|
131
247
|
@staticmethod
|
|
132
248
|
def _get_model_data(predictor_record, integraion_record, with_secrets: bool = True):
|
|
@@ -279,18 +395,29 @@ class Project:
|
|
|
279
395
|
]
|
|
280
396
|
return data
|
|
281
397
|
|
|
282
|
-
def get_view(self, name):
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
.
|
|
398
|
+
def get_view(self, name: str, strict_case: bool = False) -> dict | None:
|
|
399
|
+
"""Get a view by name from the current project.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
name (str): The name of the view to retrieve.
|
|
403
|
+
strict_case (bool, optional): If True, the view name is case-sensitive. Defaults to False.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
dict | None: A dictionary with view information if found, otherwise None.
|
|
407
|
+
"""
|
|
408
|
+
query = db.session.query(db.View).filter(
|
|
409
|
+
db.View.project_id == self.id,
|
|
410
|
+
db.View.company_id == ctx.company_id,
|
|
291
411
|
)
|
|
412
|
+
if strict_case:
|
|
413
|
+
query = query.filter(db.View.name == name)
|
|
414
|
+
else:
|
|
415
|
+
query = query.filter(sa.func.lower(db.View.name) == name.lower())
|
|
416
|
+
|
|
417
|
+
view_record = query.one_or_none()
|
|
418
|
+
|
|
292
419
|
if view_record is None:
|
|
293
|
-
return
|
|
420
|
+
return None
|
|
294
421
|
return {
|
|
295
422
|
"name": view_record.name,
|
|
296
423
|
"query": view_record.query,
|
|
@@ -385,8 +512,29 @@ class ProjectController:
|
|
|
385
512
|
return [Project.from_record(x) for x in records]
|
|
386
513
|
|
|
387
514
|
def get(
|
|
388
|
-
self,
|
|
515
|
+
self,
|
|
516
|
+
id: int | None = None,
|
|
517
|
+
name: str | None = None,
|
|
518
|
+
deleted: bool = False,
|
|
519
|
+
is_default: bool = False,
|
|
520
|
+
strict_case: bool = False,
|
|
389
521
|
) -> Project:
|
|
522
|
+
"""Get a project by id or name.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
id (int | None, optional): The id of the project to retrieve. Cannot be used with 'name'.
|
|
526
|
+
name (str | None, optional): The name of the project to retrieve. Cannot be used with 'id'.
|
|
527
|
+
deleted (bool, optional): If True, include deleted projects. Defaults to False.
|
|
528
|
+
is_default (bool, optional): If True, only return the default project. Defaults to False.
|
|
529
|
+
strict_case (bool, optional): If True, the project name is case-sensitive. Defaults to False.
|
|
530
|
+
|
|
531
|
+
Raises:
|
|
532
|
+
ValueError: If both 'id' and 'name' are provided.
|
|
533
|
+
EntityNotExistsError: If the project is not found.
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
Project: The project instance matching the given criteria.
|
|
537
|
+
"""
|
|
390
538
|
if id is not None and name is not None:
|
|
391
539
|
raise ValueError("Both 'id' and 'name' can't be provided at the same time")
|
|
392
540
|
|
|
@@ -396,7 +544,10 @@ class ProjectController:
|
|
|
396
544
|
if id is not None:
|
|
397
545
|
q = q.filter_by(id=id)
|
|
398
546
|
elif name is not None:
|
|
399
|
-
|
|
547
|
+
if strict_case:
|
|
548
|
+
q = q.filter((db.Project.name == name))
|
|
549
|
+
else:
|
|
550
|
+
q = q.filter((sa.func.lower(db.Project.name) == sa.func.lower(name)))
|
|
400
551
|
|
|
401
552
|
if deleted is True:
|
|
402
553
|
q = q.filter((db.Project.deleted_at != sa.null()))
|
|
@@ -12,64 +12,90 @@ class ViewController:
|
|
|
12
12
|
from mindsdb.interfaces.database.database import DatabaseController
|
|
13
13
|
|
|
14
14
|
database_controller = DatabaseController()
|
|
15
|
-
project_databases_dict = database_controller.get_dict(filter_type=
|
|
15
|
+
project_databases_dict = database_controller.get_dict(filter_type="project")
|
|
16
16
|
|
|
17
17
|
if project_name not in project_databases_dict:
|
|
18
|
-
raise EntityNotExistsError(
|
|
18
|
+
raise EntityNotExistsError("Can not find project", project_name)
|
|
19
19
|
|
|
20
|
-
project_id = project_databases_dict[project_name][
|
|
20
|
+
project_id = project_databases_dict[project_name]["id"]
|
|
21
21
|
view_record = (
|
|
22
22
|
db.session.query(db.View.id)
|
|
23
23
|
.filter(
|
|
24
|
-
func.lower(db.View.name) == name,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
).first()
|
|
24
|
+
func.lower(db.View.name) == name, db.View.company_id == ctx.company_id, db.View.project_id == project_id
|
|
25
|
+
)
|
|
26
|
+
.first()
|
|
28
27
|
)
|
|
29
28
|
if view_record is not None:
|
|
30
|
-
raise EntityExistsError(
|
|
29
|
+
raise EntityExistsError("View already exists", name)
|
|
31
30
|
|
|
32
|
-
view_record = db.View(
|
|
33
|
-
name=name,
|
|
34
|
-
company_id=ctx.company_id,
|
|
35
|
-
query=query,
|
|
36
|
-
project_id=project_id
|
|
37
|
-
)
|
|
31
|
+
view_record = db.View(name=name, company_id=ctx.company_id, query=query, project_id=project_id)
|
|
38
32
|
db.session.add(view_record)
|
|
39
33
|
db.session.commit()
|
|
40
34
|
|
|
41
|
-
def update(self, name, query, project_name):
|
|
42
|
-
|
|
35
|
+
def update(self, name: str, query: str, project_name: str, strict_case: bool = False):
|
|
36
|
+
"""Update the SQL query of an existing view in the specified project.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
name (str): The name of the view to update.
|
|
40
|
+
query (str): The new SQL query for the view.
|
|
41
|
+
project_name (str): The name of the project containing the view.
|
|
42
|
+
strict_case (bool, optional): If True, the view name is case-sensitive. If False, the name comparison is case-insensitive. Defaults to False.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
EntityNotExistsError: If the view with the specified name does not exist in the given project.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
None
|
|
49
|
+
"""
|
|
43
50
|
project_record = get_project_record(project_name)
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
q = db.session.query(db.View).filter(
|
|
53
|
+
db.View.company_id == ctx.company_id, db.View.project_id == project_record.id
|
|
54
|
+
)
|
|
55
|
+
if strict_case:
|
|
56
|
+
q = q.filter(db.View.name == name)
|
|
57
|
+
else:
|
|
58
|
+
q = q.filter(func.lower(db.View.name) == func.lower(name))
|
|
59
|
+
|
|
60
|
+
rec = q.first()
|
|
50
61
|
if rec is None:
|
|
51
|
-
raise EntityNotExistsError(
|
|
62
|
+
raise EntityNotExistsError("View not found", name)
|
|
52
63
|
rec.query = query
|
|
53
64
|
db.session.commit()
|
|
54
65
|
|
|
55
|
-
def delete(self, name, project_name):
|
|
56
|
-
name
|
|
66
|
+
def delete(self, name: str, project_name: str, strict_case: bool = False) -> None:
|
|
67
|
+
"""Remove a view with the specified name from the given project.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
name (str): The name of the view to remove.
|
|
71
|
+
project_name (str): The name of the project containing the view.
|
|
72
|
+
strict_case (bool, optional): If True, the view name is case-sensitive. Defaults to False.
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
EntityNotExistsError: If the view does not exist.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
None
|
|
79
|
+
"""
|
|
57
80
|
project_record = get_project_record(project_name)
|
|
58
81
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
query = db.session.query(db.View).filter(
|
|
83
|
+
db.View.company_id == ctx.company_id, db.View.project_id == project_record.id
|
|
84
|
+
)
|
|
85
|
+
if strict_case:
|
|
86
|
+
query = query.filter(db.View.name == name)
|
|
87
|
+
else:
|
|
88
|
+
query = query.filter(func.lower(db.View.name) == func.lower(name))
|
|
89
|
+
|
|
90
|
+
record = query.first()
|
|
91
|
+
if record is None:
|
|
92
|
+
raise EntityNotExistsError("View not found", name)
|
|
93
|
+
db.session.delete(record)
|
|
67
94
|
db.session.commit()
|
|
68
95
|
|
|
69
|
-
query_context_controller.drop_query_context(
|
|
96
|
+
query_context_controller.drop_query_context("view", record.id)
|
|
70
97
|
|
|
71
98
|
def list(self, project_name):
|
|
72
|
-
|
|
73
99
|
project_names = {}
|
|
74
100
|
for project in get_project_records():
|
|
75
101
|
if project_name is not None and project.name != project_name:
|
|
@@ -77,49 +103,49 @@ class ViewController:
|
|
|
77
103
|
project_names[project.id] = project.name
|
|
78
104
|
|
|
79
105
|
query = db.session.query(db.View).filter(
|
|
80
|
-
db.View.company_id == ctx.company_id,
|
|
81
|
-
db.View.project_id.in_(list(project_names.keys()))
|
|
106
|
+
db.View.company_id == ctx.company_id, db.View.project_id.in_(list(project_names.keys()))
|
|
82
107
|
)
|
|
83
108
|
|
|
84
109
|
data = []
|
|
85
110
|
|
|
86
111
|
for record in query:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
112
|
+
data.append(
|
|
113
|
+
{
|
|
114
|
+
"id": record.id,
|
|
115
|
+
"name": record.name,
|
|
116
|
+
"project": project_names[record.project_id],
|
|
117
|
+
"query": record.query,
|
|
118
|
+
}
|
|
119
|
+
)
|
|
94
120
|
|
|
95
121
|
return data
|
|
96
122
|
|
|
97
123
|
def _get_view_record_data(self, record):
|
|
98
|
-
return {
|
|
99
|
-
'id': record.id,
|
|
100
|
-
'name': record.name,
|
|
101
|
-
'query': record.query
|
|
102
|
-
}
|
|
124
|
+
return {"id": record.id, "name": record.name, "query": record.query}
|
|
103
125
|
|
|
104
126
|
def get(self, id=None, name=None, project_name=None):
|
|
105
127
|
project_record = get_project_record(project_name)
|
|
106
128
|
|
|
107
129
|
if id is not None:
|
|
108
|
-
records =
|
|
109
|
-
|
|
110
|
-
project_id=project_record.id,
|
|
111
|
-
|
|
112
|
-
)
|
|
130
|
+
records = (
|
|
131
|
+
db.session.query(db.View)
|
|
132
|
+
.filter_by(id=id, project_id=project_record.id, company_id=ctx.company_id)
|
|
133
|
+
.all()
|
|
134
|
+
)
|
|
113
135
|
elif name is not None:
|
|
114
|
-
records =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
136
|
+
records = (
|
|
137
|
+
db.session.query(db.View)
|
|
138
|
+
.filter(
|
|
139
|
+
func.lower(db.View.name) == name.lower(),
|
|
140
|
+
db.View.project_id == project_record.id,
|
|
141
|
+
db.View.company_id == ctx.company_id,
|
|
142
|
+
)
|
|
143
|
+
.all()
|
|
144
|
+
)
|
|
119
145
|
if len(records) == 0:
|
|
120
146
|
if name is None:
|
|
121
|
-
name = f
|
|
122
|
-
raise EntityNotExistsError("Can't find view", f
|
|
147
|
+
name = f"id={id}"
|
|
148
|
+
raise EntityNotExistsError("Can't find view", f"{project_name}.{name}")
|
|
123
149
|
elif len(records) > 1:
|
|
124
150
|
raise Exception(f"There are multiple views with name/id: {name}/{id}")
|
|
125
151
|
record = records[0]
|