MindsDB 25.8.3.0__py3-none-any.whl → 25.9.1.1__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 +3 -45
- mindsdb/api/a2a/__init__.py +52 -0
- mindsdb/api/a2a/agent.py +11 -12
- mindsdb/api/a2a/common/server/server.py +17 -36
- mindsdb/api/a2a/common/server/task_manager.py +14 -28
- mindsdb/api/a2a/task_manager.py +20 -21
- mindsdb/api/a2a/utils.py +1 -1
- mindsdb/api/common/middleware.py +106 -0
- mindsdb/api/executor/utilities/mysql_to_duckdb_functions.py +466 -18
- mindsdb/api/executor/utilities/sql.py +9 -31
- mindsdb/api/http/initialize.py +34 -43
- mindsdb/api/http/namespaces/auth.py +6 -14
- mindsdb/api/http/namespaces/config.py +0 -2
- mindsdb/api/http/namespaces/default.py +74 -106
- mindsdb/api/http/namespaces/file.py +9 -3
- mindsdb/api/http/namespaces/handlers.py +77 -87
- mindsdb/api/http/start.py +29 -47
- mindsdb/api/litellm/start.py +11 -10
- mindsdb/api/mcp/__init__.py +165 -0
- mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +33 -64
- mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +86 -85
- mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/crate_handler/crate_handler.py +3 -7
- mindsdb/integrations/handlers/derby_handler/derby_handler.py +32 -34
- mindsdb/integrations/handlers/documentdb_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/dummy_data_handler/dummy_data_handler.py +12 -13
- mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/google_books_handler/google_books_handler.py +45 -44
- mindsdb/integrations/handlers/google_calendar_handler/google_calendar_handler.py +101 -95
- mindsdb/integrations/handlers/google_content_shopping_handler/google_content_shopping_handler.py +129 -129
- mindsdb/integrations/handlers/google_fit_handler/google_fit_handler.py +59 -43
- mindsdb/integrations/handlers/google_search_handler/google_search_handler.py +38 -39
- mindsdb/integrations/handlers/informix_handler/informix_handler.py +5 -18
- mindsdb/integrations/handlers/lightfm_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
- mindsdb/integrations/handlers/maxdb_handler/maxdb_handler.py +22 -28
- mindsdb/integrations/handlers/monetdb_handler/monetdb_handler.py +3 -7
- mindsdb/integrations/handlers/mongodb_handler/mongodb_handler.py +53 -67
- mindsdb/integrations/handlers/mongodb_handler/requirements.txt +1 -0
- mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_ast.py +43 -68
- mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_parser.py +17 -25
- mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_query.py +10 -16
- mindsdb/integrations/handlers/mongodb_handler/utils/mongodb_render.py +43 -69
- mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
- mindsdb/integrations/libs/base.py +1 -1
- mindsdb/integrations/libs/llm/config.py +15 -0
- mindsdb/integrations/libs/llm/utils.py +15 -0
- mindsdb/interfaces/agents/constants.py +1 -0
- mindsdb/interfaces/agents/langchain_agent.py +4 -0
- mindsdb/interfaces/agents/providers.py +20 -0
- mindsdb/interfaces/knowledge_base/controller.py +25 -7
- mindsdb/utilities/config.py +15 -158
- mindsdb/utilities/log.py +0 -25
- mindsdb/utilities/render/sqlalchemy_render.py +7 -1
- mindsdb/utilities/starters.py +0 -39
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/METADATA +269 -267
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/RECORD +62 -105
- mindsdb/api/a2a/__main__.py +0 -144
- mindsdb/api/a2a/run_a2a.py +0 -86
- mindsdb/api/common/check_auth.py +0 -42
- mindsdb/api/http/gunicorn_wrapper.py +0 -17
- mindsdb/api/mcp/start.py +0 -205
- mindsdb/api/mongo/__init__.py +0 -0
- mindsdb/api/mongo/classes/__init__.py +0 -5
- mindsdb/api/mongo/classes/query_sql.py +0 -19
- mindsdb/api/mongo/classes/responder.py +0 -45
- mindsdb/api/mongo/classes/responder_collection.py +0 -34
- mindsdb/api/mongo/classes/scram.py +0 -86
- mindsdb/api/mongo/classes/session.py +0 -23
- mindsdb/api/mongo/functions/__init__.py +0 -19
- mindsdb/api/mongo/responders/__init__.py +0 -73
- mindsdb/api/mongo/responders/add_shard.py +0 -13
- mindsdb/api/mongo/responders/aggregate.py +0 -90
- mindsdb/api/mongo/responders/buildinfo.py +0 -17
- mindsdb/api/mongo/responders/coll_stats.py +0 -63
- mindsdb/api/mongo/responders/company_id.py +0 -25
- mindsdb/api/mongo/responders/connection_status.py +0 -22
- mindsdb/api/mongo/responders/count.py +0 -21
- mindsdb/api/mongo/responders/db_stats.py +0 -32
- mindsdb/api/mongo/responders/delete.py +0 -105
- mindsdb/api/mongo/responders/describe.py +0 -23
- mindsdb/api/mongo/responders/end_sessions.py +0 -13
- mindsdb/api/mongo/responders/find.py +0 -175
- mindsdb/api/mongo/responders/get_cmd_line_opts.py +0 -18
- mindsdb/api/mongo/responders/get_free_monitoring_status.py +0 -14
- mindsdb/api/mongo/responders/get_parameter.py +0 -23
- mindsdb/api/mongo/responders/getlog.py +0 -14
- mindsdb/api/mongo/responders/host_info.py +0 -28
- mindsdb/api/mongo/responders/insert.py +0 -270
- mindsdb/api/mongo/responders/is_master.py +0 -20
- mindsdb/api/mongo/responders/is_master_lower.py +0 -13
- mindsdb/api/mongo/responders/list_collections.py +0 -55
- mindsdb/api/mongo/responders/list_databases.py +0 -37
- mindsdb/api/mongo/responders/list_indexes.py +0 -22
- mindsdb/api/mongo/responders/ping.py +0 -13
- mindsdb/api/mongo/responders/recv_chunk_start.py +0 -13
- mindsdb/api/mongo/responders/replsetgetstatus.py +0 -13
- mindsdb/api/mongo/responders/sasl_continue.py +0 -34
- mindsdb/api/mongo/responders/sasl_start.py +0 -33
- mindsdb/api/mongo/responders/update_range_deletions.py +0 -12
- mindsdb/api/mongo/responders/whatsmyuri.py +0 -18
- mindsdb/api/mongo/server.py +0 -388
- mindsdb/api/mongo/start.py +0 -15
- mindsdb/api/mongo/utilities/__init__.py +0 -0
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/WHEEL +0 -0
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/top_level.txt +0 -0
|
@@ -10,13 +10,13 @@ from pymongo import MongoClient
|
|
|
10
10
|
from pymongo.errors import ServerSelectionTimeoutError, OperationFailure, ConfigurationError, InvalidURI
|
|
11
11
|
from typing import Text, List, Dict, Any, Union
|
|
12
12
|
|
|
13
|
-
from mindsdb.
|
|
14
|
-
from mindsdb.
|
|
13
|
+
from mindsdb.integrations.handlers.mongodb_handler.utils.mongodb_query import MongoQuery
|
|
14
|
+
from mindsdb.integrations.handlers.mongodb_handler.utils.mongodb_parser import MongodbParser
|
|
15
15
|
from mindsdb.integrations.libs.base import DatabaseHandler
|
|
16
16
|
from mindsdb.integrations.libs.response import (
|
|
17
17
|
HandlerStatusResponse as StatusResponse,
|
|
18
18
|
HandlerResponse as Response,
|
|
19
|
-
RESPONSE_TYPE
|
|
19
|
+
RESPONSE_TYPE,
|
|
20
20
|
)
|
|
21
21
|
from mindsdb.utilities import log
|
|
22
22
|
from .utils.mongodb_render import MongodbRender
|
|
@@ -31,7 +31,7 @@ class MongoDBHandler(DatabaseHandler):
|
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
33
|
_SUBSCRIBE_SLEEP_INTERVAL = 0.5
|
|
34
|
-
name =
|
|
34
|
+
name = "mongodb"
|
|
35
35
|
|
|
36
36
|
def __init__(self, name: Text, **kwargs: Any) -> None:
|
|
37
37
|
"""
|
|
@@ -42,13 +42,13 @@ class MongoDBHandler(DatabaseHandler):
|
|
|
42
42
|
kwargs: Arbitrary keyword arguments including the connection data.
|
|
43
43
|
"""
|
|
44
44
|
super().__init__(name)
|
|
45
|
-
connection_data = kwargs[
|
|
45
|
+
connection_data = kwargs["connection_data"]
|
|
46
46
|
self.host = connection_data.get("host")
|
|
47
47
|
self.port = int(connection_data.get("port") or 27017)
|
|
48
48
|
self.user = connection_data.get("username")
|
|
49
49
|
self.password = connection_data.get("password")
|
|
50
|
-
self.database = connection_data.get(
|
|
51
|
-
self.flatten_level = connection_data.get(
|
|
50
|
+
self.database = connection_data.get("database")
|
|
51
|
+
self.flatten_level = connection_data.get("flatten_level", 0)
|
|
52
52
|
|
|
53
53
|
self.connection = None
|
|
54
54
|
self.is_connected = False
|
|
@@ -72,31 +72,27 @@ class MongoDBHandler(DatabaseHandler):
|
|
|
72
72
|
"""
|
|
73
73
|
kwargs = {}
|
|
74
74
|
if isinstance(self.user, str) and len(self.user) > 0:
|
|
75
|
-
kwargs[
|
|
75
|
+
kwargs["username"] = self.user
|
|
76
76
|
|
|
77
77
|
if isinstance(self.password, str) and len(self.password) > 0:
|
|
78
|
-
kwargs[
|
|
78
|
+
kwargs["password"] = self.password
|
|
79
79
|
|
|
80
|
-
if re.match(r
|
|
81
|
-
kwargs[
|
|
80
|
+
if re.match(r"/?.*tls=true", self.host.lower()):
|
|
81
|
+
kwargs["tls"] = True
|
|
82
82
|
|
|
83
|
-
if re.match(r
|
|
84
|
-
kwargs[
|
|
83
|
+
if re.match(r"/?.*tls=false", self.host.lower()):
|
|
84
|
+
kwargs["tls"] = False
|
|
85
85
|
|
|
86
86
|
try:
|
|
87
|
-
connection = MongoClient(
|
|
88
|
-
self.host,
|
|
89
|
-
port=self.port,
|
|
90
|
-
**kwargs
|
|
91
|
-
)
|
|
87
|
+
connection = MongoClient(self.host, port=self.port, **kwargs)
|
|
92
88
|
except InvalidURI as invalid_uri_error:
|
|
93
|
-
logger.error(f
|
|
89
|
+
logger.error(f"Invalid URI provided for MongoDB connection: {invalid_uri_error}!")
|
|
94
90
|
raise
|
|
95
91
|
except ConfigurationError as config_error:
|
|
96
|
-
logger.error(f
|
|
92
|
+
logger.error(f"Configuration error connecting to MongoDB: {config_error}!")
|
|
97
93
|
raise
|
|
98
94
|
except Exception as unknown_error:
|
|
99
|
-
logger.error(f
|
|
95
|
+
logger.error(f"Unknown error connecting to MongoDB: {unknown_error}!")
|
|
100
96
|
raise
|
|
101
97
|
|
|
102
98
|
# Get the database name from the connection if it's not provided.
|
|
@@ -107,7 +103,9 @@ class MongoDBHandler(DatabaseHandler):
|
|
|
107
103
|
self.connection = connection
|
|
108
104
|
return self.connection
|
|
109
105
|
|
|
110
|
-
def subscribe(
|
|
106
|
+
def subscribe(
|
|
107
|
+
self, stop_event: threading.Event, callback: callable, table_name: Text, columns: List = None, **kwargs: Any
|
|
108
|
+
) -> None:
|
|
111
109
|
"""
|
|
112
110
|
Subscribes to changes in a MongoDB collection and calls the provided callback function when changes occur.
|
|
113
111
|
|
|
@@ -131,26 +129,26 @@ class MongoDBHandler(DatabaseHandler):
|
|
|
131
129
|
time.sleep(self._SUBSCRIBE_SLEEP_INTERVAL)
|
|
132
130
|
continue
|
|
133
131
|
|
|
134
|
-
_id = res[
|
|
135
|
-
if res[
|
|
132
|
+
_id = res["documentKey"]["_id"]
|
|
133
|
+
if res["operationType"] == "insert":
|
|
136
134
|
if columns is not None:
|
|
137
|
-
updated_columns = set(res[
|
|
135
|
+
updated_columns = set(res["fullDocument"].keys())
|
|
138
136
|
if not set(columns) & set(updated_columns):
|
|
139
137
|
# Do nothing.
|
|
140
138
|
continue
|
|
141
139
|
|
|
142
|
-
callback(row=res[
|
|
140
|
+
callback(row=res["fullDocument"], key={"_id": _id})
|
|
143
141
|
|
|
144
|
-
if res[
|
|
142
|
+
if res["operationType"] == "update":
|
|
145
143
|
if columns is not None:
|
|
146
|
-
updated_columns = set(res[
|
|
144
|
+
updated_columns = set(res["updateDescription"]["updatedFields"].keys())
|
|
147
145
|
if not set(columns) & set(updated_columns):
|
|
148
146
|
# Do nothing.
|
|
149
147
|
continue
|
|
150
148
|
|
|
151
149
|
# Get the full document.
|
|
152
|
-
full_doc = con[self.database][table_name].find_one(res[
|
|
153
|
-
callback(row=full_doc, key={
|
|
150
|
+
full_doc = con[self.database][table_name].find_one(res["documentKey"])
|
|
151
|
+
callback(row=full_doc, key={"_id": _id})
|
|
154
152
|
|
|
155
153
|
def disconnect(self) -> None:
|
|
156
154
|
"""
|
|
@@ -178,14 +176,20 @@ class MongoDBHandler(DatabaseHandler):
|
|
|
178
176
|
|
|
179
177
|
# Check if the database exists.
|
|
180
178
|
if self.database not in con.list_database_names():
|
|
181
|
-
raise ValueError(f
|
|
179
|
+
raise ValueError(f"Database {self.database} not found!")
|
|
182
180
|
|
|
183
181
|
response.success = True
|
|
184
|
-
except (
|
|
185
|
-
|
|
182
|
+
except (
|
|
183
|
+
InvalidURI,
|
|
184
|
+
ServerSelectionTimeoutError,
|
|
185
|
+
OperationFailure,
|
|
186
|
+
ConfigurationError,
|
|
187
|
+
ValueError,
|
|
188
|
+
) as known_error:
|
|
189
|
+
logger.error(f"Error connecting to MongoDB {self.database}, {known_error}!")
|
|
186
190
|
response.error_message = str(known_error)
|
|
187
191
|
except Exception as unknown_error:
|
|
188
|
-
logger.error(f
|
|
192
|
+
logger.error(f"Unknown error connecting to MongoDB {self.database}, {unknown_error}!")
|
|
189
193
|
response.error_message = str(unknown_error)
|
|
190
194
|
|
|
191
195
|
if response.success and need_to_close:
|
|
@@ -211,13 +215,10 @@ class MongoDBHandler(DatabaseHandler):
|
|
|
211
215
|
|
|
212
216
|
if isinstance(query, dict):
|
|
213
217
|
# Fallback for the previous API.
|
|
214
|
-
mquery = MongoQuery(query[
|
|
218
|
+
mquery = MongoQuery(query["collection"])
|
|
215
219
|
|
|
216
|
-
for c in query[
|
|
217
|
-
mquery.add_step({
|
|
218
|
-
'method': c['method'],
|
|
219
|
-
'args': c['args']
|
|
220
|
-
})
|
|
220
|
+
for c in query["call"]:
|
|
221
|
+
mquery.add_step({"method": c["method"], "args": c["args"]})
|
|
221
222
|
|
|
222
223
|
query = mquery
|
|
223
224
|
|
|
@@ -229,16 +230,15 @@ class MongoDBHandler(DatabaseHandler):
|
|
|
229
230
|
# Check if the collection exists.
|
|
230
231
|
if collection not in con[database].list_collection_names():
|
|
231
232
|
return Response(
|
|
232
|
-
RESPONSE_TYPE.ERROR,
|
|
233
|
-
error_message=f'Collection {collection} not found in database {database}!'
|
|
233
|
+
RESPONSE_TYPE.ERROR, error_message=f"Collection {collection} not found in database {database}!"
|
|
234
234
|
)
|
|
235
235
|
|
|
236
236
|
try:
|
|
237
237
|
cursor = con[database][collection]
|
|
238
238
|
|
|
239
239
|
for step in query.pipeline:
|
|
240
|
-
fnc = getattr(cursor, step[
|
|
241
|
-
cursor = fnc(*step[
|
|
240
|
+
fnc = getattr(cursor, step["method"])
|
|
241
|
+
cursor = fnc(*step["args"])
|
|
242
242
|
|
|
243
243
|
result = []
|
|
244
244
|
if not isinstance(cursor, pymongo.results.UpdateResult):
|
|
@@ -254,16 +254,10 @@ class MongoDBHandler(DatabaseHandler):
|
|
|
254
254
|
columns = list(self.get_columns(collection).data_frame.Field)
|
|
255
255
|
df = pd.DataFrame([], columns=columns)
|
|
256
256
|
|
|
257
|
-
response = Response(
|
|
258
|
-
RESPONSE_TYPE.TABLE,
|
|
259
|
-
df
|
|
260
|
-
)
|
|
257
|
+
response = Response(RESPONSE_TYPE.TABLE, df)
|
|
261
258
|
except Exception as e:
|
|
262
|
-
logger.error(f
|
|
263
|
-
response = Response(
|
|
264
|
-
RESPONSE_TYPE.ERROR,
|
|
265
|
-
error_message=str(e)
|
|
266
|
-
)
|
|
259
|
+
logger.error(f"Error running query: {query} on {self.database}.{collection}!")
|
|
260
|
+
response = Response(RESPONSE_TYPE.ERROR, error_message=str(e))
|
|
267
261
|
|
|
268
262
|
return response
|
|
269
263
|
|
|
@@ -289,7 +283,7 @@ class MongoDBHandler(DatabaseHandler):
|
|
|
289
283
|
if level > 0:
|
|
290
284
|
if isinstance(v, dict):
|
|
291
285
|
for k2, v2 in self.flatten(v, level=level - 1).items():
|
|
292
|
-
add[f
|
|
286
|
+
add[f"{k}.{k2}"] = v2
|
|
293
287
|
del_keys.append(k)
|
|
294
288
|
|
|
295
289
|
if add:
|
|
@@ -324,15 +318,10 @@ class MongoDBHandler(DatabaseHandler):
|
|
|
324
318
|
"""
|
|
325
319
|
con = self.connect()
|
|
326
320
|
collections = con[self.database].list_collection_names()
|
|
327
|
-
collections_ar = [
|
|
328
|
-
|
|
329
|
-
]
|
|
330
|
-
df = pd.DataFrame(collections_ar, columns=['table_name'])
|
|
321
|
+
collections_ar = [[i] for i in collections]
|
|
322
|
+
df = pd.DataFrame(collections_ar, columns=["table_name"])
|
|
331
323
|
|
|
332
|
-
response = Response(
|
|
333
|
-
RESPONSE_TYPE.TABLE,
|
|
334
|
-
df
|
|
335
|
-
)
|
|
324
|
+
response = Response(RESPONSE_TYPE.TABLE, df)
|
|
336
325
|
|
|
337
326
|
return response
|
|
338
327
|
|
|
@@ -363,10 +352,7 @@ class MongoDBHandler(DatabaseHandler):
|
|
|
363
352
|
for k, v in record.items():
|
|
364
353
|
data.append([k, type(v).__name__])
|
|
365
354
|
|
|
366
|
-
df = pd.DataFrame(data, columns=[
|
|
355
|
+
df = pd.DataFrame(data, columns=["Field", "Type"])
|
|
367
356
|
|
|
368
|
-
response = Response(
|
|
369
|
-
RESPONSE_TYPE.TABLE,
|
|
370
|
-
df
|
|
371
|
-
)
|
|
357
|
+
response = Response(RESPONSE_TYPE.TABLE, df)
|
|
372
358
|
return response
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pymongo == 4.8.0
|
|
@@ -6,9 +6,8 @@ from mindsdb_sql_parser.ast import OrderBy, Identifier, Star, Select, Constant,
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class MongoToAst:
|
|
9
|
-
|
|
10
9
|
"""
|
|
11
|
-
|
|
10
|
+
Converts query mongo to AST format
|
|
12
11
|
"""
|
|
13
12
|
|
|
14
13
|
def from_mongoqeury(self, query):
|
|
@@ -19,24 +18,22 @@ class MongoToAst:
|
|
|
19
18
|
filter, projection = None, None
|
|
20
19
|
sort, limit, skip = None, None, None
|
|
21
20
|
for step in query.pipeline:
|
|
22
|
-
if step[
|
|
23
|
-
filter = step[
|
|
21
|
+
if step["method"] == "find":
|
|
22
|
+
filter = step["args"][0]
|
|
24
23
|
if len(step) > 1:
|
|
25
|
-
projection = step[
|
|
26
|
-
elif step[
|
|
27
|
-
sort = step[
|
|
28
|
-
elif step[
|
|
29
|
-
limit = step[
|
|
30
|
-
elif step[
|
|
31
|
-
skip = step[
|
|
32
|
-
|
|
33
|
-
return self.find(collection, filter=filter,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
filter=None, sort=None, projection=None,
|
|
39
|
-
limit=None, skip=None, **kwargs):
|
|
24
|
+
projection = step["args"][1]
|
|
25
|
+
elif step["method"] == "sort":
|
|
26
|
+
sort = step["args"][0]
|
|
27
|
+
elif step["method"] == "limit":
|
|
28
|
+
limit = step["args"][0]
|
|
29
|
+
elif step["method"] == "skip":
|
|
30
|
+
skip = step["args"][0]
|
|
31
|
+
|
|
32
|
+
return self.find(collection, filter=filter, sort=sort, projection=projection, limit=limit, skip=skip)
|
|
33
|
+
|
|
34
|
+
def find(
|
|
35
|
+
self, collection: t.Union[list, str], filter=None, sort=None, projection=None, limit=None, skip=None, **kwargs
|
|
36
|
+
):
|
|
40
37
|
# https://www.mongodb.com/docs/v4.2/reference/method/db.collection.find/
|
|
41
38
|
|
|
42
39
|
order_by = None
|
|
@@ -44,12 +41,7 @@ class MongoToAst:
|
|
|
44
41
|
# sort is dict
|
|
45
42
|
order_by = []
|
|
46
43
|
for col, direction in sort.items():
|
|
47
|
-
order_by.append(
|
|
48
|
-
OrderBy(
|
|
49
|
-
field=Identifier(parts=[col]),
|
|
50
|
-
direction='DESC' if direction == -1 else 'ASC'
|
|
51
|
-
)
|
|
52
|
-
)
|
|
44
|
+
order_by.append(OrderBy(field=Identifier(parts=[col]), direction="DESC" if direction == -1 else "ASC"))
|
|
53
45
|
|
|
54
46
|
if projection is not None:
|
|
55
47
|
targets = []
|
|
@@ -59,9 +51,7 @@ class MongoToAst:
|
|
|
59
51
|
alias = Identifier(parts=[alias])
|
|
60
52
|
else:
|
|
61
53
|
alias = None
|
|
62
|
-
targets.append(
|
|
63
|
-
Identifier(path_str=col, alias=alias)
|
|
64
|
-
)
|
|
54
|
+
targets.append(Identifier(path_str=col, alias=alias))
|
|
65
55
|
else:
|
|
66
56
|
targets = [Star()]
|
|
67
57
|
|
|
@@ -92,13 +82,13 @@ class MongoToAst:
|
|
|
92
82
|
|
|
93
83
|
def convert_filter(self, filter):
|
|
94
84
|
cond_ops = {
|
|
95
|
-
|
|
96
|
-
|
|
85
|
+
"$and": "and",
|
|
86
|
+
"$or": "or",
|
|
97
87
|
}
|
|
98
88
|
|
|
99
89
|
ast_filter = None
|
|
100
90
|
for k, v in filter.items():
|
|
101
|
-
if k in (
|
|
91
|
+
if k in ("$or", "$and"):
|
|
102
92
|
# suppose it is one key in dict
|
|
103
93
|
|
|
104
94
|
op = cond_ops[k]
|
|
@@ -116,7 +106,7 @@ class MongoToAst:
|
|
|
116
106
|
arg1 = BinaryOperation(op=op, args=[arg1, node])
|
|
117
107
|
|
|
118
108
|
return arg1
|
|
119
|
-
if k in (
|
|
109
|
+
if k in ("$where", "$expr"):
|
|
120
110
|
# try to parse simple expression like 'this.saledate > this.latest'
|
|
121
111
|
return MongoWhereParser(v).to_ast()
|
|
122
112
|
|
|
@@ -129,25 +119,12 @@ class MongoToAst:
|
|
|
129
119
|
if ast_filter is None:
|
|
130
120
|
ast_filter = ast_com
|
|
131
121
|
else:
|
|
132
|
-
ast_filter = BinaryOperation(op=
|
|
133
|
-
ast_filter,
|
|
134
|
-
ast_com
|
|
135
|
-
])
|
|
122
|
+
ast_filter = BinaryOperation(op="and", args=[ast_filter, ast_com])
|
|
136
123
|
return ast_filter
|
|
137
124
|
|
|
138
125
|
def handle_filter(self, value):
|
|
139
|
-
ops = {
|
|
140
|
-
|
|
141
|
-
'$gt': '>',
|
|
142
|
-
'$lt': '<',
|
|
143
|
-
'$le': '<=',
|
|
144
|
-
'$ne': '!=',
|
|
145
|
-
'$eq': '='
|
|
146
|
-
}
|
|
147
|
-
in_ops = {
|
|
148
|
-
'$in': 'in',
|
|
149
|
-
'$nin': 'not in'
|
|
150
|
-
}
|
|
126
|
+
ops = {"$ge": ">=", "$gt": ">", "$lt": "<", "$le": "<=", "$ne": "!=", "$eq": "="}
|
|
127
|
+
in_ops = {"$in": "in", "$nin": "not in"}
|
|
151
128
|
|
|
152
129
|
if isinstance(value, dict):
|
|
153
130
|
key, value = list(value.items())[0]
|
|
@@ -158,18 +135,18 @@ class MongoToAst:
|
|
|
158
135
|
if key in in_ops:
|
|
159
136
|
op = in_ops[key]
|
|
160
137
|
if not isinstance(value, list):
|
|
161
|
-
raise NotImplementedError(f
|
|
138
|
+
raise NotImplementedError(f"Unknown type {key}, {value}")
|
|
162
139
|
value = Tuple(value)
|
|
163
140
|
|
|
164
141
|
return op, value
|
|
165
142
|
|
|
166
|
-
raise NotImplementedError(f
|
|
143
|
+
raise NotImplementedError(f"Unknown type {key}")
|
|
167
144
|
|
|
168
145
|
elif isinstance(value, list):
|
|
169
|
-
raise NotImplementedError(f
|
|
146
|
+
raise NotImplementedError(f"Unknown filter {value}")
|
|
170
147
|
else:
|
|
171
148
|
# is simple type
|
|
172
|
-
op =
|
|
149
|
+
op = "="
|
|
173
150
|
value = value
|
|
174
151
|
return op, value
|
|
175
152
|
|
|
@@ -181,13 +158,12 @@ class MongoWhereParser:
|
|
|
181
158
|
def to_ast(self):
|
|
182
159
|
# parse as python string
|
|
183
160
|
# replace '=' with '=='
|
|
184
|
-
query = re.sub(r
|
|
161
|
+
query = re.sub(r"([^=><])=([^=])", r"\1==\2", self.query)
|
|
185
162
|
|
|
186
|
-
tree = py_ast.parse(query, mode=
|
|
163
|
+
tree = py_ast.parse(query, mode="eval")
|
|
187
164
|
return self.process(tree.body)
|
|
188
165
|
|
|
189
166
|
def process(self, node):
|
|
190
|
-
|
|
191
167
|
if isinstance(node, py_ast.BoolOp):
|
|
192
168
|
# is AND or OR
|
|
193
169
|
op = node.op.__class__.__name__
|
|
@@ -202,7 +178,7 @@ class MongoWhereParser:
|
|
|
202
178
|
if isinstance(node, py_ast.Compare):
|
|
203
179
|
# it is
|
|
204
180
|
if len(node.ops) != 1:
|
|
205
|
-
raise NotImplementedError(f
|
|
181
|
+
raise NotImplementedError(f"Multiple ops {node.ops}")
|
|
206
182
|
op = self.compare_op(node.ops[0])
|
|
207
183
|
arg1 = self.process(node.left)
|
|
208
184
|
arg2 = self.process(node.comparators[0])
|
|
@@ -210,7 +186,7 @@ class MongoWhereParser:
|
|
|
210
186
|
|
|
211
187
|
if isinstance(node, py_ast.Name):
|
|
212
188
|
# is special operator: latest, ...
|
|
213
|
-
if node.id ==
|
|
189
|
+
if node.id == "latest":
|
|
214
190
|
return Latest()
|
|
215
191
|
|
|
216
192
|
if isinstance(node, py_ast.Constant):
|
|
@@ -228,28 +204,27 @@ class MongoWhereParser:
|
|
|
228
204
|
|
|
229
205
|
if isinstance(node, py_ast.Attribute):
|
|
230
206
|
# is 'this.field' - is attribute
|
|
231
|
-
if node.value.id !=
|
|
232
|
-
raise NotImplementedError(f
|
|
207
|
+
if node.value.id != "this":
|
|
208
|
+
raise NotImplementedError(f"Unknown variable {node.value.id}")
|
|
233
209
|
return Identifier(parts=[node.attr])
|
|
234
210
|
|
|
235
|
-
raise NotImplementedError(f
|
|
211
|
+
raise NotImplementedError(f"Unknown node {node}")
|
|
236
212
|
|
|
237
213
|
def compare_op(self, op):
|
|
238
|
-
|
|
239
214
|
opname = op.__class__.__name__
|
|
240
215
|
|
|
241
216
|
# TODO: in, not
|
|
242
217
|
|
|
243
218
|
ops = {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
219
|
+
"Eq": "=",
|
|
220
|
+
"NotEq": "!=",
|
|
221
|
+
"Gt": ">",
|
|
222
|
+
"Lt": "<",
|
|
223
|
+
"GtE": ">=",
|
|
224
|
+
"LtE": "<=",
|
|
250
225
|
}
|
|
251
226
|
if opname not in ops:
|
|
252
|
-
raise NotImplementedError(f
|
|
227
|
+
raise NotImplementedError(f"Unknown $where op: {opname}")
|
|
253
228
|
return ops[opname]
|
|
254
229
|
|
|
255
230
|
@staticmethod
|
mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_parser.py
RENAMED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import ast as py_ast
|
|
3
2
|
|
|
4
3
|
import dateutil.parser
|
|
@@ -8,30 +7,27 @@ from .mongodb_query import MongoQuery
|
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class MongodbParser:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
"""
|
|
11
|
+
Converts string into MongoQuery
|
|
12
|
+
"""
|
|
14
13
|
|
|
15
14
|
def from_string(self, call_str):
|
|
16
|
-
tree = py_ast.parse(call_str.strip(), mode=
|
|
15
|
+
tree = py_ast.parse(call_str.strip(), mode="eval")
|
|
17
16
|
calls = self.process(tree.body)
|
|
18
17
|
# first call contents collection
|
|
19
|
-
method1 = calls[0][
|
|
18
|
+
method1 = calls[0]["method"]
|
|
20
19
|
if len(method1) < 2:
|
|
21
|
-
raise IndexError(
|
|
20
|
+
raise IndexError("Collection not found")
|
|
22
21
|
collection = method1[-2]
|
|
23
22
|
|
|
24
23
|
mquery = MongoQuery(collection)
|
|
25
24
|
|
|
26
25
|
# keep only last name
|
|
27
|
-
calls[0][
|
|
26
|
+
calls[0]["method"] = [method1[-1]]
|
|
28
27
|
|
|
29
28
|
# convert method names: get first item of list
|
|
30
29
|
for c in calls:
|
|
31
|
-
mquery.add_step({
|
|
32
|
-
'method': c['method'][0],
|
|
33
|
-
'args': c['args']
|
|
34
|
-
})
|
|
30
|
+
mquery.add_step({"method": c["method"][0], "args": c["args"]})
|
|
35
31
|
|
|
36
32
|
return mquery
|
|
37
33
|
|
|
@@ -49,20 +45,17 @@ class MongodbParser:
|
|
|
49
45
|
func = node.func.id
|
|
50
46
|
|
|
51
47
|
# special functions:
|
|
52
|
-
if func ==
|
|
48
|
+
if func == "ISODate":
|
|
53
49
|
return dateutil.parser.isoparse(args[0])
|
|
54
|
-
if func ==
|
|
50
|
+
if func == "ObjectId":
|
|
55
51
|
return ObjectId(args[0])
|
|
56
52
|
elif isinstance(node.func, py_ast.Attribute):
|
|
57
53
|
# it can be an attribute or pipeline
|
|
58
54
|
previous_call, func = self.process_func_name(node.func)
|
|
59
55
|
else:
|
|
60
|
-
raise NotImplementedError(f
|
|
56
|
+
raise NotImplementedError(f"Unknown function type: {node.func}")
|
|
61
57
|
|
|
62
|
-
call = [{
|
|
63
|
-
'method': func,
|
|
64
|
-
'args': args
|
|
65
|
-
}]
|
|
58
|
+
call = [{"method": func, "args": args}]
|
|
66
59
|
if previous_call is not None:
|
|
67
60
|
call = previous_call + call
|
|
68
61
|
|
|
@@ -75,7 +68,6 @@ class MongodbParser:
|
|
|
75
68
|
return elements
|
|
76
69
|
|
|
77
70
|
if isinstance(node, py_ast.Dict):
|
|
78
|
-
|
|
79
71
|
keys = []
|
|
80
72
|
for node2 in node.keys:
|
|
81
73
|
if isinstance(node2, py_ast.Constant):
|
|
@@ -85,7 +77,7 @@ class MongodbParser:
|
|
|
85
77
|
elif isinstance(node2, py_ast.Name):
|
|
86
78
|
value = node2.id
|
|
87
79
|
else:
|
|
88
|
-
raise NotImplementedError(f
|
|
80
|
+
raise NotImplementedError(f"Unknown dict key {node2}")
|
|
89
81
|
|
|
90
82
|
keys.append(value)
|
|
91
83
|
|
|
@@ -98,11 +90,11 @@ class MongodbParser:
|
|
|
98
90
|
if isinstance(node, py_ast.Name):
|
|
99
91
|
# special attributes
|
|
100
92
|
name = node.id
|
|
101
|
-
if name ==
|
|
93
|
+
if name == "true":
|
|
102
94
|
return True
|
|
103
|
-
elif name ==
|
|
95
|
+
elif name == "false":
|
|
104
96
|
return False
|
|
105
|
-
elif name ==
|
|
97
|
+
elif name == "null":
|
|
106
98
|
return None
|
|
107
99
|
|
|
108
100
|
if isinstance(node, py_ast.Constant):
|
|
@@ -122,7 +114,7 @@ class MongodbParser:
|
|
|
122
114
|
value = self.process(node.operand)
|
|
123
115
|
return -value
|
|
124
116
|
|
|
125
|
-
raise NotImplementedError(f
|
|
117
|
+
raise NotImplementedError(f"Unknown node {node}")
|
|
126
118
|
|
|
127
119
|
def process_func_name(self, node):
|
|
128
120
|
previous_call = None
|
mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_query.py
RENAMED
|
@@ -4,17 +4,15 @@ from bson import ObjectId
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class MongoJSONEncoder(json.JSONEncoder):
|
|
7
|
-
|
|
8
7
|
def default(self, obj):
|
|
9
8
|
if isinstance(obj, dt.datetime):
|
|
10
|
-
return f
|
|
9
|
+
return f"ISODate({obj.isoformat()})"
|
|
11
10
|
if isinstance(obj, ObjectId):
|
|
12
|
-
return f
|
|
11
|
+
return f"ObjectId({str(obj)})"
|
|
13
12
|
return super(MongoJSONEncoder, self).default(obj)
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
class MongoQuery:
|
|
17
|
-
|
|
18
16
|
def __init__(self, collection, pipline=None):
|
|
19
17
|
self.collection = collection
|
|
20
18
|
self.pipeline = []
|
|
@@ -30,10 +28,8 @@ class MongoQuery:
|
|
|
30
28
|
# 'args': [{c: 3}]
|
|
31
29
|
# }
|
|
32
30
|
|
|
33
|
-
if
|
|
34
|
-
|
|
35
|
-
or not isinstance(step['args'], list):
|
|
36
|
-
raise AttributeError(f'Wrong step {step}')
|
|
31
|
+
if "method" not in step or "args" not in step or not isinstance(step["args"], list):
|
|
32
|
+
raise AttributeError(f"Wrong step {step}")
|
|
37
33
|
|
|
38
34
|
self.pipeline.append(step)
|
|
39
35
|
|
|
@@ -43,10 +39,8 @@ class MongoQuery:
|
|
|
43
39
|
def __getattr__(self, item):
|
|
44
40
|
# return callback to save step of pipeline
|
|
45
41
|
def fnc(*args):
|
|
46
|
-
self.pipeline.append({
|
|
47
|
-
|
|
48
|
-
'args': args
|
|
49
|
-
})
|
|
42
|
+
self.pipeline.append({"method": item, "args": args})
|
|
43
|
+
|
|
50
44
|
return fnc
|
|
51
45
|
|
|
52
46
|
def __str__(self):
|
|
@@ -72,13 +66,13 @@ class MongoQuery:
|
|
|
72
66
|
"db_test.fish.find({a:1}, {b:2}).sort({c:3})"
|
|
73
67
|
"""
|
|
74
68
|
|
|
75
|
-
call_str = f
|
|
69
|
+
call_str = f"db.{self.collection}"
|
|
76
70
|
for step in self.pipeline:
|
|
77
71
|
args_str = []
|
|
78
|
-
for arg in step[
|
|
72
|
+
for arg in step["args"]:
|
|
79
73
|
args_str.append(MongoJSONEncoder().encode(arg))
|
|
80
|
-
call_str += f
|
|
74
|
+
call_str += f".{step['method']}({','.join(args_str)})"
|
|
81
75
|
return call_str
|
|
82
76
|
|
|
83
77
|
def __repr__(self):
|
|
84
|
-
return f
|
|
78
|
+
return f"MongoQuery({self.collection}, {str(self.pipeline)})"
|