MindsDB 25.8.2.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 +5 -45
- mindsdb/api/a2a/__init__.py +52 -0
- mindsdb/api/a2a/agent.py +17 -28
- mindsdb/api/a2a/common/server/server.py +17 -36
- mindsdb/api/a2a/common/server/task_manager.py +14 -28
- mindsdb/api/a2a/common/types.py +3 -4
- mindsdb/api/a2a/task_manager.py +43 -55
- mindsdb/api/a2a/utils.py +63 -0
- mindsdb/api/common/middleware.py +106 -0
- mindsdb/api/http/initialize.py +13 -15
- mindsdb/api/http/namespaces/agents.py +6 -7
- 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 +17 -2
- mindsdb/interfaces/agents/langchain_agent.py +83 -18
- mindsdb/interfaces/knowledge_base/controller.py +3 -1
- 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 +3 -155
- mindsdb/utilities/fs.py +10 -4
- mindsdb/utilities/log.py +0 -25
- mindsdb/utilities/starters.py +0 -39
- {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/METADATA +265 -263
- {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/RECORD +54 -98
- 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.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/top_level.txt +0 -0
|
@@ -63,7 +63,7 @@ from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import (
|
|
|
63
63
|
NULL_VALUE,
|
|
64
64
|
COMMANDS,
|
|
65
65
|
ERR,
|
|
66
|
-
getConstName
|
|
66
|
+
getConstName,
|
|
67
67
|
)
|
|
68
68
|
from mindsdb.api.executor.data_types.answer import ExecuteAnswer
|
|
69
69
|
from mindsdb.api.executor.data_types.response_type import RESPONSE_TYPE
|
|
@@ -73,7 +73,7 @@ from mindsdb.api.mysql.mysql_proxy.utilities import (
|
|
|
73
73
|
)
|
|
74
74
|
from mindsdb.api.executor import exceptions as exec_exc
|
|
75
75
|
|
|
76
|
-
from mindsdb.api.common.
|
|
76
|
+
from mindsdb.api.common.middleware import check_auth
|
|
77
77
|
from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import MYSQL_DATA_TYPE
|
|
78
78
|
from mindsdb.api.executor.sql_query.result_set import Column, ResultSet
|
|
79
79
|
from mindsdb.utilities import log
|
|
@@ -116,10 +116,7 @@ class SQLAnswer:
|
|
|
116
116
|
return {
|
|
117
117
|
"type": RESPONSE_TYPE.TABLE,
|
|
118
118
|
"data": data,
|
|
119
|
-
"column_names": [
|
|
120
|
-
column.alias or column.name
|
|
121
|
-
for column in self.result_set.columns
|
|
122
|
-
],
|
|
119
|
+
"column_names": [column.alias or column.name for column in self.result_set.columns],
|
|
123
120
|
}
|
|
124
121
|
elif self.resp_type == RESPONSE_TYPE.ERROR:
|
|
125
122
|
return {
|
|
@@ -149,17 +146,13 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
149
146
|
super().__init__(request, client_address, server)
|
|
150
147
|
|
|
151
148
|
def init_session(self):
|
|
152
|
-
logger.debug(
|
|
153
|
-
"New connection [{ip}:{port}]".format(
|
|
154
|
-
ip=self.client_address[0], port=self.client_address[1]
|
|
155
|
-
)
|
|
156
|
-
)
|
|
149
|
+
logger.debug("New connection [{ip}:{port}]".format(ip=self.client_address[0], port=self.client_address[1]))
|
|
157
150
|
|
|
158
151
|
if self.server.connection_id >= 65025:
|
|
159
152
|
self.server.connection_id = 0
|
|
160
153
|
self.server.connection_id += 1
|
|
161
154
|
self.connection_id = self.server.connection_id
|
|
162
|
-
self.session = SessionController(api_type=
|
|
155
|
+
self.session = SessionController(api_type="sql")
|
|
163
156
|
|
|
164
157
|
if hasattr(self.server, "salt") and isinstance(self.server.salt, str):
|
|
165
158
|
self.salt = self.server.salt
|
|
@@ -223,10 +216,9 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
223
216
|
self.session.is_ssl = True
|
|
224
217
|
|
|
225
218
|
ssl_context = ssl.SSLContext()
|
|
219
|
+
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
|
226
220
|
ssl_context.load_cert_chain(self.server.cert_path)
|
|
227
|
-
ssl_socket = ssl_context.wrap_socket(
|
|
228
|
-
self.socket, server_side=True, do_handshake_on_connect=True
|
|
229
|
-
)
|
|
221
|
+
ssl_socket = ssl_context.wrap_socket(self.socket, server_side=True, do_handshake_on_connect=True)
|
|
230
222
|
|
|
231
223
|
self.socket = ssl_socket
|
|
232
224
|
handshake_resp = self.packet(HandshakeResponsePacket)
|
|
@@ -245,10 +237,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
245
237
|
else "mysql_native_password"
|
|
246
238
|
)
|
|
247
239
|
|
|
248
|
-
if
|
|
249
|
-
new_method == "caching_sha2_password"
|
|
250
|
-
and self.session.is_ssl is False
|
|
251
|
-
):
|
|
240
|
+
if new_method == "caching_sha2_password" and self.session.is_ssl is False:
|
|
252
241
|
logger.warning(
|
|
253
242
|
f"Check auth, user={username}, ssl={self.session.is_ssl}, auth_method={client_auth_plugin}: "
|
|
254
243
|
"error: cant switch to caching_sha2_password without SSL"
|
|
@@ -307,9 +296,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
307
296
|
f"connecting to database {self.session.database}"
|
|
308
297
|
)
|
|
309
298
|
|
|
310
|
-
auth_data = self.server.check_auth(
|
|
311
|
-
username, password, scramble_func, self.salt, ctx.company_id
|
|
312
|
-
)
|
|
299
|
+
auth_data = self.server.check_auth(username, password, scramble_func, self.salt, ctx.company_id)
|
|
313
300
|
if auth_data["success"]:
|
|
314
301
|
self.session.username = auth_data["username"]
|
|
315
302
|
self.session.auth = True
|
|
@@ -349,9 +336,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
349
336
|
elif answer.type == RESPONSE_TYPE.OK:
|
|
350
337
|
self.packet(OkPacket, state_track=answer.state_track, affected_rows=answer.affected_rows).send()
|
|
351
338
|
elif answer.type == RESPONSE_TYPE.ERROR:
|
|
352
|
-
self.packet(
|
|
353
|
-
ErrPacket, err_code=answer.error_code, msg=answer.error_message
|
|
354
|
-
).send()
|
|
339
|
+
self.packet(ErrPacket, err_code=answer.error_code, msg=answer.error_message).send()
|
|
355
340
|
|
|
356
341
|
def _get_column_defenition_packets(self, columns: dict, data=None):
|
|
357
342
|
if data is None:
|
|
@@ -370,14 +355,14 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
370
355
|
flags = column.get("flags", 0)
|
|
371
356
|
if isinstance(flags, list):
|
|
372
357
|
flags = sum(flags)
|
|
373
|
-
if column.get(
|
|
358
|
+
if column.get("size") is None:
|
|
374
359
|
length = 1
|
|
375
360
|
for row in data:
|
|
376
361
|
if isinstance(row, dict):
|
|
377
362
|
length = max(len(str(row[column_alias])), length)
|
|
378
363
|
else:
|
|
379
364
|
length = max(len(str(row[i])), length)
|
|
380
|
-
column[
|
|
365
|
+
column["size"] = 1
|
|
381
366
|
|
|
382
367
|
packets.append(
|
|
383
368
|
self.packet(
|
|
@@ -397,7 +382,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
397
382
|
|
|
398
383
|
def get_table_packets(self, result_set: ResultSet, status=0):
|
|
399
384
|
data_frame, columns_dict = dump_result_set_to_mysql(result_set)
|
|
400
|
-
data = data_frame.to_dict(
|
|
385
|
+
data = data_frame.to_dict("split")["data"]
|
|
401
386
|
|
|
402
387
|
# TODO remove columns order
|
|
403
388
|
packets = [self.packet(ColumnCountPacket, count=len(columns_dict))]
|
|
@@ -431,10 +416,12 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
431
416
|
|
|
432
417
|
chunk_size = 100
|
|
433
418
|
for start in range(0, len(df), chunk_size):
|
|
434
|
-
string = b"".join(
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
419
|
+
string = b"".join(
|
|
420
|
+
[
|
|
421
|
+
self.packet(body=body, length=len(body)).accum()
|
|
422
|
+
for body in df[start : start + chunk_size].applymap(apply_f).values.sum(axis=1)
|
|
423
|
+
]
|
|
424
|
+
)
|
|
438
425
|
self.socket.sendall(string)
|
|
439
426
|
|
|
440
427
|
def decode_utf(self, text):
|
|
@@ -510,7 +497,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
510
497
|
resp = SQLAnswer(
|
|
511
498
|
resp_type=RESPONSE_TYPE.OK,
|
|
512
499
|
state_track=executor_answer.state_track,
|
|
513
|
-
affected_rows=executor_answer.affected_rows
|
|
500
|
+
affected_rows=executor_answer.affected_rows,
|
|
514
501
|
)
|
|
515
502
|
else:
|
|
516
503
|
resp = SQLAnswer(
|
|
@@ -519,7 +506,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
519
506
|
result_set=executor_answer.data,
|
|
520
507
|
status=executor.server_status,
|
|
521
508
|
affected_rows=executor_answer.affected_rows,
|
|
522
|
-
mysql_types=executor_answer.data.mysql_types
|
|
509
|
+
mysql_types=executor_answer.data.mysql_types,
|
|
523
510
|
)
|
|
524
511
|
|
|
525
512
|
# Increment the counter and include metadata in attributes
|
|
@@ -568,15 +555,13 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
568
555
|
executor_answer: ExecuteAnswer = executor.executor_answer
|
|
569
556
|
|
|
570
557
|
if executor_answer.data is None:
|
|
571
|
-
resp = SQLAnswer(
|
|
572
|
-
resp_type=RESPONSE_TYPE.OK, state_track=executor_answer.state_track
|
|
573
|
-
)
|
|
558
|
+
resp = SQLAnswer(resp_type=RESPONSE_TYPE.OK, state_track=executor_answer.state_track)
|
|
574
559
|
return self.send_query_answer(resp)
|
|
575
560
|
|
|
576
561
|
# TODO prepared_stmt['type'] == 'lock' is not used but it works
|
|
577
562
|
result_set = executor_answer.data
|
|
578
563
|
data_frame, columns_dict = dump_result_set_to_mysql(result_set)
|
|
579
|
-
data = data_frame.to_dict(
|
|
564
|
+
data = data_frame.to_dict("split")["data"]
|
|
580
565
|
|
|
581
566
|
packages = [self.packet(ColumnCountPacket, count=len(columns_dict))]
|
|
582
567
|
packages.extend(self._get_column_defenition_packets(columns_dict))
|
|
@@ -586,9 +571,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
586
571
|
|
|
587
572
|
# send all
|
|
588
573
|
for row in data:
|
|
589
|
-
packages.append(
|
|
590
|
-
self.packet(BinaryResultsetRowPacket, data=row, columns=columns_dict)
|
|
591
|
-
)
|
|
574
|
+
packages.append(self.packet(BinaryResultsetRowPacket, data=row, columns=columns_dict))
|
|
592
575
|
|
|
593
576
|
server_status = executor.server_status or 0x0002
|
|
594
577
|
packages.append(self.last_packet(status=server_status))
|
|
@@ -603,17 +586,13 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
603
586
|
executor_answer: ExecuteAnswer = executor.executor_answer
|
|
604
587
|
|
|
605
588
|
if executor_answer.data is None:
|
|
606
|
-
resp = SQLAnswer(
|
|
607
|
-
resp_type=RESPONSE_TYPE.OK, state_track=executor_answer.state_track
|
|
608
|
-
)
|
|
589
|
+
resp = SQLAnswer(resp_type=RESPONSE_TYPE.OK, state_track=executor_answer.state_track)
|
|
609
590
|
return self.send_query_answer(resp)
|
|
610
591
|
|
|
611
592
|
packages = []
|
|
612
593
|
columns = self.to_mysql_columns(executor_answer.data.columns)
|
|
613
594
|
for row in executor_answer.data[fetched:limit].to_lists():
|
|
614
|
-
packages.append(
|
|
615
|
-
self.packet(BinaryResultsetRowPacket, data=row, columns=columns)
|
|
616
|
-
)
|
|
595
|
+
packages.append(self.packet(BinaryResultsetRowPacket, data=row, columns=columns))
|
|
617
596
|
|
|
618
597
|
prepared_stmt["fetched"] += len(executor_answer.data[fetched:limit])
|
|
619
598
|
|
|
@@ -656,9 +635,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
656
635
|
else:
|
|
657
636
|
ctx.user_class = cloud_connection["user_class"]
|
|
658
637
|
ctx.email_confirmed = cloud_connection["email_confirmed"]
|
|
659
|
-
self.client_capabilities = ClentCapabilities(
|
|
660
|
-
cloud_connection["client_capabilities"]
|
|
661
|
-
)
|
|
638
|
+
self.client_capabilities = ClentCapabilities(cloud_connection["client_capabilities"])
|
|
662
639
|
self.session.database = cloud_connection["database"]
|
|
663
640
|
self.session.username = "cloud"
|
|
664
641
|
self.session.auth = True
|
|
@@ -678,9 +655,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
678
655
|
logger.debug("Session closed by client")
|
|
679
656
|
return
|
|
680
657
|
|
|
681
|
-
logger.debug(
|
|
682
|
-
"Command TYPE: {type}".format(type=getConstName(COMMANDS, p.type.value))
|
|
683
|
-
)
|
|
658
|
+
logger.debug("Command TYPE: {type}".format(type=getConstName(COMMANDS, p.type.value)))
|
|
684
659
|
|
|
685
660
|
command_names = {
|
|
686
661
|
COMMANDS.COM_QUERY: "COM_QUERY",
|
|
@@ -705,10 +680,8 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
705
680
|
if p.type.value == COMMANDS.COM_QUERY:
|
|
706
681
|
sql = self.decode_utf(p.sql.value)
|
|
707
682
|
sql = clear_sql(sql)
|
|
708
|
-
logger.debug(f
|
|
709
|
-
profiler.set_meta(
|
|
710
|
-
query=sql, api="mysql", environment=config.get("environment")
|
|
711
|
-
)
|
|
683
|
+
logger.debug(f"Incoming query: {sql}")
|
|
684
|
+
profiler.set_meta(query=sql, api="mysql", environment=config.get("environment"))
|
|
712
685
|
with profiler.Context("mysql_query_processing"):
|
|
713
686
|
response = self.process_query(sql)
|
|
714
687
|
elif p.type.value == COMMANDS.COM_STMT_PREPARE:
|
|
@@ -791,9 +764,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
791
764
|
# any other exception
|
|
792
765
|
error_type = "unexpected"
|
|
793
766
|
error_traceback = traceback.format_exc()
|
|
794
|
-
logger.error(
|
|
795
|
-
f"ERROR while executing query\n" f"{error_traceback}\n" f"{e}"
|
|
796
|
-
)
|
|
767
|
+
logger.error(f"ERROR while executing query\n{error_traceback}\n{e}")
|
|
797
768
|
error_code = ERR.ER_SYNTAX_ERROR
|
|
798
769
|
response = SQLAnswer(
|
|
799
770
|
resp_type=RESPONSE_TYPE.ERROR,
|
|
@@ -841,7 +812,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
841
812
|
if "db" in context:
|
|
842
813
|
self.session.database = context["db"]
|
|
843
814
|
else:
|
|
844
|
-
self.session.database = config.get(
|
|
815
|
+
self.session.database = config.get("default_project")
|
|
845
816
|
|
|
846
817
|
if "profiling" in context:
|
|
847
818
|
self.session.profiling = context["profiling"]
|
|
@@ -851,9 +822,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
|
|
|
851
822
|
self.session.show_secrets = context["show_secrets"]
|
|
852
823
|
|
|
853
824
|
def get_context(self):
|
|
854
|
-
context = {
|
|
855
|
-
"show_secrets": self.session.show_secrets
|
|
856
|
-
}
|
|
825
|
+
context = {"show_secrets": self.session.show_secrets}
|
|
857
826
|
if self.session.database is not None:
|
|
858
827
|
context["db"] = self.session.database
|
|
859
828
|
if self.session.profiling is True:
|
|
@@ -14,17 +14,38 @@ from mindsdb.api.executor.controllers import SessionController
|
|
|
14
14
|
from mindsdb.api.postgres.postgres_proxy.executor import Executor
|
|
15
15
|
from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import CHARSET_NUMBERS
|
|
16
16
|
from mindsdb.api.executor.data_types.response_type import RESPONSE_TYPE
|
|
17
|
-
from mindsdb.api.common.
|
|
17
|
+
from mindsdb.api.common.middleware import check_auth
|
|
18
18
|
from mindsdb.api.mysql.mysql_proxy.mysql_proxy import SQLAnswer
|
|
19
19
|
from mindsdb.api.postgres.postgres_proxy.postgres_packets.errors import POSTGRES_SYNTAX_ERROR_CODE
|
|
20
20
|
from mindsdb.api.postgres.postgres_proxy.postgres_packets.postgres_fields import GenericField, PostgresField
|
|
21
|
-
from mindsdb.api.postgres.postgres_proxy.postgres_packets.postgres_message_formats import
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
from mindsdb.api.postgres.postgres_proxy.postgres_packets.postgres_message_formats import (
|
|
22
|
+
Terminate,
|
|
23
|
+
Query,
|
|
24
|
+
AuthenticationClearTextPassword,
|
|
25
|
+
AuthenticationOk,
|
|
26
|
+
RowDescriptions,
|
|
27
|
+
DataRow,
|
|
28
|
+
CommandComplete,
|
|
29
|
+
ReadyForQuery,
|
|
30
|
+
ConnectionFailure,
|
|
31
|
+
ParameterStatus,
|
|
32
|
+
Error,
|
|
33
|
+
Execute,
|
|
34
|
+
Bind,
|
|
35
|
+
Parse,
|
|
36
|
+
Sync,
|
|
37
|
+
ParseComplete,
|
|
38
|
+
InvalidSQLStatementName,
|
|
39
|
+
BindComplete,
|
|
40
|
+
Describe,
|
|
41
|
+
DataException,
|
|
42
|
+
ParameterDescription,
|
|
43
|
+
)
|
|
25
44
|
from mindsdb.api.postgres.postgres_proxy.postgres_packets.postgres_message import PostgresMessage
|
|
26
|
-
from mindsdb.api.postgres.postgres_proxy.postgres_packets.postgres_packets import
|
|
27
|
-
|
|
45
|
+
from mindsdb.api.postgres.postgres_proxy.postgres_packets.postgres_packets import (
|
|
46
|
+
PostgresPacketReader,
|
|
47
|
+
PostgresPacketBuilder,
|
|
48
|
+
)
|
|
28
49
|
from mindsdb.api.postgres.postgres_proxy.utilities import strip_null_byte
|
|
29
50
|
from mindsdb.utilities.config import config
|
|
30
51
|
from mindsdb.utilities.context import context as ctx
|
|
@@ -38,8 +59,8 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
38
59
|
|
|
39
60
|
def __init__(self, request, client_address, server):
|
|
40
61
|
self.logger = log.getLogger(__name__)
|
|
41
|
-
self.charset =
|
|
42
|
-
self.charset_text_type = CHARSET_NUMBERS[
|
|
62
|
+
self.charset = "utf8"
|
|
63
|
+
self.charset_text_type = CHARSET_NUMBERS["utf8_general_ci"]
|
|
43
64
|
self.session = None
|
|
44
65
|
self.client_capabilities = None
|
|
45
66
|
self.user_parameters = None
|
|
@@ -48,17 +69,16 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
48
69
|
self.is_cloud = False
|
|
49
70
|
self.named_portals = {}
|
|
50
71
|
self.unnamed_portal = None
|
|
51
|
-
self.transaction_status = b
|
|
72
|
+
self.transaction_status = b"I" # I: Idle, T: Transaction Block, E: Failed Transaction Block
|
|
52
73
|
super().__init__(request, client_address, server)
|
|
53
74
|
|
|
54
75
|
def handle(self) -> None:
|
|
55
|
-
|
|
56
76
|
ctx.set_default()
|
|
57
77
|
self.init_session()
|
|
58
|
-
self.logger.debug(
|
|
78
|
+
self.logger.debug("handle new incoming connection")
|
|
59
79
|
cloud_connection = self.is_cloud_connection()
|
|
60
80
|
if cloud_connection["is_cloud"]:
|
|
61
|
-
ctx.company_id = cloud_connection.get(
|
|
81
|
+
ctx.company_id = cloud_connection.get("company_id")
|
|
62
82
|
self.is_cloud = True
|
|
63
83
|
|
|
64
84
|
self.message_map: Dict[Type[PostgresMessage], Callable[[Any], bool]] = {
|
|
@@ -68,7 +88,7 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
68
88
|
Bind: self.bind,
|
|
69
89
|
Execute: self.execute,
|
|
70
90
|
Describe: self.describe,
|
|
71
|
-
Sync: self.sync
|
|
91
|
+
Sync: self.sync,
|
|
72
92
|
}
|
|
73
93
|
self.client_buffer = PostgresPacketReader(self.rfile)
|
|
74
94
|
if self.is_cloud:
|
|
@@ -83,69 +103,59 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
83
103
|
self.main_loop()
|
|
84
104
|
|
|
85
105
|
def is_cloud_connection(self):
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
is_cloud = config.get(
|
|
96
|
-
|
|
97
|
-
if sys.platform !=
|
|
98
|
-
return {
|
|
99
|
-
'is_cloud': False
|
|
100
|
-
}
|
|
106
|
+
"""Determine source of connection. Must be call before handshake.
|
|
107
|
+
Idea based on: real mysql connection does not send anything before server handshake, so
|
|
108
|
+
socket should be in 'out' state. In opposite, clout connection sends '0000' right after
|
|
109
|
+
connection. '0000' selected because in real mysql connection it should be length of package,
|
|
110
|
+
and it can not be 0.
|
|
111
|
+
|
|
112
|
+
Copied from mysql_proxy
|
|
113
|
+
TODO: Extract method into common
|
|
114
|
+
"""
|
|
115
|
+
is_cloud = config.get("cloud", False)
|
|
116
|
+
|
|
117
|
+
if sys.platform != "linux" or is_cloud is False:
|
|
118
|
+
return {"is_cloud": False}
|
|
101
119
|
|
|
102
120
|
read_poller = select.poll()
|
|
103
121
|
read_poller.register(self.request, select.POLLIN)
|
|
104
122
|
events = read_poller.poll(30)
|
|
105
123
|
|
|
106
124
|
if len(events) == 0:
|
|
107
|
-
return {
|
|
108
|
-
'is_cloud': False
|
|
109
|
-
}
|
|
125
|
+
return {"is_cloud": False}
|
|
110
126
|
|
|
111
127
|
first_byte = self.request.recv(4, socket.MSG_PEEK)
|
|
112
|
-
if first_byte == b
|
|
128
|
+
if first_byte == b"\x00\x00\x00\x00":
|
|
113
129
|
self.request.recv(4)
|
|
114
130
|
client_capabilities = self.request.recv(8)
|
|
115
|
-
client_capabilities = struct.unpack(
|
|
131
|
+
client_capabilities = struct.unpack("L", client_capabilities)[0]
|
|
116
132
|
|
|
117
133
|
company_id = self.request.recv(4)
|
|
118
|
-
company_id = struct.unpack(
|
|
134
|
+
company_id = struct.unpack("I", company_id)[0]
|
|
119
135
|
|
|
120
136
|
user_class = self.request.recv(1)
|
|
121
|
-
user_class = struct.unpack(
|
|
137
|
+
user_class = struct.unpack("B", user_class)[0]
|
|
122
138
|
|
|
123
139
|
database_name_len = self.request.recv(2)
|
|
124
|
-
database_name_len = struct.unpack(
|
|
140
|
+
database_name_len = struct.unpack("H", database_name_len)[0]
|
|
125
141
|
|
|
126
|
-
database_name =
|
|
142
|
+
database_name = ""
|
|
127
143
|
if database_name_len > 0:
|
|
128
144
|
database_name = self.request.recv(database_name_len).decode()
|
|
129
145
|
|
|
130
146
|
return {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
147
|
+
"is_cloud": True,
|
|
148
|
+
"client_capabilities": client_capabilities,
|
|
149
|
+
"company_id": company_id,
|
|
150
|
+
"user_class": user_class,
|
|
151
|
+
"database": database_name,
|
|
136
152
|
}
|
|
137
153
|
|
|
138
|
-
return {
|
|
139
|
-
'is_cloud': False
|
|
140
|
-
}
|
|
154
|
+
return {"is_cloud": False}
|
|
141
155
|
|
|
142
156
|
def parse(self, message: Parse):
|
|
143
157
|
self.logger.info("Postgres_Proxy: Parsing")
|
|
144
|
-
executor = Executor(
|
|
145
|
-
session=self.session,
|
|
146
|
-
proxy_server=self,
|
|
147
|
-
charset=self.charset
|
|
148
|
-
)
|
|
158
|
+
executor = Executor(session=self.session, proxy_server=self, charset=self.charset)
|
|
149
159
|
# TODO: Remove comment if unneeded ot use session since we're storing in this proxy class per session anyway
|
|
150
160
|
# stmt_id = self.session.register_stmt(executor)
|
|
151
161
|
executor.stmt_prepare(sql=message.query)
|
|
@@ -179,7 +189,7 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
179
189
|
|
|
180
190
|
def describe(self, message: Describe):
|
|
181
191
|
self.logger.info("Postgres_Proxy: Describing")
|
|
182
|
-
if message.describe_type == b
|
|
192
|
+
if message.describe_type == b"P":
|
|
183
193
|
if message.name:
|
|
184
194
|
describing = self.named_portals[message.name]
|
|
185
195
|
elif self.unnamed_statement:
|
|
@@ -187,7 +197,7 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
187
197
|
else:
|
|
188
198
|
self.send(InvalidSQLStatementName("Portal Does not Exist"))
|
|
189
199
|
return True
|
|
190
|
-
elif message.describe_type == b
|
|
200
|
+
elif message.describe_type == b"S":
|
|
191
201
|
if message.name:
|
|
192
202
|
describing = self.named_statements[message.name]
|
|
193
203
|
elif self.unnamed_statement:
|
|
@@ -227,8 +237,7 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
227
237
|
return True
|
|
228
238
|
|
|
229
239
|
def init_session(self):
|
|
230
|
-
self.logger.info(
|
|
231
|
-
ip=self.client_address[0], port=self.client_address[1]))
|
|
240
|
+
self.logger.info("New connection [{ip}:{port}]".format(ip=self.client_address[0], port=self.client_address[1]))
|
|
232
241
|
self.logger.debug(self.__dict__)
|
|
233
242
|
|
|
234
243
|
if self.server.connection_id >= 65025:
|
|
@@ -236,9 +245,9 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
236
245
|
self.server.connection_id += 1
|
|
237
246
|
self.connection_id = self.server.connection_id
|
|
238
247
|
self.session = SessionController()
|
|
239
|
-
self.session.database = config.get(
|
|
248
|
+
self.session.database = config.get("default_project")
|
|
240
249
|
|
|
241
|
-
if hasattr(self.server,
|
|
250
|
+
if hasattr(self.server, "salt") and isinstance(self.server.salt, str):
|
|
242
251
|
self.salt = self.server.salt
|
|
243
252
|
else:
|
|
244
253
|
self.salt = base64.b64encode(os.urandom(15)).decode()
|
|
@@ -247,14 +256,10 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
247
256
|
|
|
248
257
|
self.current_transaction = None
|
|
249
258
|
|
|
250
|
-
self.logger.debug(
|
|
259
|
+
self.logger.debug("session salt: {salt}".format(salt=self.salt))
|
|
251
260
|
|
|
252
261
|
def process_query(self, sql):
|
|
253
|
-
executor = Executor(
|
|
254
|
-
session=self.session,
|
|
255
|
-
proxy_server=self,
|
|
256
|
-
charset=self.charset
|
|
257
|
-
)
|
|
262
|
+
executor = Executor(session=self.session, proxy_server=self, charset=self.charset)
|
|
258
263
|
self.logger.debug("processing query\n%s", sql)
|
|
259
264
|
try:
|
|
260
265
|
executor.query_execute(sql)
|
|
@@ -262,7 +267,7 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
262
267
|
return SQLAnswer(
|
|
263
268
|
resp_type=RESPONSE_TYPE.ERROR,
|
|
264
269
|
error_message=str(e).encode(self.get_encoding()),
|
|
265
|
-
error_code=POSTGRES_SYNTAX_ERROR_CODE.encode(self.get_encoding())
|
|
270
|
+
error_code=POSTGRES_SYNTAX_ERROR_CODE.encode(self.get_encoding()),
|
|
266
271
|
)
|
|
267
272
|
return self.return_executor_data(executor)
|
|
268
273
|
|
|
@@ -277,7 +282,7 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
277
282
|
resp_type=RESPONSE_TYPE.TABLE,
|
|
278
283
|
state_track=executor.state_track,
|
|
279
284
|
result_set=executor.data,
|
|
280
|
-
status=executor.server_status
|
|
285
|
+
status=executor.server_status,
|
|
281
286
|
)
|
|
282
287
|
return resp
|
|
283
288
|
|
|
@@ -295,7 +300,7 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
295
300
|
self.client_buffer.read_verify_ssl_request()
|
|
296
301
|
# self.send(NoticeResponse()) -- Should Probably not send. Looks in protocol manual to be sent for warning
|
|
297
302
|
self.logger.debug("Sending No to SSL Request")
|
|
298
|
-
PostgresPacketBuilder().write_char(b
|
|
303
|
+
PostgresPacketBuilder().write_char(b"N", self.wfile)
|
|
299
304
|
self.user_parameters = self.client_buffer.read_startup_message()
|
|
300
305
|
|
|
301
306
|
def authenticate(self, ask_for_password=False):
|
|
@@ -303,12 +308,12 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
303
308
|
self.send(AuthenticationClearTextPassword())
|
|
304
309
|
password = self.client_buffer.read_authentication(encoding=self.charset)
|
|
305
310
|
else:
|
|
306
|
-
password =
|
|
307
|
-
username = self.user_parameters[b
|
|
311
|
+
password = ""
|
|
312
|
+
username = self.user_parameters[b"user"].decode(encoding=self.charset)
|
|
308
313
|
auth_data = self.server.check_auth(username, password, scramble_func, self.salt, ctx.company_id)
|
|
309
|
-
if auth_data[
|
|
314
|
+
if auth_data["success"]:
|
|
310
315
|
self.logger.debug("Authentication succeeded")
|
|
311
|
-
self.session.username = auth_data[
|
|
316
|
+
self.session.username = auth_data["username"]
|
|
312
317
|
self.session.auth = True
|
|
313
318
|
self.send(AuthenticationOk())
|
|
314
319
|
return True
|
|
@@ -329,9 +334,9 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
329
334
|
def return_ok(self, sql, rows: int = 0):
|
|
330
335
|
command = self.get_command(sql)
|
|
331
336
|
if command == "BEGIN":
|
|
332
|
-
self.transaction_status = b
|
|
337
|
+
self.transaction_status = b"T"
|
|
333
338
|
if command == "COMMIT":
|
|
334
|
-
self.transaction_status = b
|
|
339
|
+
self.transaction_status = b"I"
|
|
335
340
|
if command == "CREATE":
|
|
336
341
|
command = "SELECT"
|
|
337
342
|
if command in ("INSERT", "DELETE", "UPDATE", "SELECT", "MOVE", "FETCH", "COPY"):
|
|
@@ -347,7 +352,7 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
347
352
|
if type(sql) == bytes:
|
|
348
353
|
encoding = self.get_encoding()
|
|
349
354
|
sql: str = sql.decode(encoding)
|
|
350
|
-
return strip_null_byte(sql).strip(
|
|
355
|
+
return strip_null_byte(sql).strip(";")
|
|
351
356
|
|
|
352
357
|
def return_table(self, sql_answer: SQLAnswer, row_descs=True):
|
|
353
358
|
fields = self.to_postgres_fields(sql_answer.result_set.columns)
|
|
@@ -356,7 +361,7 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
356
361
|
self.send(RowDescriptions(fields=fields))
|
|
357
362
|
self.send(DataRow(rows=rows))
|
|
358
363
|
encoding = self.get_encoding()
|
|
359
|
-
tag = (
|
|
364
|
+
tag = ("SELECT %s" % str(len(rows))).encode(encoding)
|
|
360
365
|
self.send(CommandComplete(tag=tag))
|
|
361
366
|
return True
|
|
362
367
|
|
|
@@ -389,11 +394,7 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
389
394
|
fields = []
|
|
390
395
|
i = 0
|
|
391
396
|
for column in columns:
|
|
392
|
-
fields.append(GenericField(
|
|
393
|
-
name=column['name'],
|
|
394
|
-
object_id=column['type'].value,
|
|
395
|
-
column_id=i
|
|
396
|
-
))
|
|
397
|
+
fields.append(GenericField(name=column["name"], object_id=column["type"].value, column_id=i))
|
|
397
398
|
i += 1
|
|
398
399
|
return fields
|
|
399
400
|
|
|
@@ -410,12 +411,12 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
410
411
|
column = json.dumps(column)
|
|
411
412
|
if isinstance(column, datetime.date) or isinstance(column, datetime.datetime):
|
|
412
413
|
try:
|
|
413
|
-
column = datetime.datetime.strftime(column,
|
|
414
|
+
column = datetime.datetime.strftime(column, "%Y-%m-%d")
|
|
414
415
|
except ValueError:
|
|
415
416
|
try:
|
|
416
|
-
column = datetime.datetime.strftime(column,
|
|
417
|
+
column = datetime.datetime.strftime(column, "%Y-%m-%dT%H:%M:%S")
|
|
417
418
|
except ValueError:
|
|
418
|
-
column = datetime.datetime.strptime(column,
|
|
419
|
+
column = datetime.datetime.strptime(column, "%Y-%m-%dT%H:%M:%S.%f")
|
|
419
420
|
if isinstance(column, bool):
|
|
420
421
|
if column:
|
|
421
422
|
column = "true"
|
|
@@ -427,7 +428,7 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
427
428
|
|
|
428
429
|
def send_initial_data(self):
|
|
429
430
|
server_encoding = self.charset.encode(self.charset)
|
|
430
|
-
client_encoding = self.user_parameters.get(b
|
|
431
|
+
client_encoding = self.user_parameters.get(b"client_encoding", server_encoding)
|
|
431
432
|
# TODO: Send BackendKeyData Here (55.2.1)
|
|
432
433
|
self.send(ParameterStatus(name=b"server_version", value=b"14.6"))
|
|
433
434
|
self.send(ParameterStatus(name=b"server_encoding", value=server_encoding))
|
|
@@ -459,8 +460,8 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
|
|
|
459
460
|
|
|
460
461
|
@staticmethod
|
|
461
462
|
def startProxy():
|
|
462
|
-
host = config[
|
|
463
|
-
port = int(config[
|
|
463
|
+
host = config["api"]["postgres"]["host"]
|
|
464
|
+
port = int(config["api"]["postgres"]["port"])
|
|
464
465
|
server = TcpServer((host, port), PostgresProxyHandler)
|
|
465
466
|
server.connection_id = 0
|
|
466
467
|
server.mindsdb_config = config
|
|
@@ -56,9 +56,7 @@ class CrateHandler(DatabaseHandler):
|
|
|
56
56
|
if self.is_connected:
|
|
57
57
|
return self.connection
|
|
58
58
|
|
|
59
|
-
is_local = (
|
|
60
|
-
self.host.startswith("localhost") or self.host == "127.0.0.1"
|
|
61
|
-
)
|
|
59
|
+
is_local = self.host.startswith("localhost") or self.host == "127.0.0.1"
|
|
62
60
|
|
|
63
61
|
try:
|
|
64
62
|
# Build URL based on connection type
|
|
@@ -121,7 +119,7 @@ class CrateHandler(DatabaseHandler):
|
|
|
121
119
|
"""Receive raw query and act upon it somehow.
|
|
122
120
|
Args:
|
|
123
121
|
query (Any): query in native format (str for sql databases,
|
|
124
|
-
|
|
122
|
+
etc)
|
|
125
123
|
Returns:
|
|
126
124
|
HandlerResponse
|
|
127
125
|
"""
|
|
@@ -136,9 +134,7 @@ class CrateHandler(DatabaseHandler):
|
|
|
136
134
|
result = cur.fetchall()
|
|
137
135
|
response = Response(
|
|
138
136
|
RESPONSE_TYPE.TABLE,
|
|
139
|
-
data_frame=pd.DataFrame(
|
|
140
|
-
result, columns=[x[0] for x in cur.description]
|
|
141
|
-
),
|
|
137
|
+
data_frame=pd.DataFrame(result, columns=[x[0] for x in cur.description]),
|
|
142
138
|
)
|
|
143
139
|
else:
|
|
144
140
|
response = Response(RESPONSE_TYPE.OK)
|