MindsDB 25.1.3.0__py3-none-any.whl → 25.1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of MindsDB might be problematic. Click here for more details.

Files changed (55) hide show
  1. {MindsDB-25.1.3.0.dist-info → MindsDB-25.1.4.0.dist-info}/METADATA +254 -253
  2. {MindsDB-25.1.3.0.dist-info → MindsDB-25.1.4.0.dist-info}/RECORD +55 -52
  3. mindsdb/__about__.py +1 -1
  4. mindsdb/api/executor/__init__.py +0 -1
  5. mindsdb/api/executor/command_executor.py +2 -1
  6. mindsdb/api/executor/data_types/answer.py +1 -1
  7. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +7 -2
  8. mindsdb/api/executor/datahub/datanodes/project_datanode.py +8 -1
  9. mindsdb/api/executor/sql_query/__init__.py +1 -0
  10. mindsdb/api/executor/sql_query/steps/map_reduce_step.py +6 -39
  11. mindsdb/api/http/namespaces/knowledge_bases.py +3 -3
  12. mindsdb/api/http/namespaces/sql.py +3 -1
  13. mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +2 -1
  14. mindsdb/api/postgres/postgres_proxy/executor/executor.py +2 -1
  15. mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +2 -2
  16. mindsdb/integrations/handlers/chromadb_handler/requirements.txt +1 -1
  17. mindsdb/integrations/handlers/file_handler/tests/test_file_handler.py +17 -1
  18. mindsdb/integrations/handlers/jira_handler/jira_handler.py +15 -1
  19. mindsdb/integrations/handlers/jira_handler/jira_table.py +52 -31
  20. mindsdb/integrations/handlers/langchain_handler/requirements.txt +1 -1
  21. mindsdb/integrations/handlers/pinecone_handler/pinecone_handler.py +123 -72
  22. mindsdb/integrations/handlers/pinecone_handler/requirements.txt +1 -1
  23. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +9 -3
  24. mindsdb/integrations/handlers/slack_handler/slack_handler.py +13 -2
  25. mindsdb/integrations/handlers/slack_handler/slack_tables.py +21 -1
  26. mindsdb/integrations/libs/ml_handler_process/learn_process.py +1 -1
  27. mindsdb/integrations/utilities/rag/loaders/vector_store_loader/pgvector.py +1 -1
  28. mindsdb/integrations/utilities/rag/pipelines/rag.py +73 -18
  29. mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +166 -108
  30. mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +30 -12
  31. mindsdb/integrations/utilities/rag/settings.py +6 -2
  32. mindsdb/interfaces/agents/agents_controller.py +3 -5
  33. mindsdb/interfaces/agents/langchain_agent.py +112 -150
  34. mindsdb/interfaces/agents/langfuse_callback_handler.py +0 -37
  35. mindsdb/interfaces/agents/mindsdb_database_agent.py +15 -13
  36. mindsdb/interfaces/chatbot/chatbot_controller.py +7 -11
  37. mindsdb/interfaces/chatbot/chatbot_task.py +16 -5
  38. mindsdb/interfaces/chatbot/memory.py +58 -13
  39. mindsdb/interfaces/database/projects.py +17 -15
  40. mindsdb/interfaces/database/views.py +12 -25
  41. mindsdb/interfaces/knowledge_base/controller.py +6 -1
  42. mindsdb/interfaces/model/functions.py +15 -4
  43. mindsdb/interfaces/model/model_controller.py +4 -7
  44. mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +47 -38
  45. mindsdb/interfaces/skills/skill_tool.py +97 -53
  46. mindsdb/interfaces/skills/sql_agent.py +77 -36
  47. mindsdb/interfaces/storage/db.py +1 -1
  48. mindsdb/migrations/versions/2025-01-15_c06c35f7e8e1_project_company.py +88 -0
  49. mindsdb/utilities/context.py +2 -1
  50. mindsdb/utilities/langfuse.py +264 -0
  51. mindsdb/utilities/partitioning.py +52 -0
  52. mindsdb/utilities/render/sqlalchemy_render.py +7 -1
  53. {MindsDB-25.1.3.0.dist-info → MindsDB-25.1.4.0.dist-info}/LICENSE +0 -0
  54. {MindsDB-25.1.3.0.dist-info → MindsDB-25.1.4.0.dist-info}/WHEEL +0 -0
  55. {MindsDB-25.1.3.0.dist-info → MindsDB-25.1.4.0.dist-info}/top_level.txt +0 -0
