MindsDB 25.3.4.1__py3-none-any.whl → 25.4.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.

Files changed (31) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +21 -4
  3. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +5 -2
  4. mindsdb/api/executor/datahub/datanodes/system_tables.py +131 -138
  5. mindsdb/api/mcp/__init__.py +0 -0
  6. mindsdb/api/mcp/start.py +152 -0
  7. mindsdb/api/mysql/mysql_proxy/libs/constants/mysql.py +74 -0
  8. mindsdb/integrations/handlers/confluence_handler/confluence_api_client.py +14 -2
  9. mindsdb/integrations/handlers/ms_teams_handler/ms_graph_api_teams_client.py +278 -55
  10. mindsdb/integrations/handlers/ms_teams_handler/ms_teams_handler.py +52 -21
  11. mindsdb/integrations/handlers/ms_teams_handler/ms_teams_tables.py +6 -29
  12. mindsdb/integrations/handlers/mssql_handler/mssql_handler.py +37 -1
  13. mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +28 -1
  14. mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +53 -5
  15. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +37 -1
  16. mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +42 -1
  17. mindsdb/integrations/libs/vectordatabase_handler.py +20 -20
  18. mindsdb/integrations/utilities/handlers/auth_utilities/__init__.py +1 -1
  19. mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/__init__.py +1 -1
  20. mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/ms_graph_api_auth_utilities.py +97 -18
  21. mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +121 -11
  22. mindsdb/interfaces/database/projects.py +15 -0
  23. mindsdb/interfaces/knowledge_base/controller.py +78 -2
  24. mindsdb/utilities/config.py +8 -0
  25. mindsdb/utilities/render/sqlalchemy_render.py +30 -6
  26. mindsdb/utilities/starters.py +7 -0
  27. {mindsdb-25.3.4.1.dist-info → mindsdb-25.4.1.0.dist-info}/METADATA +233 -234
  28. {mindsdb-25.3.4.1.dist-info → mindsdb-25.4.1.0.dist-info}/RECORD +31 -29
  29. {mindsdb-25.3.4.1.dist-info → mindsdb-25.4.1.0.dist-info}/WHEEL +0 -0
  30. {mindsdb-25.3.4.1.dist-info → mindsdb-25.4.1.0.dist-info}/licenses/LICENSE +0 -0
  31. {mindsdb-25.3.4.1.dist-info → mindsdb-25.4.1.0.dist-info}/top_level.txt +0 -0
mindsdb/__about__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  __title__ = 'MindsDB'
2
2
  __package_name__ = 'mindsdb'
3
- __version__ = '25.3.4.1'
3
+ __version__ = '25.4.1.0'
4
4
  __description__ = "MindsDB's AI SQL Server enables developers to build AI tools that need access to real-time data to perform their tasks"
5
5
  __email__ = "jorge@mindsdb.com"
6
6
  __author__ = 'MindsDB Inc'
mindsdb/__main__.py CHANGED
@@ -24,7 +24,8 @@ from mindsdb.__about__ import __version__ as mindsdb_version
24
24
  from mindsdb.utilities.config import config
25
25
  from mindsdb.utilities.exception import EntityNotExistsError
26
26
  from mindsdb.utilities.starters import (
27
- start_http, start_mysql, start_mongo, start_postgres, start_ml_task_queue, start_scheduler, start_tasks
27
+ start_http, start_mysql, start_mongo, start_postgres, start_ml_task_queue, start_scheduler, start_tasks,
28
+ start_mcp
28
29
  )
29
30
  from mindsdb.utilities.ps import is_pid_listen_port, get_child_pids
30
31
  from mindsdb.utilities.functions import get_versions_where_predictors_become_obsolete
@@ -57,6 +58,7 @@ class TrunkProcessEnum(Enum):
57
58
  JOBS = 'jobs'
58
59
  TASKS = 'tasks'
59
60
  ML_TASK_QUEUE = 'ml_task_queue'
61
+ MCP = 'mcp'
60
62
 
61
63
  @classmethod
62
64
  def _missing_(cls, value):
@@ -221,9 +223,9 @@ if __name__ == '__main__':
221
223
  ctx.set_default()
