MindsDB 25.8.3.0__py3-none-any.whl → 25.9.1.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 +2 -44
- 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/http/initialize.py +13 -15
- 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/start.py +25 -44
- 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/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/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/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/libs/base.py +1 -1
- mindsdb/interfaces/agents/constants.py +1 -0
- mindsdb/interfaces/knowledge_base/controller.py +3 -1
- mindsdb/utilities/config.py +3 -155
- mindsdb/utilities/log.py +0 -25
- mindsdb/utilities/starters.py +0 -39
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.0.dist-info}/METADATA +263 -261
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.0.dist-info}/RECORD +47 -91
- 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.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.0.dist-info}/top_level.txt +0 -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)})"
|
|
@@ -5,7 +5,7 @@ from bson.objectid import ObjectId
|
|
|
5
5
|
from mindsdb_sql_parser.ast import Select, Update, Identifier, Star, Constant, Tuple, BinaryOperation, Latest, TypeCast
|
|
6
6
|
from mindsdb_sql_parser.ast.base import ASTNode
|
|
7
7
|
|
|
8
|
-
from mindsdb.
|
|
8
|
+
from mindsdb.integrations.handlers.mongodb_handler.utils.mongodb_query import MongoQuery
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class MongodbRender:
|
|
@@ -27,7 +27,7 @@ class MongodbRender:
|
|
|
27
27
|
return self.select(node)
|
|
28
28
|
elif isinstance(node, Update):
|
|
29
29
|
return self.update(node)
|
|
30
|
-
raise NotImplementedError(f
|
|
30
|
+
raise NotImplementedError(f"Unknown statement: {node.__class__.__name__}")
|
|
31
31
|
|
|
32
32
|
def update(self, node: Update) -> MongoQuery:
|
|
33
33
|
"""
|
|
@@ -43,17 +43,8 @@ class MongodbRender:
|
|
|
43
43
|
mquery = MongoQuery(collection)
|
|
44
44
|
|
|
45
45
|
filters = self.handle_where(node.where)
|
|
46
|
-
row = {
|
|
47
|
-
|
|
48
|
-
for k, v in node.update_columns.items()
|
|
49
|
-
}
|
|
50
|
-
mquery.add_step({
|
|
51
|
-
'method': 'update_many',
|
|
52
|
-
'args': [
|
|
53
|
-
filters,
|
|
54
|
-
{"$set": row}
|
|
55
|
-
]
|
|
56
|
-
})
|
|
46
|
+
row = {k: v.value for k, v in node.update_columns.items()}
|
|
47
|
+
mquery.add_step({"method": "update_many", "args": [filters, {"$set": row}]})
|
|
57
48
|
return mquery
|
|
58
49
|
|
|
59
50
|
def select(self, node: Select):
|
|
@@ -67,7 +58,7 @@ class MongodbRender:
|
|
|
67
58
|
MongoQuery: The converted MongoQuery instance.
|
|
68
59
|
"""
|
|
69
60
|
if not isinstance(node.from_table, Identifier):
|
|
70
|
-
raise NotImplementedError(f
|
|
61
|
+
raise NotImplementedError(f"Not supported from {node.from_table}")
|
|
71
62
|
|
|
72
63
|
collection = node.from_table.parts[-1]
|
|
73
64
|
|
|
@@ -77,10 +68,10 @@ class MongodbRender:
|
|
|
77
68
|
filters = self.handle_where(node.where)
|
|
78
69
|
|
|
79
70
|
group = {}
|
|
80
|
-
project = {
|
|
71
|
+
project = {"_id": 0} # Hide _id field when it has not been explicitly requested.
|
|
81
72
|
if node.distinct:
|
|
82
73
|
# Group by distinct fields.
|
|
83
|
-
group = {
|
|
74
|
+
group = {"_id": {}}
|
|
84
75
|
|
|
85
76
|
if node.targets is not None:
|
|
86
77
|
for col in node.targets:
|
|
@@ -95,12 +86,12 @@ class MongodbRender:
|
|
|
95
86
|
else:
|
|
96
87
|
alias = col.alias.parts[-1]
|
|
97
88
|
|
|
98
|
-
project[alias] = f
|
|
89
|
+
project[alias] = f"${name}" # Project field.
|
|
99
90
|
|
|
100
91
|
# Group by distinct fields.
|
|
101
92
|
if node.distinct:
|
|
102
|
-
group[
|
|
103
|
-
group[name] = {
|
|
93
|
+
group["_id"][name] = f"${name}" # Group field.
|
|
94
|
+
group[name] = {"$first": f"${name}"} # Show field.
|
|
104
95
|
|
|
105
96
|
elif isinstance(col, Constant):
|
|
106
97
|
val = str(col.value) # Convert to string becuase it is interpreted as an index.
|
|
@@ -112,19 +103,19 @@ class MongodbRender:
|
|
|
112
103
|
|
|
113
104
|
if node.group_by is not None:
|
|
114
105
|
# TODO
|
|
115
|
-
raise NotImplementedError(f
|
|
106
|
+
raise NotImplementedError(f"Group {node.group_by}")
|
|
116
107
|
|
|
117
108
|
sort = {}
|
|
118
109
|
if node.order_by is not None:
|
|
119
110
|
for col in node.order_by:
|
|
120
111
|
name = col.field.parts[-1]
|
|
121
|
-
direction = 1 if col.direction.upper() ==
|
|
112
|
+
direction = 1 if col.direction.upper() == "ASC" else -1
|
|
122
113
|
sort[name] = direction
|
|
123
114
|
|
|
124
115
|
# Compose the MongoDB query.
|
|
125
116
|
mquery = MongoQuery(collection)
|
|
126
117
|
|
|
127
|
-
method =
|
|
118
|
+
method = "aggregate"
|
|
128
119
|
arg = []
|
|
129
120
|
|
|
130
121
|
# MongoDB related pipeline steps for the aggregate method.
|
|
@@ -150,10 +141,7 @@ class MongodbRender:
|
|
|
150
141
|
if node.limit is not None:
|
|
151
142
|
arg.append({"$limit": int(node.limit.value)})
|
|
152
143
|
|
|
153
|
-
mquery.add_step({
|
|
154
|
-
'method': method,
|
|
155
|
-
'args': [arg]
|
|
156
|
-
})
|
|
144
|
+
mquery.add_step({"method": method, "args": [arg]})
|
|
157
145
|
|
|
158
146
|
return mquery
|
|
159
147
|
|
|
@@ -168,34 +156,34 @@ class MongodbRender:
|
|
|
168
156
|
dict: The converted MongoDB query filters.
|
|
169
157
|
"""
|
|
170
158
|
# TODO: UnaryOperation, function.
|
|
171
|
-
if
|
|
172
|
-
raise NotImplementedError(f
|
|
159
|
+
if type(node) not in [BinaryOperation]:
|
|
160
|
+
raise NotImplementedError(f"Not supported type {type(node)}")
|
|
173
161
|
|
|
174
162
|
op = node.op.lower()
|
|
175
163
|
arg1, arg2 = node.args
|
|
176
164
|
|
|
177
|
-
if op in (
|
|
165
|
+
if op in ("and", "or"):
|
|
178
166
|
query1 = self.handle_where(arg1)
|
|
179
167
|
query2 = self.handle_where(arg2)
|
|
180
168
|
|
|
181
169
|
ops = {
|
|
182
|
-
|
|
183
|
-
|
|
170
|
+
"and": "$and",
|
|
171
|
+
"or": "$or",
|
|
184
172
|
}
|
|
185
173
|
query = {ops[op]: [query1, query2]}
|
|
186
174
|
return query
|
|
187
175
|
|
|
188
176
|
ops_map = {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
177
|
+
">=": "$gte",
|
|
178
|
+
">": "$gt",
|
|
179
|
+
"<": "$lt",
|
|
180
|
+
"<=": "$lte",
|
|
181
|
+
"<>": "$ne",
|
|
182
|
+
"!=": "$ne",
|
|
183
|
+
"=": "$eq",
|
|
184
|
+
"==": "$eq",
|
|
185
|
+
"is": "$eq",
|
|
186
|
+
"is not": "$ne",
|
|
199
187
|
}
|
|
200
188
|
|
|
201
189
|
if isinstance(arg1, Identifier):
|
|
@@ -203,35 +191,29 @@ class MongodbRender:
|
|
|
203
191
|
# Simple operation.
|
|
204
192
|
if isinstance(arg2, Constant):
|
|
205
193
|
# Identifier and Constant.
|
|
206
|
-
val = ObjectId(arg2.value) if var_name ==
|
|
207
|
-
if op in (
|
|
194
|
+
val = ObjectId(arg2.value) if var_name == "_id" else arg2.value
|
|
195
|
+
if op in ("=", "=="):
|
|
208
196
|
pass
|
|
209
197
|
elif op in ops_map:
|
|
210
198
|
op2 = ops_map[op]
|
|
211
199
|
val = {op2: val}
|
|
212
200
|
else:
|
|
213
|
-
raise NotImplementedError(f
|
|
201
|
+
raise NotImplementedError(f"Not supported operator {op}")
|
|
214
202
|
|
|
215
203
|
return {var_name: val}
|
|
216
204
|
|
|
217
205
|
# IN condition.
|
|
218
206
|
elif isinstance(arg2, Tuple):
|
|
219
207
|
# Should be IN, NOT IN.
|
|
220
|
-
ops = {
|
|
221
|
-
'in': '$in',
|
|
222
|
-
'not in': '$nin'
|
|
223
|
-
}
|
|
208
|
+
ops = {"in": "$in", "not in": "$nin"}
|
|
224
209
|
# Must be list of Constants.
|
|
225
|
-
values = [
|
|
226
|
-
i.value
|
|
227
|
-
for i in arg2.items
|
|
228
|
-
]
|
|
210
|
+
values = [i.value for i in arg2.items]
|
|
229
211
|
|
|
230
212
|
if op in ops:
|
|
231
213
|
op2 = ops[op]
|
|
232
214
|
cond = {op2: values}
|
|
233
215
|
else:
|
|
234
|
-
raise NotImplementedError(f
|
|
216
|
+
raise NotImplementedError(f"Not supported operator {op}")
|
|
235
217
|
|
|
236
218
|
return {var_name: cond}
|
|
237
219
|
|
|
@@ -242,13 +224,9 @@ class MongodbRender:
|
|
|
242
224
|
if op in ops_map:
|
|
243
225
|
op2 = ops_map[op]
|
|
244
226
|
else:
|
|
245
|
-
raise NotImplementedError(f
|
|
227
|
+
raise NotImplementedError(f"Not supported operator {op}")
|
|
246
228
|
|
|
247
|
-
return {
|
|
248
|
-
'$expr': {
|
|
249
|
-
op2: [val1, val2]
|
|
250
|
-
}
|
|
251
|
-
}
|
|
229
|
+
return {"$expr": {op2: [val1, val2]}}
|
|
252
230
|
|
|
253
231
|
def where_element_convert(self, node: Union[Identifier, Latest, Constant, TypeCast]) -> Any:
|
|
254
232
|
"""
|
|
@@ -265,22 +243,18 @@ class MongodbRender:
|
|
|
265
243
|
RuntimeError: If the date format is not supported.
|
|
266
244
|
"""
|
|
267
245
|
if isinstance(node, Identifier):
|
|
268
|
-
return f
|
|
246
|
+
return f"${node.parts[-1]}"
|
|
269
247
|
elif isinstance(node, Latest):
|
|
270
|
-
return
|
|
248
|
+
return "LATEST"
|
|
271
249
|
elif isinstance(node, Constant):
|
|
272
250
|
return node.value
|
|
273
|
-
elif isinstance(node, TypeCast)
|
|
274
|
-
|
|
275
|
-
formats = [
|
|
276
|
-
"%Y-%m-%d",
|
|
277
|
-
"%Y-%m-%dT%H:%M:%S.%f"
|
|
278
|
-
]
|
|
251
|
+
elif isinstance(node, TypeCast) and node.type_name.upper() in ("DATE", "DATETIME"):
|
|
252
|
+
formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S.%f"]
|
|
279
253
|
for format in formats:
|
|
280
254
|
try:
|
|
281
255
|
return dt.datetime.strptime(node.arg.value, format)
|
|
282
256
|
except ValueError:
|
|
283
257
|
pass
|
|
284
|
-
raise RuntimeError(f
|
|
258
|
+
raise RuntimeError(f"Not supported date format. Supported: {formats}")
|
|
285
259
|
else:
|
|
286
|
-
raise NotImplementedError(f
|
|
260
|
+
raise NotImplementedError(f"Unknown where element {node}")
|
|
@@ -208,6 +208,7 @@ DEFAULT_TEMPERATURE = 0.0
|
|
|
208
208
|
USER_COLUMN = "question"
|
|
209
209
|
DEFAULT_EMBEDDINGS_MODEL_PROVIDER = "openai"
|
|
210
210
|
DEFAULT_EMBEDDINGS_MODEL_CLASS = OpenAIEmbeddings
|
|
211
|
+
MAX_INSERT_BATCH_SIZE = 50_000
|
|
211
212
|
DEFAULT_TIKTOKEN_MODEL_NAME = os.getenv("DEFAULT_TIKTOKEN_MODEL_NAME", "gpt-4")
|
|
212
213
|
AGENT_CHUNK_POLLING_INTERVAL_SECONDS = os.getenv("AGENT_CHUNK_POLLING_INTERVAL_SECONDS", 1.0)
|
|
213
214
|
DEFAULT_TEXT2SQL_DATABASE = "mindsdb"
|