@@ -55,9 +55,23 @@ class JiraHandler(APIHandler):
55
55
  return self.connection
56
56
 
57
57
  s = requests.Session()
58
+ if self.connection_data.get("cloud", False):
59
+ params = {
60
+ "cloud": True,
61
+ "username": self.connection_data['jira_username'],
62
+ "password": self.connection_data['jira_api_token'],
63
+ "url": self.connection_data['jira_url'],
64
+ }
65
+ else:
66
+ params = {
67
+ "cloud": False,
68
+ "url": self.connection_data['jira_url'],
69
+ "session": s
70
+ }
71
+
58
72
  s.headers['Authorization'] = f"Bearer {self.connection_data['jira_api_token']}"
59
73
 
60
- self.connection = Jira(url= self.connection_data['jira_url'], session=s)
74
+ self.connection = Jira(**params)
61
75
  self.is_connected = True
62
76
 
63
77
 
@@ -10,9 +10,26 @@ from mindsdb_sql_parser import ast
10
10
 
11
11
  logger = log.getLogger(__name__)
12
12
 
13
+
14
+ def flatten_json(nested_json, parent_key="", separator="."):
15
+ """
16
+ Recursively flattens a nested JSON object into a dictionary with dot notation keys.
17
+ """
18
+ items = []
19
+ for k, v in nested_json.items():
20
+ new_key = f"{parent_key}{separator}{k}" if parent_key else k
21
+ if isinstance(v, dict):
22
+ items.extend(flatten_json(v, new_key, separator=separator).items())
23
+ else:
24
+ items.append((new_key, v))
25
+ return dict(items)
26
+
27
+
13
28
  class JiraProjectsTable(APITable):
14
29
  """Jira Projects Table implementation"""
30
+
15
31
  _MAX_API_RESULTS = 100
32
+
16
33
  def select(self, query: ast.Select) -> pd.DataFrame:
17
34
  """Pulls data from the Jira "get_all_project_issues" API endpoint
18
35
  Parameters
@@ -42,8 +59,8 @@ class JiraProjectsTable(APITable):
42
59
 
43
60
  for an_order in query.order_by:
44
61
  if an_order.field.parts[0] != "key":
45
- continue
46
- if an_order.field.parts[1] in ["reporter","assignee","status"]:
62
+ continue
63
+ if an_order.field.parts[1] in ["reporter", "assignee", "status"]:
47
64
  if issues_kwargs != {}:
48
65
  raise ValueError(
49
66
  "Duplicate order conditions found for reporter,status and assignee"
@@ -61,9 +78,9 @@ class JiraProjectsTable(APITable):
61
78
  raise ValueError(
62
79
  f"Order by unknown column {an_order.field.parts[1]}"
63
80
  )
64
- project = self.handler.connection_data['project']
81
+ project = self.handler.connection_data["project"]
65
82
  jira_project_df = self.call_jira_api(project)
66
-
83
+
67
84
  selected_columns = []
68
85
  for target in query.targets:
69
86
  if isinstance(target, ast.Star):
@@ -74,7 +91,6 @@ class JiraProjectsTable(APITable):
74
91
  else:
75
92
  raise ValueError(f"Unknown query target {type(target)}")
76
93
 
77
-
78
94
  if len(jira_project_df) == 0:
79
95
  jira_project_df = pd.DataFrame([], columns=selected_columns)
80
96
  return jira_project_df
@@ -88,7 +104,7 @@ class JiraProjectsTable(APITable):
88
104
  by=order_by_conditions["columns"],
89
105
  ascending=order_by_conditions["ascending"],
90
106
  )
91
-
107
+
92
108
  if query.limit:
93
109
  jira_project_df = jira_project_df.head(total_results)
94
110
 
@@ -102,12 +118,12 @@ class JiraProjectsTable(APITable):
102
118
  List of columns
103
119
  """