222
224
 
223
225
  # ---- CHECK SYSTEM ----
224
- if not (sys.version_info[0] >= 3 and sys.version_info[1] >= 9):
226
+ if not (sys.version_info[0] >= 3 and sys.version_info[1] >= 10):
225
227
  print("""
226
- MindsDB requires Python >= 3.9 to run
228
+ MindsDB requires Python >= 3.10 to run
227
229
 
228
230
  Once you have supported Python version installed you can start mindsdb as follows:
229
231
 
@@ -385,6 +387,7 @@ if __name__ == '__main__':
385
387
 
386
388
  http_api_config = config['api']['http']
387
389
  mysql_api_config = config['api']['mysql']
390
+ mcp_api_config = config['api']['mcp']
388
391
  trunc_processes_struct = {
389
392
  TrunkProcessEnum.HTTP: TrunkProcessData(
390
393
  name=TrunkProcessEnum.HTTP.value,
@@ -434,11 +437,25 @@ if __name__ == '__main__':
434
437
  name=TrunkProcessEnum.ML_TASK_QUEUE.value,
435
438
  entrypoint=start_ml_task_queue,
436
439
  args=(config.cmd_args.verbose,)
440
+ ),
441
+ TrunkProcessEnum.MCP: TrunkProcessData(
442
+ name=TrunkProcessEnum.MCP.value,
443
+ entrypoint=start_mcp,
444
+ port=mcp_api_config.get('port', 47337),
445
+ args=(config.cmd_args.verbose,),
446
+ restart_on_failure=mcp_api_config.get('restart_on_failure', False),
447
+ max_restart_count=mcp_api_config.get('max_restart_count', TrunkProcessData.max_restart_count),
448
+ max_restart_interval_seconds=mcp_api_config.get(
449
+ 'max_restart_interval_seconds', TrunkProcessData.max_restart_interval_seconds
450
+ )
437
451
  )
438
452
  }
439
453
 
440
454
  for api_enum in api_arr:
441
- trunc_processes_struct[api_enum].need_to_run = True
455
+ if api_enum in trunc_processes_struct:
456
+ trunc_processes_struct[api_enum].need_to_run = True
457
+ else:
458
+ logger.error(f"ERROR: {api_enum} API is not a valid api in config")
442
459
 
443
460
  if config['jobs']['disable'] is False:
444
461
  trunc_processes_struct[TrunkProcessEnum.JOBS].need_to_run = True
@@ -64,6 +64,7 @@ class IntegrationDataNode(DataNode):
64
64
  df = response.data_frame
65
65
  # case independent
66
66
  columns = [str(c).lower() for c in df.columns]
67
+ df.columns = columns
67
68
 
68
69
  col_name = None
69
70
  # looking for specific column names
@@ -78,8 +79,10 @@ class IntegrationDataNode(DataNode):
78
79
  names = df[df.columns[col_name]]
79
80
 
80
81
  # type
81
- if 'type' in columns:
82
- types = df[df.columns[columns.index('type')]]
82
+ if 'mysql_data_type' in columns:
83
+ types = df['mysql_data_type']
84
+ elif 'type' in columns:
85
+ types = df['type']
83
86
  else:
84
87
  types = [None] * len(names)
85
88
 
@@ -1,14 +1,17 @@
1
+ from typing import Optional, Literal
2
+ from dataclasses import dataclass, astuple, fields
1
3
 
2
4
  import pandas as pd
3
5
  from mindsdb_sql_parser.ast.base import ASTNode
4
- from mindsdb.integrations.utilities.sql_utils import extract_comparison_conditions
5
6
 
7
+ from mindsdb.utilities import log
8
+ from mindsdb.utilities.config import config
9
+ from mindsdb.integrations.utilities.sql_utils import extract_comparison_conditions
10
+ from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import MYSQL_DATA_TYPE
6
11
  from mindsdb.api.executor.datahub.classes.tables_row import (
7
12
  TABLES_ROW_TYPE,
8
13
  TablesRow,
9
14
  )
10
- from mindsdb.utilities import log
11
- from mindsdb.utilities.config import config
12
15
 
13
16
  logger = log.getLogger(__name__)
14
17
 
@@ -162,132 +165,108 @@ class TablesTable(Table):
162
165
  return df
163
166
 
164
167
 
165
- class ColumnsTable(Table):
168
+ @dataclass
169
+ class ColumnsTableRow:
170
+ """Represents a row in the COLUMNS table.
171
+ Fields description: https://dev.mysql.com/doc/refman/8.4/en/information-schema-columns-table.html
172
+ NOTE: attrs order matter, don't change it.
173
+ """
174
+ TABLE_CATALOG: Literal['def'] = 'def'
175
+ TABLE_SCHEMA: Optional[str] = None
176
+ TABLE_NAME: Optional[str] = None
177
+ COLUMN_NAME: Optional[str] = None
178
+ ORDINAL_POSITION: int = 0
179
+ COLUMN_DEFAULT: Optional[str] = None
180
+ IS_NULLABLE: Literal['YES', 'NO'] = 'YES'
181
+ DATA_TYPE: str = MYSQL_DATA_TYPE.VARCHAR.value
182
+ CHARACTER_MAXIMUM_LENGTH: Optional[int] = None
183
+ CHARACTER_OCTET_LENGTH: Optional[int] = None
184
+ NUMERIC_PRECISION: Optional[int] = None
185
+ NUMERIC_SCALE: Optional[int] = None
186
+ DATETIME_PRECISION: Optional[int] = None
187
+ CHARACTER_SET_NAME: Optional[str] = None
188
+ COLLATION_NAME: Optional[str] = None
189
+ COLUMN_TYPE: Optional[str] = None
190
+ COLUMN_KEY: Optional[str] = None
191
+ EXTRA: Optional[str] = None
192
+ PRIVILEGES: str = 'select'
193
+ COLUMN_COMMENT: Optional[str] = None
194
+ GENERATION_EXPRESSION: Optional[str] = None
195
+
196
+ def __post_init__(self):
197
+ # region check mandatory fields
198
+ mandatory_fields = ['TABLE_SCHEMA', 'TABLE_NAME', 'COLUMN_NAME']
199
+ if any(getattr(self, field_name) is None for field_name in mandatory_fields):
200
+ raise ValueError('One of mandatory fields is missed when creating ColumnsTableRow')
201
+ # endregion
202
+
203
+ # region set default values depend on type
204
+ defaults = {
205
+ 'COLUMN_TYPE': self.DATA_TYPE
206
+ }
207
+ if MYSQL_DATA_TYPE(self.DATA_TYPE) in (
208
+ MYSQL_DATA_TYPE.TIMESTAMP,
209
+ MYSQL_DATA_TYPE.DATETIME,
210
+ MYSQL_DATA_TYPE.DATE
211
+ ):
212
+ defaults = {
213
+ 'DATETIME_PRECISION': 0,
214
+ 'COLUMN_TYPE': self.DATA_TYPE
215
+ }
216
+ elif MYSQL_DATA_TYPE(self.DATA_TYPE) in (
217
+ MYSQL_DATA_TYPE.FLOAT,
218
+ MYSQL_DATA_TYPE.DOUBLE,
219
+ MYSQL_DATA_TYPE.DECIMAL
220
+ ):
221
+ defaults = {
222
+ 'NUMERIC_PRECISION': 12,
223
+ 'NUMERIC_SCALE': 0,
224
+ 'COLUMN_TYPE': self.DATA_TYPE
225
+ }
226
+ elif MYSQL_DATA_TYPE(self.DATA_TYPE) in (
227
+ MYSQL_DATA_TYPE.TINYINT,
228
+ MYSQL_DATA_TYPE.SMALLINT,
229
+ MYSQL_DATA_TYPE.MEDIUMINT,
230
+ MYSQL_DATA_TYPE.INT,
231
+ MYSQL_DATA_TYPE.BIGINT
232
+ ):
233
+ defaults = {
234
+ 'NUMERIC_PRECISION': 20,
235
+ 'NUMERIC_SCALE': 0,
236
+ 'COLUMN_TYPE': self.DATA_TYPE
237
+ }
238
+ elif MYSQL_DATA_TYPE(self.DATA_TYPE) is MYSQL_DATA_TYPE.VARCHAR:
239
+ defaults = {
240
+ 'CHARACTER_MAXIMUM_LENGTH': 1024,
241
+ 'CHARACTER_OCTET_LENGTH': 3072,
242
+ 'CHARACTER_SET_NAME': 'utf8',
243
+ 'COLLATION_NAME': 'utf8_bin',
244
+ 'COLUMN_TYPE': 'varchar(1024)'
245
+ }
246
+ else:
247
+ # show as MYSQL_DATA_TYPE.TEXT:
248
+ defaults = {
249
+ 'CHARACTER_MAXIMUM_LENGTH': 65535, # from https://bugs.mysql.com/bug.php?id=90685
250
+ 'CHARACTER_OCTET_LENGTH': 65535, #
251
+ 'CHARACTER_SET_NAME': 'utf8',
252
+ 'COLLATION_NAME': 'utf8_bin',
253
+ 'COLUMN_TYPE': 'text'
254
+ }
255
+
256
+ for key, value in defaults.items():
257
+ setattr(self, key, value)
166
258
 
259
+ self.DATA_TYPE = self.DATA_TYPE.lower()
260
+ self.COLUMN_TYPE = self.COLUMN_TYPE.lower()
261
+ # endregion
262
+
263
+
264
+ class ColumnsTable(Table):
167
265
  name = 'COLUMNS'
168
- columns = [
169
- "TABLE_CATALOG",
170
- "TABLE_SCHEMA",
171
- "TABLE_NAME",
172
- "COLUMN_NAME",
173
- "ORDINAL_POSITION",
174
- "COLUMN_DEFAULT",
175
- "IS_NULLABLE",
176
- "DATA_TYPE",
177
- "CHARACTER_MAXIMUM_LENGTH",
178
- "CHARACTER_OCTET_LENGTH",
179
- "NUMERIC_PRECISION",
180
- "NUMERIC_SCALE",
181
- "DATETIME_PRECISION",
182
- "CHARACTER_SET_NAME",
183
- "COLLATION_NAME",
184
- "COLUMN_TYPE",
185
- "COLUMN_KEY",
186
- "EXTRA",
187
- "PRIVILEGES",
188
- "COLUMN_COMMENT",
189
- "GENERATION_EXPRESSION",
190
- ]
266
+ columns = [field.name for field in fields(ColumnsTableRow)]
191
267
 
192
268
  @classmethod
193
269
  def get_data(cls, inf_schema=None, query: ASTNode = None, **kwargs):
194
-
195
- # NOTE there is a lot of types in mysql, but listed below should be enough for our purposes
196
- row_templates = {
197
- "text": [
198
- "def",
199
- "SCHEMA_NAME",
200
- "TABLE_NAME",
201
- "COLUMN_NAME",
202
- "COL_INDEX",
203
- None,
204
- "YES",
205
- "varchar",
206
- 1024,
207
- 3072,
208
- None,
209
- None,
210
- None,
211
- "utf8",
212
- "utf8_bin",
213
- "varchar(1024)",
214
- None,
215
- None,
216
- "select",
217
- None,
218
- None,
219
- ],
220
- "timestamp": [
221
- "def",
222
- "SCHEMA_NAME",
223
- "TABLE_NAME",
224
- "COLUMN_NAME",
225
- "COL_INDEX",
226
- "CURRENT_TIMESTAMP",
227
- "YES",
228
- "timestamp",
229
- None,
230
- None,
231
- None,
232
- None,
233
- 0,
234
- None,
235
- None,
236
- "timestamp",
237
- None,
238
- None,
239
- "select",
240
- None,
241
- None,
242
- ],
243
- "bigint": [
244
- "def",
245
- "SCHEMA_NAME",
246
- "TABLE_NAME",
247
- "COLUMN_NAME",
248
- "COL_INDEX",
249
- None,
250
- "YES",
251
- "bigint",
252
- None,
253
- None,
254
- 20,
255
- 0,
256
- None,
257
- None,
258
- None,
259
- "bigint unsigned",
260
- None,
261
- None,
262
- "select",
263
- None,
264
- None,
265
- ],
266
- "float": [
267
- "def",
268
- "SCHEMA_NAME",
269
- "TABLE_NAME",
270
- "COLUMN_NAME",
271
- "COL_INDEX",
272
- None,
273
- "YES",
274
- "float",
275
- None,
276
- None,
277
- 12,
278
- 0,
279
- None,
280
- None,
281
- None,
282
- "float",
283
- None,
284
- None,
285
- "select",
286
- None,
287
- None,
288
- ],
289
- }
290
-
291
270
  result = []
292
271
 
293
272
  databases, tables_names = _get_scope(query)
@@ -320,20 +299,34 @@ class ColumnsTable(Table):
320
299
  for i, column in enumerate(table_columns):
321
300
  column_name = column['name']
322
301
  column_type = column.get('type', 'text')
323
- if column_type in ('double precision', 'real', 'numeric'):
324
- column_type = 'float'
325
- elif column_type in ('integer', 'smallint', 'int'):
326
- column_type = 'bigint'
327
- elif column_type in ('timestamp without time zone', 'timestamp with time zone', 'date'):
328
- column_type = 'timestamp'
329
- elif column_type not in row_templates:
330
- column_type = 'text'
331
- result_row = row_templates[column_type].copy()
332
- result_row[1] = db_name
333
- result_row[2] = table_name
334
- result_row[3] = column_name
335
- result_row[4] = i
336
- result.append(result_row)
302
+
303
+ # region infer type
304
+ if isinstance(column_type, MYSQL_DATA_TYPE) is False:
305
+ if column_type in ('double precision', 'real', 'numeric', 'float'):
306
+ column_type = MYSQL_DATA_TYPE.FLOAT
307
+ elif column_type in ('integer', 'smallint', 'int', 'bigint'):
308
+ column_type = MYSQL_DATA_TYPE.BIGINT
309
+ elif column_type in (
310
+ 'timestamp without time zone',
311
+ 'timestamp with time zone',
312
+ 'date', 'timestamp'
313
+ ):
314
+ column_type = MYSQL_DATA_TYPE.DATETIME
315
+ else:
316
+ column_type = MYSQL_DATA_TYPE.VARCHAR
317
+ # endregion
318
+
319
+ column_row = astuple(
320
+ ColumnsTableRow(
321
+ TABLE_SCHEMA=db_name,
322
+ TABLE_NAME=table_name,
323
+ COLUMN_NAME=column_name,
324
+ DATA_TYPE=column_type.value,
325
+ ORDINAL_POSITION=i
326
+ )
327
+ )
328
+
329
+ result.append(column_row)
337
330
 
338
331
  df = pd.DataFrame(result, columns=cls.columns)
339
332
  return df
File without changes
@@ -0,0 +1,152 @@
1
+ from contextlib import asynccontextmanager
2
+ from collections.abc import AsyncIterator
3
+ from typing import Optional, Dict, Any
4
+ from dataclasses import dataclass
5
+
6
+ from mcp.server.fastmcp import FastMCP
7
+ from mindsdb.api.mysql.mysql_proxy.classes.fake_mysql_proxy import FakeMysqlProxy
8
+ from mindsdb.api.executor.data_types.response_type import RESPONSE_TYPE as SQL_RESPONSE_TYPE
9
+ from mindsdb.utilities import log
10
+ from mindsdb.utilities.config import Config
11
+ from mindsdb.interfaces.storage import db
12
+
13
+ logger = log.getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class AppContext:
18
+ db: Any
19
+
20
+
21
+ @asynccontextmanager
22
+ async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
23
+ """Manage application lifecycle with type-safe context"""
24
+ # Initialize on startup
25
+ db.init()
26
+ try:
27
+ yield AppContext(db=db)
28
+ finally:
29
+ # TODO: We need better way to handle this in storage/db.py
30
+ pass
31
+
32
+
33
+ # Configure server with lifespan
34
+ mcp = FastMCP(
35
+ "MindsDB",
36
+ lifespan=app_lifespan,
37
+ dependencies=["mindsdb"] # Add any additional dependencies
38
+ )
39
+ # MCP Queries
40
+ LISTING_QUERY = "SHOW DATABASES"
41
+
42
+
43
+ @mcp.tool()
44
+ def query(query: str, context: Optional[Dict] = None) -> Dict[str, Any]:
45
+ """
46
+ Execute a SQL query against MindsDB
47
+
48
+ Args:
49
+ query: The SQL query to execute
50
+ context: Optional context parameters for the query
51
+
52
+ Returns:
53
+ Dict containing the query results or error information
54
+ """
55
+
56
+ if context is None:
57
+ context = {}
58
+
59
+ logger.debug(f'Incoming MCP query: {query}')
60
+
61
+ mysql_proxy = FakeMysqlProxy()
62
+ mysql_proxy.set_context(context)
63
+
64
+ try:
65
+ result = mysql_proxy.process_query(query)
66
+
67
+ if result.type == SQL_RESPONSE_TYPE.OK:
68
+ return {"type": SQL_RESPONSE_TYPE.OK}
69
+
70
+ if result.type == SQL_RESPONSE_TYPE.TABLE:
71
+ return {
72
+ "type": SQL_RESPONSE_TYPE.TABLE,
73
+ "data": result.data.to_lists(json_types=True),
74
+ "column_names": [
75
+ x["alias"] or x["name"] if "alias" in x else x["name"]
76
+ for x in result.columns
77
+ ],
78
+ }
79
+ else:
80
+ return {
81
+ "type": SQL_RESPONSE_TYPE.ERROR,
82
+ "error_code": 0,
83
+ "error_message": "Unknown response type"
84
+ }
85
+
86
+ except Exception as e:
87
+ logger.error(f"Error processing query: {str(e)}")
88
+ return {
89
+ "type": SQL_RESPONSE_TYPE.ERROR,
90
+ "error_code": 0,
91
+ "error_message": str(e)
92
+ }
93
+
94
+
95
+ @mcp.tool()
96
+ def list_databases() -> Dict[str, Any]:
97
+ """
98
+ List all databases in MindsDB along with their tables
99
+
100
+ Returns:
101
+ Dict containing the list of databases and their tables
102
+ """
103
+
104
+ mysql_proxy = FakeMysqlProxy()
105
+
106
+ try:
107
+ result = mysql_proxy.process_query(LISTING_QUERY)
108
+ if result.type == SQL_RESPONSE_TYPE.ERROR:
109
+ return {
110
+ "type": "error",
111
+ "error_code": result.error_code,
112
+ "error_message": result.error_message,
113
+ }
114
+
115
+ elif result.type == SQL_RESPONSE_TYPE.OK:
116
+ return {"type": "ok"}
117
+
118
+ elif result.type == SQL_RESPONSE_TYPE.TABLE:
119
+ data = result.data.to_lists(json_types=True)
120
+ return data
121
+
122
+ except Exception as e:
123
+ return {
124
+ "type": "error",
125
+ "error_code": 0,
126
+ "error_message": str(e),
127
+ }
128
+
129
+
130
+ def start(*args, **kwargs):
131
+ """Start the MCP server
132
+ Args:
133
+ host (str): Host to bind to
134
+ port (int): Port to listen on
135
+ """
136
+ config = Config()
137
+ port = int(config['api'].get('mcp', {}).get('port', 47337))
138
+ host = config['api'].get('mcp', {}).get('host', '127.0.0.1')
139
+
140
+ logger.info(f"Starting MCP server on {host}:{port}")
141
+ mcp.settings.host = host
142
+ mcp.settings.port = port
143
+
144
+ try:
145
+ mcp.run(transport="sse") # Use SSE transport instead of stdio
146
+ except Exception as e:
147
+ logger.error(f"Error starting MCP server: {str(e)}")
148
+ raise
149
+
150
+
151
+ if __name__ == "__main__":
152
+ start()
@@ -8,6 +8,7 @@
8
8
  * permission of MindsDB Inc
9
9
  *******************************************************
10
10
  """
11
+ import enum
11
12
 
12
13
  # CAPABILITIES
13
14
  # As defined in : https://dev.mysql.com/doc/dev/mysql-server/8.0.0/group__group__cs__capabilities__flags.html
@@ -101,6 +102,8 @@ COMMANDS = COMMANDS()
101
102
 
102
103
 
103
104
  # FIELD TYPES
105
+ # https://dev.mysql.com/doc/dev/mysql-server/latest/field__types_8h_source.html
106
+ # https://mariadb.com/kb/en/result-set-packets/
104
107
  class TYPES(object):
105
108
  __slots__ = ()
106
109
  MYSQL_TYPE_DECIMAL = 0
@@ -123,6 +126,11 @@ class TYPES(object):
123
126
  MYSQL_TYPE_TIMESTAMP2 = 17
124
127
  MYSQL_TYPE_DATETIME2 = 18
125
128
  MYSQL_TYPE_TIME2 = 19
129
+ MYSQL_TYPE_TYPED_ARRAY = 20
130
+ MYSQL_TYPE_VECTOR = 242,
131
+ MYSQL_TYPE_INVALID = 243,
132
+ MYSQL_TYPE_BOOL = 244,
133
+ MYSQL_TYPE_JSON = 245,
126
134
  MYSQL_TYPE_NEWDECIMAL = 246
127
135
  MYSQL_TYPE_ENUM = 247
128
136
  MYSQL_TYPE_SET = 248
@@ -135,9 +143,75 @@ class TYPES(object):
135
143
  MYSQL_TYPE_GEOMETRY = 255
136
144
 
137
145
 
146
+ C_TYPES = TYPES()
138
147
  TYPES = TYPES()
139
148
 
140
149
 
150
+ class MYSQL_DATA_TYPE(enum.Enum):
151
+ TINYINT = 'TINYINT'
152
+ SMALLINT = 'SMALLINT'
153
+ MEDIUMINT = 'MEDIUMINT'
154
+ INT = 'INT'
155
+ BIGINT = 'BIGINT'
156
+ FLOAT = 'FLOAT'
157
+ DOUBLE = 'DOUBLE'
158
+ DECIMAL = 'DECIMAL'
159
+ YEAR = 'YEAR'
160
+ TIME = 'TIME'
161
+ DATE = 'DATE'
162
+ DATETIME = 'DATETIME'
163
+ TIMESTAMP = 'TIMESTAMP'
164
+ CHAR = 'CHAR'
165
+ BINARY = 'BINARY'
166
+ VARCHAR = 'VARCHAR'
167
+ VARBINARY = 'VARBINARY'
168
+ TINYBLOB = 'TINYBLOB'
169
+ TINYTEXT = 'TINYTEXT'
170
+ BLOB = 'BLOB'
171
+ TEXT = 'TEXT'
172
+ MEDIUMBLOB = 'MEDIUMBLOB'
173
+ MEDIUMTEXT = 'MEDIUMTEXT'
174
+ LONGBLOB = 'LONGBLOB'
175
+ LONGTEXT = 'LONGTEXT'
176
+ BIT = 'BIT'
177
+ BOOL = 'BOOL'
178
+ BOOLEAN = 'BOOLEAN'
179
+
180
+
181
+ # Map between data types and C types
182
+ # https://dev.mysql.com/doc/c-api/8.0/en/c-api-prepared-statement-type-codes.html
183
+ DATA_C_TYPE_MAP = {
184
+ MYSQL_DATA_TYPE.TINYINT: C_TYPES.MYSQL_TYPE_TINY,
185
+ MYSQL_DATA_TYPE.SMALLINT: C_TYPES.MYSQL_TYPE_SHORT,
186
+ MYSQL_DATA_TYPE.MEDIUMINT: C_TYPES.MYSQL_TYPE_INT24,
187
+ MYSQL_DATA_TYPE.INT: C_TYPES.MYSQL_TYPE_LONG,
188
+ MYSQL_DATA_TYPE.BIGINT: C_TYPES.MYSQL_TYPE_LONGLONG,
189
+ MYSQL_DATA_TYPE.FLOAT: C_TYPES.MYSQL_TYPE_FLOAT,
190
+ MYSQL_DATA_TYPE.DOUBLE: C_TYPES.MYSQL_TYPE_DOUBLE,
191
+ MYSQL_DATA_TYPE.DECIMAL: C_TYPES.MYSQL_TYPE_NEWDECIMAL,
192
+ MYSQL_DATA_TYPE.YEAR: C_TYPES.MYSQL_TYPE_SHORT,
193
+ MYSQL_DATA_TYPE.TIME: C_TYPES.MYSQL_TYPE_TIME,
194
+ MYSQL_DATA_TYPE.DATE: C_TYPES.MYSQL_TYPE_DATE,
195
+ MYSQL_DATA_TYPE.DATETIME: C_TYPES.MYSQL_TYPE_DATETIME,
196
+ MYSQL_DATA_TYPE.TIMESTAMP: C_TYPES.MYSQL_TYPE_TIMESTAMP,
197
+ MYSQL_DATA_TYPE.CHAR: C_TYPES.MYSQL_TYPE_STRING,
198
+ MYSQL_DATA_TYPE.BINARY: C_TYPES.MYSQL_TYPE_STRING,
199
+ MYSQL_DATA_TYPE.VARCHAR: C_TYPES.MYSQL_TYPE_VAR_STRING,
200
+ MYSQL_DATA_TYPE.VARBINARY: C_TYPES.MYSQL_TYPE_VAR_STRING,
201
+ MYSQL_DATA_TYPE.TINYBLOB: C_TYPES.MYSQL_TYPE_TINY_BLOB,
202
+ MYSQL_DATA_TYPE.TINYTEXT: C_TYPES.MYSQL_TYPE_TINY_BLOB,
203
+ MYSQL_DATA_TYPE.BLOB: C_TYPES.MYSQL_TYPE_BLOB,
204
+ MYSQL_DATA_TYPE.TEXT: C_TYPES.MYSQL_TYPE_BLOB,
205
+ MYSQL_DATA_TYPE.MEDIUMBLOB: C_TYPES.MYSQL_TYPE_MEDIUM_BLOB,
206
+ MYSQL_DATA_TYPE.MEDIUMTEXT: C_TYPES.MYSQL_TYPE_MEDIUM_BLOB,
207
+ MYSQL_DATA_TYPE.LONGBLOB: C_TYPES.MYSQL_TYPE_LONG_BLOB,
208
+ MYSQL_DATA_TYPE.LONGTEXT: C_TYPES.MYSQL_TYPE_LONG_BLOB,
209
+ MYSQL_DATA_TYPE.BIT: C_TYPES.MYSQL_TYPE_BIT,
210
+ MYSQL_DATA_TYPE.BOOL: C_TYPES.MYSQL_TYPE_TINY,
211
+ MYSQL_DATA_TYPE.BOOLEAN: C_TYPES.MYSQL_TYPE_TINY
212
+ }
213
+
214
+
141
215
  class FIELD_FLAG(object):
142
216
  __slots__ = ()
143
217
  NOT_NULL = 1 # field cannot be null
@@ -149,8 +149,20 @@ class ConfluenceAPIClient:
149
149
  results.extend(response["results"])
150
150
 
151
151
  while response["_links"].get("next"):
152
- params["cursor"] = response["_links"].get("next")
153
- response = self._make_request("GET", url, params)
152
+ next_url = response["_links"].get("next")
153
+ next_params = {}
154
+ if params:
155
+ next_params.update(params)
156
+ if "cursor=" in next_url:
157
+ # cursor= is 7 characters long
158
+ cursor_start = next_url.find("cursor=") + 7
159
+ cursor_value = next_url[cursor_start:]
160
+ if "&" in cursor_value:
161
+ cursor_value = cursor_value.split("&")[0]
162
+ next_params["cursor"] = cursor_value
163
+ response = self._make_request("GET", url, next_params)
164
+ else:
165
+ response = self._make_request("GET", next_url)
154
166
  results.extend(response["results"])
155
167
 
156
168
  return results