MindsDB 25.7.4.0__py3-none-any.whl → 25.8.3.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 +13 -1
- mindsdb/api/a2a/agent.py +6 -16
- mindsdb/api/a2a/common/types.py +3 -4
- mindsdb/api/a2a/task_manager.py +24 -35
- mindsdb/api/a2a/utils.py +63 -0
- mindsdb/api/executor/command_executor.py +9 -15
- 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 +2 -1
- mindsdb/api/http/namespaces/agents.py +6 -7
- mindsdb/api/http/namespaces/views.py +56 -72
- mindsdb/integrations/handlers/db2_handler/db2_handler.py +19 -23
- 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/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 +1 -2
- 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/salesforce_handler/constants.py +9 -2
- mindsdb/integrations/libs/llm/config.py +0 -14
- mindsdb/integrations/libs/llm/utils.py +0 -15
- mindsdb/integrations/utilities/files/file_reader.py +5 -19
- mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +1 -1
- mindsdb/interfaces/agents/agents_controller.py +83 -45
- mindsdb/interfaces/agents/constants.py +16 -3
- mindsdb/interfaces/agents/langchain_agent.py +84 -21
- mindsdb/interfaces/database/projects.py +111 -7
- mindsdb/interfaces/knowledge_base/controller.py +7 -1
- 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 +14 -15
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +7 -1
- mindsdb/interfaces/skills/skill_tool.py +7 -1
- mindsdb/interfaces/skills/sql_agent.py +6 -2
- mindsdb/utilities/config.py +2 -0
- mindsdb/utilities/fs.py +60 -17
- {mindsdb-25.7.4.0.dist-info → mindsdb-25.8.3.0.dist-info}/METADATA +277 -262
- {mindsdb-25.7.4.0.dist-info → mindsdb-25.8.3.0.dist-info}/RECORD +57 -56
- 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.4.0.dist-info → mindsdb-25.8.3.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.7.4.0.dist-info → mindsdb-25.8.3.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.7.4.0.dist-info → mindsdb-25.8.3.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.
|
|
3
|
+
__version__ = "25.8.3.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
|
@@ -39,7 +39,7 @@ from mindsdb.utilities.starters import (
|
|
|
39
39
|
)
|
|
40
40
|
from mindsdb.utilities.ps import is_pid_listen_port, get_child_pids
|
|
41
41
|
import mindsdb.interfaces.storage.db as db
|
|
42
|
-
from mindsdb.utilities.fs import clean_process_marks, clean_unlinked_process_marks
|
|
42
|
+
from mindsdb.utilities.fs import clean_process_marks, clean_unlinked_process_marks, create_pid_file, delete_pid_file
|
|
43
43
|
from mindsdb.utilities.context import context as ctx
|
|
44
44
|
from mindsdb.utilities.auth import register_oauth_client, get_aws_meta_data
|
|
45
45
|
from mindsdb.utilities.sentry import sentry_sdk # noqa: F401
|
|
@@ -134,6 +134,9 @@ class TrunkProcessData:
|
|
|
134
134
|
|
|
135
135
|
def close_api_gracefully(trunc_processes_struct):
|
|
136
136
|
_stop_event.set()
|
|
137
|
+
|
|
138
|
+
delete_pid_file()
|
|
139
|
+
|
|
137
140
|
try:
|
|
138
141
|
for trunc_processes_data in trunc_processes_struct.values():
|
|
139
142
|
process = trunc_processes_data.process
|
|
@@ -335,6 +338,13 @@ if __name__ == "__main__":
|
|
|
335
338
|
print(f"MindsDB {mindsdb_version}")
|
|
336
339
|
sys.exit(0)
|
|
337
340
|
|
|
341
|
+
if config.cmd_args.update_gui:
|
|
342
|
+
from mindsdb.api.http.initialize import initialize_static
|
|
343
|
+
|
|
344
|
+
logger.info("Updating the GUI version")
|
|
345
|
+
initialize_static()
|
|
346
|
+
sys.exit(0)
|
|
347
|
+
|
|
338
348
|
config.raise_warnings(logger=logger)
|
|
339
349
|
os.environ["MINDSDB_RUNTIME"] = "1"
|
|
340
350
|
|
|
@@ -510,6 +520,8 @@ if __name__ == "__main__":
|
|
|
510
520
|
if config.cmd_args.ml_task_queue_consumer is True:
|
|
511
521
|
trunc_processes_struct[TrunkProcessEnum.ML_TASK_QUEUE].need_to_run = True
|
|
512
522
|
|
|
523
|
+
create_pid_file()
|
|
524
|
+
|
|
513
525
|
for trunc_process_data in trunc_processes_struct.values():
|
|
514
526
|
if trunc_process_data.started is True or trunc_process_data.need_to_run is False:
|
|
515
527
|
continue
|
mindsdb/api/a2a/agent.py
CHANGED
|
@@ -3,7 +3,7 @@ from typing import Any, AsyncIterable, Dict, List
|
|
|
3
3
|
import requests
|
|
4
4
|
import logging
|
|
5
5
|
import httpx
|
|
6
|
-
from mindsdb.api.a2a.utils import to_serializable
|
|
6
|
+
from mindsdb.api.a2a.utils import to_serializable, convert_a2a_message_to_qa_format
|
|
7
7
|
from mindsdb.api.a2a.constants import DEFAULT_STREAM_TIMEOUT
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger(__name__)
|
|
@@ -117,22 +117,12 @@ class MindsDBAgent:
|
|
|
117
117
|
"""Stream responses from the MindsDB agent (uses streaming API endpoint)."""
|
|
118
118
|
try:
|
|
119
119
|
logger.info(f"Using streaming API for query: {query[:100]}...")
|
|
120
|
-
|
|
120
|
+
# Create A2A message structure with history and current query
|
|
121
|
+
a2a_message = {"role": "user", "parts": [{"text": query}]}
|
|
121
122
|
if history:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
text = ""
|
|
126
|
-
for part in msg_dict.get("parts", []):
|
|
127
|
-
if part.get("type") == "text":
|
|
128
|
-
text = part.get("text", "")
|
|
129
|
-
break
|
|
130
|
-
if text:
|
|
131
|
-
if role == "user":
|
|
132
|
-
formatted_messages.append({"question": text, "answer": None})
|
|
133
|
-
elif role == "assistant" and formatted_messages:
|
|
134
|
-
formatted_messages[-1]["answer"] = text
|
|
135
|
-
formatted_messages.append({"question": query, "answer": None})
|
|
123
|
+
a2a_message["history"] = history
|
|
124
|
+
# Convert to Q&A format using centralized utility
|
|
125
|
+
formatted_messages = convert_a2a_message_to_qa_format(a2a_message)
|
|
136
126
|
logger.debug(f"Formatted messages for agent: {formatted_messages}")
|
|
137
127
|
streaming_response = self.streaming_invoke(formatted_messages, timeout=timeout)
|
|
138
128
|
async for chunk in streaming_response:
|
mindsdb/api/a2a/common/types.py
CHANGED
|
@@ -35,9 +35,7 @@ class FileContent(BaseModel):
|
|
|
35
35
|
if not (self.bytes or self.uri):
|
|
36
36
|
raise ValueError("Either 'bytes' or 'uri' must be present in the file data")
|
|
37
37
|
if self.bytes and self.uri:
|
|
38
|
-
raise ValueError(
|
|
39
|
-
"Only one of 'bytes' or 'uri' can be present in the file data"
|
|
40
|
-
)
|
|
38
|
+
raise ValueError("Only one of 'bytes' or 'uri' can be present in the file data")
|
|
41
39
|
return self
|
|
42
40
|
|
|
43
41
|
|
|
@@ -57,9 +55,10 @@ Part = Annotated[Union[TextPart, FilePart, DataPart], Field(discriminator="type"
|
|
|
57
55
|
|
|
58
56
|
|
|
59
57
|
class Message(BaseModel):
|
|
60
|
-
role: Literal["user", "agent"]
|
|
58
|
+
role: Literal["user", "agent", "assistant"]
|
|
61
59
|
parts: List[Part]
|
|
62
60
|
metadata: dict[str, Any] | None = None
|
|
61
|
+
history: Optional[List["Message"]] = None
|
|
63
62
|
|
|
64
63
|
|
|
65
64
|
class TaskStatus(BaseModel):
|
mindsdb/api/a2a/task_manager.py
CHANGED
|
@@ -18,7 +18,7 @@ from mindsdb.api.a2a.common.types import (
|
|
|
18
18
|
)
|
|
19
19
|
from mindsdb.api.a2a.common.server.task_manager import InMemoryTaskManager
|
|
20
20
|
from mindsdb.api.a2a.agent import MindsDBAgent
|
|
21
|
-
from mindsdb.api.a2a.utils import to_serializable
|
|
21
|
+
from mindsdb.api.a2a.utils import to_serializable, convert_a2a_message_to_qa_format
|
|
22
22
|
|
|
23
23
|
from typing import Union
|
|
24
24
|
import logging
|
|
@@ -94,22 +94,8 @@ class AgentTaskManager(InMemoryTaskManager):
|
|
|
94
94
|
|
|
95
95
|
agent = self._create_agent(agent_name)
|
|
96
96
|
|
|
97
|
-
# Get the history from the task
|
|
97
|
+
# Get the history from the task object (where it was properly extracted and stored)
|
|
98
98
|
history = task.history if task and task.history else []
|
|
99
|
-
logger.info(f"Using history with length {len(history)} for request")
|
|
100
|
-
|
|
101
|
-
# Log the history for debugging
|
|
102
|
-
logger.info(f"Conversation history for task {task_send_params.id}:")
|
|
103
|
-
for idx, msg in enumerate(history):
|
|
104
|
-
# Convert Message object to dict if needed
|
|
105
|
-
msg_dict = msg.dict() if hasattr(msg, "dict") else msg
|
|
106
|
-
role = msg_dict.get("role", "unknown")
|
|
107
|
-
text = ""
|
|
108
|
-
for part in msg_dict.get("parts", []):
|
|
109
|
-
if part.get("type") == "text":
|
|
110
|
-
text = part.get("text", "")
|
|
111
|
-
break
|
|
112
|
-
logger.info(f"Message {idx + 1} ({role}): {text[:100]}...")
|
|
113
99
|
|
|
114
100
|
if not streaming:
|
|
115
101
|
# If streaming is disabled, use invoke and return a single response
|
|
@@ -183,17 +169,16 @@ class AgentTaskManager(InMemoryTaskManager):
|
|
|
183
169
|
# If streaming is enabled (default), use the streaming implementation
|
|
184
170
|
try:
|
|
185
171
|
logger.debug(f"[TaskManager] Entering agent.stream() at {time.time()}")
|
|
186
|
-
#
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
)
|
|
196
|
-
async for item in agent.streaming_invoke(agent_messages, timeout=60):
|
|
172
|
+
# Create A2A message structure and convert using centralized utility
|
|
173
|
+
a2a_message = task_send_params.message.model_dump()
|
|
174
|
+
if history:
|
|
175
|
+
a2a_message["history"] = [msg.model_dump() if hasattr(msg, "model_dump") else msg for msg in history]
|
|
176
|
+
|
|
177
|
+
# Convert to Q&A format using centralized utility function
|
|
178
|
+
all_messages = convert_a2a_message_to_qa_format(a2a_message)
|
|
179
|
+
|
|
180
|
+
logger.debug(f"Sending {len(all_messages)} total messages to streaming agent")
|
|
181
|
+
async for item in agent.streaming_invoke(all_messages, timeout=60):
|
|
197
182
|
# Clean up: Remove verbose debug logs, keep only errors and essential info
|
|
198
183
|
if isinstance(item, dict) and "artifact" in item and "parts" in item["artifact"]:
|
|
199
184
|
item["artifact"]["parts"] = [to_serializable(p) for p in item["artifact"]["parts"]]
|
|
@@ -235,19 +220,23 @@ class AgentTaskManager(InMemoryTaskManager):
|
|
|
235
220
|
message = task_send_params.message
|
|
236
221
|
message_dict = message.dict() if hasattr(message, "dict") else message
|
|
237
222
|
|
|
238
|
-
# Get history from request if available
|
|
223
|
+
# Get history from request if available - check both locations
|
|
239
224
|
history = []
|
|
225
|
+
|
|
226
|
+
# First check if history is at top level (task_send_params.history)
|
|
240
227
|
if hasattr(task_send_params, "history") and task_send_params.history:
|
|
241
|
-
# Convert each history item to dict if needed
|
|
228
|
+
# Convert each history item to dict if needed
|
|
242
229
|
for item in task_send_params.history:
|
|
243
|
-
item_dict = item.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
230
|
+
item_dict = item.model_dump() if hasattr(item, "model_dump") else item
|
|
231
|
+
history.append(item_dict)
|
|
232
|
+
# Also check if history is nested under message (message.history)
|
|
233
|
+
elif hasattr(task_send_params.message, "history") and task_send_params.message.history:
|
|
234
|
+
for item in task_send_params.message.history:
|
|
235
|
+
item_dict = item.model_dump() if hasattr(item, "model_dump") else item
|
|
247
236
|
history.append(item_dict)
|
|
248
237
|
|
|
249
|
-
#
|
|
250
|
-
|
|
238
|
+
# DO NOT add current message to history - it should be processed separately
|
|
239
|
+
# The current message will be extracted during streaming from task_send_params.message
|
|
251
240
|
|
|
252
241
|
# Create a new task
|
|
253
242
|
task = Task(
|
mindsdb/api/a2a/utils.py
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
from mindsdb.utilities.log import getLogger
|
|
3
|
+
|
|
4
|
+
logger = getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
1
7
|
def to_serializable(obj):
|
|
2
8
|
# Primitives
|
|
3
9
|
if isinstance(obj, (str, int, float, bool, type(None))):
|
|
@@ -19,3 +25,60 @@ def to_serializable(obj):
|
|
|
19
25
|
return [to_serializable(v) for v in obj]
|
|
20
26
|
# Fallback: string
|
|
21
27
|
return str(obj)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def convert_a2a_message_to_qa_format(a2a_message: Dict) -> List[Dict[str, str]]:
|
|
31
|
+
"""
|
|
32
|
+
Convert A2A message format to question/answer format.
|
|
33
|
+
|
|
34
|
+
This is the format that the langchain agent expects and ensure effective multi-turn conversation
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
a2a_message: A2A message containing history and current message parts
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
List of messages in question/answer format
|
|
41
|
+
"""
|
|
42
|
+
converted_messages = []
|
|
43
|
+
|
|
44
|
+
# Process conversation history first
|
|
45
|
+
if "history" in a2a_message:
|
|
46
|
+
for hist_msg in a2a_message["history"]:
|
|
47
|
+
if hist_msg.get("role") == "user":
|
|
48
|
+
# Extract text from parts
|
|
49
|
+
text = ""
|
|
50
|
+
for part in hist_msg.get("parts", []):
|
|
51
|
+
if part.get("type") == "text":
|
|
52
|
+
text = part.get("text", "")
|
|
53
|
+
break
|
|
54
|
+
# Create question with empty answer initially
|
|
55
|
+
converted_messages.append({"question": text, "answer": ""})
|
|
56
|
+
elif hist_msg.get("role") in ["agent", "assistant"]:
|
|
57
|
+
# Extract text from parts
|
|
58
|
+
text = ""
|
|
59
|
+
for part in hist_msg.get("parts", []):
|
|
60
|
+
if part.get("type") == "text":
|
|
61
|
+
text = part.get("text", "")
|
|
62
|
+
break
|
|
63
|
+
# Pair with the most recent question that has empty answer
|
|
64
|
+
paired = False
|
|
65
|
+
for i in range(len(converted_messages) - 1, -1, -1):
|
|
66
|
+
if converted_messages[i].get("answer") == "":
|
|
67
|
+
converted_messages[i]["answer"] = text
|
|
68
|
+
paired = True
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
if not paired:
|
|
72
|
+
logger.warning("Could not pair agent response with question (no empty answer found)")
|
|
73
|
+
|
|
74
|
+
logger.debug(f"Converted {len(a2a_message['history'])} A2A history messages to Q&A format")
|
|
75
|
+
|
|
76
|
+
# Add current message as final question with empty answer
|
|
77
|
+
current_text = ""
|
|
78
|
+
for part in a2a_message.get("parts", []):
|
|
79
|
+
if part.get("type") == "text":
|
|
80
|
+
current_text = part.get("text", "")
|
|
81
|
+
break
|
|
82
|
+
converted_messages.append({"question": current_text, "answer": ""})
|
|
83
|
+
|
|
84
|
+
return converted_messages
|
|
@@ -1195,11 +1195,17 @@ class ExecuteCommands:
|
|
|
1195
1195
|
msg = dedent(
|
|
1196
1196
|
f"""\
|
|
1197
1197
|
The '{handler_module_meta["name"]}' handler cannot be used. Reason is:
|
|
1198
|
-
{handler_module_meta["import"]["error_message"]}
|
|
1198
|
+
{handler_module_meta["import"]["error_message"] or msg}
|
|
1199
1199
|
"""
|
|
1200
1200
|
)
|
|
1201
1201
|
is_cloud = self.session.config.get("cloud", False)
|
|
1202
|
-
if
|
|
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
|
+
):
|
|
1203
1209
|
logger.info(get_handler_install_message(handler_module_meta["name"]))
|
|
1204
1210
|
ast_drop = DropMLEngine(name=Identifier(name))
|
|
1205
1211
|
self.answer_drop_ml_engine(ast_drop)
|
|
@@ -1342,24 +1348,12 @@ class ExecuteCommands:
|
|
|
1342
1348
|
from_table=NativeQuery(integration=statement.from_table, query=statement.query_str),
|
|
1343
1349
|
)
|
|
1344
1350
|
query_str = query.to_string()
|
|
1345
|
-
else:
|
|
1346
|
-
query = parse_sql(query_str)
|
|
1347
|
-
|
|
1348
|
-
if isinstance(query, Select):
|
|
1349
|
-
# check create view sql
|
|
1350
|
-
query.limit = Constant(1)
|
|
1351
|
-
|
|
1352
|
-
query_context_controller.set_context(query_context_controller.IGNORE_CONTEXT)
|
|
1353
|
-
try:
|
|
1354
|
-
SQLQuery(query, session=self.session, database=database_name)
|
|
1355
|
-
finally:
|
|
1356
|
-
query_context_controller.release_context(query_context_controller.IGNORE_CONTEXT)
|
|
1357
1351
|
|
|
1358
1352
|
project = self.session.database_controller.get_project(project_name)
|
|
1359
1353
|
|
|
1360
1354
|
if isinstance(statement, CreateView):
|
|
1361
1355
|
try:
|
|
1362
|
-
project.create_view(view_name, query=query_str)
|
|
1356
|
+
project.create_view(view_name, query=query_str, session=self.session)
|
|
1363
1357
|
except EntityExistsError:
|
|
1364
1358
|
if getattr(statement, "if_not_exists", False) is False:
|
|
1365
1359
|
raise
|
|
@@ -50,29 +50,26 @@ def get_table_alias(table_obj, default_db_name):
|
|
|
50
50
|
|
|
51
51
|
def get_fill_param_fnc(steps_data):
|
|
52
52
|
def fill_params(node, callstack=None, **kwargs):
|
|
53
|
-
if isinstance(node, Parameter):
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
rs = steps_data[node.value.step_num]
|
|
74
|
-
items = [Constant(i) for i in rs.get_column_values(col_idx=0)]
|
|
75
|
-
return Tuple(items)
|
|
53
|
+
if not isinstance(node, Parameter):
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
rs = steps_data[node.value.step_num]
|
|
57
|
+
items = [Constant(i) for i in rs.get_column_values(col_idx=0)]
|
|
58
|
+
|
|
59
|
+
is_single_item = True
|
|
60
|
+
if callstack:
|
|
61
|
+
node_prev = callstack[0]
|
|
62
|
+
if isinstance(node_prev, BinaryOperation):
|
|
63
|
+
# Check case: 'something IN Parameter()'
|
|
64
|
+
if node_prev.op.lower() == "in" and node_prev.args[1] is node:
|
|
65
|
+
is_single_item = False
|
|
66
|
+
|
|
67
|
+
if is_single_item and len(items) == 1:
|
|
68
|
+
# extract one value for option 'col=(subselect)'
|
|
69
|
+
node = items[0]
|
|
70
|
+
else:
|
|
71
|
+
node = Tuple(items)
|
|
72
|
+
return node
|
|
76
73
|
|
|
77
74
|
return fill_params
|
|
78
75
|
|
|
@@ -115,7 +112,7 @@ class FetchDataframeStepCall(BaseStepCall):
|
|
|
115
112
|
|
|
116
113
|
# if query registered, set progress
|
|
117
114
|
if self.sql_query.run_query is not None:
|
|
118
|
-
self.sql_query.run_query.set_progress(df
|
|
115
|
+
self.sql_query.run_query.set_progress(processed_rows=len(df))
|
|
119
116
|
return ResultSet.from_df(
|
|
120
117
|
df,
|
|
121
118
|
table_name=table_alias[1],
|
|
@@ -97,6 +97,7 @@ class FetchDataframePartitionCall(BaseStepCall):
|
|
|
97
97
|
for df in run_query.get_partitions(self.dn, self, query):
|
|
98
98
|
try:
|
|
99
99
|
sub_data = self.exec_sub_steps(df)
|
|
100
|
+
run_query.set_progress(processed_rows=len(df))
|
|
100
101
|
results.append(sub_data)
|
|
101
102
|
except Exception as e:
|
|
102
103
|
if on_error == "skip":
|
|
@@ -175,17 +176,22 @@ class FetchDataframePartitionCall(BaseStepCall):
|
|
|
175
176
|
# split into chunks and send to workers
|
|
176
177
|
futures = []
|
|
177
178
|
for df2 in split_data_frame(df, partition_size):
|
|
178
|
-
futures.append(executor.submit(self.exec_sub_steps, df2))
|
|
179
|
+
futures.append([executor.submit(self.exec_sub_steps, df2), len(df2)])
|
|
179
180
|
|
|
180
|
-
|
|
181
|
+
error = None
|
|
182
|
+
for future, rows_count in futures:
|
|
181
183
|
try:
|
|
182
184
|
results.append(future.result())
|
|
185
|
+
run_query.set_progress(processed_rows=rows_count)
|
|
183
186
|
except Exception as e:
|
|
184
187
|
if on_error == "skip":
|
|
185
188
|
logger.error(e)
|
|
186
189
|
else:
|
|
187
190
|
executor.shutdown()
|
|
188
|
-
|
|
191
|
+
error = e
|
|
192
|
+
|
|
193
|
+
if error:
|
|
194
|
+
raise error
|
|
189
195
|
if self.sql_query.stop_event is not None and self.sql_query.stop_event.is_set():
|
|
190
196
|
executor.shutdown()
|
|
191
197
|
raise RuntimeError("Query is interrupted")
|
|
@@ -2,7 +2,15 @@ from collections import defaultdict
|
|
|
2
2
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
|
|
5
|
-
from mindsdb_sql_parser.ast import
|
|
5
|
+
from mindsdb_sql_parser.ast import (
|
|
6
|
+
Identifier,
|
|
7
|
+
Select,
|
|
8
|
+
Star,
|
|
9
|
+
Constant,
|
|
10
|
+
Function,
|
|
11
|
+
Variable,
|
|
12
|
+
BinaryOperation,
|
|
13
|
+
)
|
|
6
14
|
|
|
7
15
|
from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import SERVER_VARIABLES
|
|
8
16
|
from mindsdb.api.executor.planner.step_result import Result
|
|
@@ -52,13 +60,8 @@ class SubSelectStepCall(BaseStepCall):
|
|
|
52
60
|
|
|
53
61
|
# inject previous step values
|
|
54
62
|
if isinstance(query, Select):
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if isinstance(node, Parameter) and isinstance(node.value, Result):
|
|
58
|
-
prev_result = self.steps_data[node.value.step_num]
|
|
59
|
-
return Constant(prev_result.get_column_values(col_idx=0)[0])
|
|
60
|
-
|
|
61
|
-
query_traversal(query, inject_values)
|
|
63
|
+
fill_params = get_fill_param_fnc(self.steps_data)
|
|
64
|
+
query_traversal(query, fill_params)
|
|
62
65
|
|
|
63
66
|
df = result.to_df()
|
|
64
67
|
res = query_df(df, query, session=self.session)
|