104
120
  return [
105
- 'key',
106
- 'summary',
107
- 'status',
108
- 'reporter',
109
- 'assignee',
110
- 'priority',
121
+ "key",
122
+ "summary",
123
+ "status",
124
+ "reporter",
125
+ "assignee",
126
+ "priority",
111
127
  ]
112
128
 
113
129
  def call_jira_api(self, project):
@@ -116,36 +132,41 @@ class JiraProjectsTable(APITable):
116
132
  max_records = jira.get_project_issues_count(project)
117
133
  max_records = 100
118
134
  jql_query = self.handler.construct_jql()
119
- max_results = self._MAX_API_RESULTS
135
+ max_results = self._MAX_API_RESULTS
120
136
  start_index = 0
121
137
  total = 1
122
138
  fields = [
123
- 'key',
124
- 'fields.summary',
125
- 'fields.status.name',
126
- 'fields.reporter.name',
127
- 'fields.assignee.name',
128
- 'fields.priority.name',
139
+ "key",
140
+ "fields.summary",
141
+ "fields.status.name",
142
+ "fields.reporter.displayName",
143
+ "fields.assignee.displayName",
144
+ "fields.priority.name",
129
145
  ]
130
146
 
131
147
  all_jira_issues_df = pd.DataFrame(columns=fields)
132
148
 
133
149
  while start_index <= total:
134
- results = self.handler.connect().jql(jql_query,start=start_index, limit=max_results)
135
- df = pd.json_normalize(results['issues'])
150
+ results = self.handler.connect().jql(
151
+ jql_query, start=start_index, limit=max_results
152
+ )
153
+ flattened_data = [flatten_json(item) for item in results["issues"]]
154
+ df = pd.DataFrame(flattened_data)
136
155
  df = df[fields]
137
156
  start_index += max_results
138
- total = max_records
157
+ total = results["total"]
139
158
  all_jira_issues_df = pd.concat([all_jira_issues_df, df], axis=0)
140
159
 
160
+ all_jira_issues_df = all_jira_issues_df.rename(
161
+ columns={
162
+ "key": "key",
163
+ "fields.summary": "summary",
164
+ "fields.reporter.displayName": "reporter",
165
+ "fields.assignee.displayName": "assignee",
166
+ "fields.priority.name": "priority",
167
+ "fields.status.name": "status",
168
+ },
169
+ errors="ignore",
170
+ )
141
171
 
142
- all_jira_issues_df = all_jira_issues_df.rename(columns={
143
- 'key': 'key',
144
- 'fields.summary': 'summary',
145
- 'fields.reporter.name':'reporter',
146
- 'fields.assignee.name':'assignee',
147
- 'fields.priority.name':'priority',
148
- 'fields.status.name':'status'})
149
-
150
172
  return all_jira_issues_df
151
-
@@ -3,6 +3,6 @@ wikipedia==1.4.0
3
3
  tiktoken
4
4
  anthropic>=0.26.1
5
5
  litellm==1.44.8
6
- chromadb # Knowledge bases.
6
+ chromadb~=0.6.3 # Knowledge bases.
7
7
  -r mindsdb/integrations/handlers/openai_handler/requirements.txt
8
8
  -r mindsdb/integrations/handlers/langchain_embedding_handler/requirements.txt
@@ -1,8 +1,10 @@
1
+ import ast
1
2
  from typing import List, Optional
2
3
 
3
- import pinecone
4
+ import numpy as np
5
+ from pinecone import Pinecone, ServerlessSpec
6
+ from pinecone.core.openapi.shared.exceptions import NotFoundException, PineconeApiException
4
7
  import pandas as pd
5
- import ast
6
8
 
7
9
  from mindsdb.integrations.libs.response import RESPONSE_TYPE
8
10
  from mindsdb.integrations.libs.response import HandlerResponse
@@ -18,32 +20,30 @@ from mindsdb.utilities import log
18
20
 
19
21
  logger = log.getLogger(__name__)
20
22
 
23
+ DEFAULT_CREATE_TABLE_PARAMS = {
24
+ "dimension": 8,
25
+ "metric": "cosine",
26
+ "spec": {
27
+ "cloud": "aws",
28
+ "region": "us-east-1"
29
+ }
30
+ }
31
+ MAX_FETCH_LIMIT = 10000
32
+ UPSERT_BATCH_SIZE = 99 # API reccomendation
33
+
21
34
 
22
35
  class PineconeHandler(VectorStoreHandler):
23
36
  """This handler handles connection and execution of the Pinecone statements."""
24
37
 
25
38
  name = "pinecone"
26
39
 
27
- def __init__(self, name: str, **kwargs):
40
+ def __init__(self, name: str, connection_data: dict, **kwargs):
28
41
  super().__init__(name)
29
- self.MAX_FETCH_LIMIT = 10000
30
- self._connection_data = kwargs.get("connection_data")
31
- self._client_config = {
32
- "api_key": self._connection_data.get("api_key"),
33
- "environment": self._connection_data.get("environment")
34
- }
35
- self._table_create_params = {
36
- "dimension": 8,
37
- "metric": "cosine",
38
- "pods": 1,
39
- "replicas": 1,
40
- "pod_type": 'p1',
41
- }
42
- for key in self._table_create_params:
43
- if key in self._connection_data:
44
- self._table_create_params[key] = self._connection_data[key]
42
+ self.connection_data = connection_data
43
+ self.kwargs = kwargs
44
+
45
+ self.connection = None
45
46
  self.is_connected = False
46
- self.connect()
47
47
 
48
48
  def __del__(self):
49
49
  if self.is_connected is True:
@@ -51,7 +51,8 @@ class PineconeHandler(VectorStoreHandler):
51
51
 
52
52
  def _get_index_handle(self, index_name):
53
53
  """Returns handler to index specified by `index_name`"""
54
- index = pinecone.Index(index_name)
54
+ connection = self.connect()
55
+ index = connection.Index(index_name)
55
56
  try:
56
57
  index.describe_index_stats()
57
58
  except Exception:
@@ -135,10 +136,15 @@ class PineconeHandler(VectorStoreHandler):
135
136
 
136
137
  def connect(self):
137
138
  """Connect to a pinecone database."""
139
+ if self.is_connected is True:
140
+ return self.connection
141
+
142
+ if 'api_key' not in self.connection_data:
143
+ raise ValueError('Required parameter (api_key) must be provided.')
144
+
138
145
  try:
139
- pinecone.init(api_key=self._client_config["api_key"], environment=self._client_config["environment"])
140
- pinecone.list_indexes()
141
- self.is_connected = True
146
+ self.connection = Pinecone(api_key=self.connection_data['api_key'])
147
+ return self.connection
142
148
  except Exception as e:
143
149
  logger.error(f"Error connecting to Pinecone client, {e}!")
144
150
  self.is_connected = False
@@ -147,55 +153,99 @@ class PineconeHandler(VectorStoreHandler):
147
153
  """Close the pinecone connection."""
148
154
  if self.is_connected is False:
149
155
  return
150
- pinecone.init(api_key="", environment="")
156
+ self.connection = None
151
157
  self.is_connected = False
152
158
 
153
159
  def check_connection(self):
154
160
  """Check the connection to pinecone."""
155
- response_code = StatusResponse(False)
161
+ response = StatusResponse(False)
162
+ need_to_close = self.is_connected is False
163
+
156
164
  try:
157
- pinecone.list_indexes()
158
- response_code.success = True
165
+ connection = self.connect()
166
+ connection.list_indexes()
167
+ response.success = True
159
168
  except Exception as e:
160
169
  logger.error(f"Error connecting to pinecone , {e}!")
161
- response_code.error_message = str(e)
162
- return response_code
170
+ response.error_message = str(e)
171
+
172
+ if response.success is True and need_to_close:
173
+ self.disconnect()
174
+ if response.success is False and self.is_connected is True:
175
+ self.is_connected = False
176
+
177
+ return response
163
178
 
164
179
  def get_tables(self) -> HandlerResponse:
165
180
  """Get the list of indexes in the pinecone database."""
166
- indexes = pinecone.list_indexes()
167
- indexes_names = pd.DataFrame(
168
- columns=["index_name"],
169
- data=[index for index in indexes],
181
+ connection = self.connect()
182
+ indexes = connection.list_indexes()
183
+ df = pd.DataFrame(
184
+ columns=["table_name"],
185
+ data=[index['name'] for index in indexes],
170
186
  )
171
- return Response(resp_type=RESPONSE_TYPE.TABLE, data_frame=indexes_names)
187
+ return Response(resp_type=RESPONSE_TYPE.TABLE, data_frame=df)
172
188
 
173
189
  def create_table(self, table_name: str, if_not_exists=True):
174
190
  """Create an index with the given name in the Pinecone database."""
175
- pinecone.create_index(name=table_name, **self._table_create_params)
191
+ connection = self.connect()
192
+
193
+ # TODO: Should other parameters be supported? Pod indexes?
194
+ # TODO: Should there be a better way to provide these parameters rather than when establishing the connection?
195
+ create_table_params = {}
196
+ for key, val in DEFAULT_CREATE_TABLE_PARAMS.items():
197
+ if key in self.connection_data:
198
+ create_table_params[key] = self.connection_data[key]
199
+ else:
200
+ create_table_params[key] = val
201
+
202
+ create_table_params["spec"] = ServerlessSpec(**create_table_params["spec"])
203
+
204
+ try:
205
+ connection.create_index(name=table_name, **create_table_params)
206
+ except PineconeApiException as pinecone_error:
207
+ if pinecone_error.status == 409 and if_not_exists:
208
+ return
209
+ raise Exception(f"Error creating index '{table_name}': {pinecone_error}")
176
210
 
177
- def insert(self, table_name: str, data: pd.DataFrame, columns: List[str] = None):
211
+ def insert(self, table_name: str, data: pd.DataFrame):
178
212
  """Insert data into pinecone index passed in through `table_name` parameter."""
179
- upsert_batch_size = 99 # API reccomendation
180
213
  index = self._get_index_handle(table_name)
181
214
  if index is None:
182
215
  raise Exception(f"Error getting index '{table_name}', are you sure the name is correct?")
183
216
 
184
217
  data.rename(columns={
185
218
  TableField.ID.value: "id",
186
- TableField.EMBEDDINGS.value: "values",
187
- TableField.METADATA.value: "metadata"},
219
+ TableField.EMBEDDINGS.value: "values"},
188
220
  inplace=True)
189
- data = data[["id", "values", "metadata"]]
190
221
 
191
- for chunk in (data[pos:pos + upsert_batch_size] for pos in range(0, len(data), upsert_batch_size)):
222
+ columns = ["id", "values"]
223
+
224
+ if TableField.METADATA.value in data.columns:
225
+ data.rename(columns={TableField.METADATA.value: "metadata"}, inplace=True)
226
+ # fill None and NaN values with empty dict
227
+ if data['metadata'].isnull().any():
228
+ data['metadata'] = data['metadata'].apply(lambda x: {} if x is None or (isinstance(x, float) and np.isnan(x)) else x)
229
+ columns.append("metadata")
230
+
231
+ data = data[columns]
232
+
233
+ # convert the embeddings to lists if they are strings
234
+ data["values"] = data["values"].apply(lambda x: ast.literal_eval(x) if isinstance(x, str) else x)
235
+
236
+ for chunk in (data[pos:pos + UPSERT_BATCH_SIZE] for pos in range(0, len(data), UPSERT_BATCH_SIZE)):
192
237
  chunk = chunk.to_dict(orient="records")
193
238
  index.upsert(vectors=chunk)
194
239
 
195
240
  def drop_table(self, table_name: str, if_exists=True):
196
241
  """Delete an index passed in through `table_name` from the pinecone ."""
197
-
198
- pinecone.delete_index(table_name)
242
+ connection = self.connect()
243
+ try:
244
+ connection.delete_index(table_name)
245
+ except NotFoundException:
246
+ if if_exists:
247
+ return
248
+ raise Exception(f"Error deleting index '{table_name}', are you sure the name is correct?")
199
249
 
200
250
  def delete(self, table_name: str, conditions: List[FilterCondition] = None):
201
251
  """Delete records in pinecone index `table_name` based on ids or based on metadata conditions."""
@@ -225,6 +275,7 @@ class PineconeHandler(VectorStoreHandler):
225
275
  limit: int = None,
226
276
  ):
227
277
  """Run query on pinecone index named `table_name` and get results."""
278
+ # TODO: Add support for namespaces.
228
279
  index = self._get_index_handle(table_name)
229
280
  if index is None:
230
281
  raise Exception(f"Error getting index '{table_name}', are you sure the name is correct?")
@@ -233,23 +284,28 @@ class PineconeHandler(VectorStoreHandler):
233
284
  "include_values": True,
234
285
  "include_metadata": True
235
286
  }
287
+
236
288
  # check for metadata filter
237
289
  metadata_filters = self._translate_metadata_condition(conditions)
238
- # check for vector filter
239
- vector_filter = (
240
- None
241
- if conditions is None
242
- else [
243
- condition.value
244
- for condition in conditions
245
- if condition.column == TableField.SEARCH_VECTOR.value
246
- ]
247
- )
248
- if vector_filter:
249
- if len(vector_filter) > 1:
290
+ if metadata_filters is not None:
291
+ query["filter"] = metadata_filters
292
+
293
+ # check for vector and id filters
294
+ vector_filters = []
295
+ id_filters = []
296
+
297
+ if conditions:
298
+ for condition in conditions:
299
+ if condition.column == TableField.SEARCH_VECTOR.value:
300
+ vector_filters.append(condition.value)
301
+ elif condition.column == TableField.ID.value:
302
+ id_filters.append(condition.value)
303
+
304
+ if vector_filters:
305
+ if len(vector_filters) > 1:
250
306
  raise Exception("You cannot have multiple search_vectors in query")
251
307
 
252
- query["vector"] = vector_filter[0]
308
+ query["vector"] = vector_filters[0]
253
309
  # For subqueries, the vector filter is a list of list of strings
254
310
  if isinstance(query["vector"], list) and isinstance(query["vector"][0], str):
255
311
  if len(query["vector"]) > 1:
@@ -260,26 +316,21 @@ class PineconeHandler(VectorStoreHandler):
260
316
  except Exception as e:
261
317
  raise Exception(f"Cannot parse the search vector '{query['vector']}'into a list: {e}")
262
318
 
263
- # check for limit
264
- if limit is not None:
265
- query["top_k"] = limit
266
- else:
267
- query["top_k"] = self.MAX_FETCH_LIMIT
268
- if metadata_filters is not None:
269
- query["filter"] = metadata_filters
270
- # check for id filter
271
- id_filters = None
272
- if conditions is not None:
273
- id_filters = [
274
- condition.value
275
- for condition in conditions
276
- if condition.column == TableField.ID.value
277
- ] or None
278
319
  if id_filters:
279
320
  if len(id_filters) > 1:
280
321
  raise Exception("You cannot have multiple IDs in query")
281
322
 
282
323
  query["id"] = id_filters[0]
324
+
325
+ if not vector_filters and not id_filters:
326
+ raise Exception("You must provide either a search_vector or an ID in the query")
327
+
328
+ # check for limit
329
+ if limit is not None:
330
+ query["top_k"] = limit
331
+ else:
332
+ query["top_k"] = MAX_FETCH_LIMIT
333
+
283
334
  # exec query
284
335
  try:
285
336
  result = index.query(**query)
@@ -1 +1 @@
1
- pinecone-client
1
+ pinecone-client==5.0.1
@@ -1,5 +1,6 @@
1
1
  import time
2
2
  import json
3
+ from typing import Optional
3
4
 
4
5
  import pandas as pd
5
6
  import psycopg
@@ -281,21 +282,27 @@ class PostgresHandler(DatabaseHandler):
281
282
  """
282
283
  return self.native_query(query)
283
284
 
284
- def get_columns(self, table_name: str) -> Response:
285
+ def get_columns(self, table_name: str, schema_name: Optional[str] = None) -> Response:
285
286
  """
286
287
  Retrieves column details for a specified table in the PostgreSQL database.
287
288
 
288
289
  Args:
289
290
  table_name (str): The name of the table for which to retrieve column information.
291
+ schema_name (str): The name of the schema in which the table is located.
290
292
 
291
293
  Returns:
292
294
  Response: A response object containing the column details, formatted as per the `Response` class.
295
+
293
296
  Raises:
294
297
  ValueError: If the 'table_name' is not a valid string.
295
298
  """
296
299
 
297
300
  if not table_name or not isinstance(table_name, str):
298
301
  raise ValueError("Invalid table name provided.")
302
+ if isinstance(schema_name, str):
303
+ schema_name = f"'{schema_name}'"
304
+ else:
305
+ schema_name = 'current_schema()'
299
306
  query = f"""
300
307
  SELECT
301
308
  column_name as "Field",
@@ -305,12 +312,11 @@ class PostgresHandler(DatabaseHandler):
305
312
  WHERE
306
313
  table_name = '{table_name}'
307
314
  AND
308
- table_schema = current_schema()
315
+ table_schema = {schema_name}
309
316
  """
310
317
  return self.native_query(query)
311
318
 
312
319
  def subscribe(self, stop_event, callback, table_name, columns=None, **kwargs):
313
-
314
320
  config = self._make_connection_args()
315
321
  config['autocommit'] = True
316
322
 
@@ -231,6 +231,9 @@ class SlackHandler(APIChatHandler):
231
231
  'polling': {
232
232
  'type': 'realtime',
233
233
  },
234
+ 'memory': {
235
+ 'type': 'handler',
236
+ },
234
237
  'tables': [
235
238
  {
236
239
  'chat_table': {
@@ -238,7 +241,7 @@ class SlackHandler(APIChatHandler):
238
241
  'chat_id_col': 'channel_id',
239
242
  'username_col': 'user',
240
243
  'text_col': 'text',
241
- 'time_col': 'thread_ts',
244
+ 'time_col': 'created_at',
242
245
  }
243
246
  },
244
247
  {
@@ -264,7 +267,7 @@ class SlackHandler(APIChatHandler):
264
267
  user_info = web_connection.auth_test().data
265
268
  return user_info['bot_id']
266
269
 
267
- def subscribe(self, stop_event: threading.Event, callback: Callable, **kwargs: Any) -> None:
270
+ def subscribe(self, stop_event: threading.Event, callback: Callable, table_name: Text, columns: List = None, **kwargs: Any) -> None:
268
271
  """
269
272
  Subscribes to the Slack API using the Socket Mode for real-time responses to messages.
270
273
 
@@ -274,6 +277,14 @@ class SlackHandler(APIChatHandler):
274
277
  table_name (Text): The name of the table to subscribe to.
275
278
  kwargs: Arbitrary keyword arguments.
276
279
  """
280
+ if table_name not in ['messages', 'threads']:
281
+ raise RuntimeError(f'Table {table_name} is not supported for subscription.')
282
+
283
+ # Raise an error if columns are provided.
284
+ # Since Slack subscriptions depend on events and not changes to the virtual tables, columns are not supported.
285
+ if columns:
286
+ raise RuntimeError('Columns are not supported for Slack subscriptions.')
287
+
277
288
  self._socket_connection = SocketModeClient(
278
289
  # This app-level token will be used only for establishing a connection.
279
290
  app_token=self.connection_data['app_token'], # xapp-A111-222-xyz
@@ -6,7 +6,7 @@ import pandas as pd
6
6
  from slack_sdk.errors import SlackApiError
7
7
 
8
8
  from mindsdb.integrations.libs.api_handler import APIResource
9
- from mindsdb.integrations.utilities.sql_utils import extract_comparison_conditions, FilterCondition, FilterOperator
9
+ from mindsdb.integrations.utilities.sql_utils import extract_comparison_conditions, FilterCondition, FilterOperator, SortColumn
10
10
  from mindsdb.utilities import log
11
11
 
12
12
  logger = log.getLogger(__name__)
@@ -203,6 +203,7 @@ class SlackMessagesTable(APIResource):
203
203
  self,
204
204
  conditions: List[FilterCondition] = None,
205
205
  limit: int = None,
206
+ sort: List[SortColumn] = None,
206
207
  **kwargs: Any
207
208
  ) -> pd.DataFrame:
208
209
  """
@@ -222,6 +223,7 @@ class SlackMessagesTable(APIResource):
222
223
  Args:
223
224
  conditions (List[FilterCondition]): The conditions to filter the messages.
224
225
  limit (int): The limit of the messages to return.
226
+ sort (List[SortColumn]): The columns to sort the messages by.
225
227
  kwargs (Any): Arbitrary keyword arguments.
226
228
 
227
229
  Raises:
@@ -306,6 +308,14 @@ class SlackMessagesTable(APIResource):
306
308
  # Translate the time stamp into a 'created_at' field.
307
309
  result['created_at'] = pd.to_datetime(result['ts'].astype(float), unit='s').dt.strftime('%Y-%m-%d %H:%M:%S')
308
310
 
311
+ # Sort the messages by the specified columns.
312
+ if sort:
313
+ result.sort_values(
314
+ by=[col.column for col in sort],
315
+ ascending=[col.ascending for col in sort],
316
+ inplace=True
317
+ )
318
+
309
319
  return result
310
320
 
311
321
  def insert(self, query: Insert):
@@ -496,6 +506,7 @@ class SlackThreadsTable(APIResource):
496
506
  self,
497
507
  conditions: List[FilterCondition] = None,
498
508
  limit: int = None,
509
+ sort: List[SortColumn] = None,
499
510
  **kwargs: Any
500
511
  ) -> pd.DataFrame:
501
512
  """
@@ -514,6 +525,7 @@ class SlackThreadsTable(APIResource):
514
525
  Args:
515
526
  conditions (List[FilterCondition]): The conditions to filter the messages.
516
527
  limit (int): The limit of the messages to return.
528
+ sort (List[SortColumn]): The columns to sort the messages by.
517
529
  kwargs (Any): Arbitrary keyword arguments.
518
530
 
519
531
  Raises:
@@ -591,6 +603,14 @@ class SlackThreadsTable(APIResource):
591
603
  result['channel_id'] = params['channel']
592
604
  result['channel_name'] = channel['name'] if 'name' in channel else None
593
605
 
606
+ # Sort the messages by the specified columns.
607
+ if sort:
608
+ result.sort_values(
609
+ by=[col.column for col in sort],
610
+ ascending=[col.ascending for col in sort],
611
+ inplace=True
612
+ )
613
+
594
614
  return result
595
615
 
596
616
  def insert(self, query: Insert):
@@ -8,7 +8,7 @@ from sqlalchemy.orm.attributes import flag_modified
8
8
  from mindsdb_sql_parser import parse_sql
9
9
  from mindsdb_sql_parser.ast import Identifier, Select, Star, NativeQuery
10
10
 
11
- from mindsdb.api.executor import SQLQuery
11
+ from mindsdb.api.executor.sql_query import SQLQuery
12
12
  import mindsdb.utilities.profiler as profiler
13
13
  from mindsdb.utilities.functions import mark_process
14
14
  from mindsdb.utilities.config import Config
@@ -72,7 +72,7 @@ class PGVectorMDB(PGVector):
72
72
  # Use inner product for sparse vectors
73
73
  distance_op = "<#>"
74
74
  # For inner product, larger values are better matches
75
- order_direction = "DESC"
75
+ order_direction = "ASC"
76
76
  else:
77
77
  # Dense vectors: expect string in JSON array format or list of floats
78
78
  if isinstance(embedding, list):