mysqlx-connector-python 8.3.0__py2.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.
mysqlx/result.py ADDED
@@ -0,0 +1,1177 @@
1
+ # Copyright (c) 2016, 2023, Oracle and/or its affiliates.
2
+ #
3
+ # This program is free software; you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License, version 2.0, as
5
+ # published by the Free Software Foundation.
6
+ #
7
+ # This program is also distributed with certain software (including
8
+ # but not limited to OpenSSL) that is licensed under separate terms,
9
+ # as designated in a particular file or component or in included license
10
+ # documentation. The authors of MySQL hereby grant you an
11
+ # additional permission to link the program and your derivative works
12
+ # with the separately licensed software that they have included with
13
+ # MySQL.
14
+ #
15
+ # Without limiting anything contained in the foregoing, this file,
16
+ # which is part of MySQL Connector/Python, is also subject to the
17
+ # Universal FOSS Exception, version 1.0, a copy of which can be found at
18
+ # http://oss.oracle.com/licenses/universal-foss-exception.
19
+ #
20
+ # This program is distributed in the hope that it will be useful, but
21
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
22
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23
+ # See the GNU General Public License, version 2.0, for more details.
24
+ #
25
+ # You should have received a copy of the GNU General Public License
26
+ # along with this program; if not, write to the Free Software Foundation, Inc.,
27
+ # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
28
+
29
+ """Implementation of the Result classes."""
30
+
31
+ from __future__ import annotations
32
+
33
+ import decimal
34
+ import struct
35
+ import sys
36
+
37
+ from datetime import datetime, timedelta
38
+ from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union
39
+
40
+ from .charsets import MYSQL_CHARACTER_SETS
41
+ from .dbdoc import DbDoc
42
+ from .helpers import decode_from_bytes, deprecated
43
+ from .types import ConnectionType, FieldTypes
44
+
45
+
46
+ # pylint: disable=missing-class-docstring,missing-function-docstring
47
+ def from_protobuf(column: Column, payload: bytes) -> Any:
48
+ if len(payload) == 0:
49
+ return None
50
+
51
+ if column.get_type() == ColumnType.STRING:
52
+ return decode_from_bytes(payload[:-1]) # Strip trailing char
53
+
54
+ try:
55
+ return ColumnProtoType.converter_map[column.get_proto_type()](payload)
56
+ except KeyError as err:
57
+ sys.stderr.write(f"{err}")
58
+ sys.stderr.write(f"{payload.encode('hex')}") # type: ignore[attr-defined]
59
+ return None
60
+
61
+
62
+ def bytes_from_protobuf(payload: bytes) -> bytes:
63
+ # Strip trailing char
64
+ return payload[:-1]
65
+
66
+
67
+ def float_from_protobuf(payload: bytes) -> float:
68
+ assert len(payload) == 4
69
+ return struct.unpack("<f", payload)[0]
70
+
71
+
72
+ def double_from_protobuf(payload: bytes) -> float:
73
+ assert len(payload) == 8
74
+ return struct.unpack("<d", payload)[0]
75
+
76
+
77
+ def varint_from_protobuf_stream(payload: bytes) -> Tuple[int, bytes]:
78
+ if len(payload) == 0:
79
+ raise ValueError("Payload is empty")
80
+
81
+ cur = 0
82
+ i = 0
83
+ shift = 0
84
+
85
+ for item in payload:
86
+ char = item if isinstance(item, int) else ord(item) # type: ignore[arg-type]
87
+ eos = (char & 0x80) == 0
88
+ cur_bits = char & 0x7F
89
+ cur_bits <<= shift
90
+ i |= cur_bits
91
+ if eos:
92
+ return i, payload[cur + 1 :]
93
+ cur += 1
94
+ shift += 7
95
+
96
+ raise EOFError("Payload too short")
97
+
98
+
99
+ def varint_from_protobuf(payload: bytes) -> int:
100
+ i, payload = varint_from_protobuf_stream(payload)
101
+ if len(payload) != 0:
102
+ raise ValueError("Payload too long")
103
+
104
+ return i
105
+
106
+
107
+ def varsint_from_protobuf(payload: bytes) -> int:
108
+ i, payload = varint_from_protobuf_stream(payload)
109
+ if len(payload) != 0:
110
+ raise ValueError("Payload too long")
111
+
112
+ # Zigzag encoded, revert it
113
+ if i & 0x1:
114
+ i = ~i
115
+ i = i >> 1
116
+ i |= 1 << 63
117
+ else:
118
+ i = i >> 1
119
+
120
+ return i
121
+
122
+
123
+ def set_from_protobuf(payload: bytes) -> List[bytes]:
124
+ set_pb: List = []
125
+ while True:
126
+ try:
127
+ field_len, payload = varint_from_protobuf_stream(payload)
128
+ if len(payload) < field_len:
129
+ if len(payload) == 0 and field_len == 1 and len(set_pb) == 0:
130
+ # Special case for empty set
131
+ return []
132
+ raise ValueError("Invalid Set encoding")
133
+
134
+ set_pb.append(payload[:field_len])
135
+ payload = payload[field_len:]
136
+ if len(payload) == 0:
137
+ # Done
138
+ break
139
+ except ValueError:
140
+ break
141
+ return set_pb
142
+
143
+
144
+ def decimal_from_protobuf(payload: bytes) -> decimal.Decimal:
145
+ digits = []
146
+ sign = None
147
+ scale = payload[0] if isinstance(payload[0], int) else ord(payload[0]) # type: ignore[arg-type]
148
+ payload = payload[1:]
149
+
150
+ for item in payload:
151
+ char = item if isinstance(item, int) else ord(item) # type: ignore[arg-type]
152
+ high_bcd = (char & 0xF0) >> 4
153
+ low_bcd = char & 0x0F
154
+ if high_bcd < 0x0A:
155
+ digits.append(high_bcd)
156
+ if low_bcd < 0x0A:
157
+ digits.append(low_bcd)
158
+ elif low_bcd == 0x0C:
159
+ sign = 0
160
+ break
161
+ elif low_bcd == 0x0D:
162
+ sign = 1
163
+ break
164
+ else:
165
+ raise ValueError("Invalid BCD")
166
+ elif high_bcd == 0x0C:
167
+ sign = 0
168
+ assert low_bcd == 0x00
169
+ break
170
+ elif high_bcd == 0x0D:
171
+ sign = 1
172
+ assert low_bcd == 0x00
173
+ break
174
+ else:
175
+ raise ValueError(f"Invalid BCD: {high_bcd}")
176
+
177
+ return decimal.Decimal((sign, digits, -scale))
178
+
179
+
180
+ def datetime_from_protobuf(payload: bytes) -> datetime:
181
+ # A sequence of varints
182
+ hour = 0
183
+ minutes = 0
184
+ seconds = 0
185
+ useconds = 0
186
+ year, payload = varint_from_protobuf_stream(payload)
187
+ month, payload = varint_from_protobuf_stream(payload)
188
+ day, payload = varint_from_protobuf_stream(payload)
189
+
190
+ try:
191
+ hour, payload = varint_from_protobuf_stream(payload)
192
+ minutes, payload = varint_from_protobuf_stream(payload)
193
+ seconds, payload = varint_from_protobuf_stream(payload)
194
+ useconds, payload = varint_from_protobuf_stream(payload)
195
+ except ValueError:
196
+ pass
197
+
198
+ return datetime(year, month, day, hour, minutes, seconds, useconds)
199
+
200
+
201
+ def time_from_protobuf(payload: bytes) -> timedelta:
202
+ # A sequence of varints
203
+ hour = 0
204
+ minutes = 0
205
+ seconds = 0
206
+ useconds = 0
207
+ negate = payload[0] == 1
208
+ payload = payload[1:]
209
+
210
+ try:
211
+ hour, payload = varint_from_protobuf_stream(payload)
212
+ minutes, payload = varint_from_protobuf_stream(payload)
213
+ seconds, payload = varint_from_protobuf_stream(payload)
214
+ useconds, payload = varint_from_protobuf_stream(payload)
215
+ except ValueError:
216
+ pass
217
+
218
+ if negate:
219
+ # Negate the first non-zero value
220
+ if hour:
221
+ hour *= -1
222
+ elif minutes:
223
+ minutes *= -1
224
+ elif seconds:
225
+ seconds *= -1
226
+ elif useconds:
227
+ useconds *= -1
228
+
229
+ return timedelta(
230
+ hours=hour, minutes=minutes, seconds=seconds, microseconds=useconds
231
+ )
232
+
233
+
234
+ class Collations:
235
+ UTF8_GENERAL_CI = 33
236
+
237
+
238
+ class ColumnType:
239
+ BIT = 1
240
+ TINYINT = 2
241
+ SMALLINT = 3
242
+ MEDIUMINT = 4
243
+ INT = 5
244
+ BIGINT = 6
245
+ REAL = 7
246
+ FLOAT = 8
247
+ DECIMAL = 9
248
+ NUMERIC = 10
249
+ DOUBLE = 11
250
+ JSON = 12
251
+ STRING = 13
252
+ BYTES = 14
253
+ TIME = 15
254
+ DATE = 16
255
+ DATETIME = 17
256
+ TIMESTAMP = 18
257
+ SET = 19
258
+ ENUM = 20
259
+ GEOMETRY = 21
260
+ XML = 22
261
+ YEAR = 23
262
+ CHAR = 24
263
+ VARCHAR = 25
264
+ BINARY = 26
265
+ VARBINARY = 27
266
+ TINYBLOB = 28
267
+ BLOB = 29
268
+ MEDIUMBLOB = 30
269
+ LONGBLOB = 31
270
+ TINYTEXT = 32
271
+ TEXT = 33
272
+ MEDIUMTEXT = 34
273
+ LONGTEXT = 35
274
+
275
+ @classmethod
276
+ def to_string(cls, needle: Any) -> Optional[str]:
277
+ for key, value in vars(cls).items():
278
+ if value == needle:
279
+ return key
280
+ return None
281
+
282
+ @classmethod
283
+ def from_string(cls, key: str) -> Any:
284
+ return getattr(cls, key.upper(), None)
285
+
286
+ @classmethod
287
+ def is_char(cls, col_type: int) -> bool:
288
+ return col_type in (
289
+ cls.CHAR,
290
+ cls.VARCHAR,
291
+ )
292
+
293
+ @classmethod
294
+ def is_binary(cls, col_type: int) -> bool:
295
+ return col_type in (
296
+ cls.BINARY,
297
+ cls.VARBINARY,
298
+ )
299
+
300
+ @classmethod
301
+ def is_text(cls, col_type: int) -> bool:
302
+ return col_type in (
303
+ cls.TEXT,
304
+ cls.TINYTEXT,
305
+ cls.MEDIUMTEXT,
306
+ cls.LONGTEXT,
307
+ )
308
+
309
+ @classmethod
310
+ def is_decimals(cls, col_type: int) -> bool:
311
+ return col_type in (
312
+ cls.REAL,
313
+ cls.DOUBLE,
314
+ cls.FLOAT,
315
+ cls.DECIMAL,
316
+ cls.NUMERIC,
317
+ )
318
+
319
+ @classmethod
320
+ def is_numeric(cls, col_type: int) -> bool:
321
+ return col_type in (
322
+ cls.BIT,
323
+ cls.TINYINT,
324
+ cls.SMALLINT,
325
+ cls.MEDIUMINT,
326
+ cls.INT,
327
+ cls.BIGINT,
328
+ )
329
+
330
+ @classmethod
331
+ def is_finite_set(cls, col_type: int) -> bool:
332
+ return col_type in (
333
+ cls.SET,
334
+ cls.ENUM,
335
+ )
336
+
337
+
338
+ class ColumnProtoType:
339
+ SINT = 1
340
+ UINT = 2
341
+ DOUBLE = 5
342
+ FLOAT = 6
343
+ BYTES = 7
344
+ TIME = 10
345
+ DATETIME = 12
346
+ SET = 15
347
+ ENUM = 16
348
+ BIT = 17
349
+ DECIMAL = 18
350
+
351
+ converter_map: Dict[int, Callable[[bytes], Any]] = {
352
+ SINT: varsint_from_protobuf,
353
+ UINT: varint_from_protobuf,
354
+ BYTES: bytes_from_protobuf,
355
+ DATETIME: datetime_from_protobuf,
356
+ TIME: time_from_protobuf,
357
+ FLOAT: float_from_protobuf,
358
+ DOUBLE: double_from_protobuf,
359
+ BIT: varint_from_protobuf,
360
+ SET: set_from_protobuf,
361
+ ENUM: bytes_from_protobuf,
362
+ DECIMAL: decimal_from_protobuf,
363
+ }
364
+
365
+
366
+ class Flags:
367
+ def __init__(self, value: int) -> None:
368
+ self._allowed_flags: Dict[str, int] = {}
369
+ self._flag_names: Dict[int, str] = {}
370
+ for key, val in self.__class__.__dict__.items():
371
+ if key.startswith("__"):
372
+ continue
373
+ if isinstance(val, int):
374
+ self._allowed_flags[key] = val
375
+ self._flag_names[val] = key
376
+ self._value: int = value
377
+
378
+ def __str__(self) -> str:
379
+ mask = 1
380
+ flag_names = []
381
+ value = self._value
382
+
383
+ for _ in range(0, 63):
384
+ mask <<= 1
385
+ flag = value & mask
386
+ if flag:
387
+ # We matched something, find the name for it
388
+ try:
389
+ flag_names.append(self._flag_names[flag])
390
+ except KeyError:
391
+ sys.stderr.write(f"{self._flag_names}")
392
+ sys.stderr.write(f"{self.__class__.__dict__}")
393
+
394
+ return ",".join(flag_names)
395
+
396
+ @property
397
+ def value(self) -> int:
398
+ return self._value
399
+
400
+ @value.setter
401
+ def value(self, val: int) -> None:
402
+ self._value = val
403
+
404
+
405
+ class ColumnFlags(Flags):
406
+ NOT_NULL = 0x0010
407
+ PRIMARY_KEY = 0x0020
408
+ UNIQUE_KEY = 0x0040
409
+ MULTIPLE_KEY = 0x0080
410
+ AUTO_INCREMENT = 0x0100
411
+
412
+
413
+ class DatetimeColumnFlags(ColumnFlags):
414
+ TIMESTAMP = 0x0001
415
+
416
+
417
+ class UIntColumnFlags(ColumnFlags):
418
+ ZEROFILL = 0x0001
419
+
420
+
421
+ class DoubleColumnFlags(ColumnFlags):
422
+ UNSIGNED = 0x0001
423
+
424
+
425
+ class FloatColumnFlags(ColumnFlags):
426
+ UNSIGNED = 0x0001
427
+
428
+
429
+ class BytesColumnFlags(ColumnFlags):
430
+ RIGHT_PAD = 0x0001
431
+
432
+
433
+ class BytesContentType(ColumnFlags):
434
+ GEOMETRY = 0x0001
435
+ JSON = 0x0002
436
+ XML = 0x0003
437
+
438
+
439
+ # pylint: enable=missing-class-docstring,missing-function-docstring
440
+
441
+
442
+ class Column:
443
+ """Represents meta data for a table column.
444
+
445
+ Args:
446
+ col_type (int): The column type.
447
+ catalog (str): The catalog.
448
+ schema (str): The schema name.
449
+ table (str): The table name.
450
+ original_table (str): The original table name.
451
+ name (str): The column name.
452
+ original_name (str): The original table name.
453
+ length (int): The column length,
454
+ collation (str): The collation name.
455
+ fractional_digits (int): The fractional digits.
456
+ flags (int): The flags.
457
+ content_type (int): The content type.
458
+
459
+ .. versionchanged:: 8.0.12
460
+ """
461
+
462
+ def __init__(
463
+ self,
464
+ col_type: int,
465
+ catalog: Optional[str] = None,
466
+ schema: Optional[str] = None,
467
+ table: Optional[str] = None,
468
+ original_table: Optional[str] = None,
469
+ name: Optional[str] = None,
470
+ original_name: Optional[str] = None,
471
+ length: Optional[int] = None,
472
+ collation: Optional[int] = None,
473
+ fractional_digits: Optional[int] = None,
474
+ flags: Optional[int] = None,
475
+ content_type: Optional[int] = None,
476
+ ) -> None:
477
+ self._schema: str = decode_from_bytes(schema)
478
+ self._name: str = decode_from_bytes(name)
479
+ self._original_name: str = decode_from_bytes(original_name)
480
+ self._table: str = decode_from_bytes(table)
481
+ self._original_table: str = decode_from_bytes(original_table)
482
+ self._proto_type: int = col_type
483
+ self._col_type: Optional[int] = None
484
+ self._catalog: Optional[str] = catalog
485
+ self._length: Optional[int] = length
486
+ self._collation: Optional[int] = collation
487
+ self._fractional_digits: Optional[int] = fractional_digits
488
+ self._flags: Optional[int] = flags
489
+ self._content_type: Optional[int] = content_type
490
+ self._number_signed: bool = False
491
+ self._is_padded: Union[bool, int] = False
492
+ self._is_binary: bool = False
493
+ self._is_bytes: bool = False
494
+ self._collation_name: Optional[str] = None
495
+ self._character_set_name: Optional[str] = None
496
+ self._zero_fill: Optional[int] = None
497
+
498
+ if self._collation > 0:
499
+ if self._collation >= len(MYSQL_CHARACTER_SETS):
500
+ raise ValueError(f"No mapping found for collation {self._collation}")
501
+ info = MYSQL_CHARACTER_SETS[self._collation]
502
+ self._character_set_name = info[0]
503
+ self._collation_name = info[1]
504
+ self._is_binary = (
505
+ "binary" in self._collation_name or "_bin" in self._collation_name
506
+ )
507
+ self._map_type()
508
+ self._is_bytes = self._col_type in (
509
+ ColumnType.GEOMETRY,
510
+ ColumnType.JSON,
511
+ ColumnType.XML,
512
+ ColumnType.BYTES,
513
+ ColumnType.STRING,
514
+ )
515
+
516
+ def __str__(self) -> str:
517
+ return str(
518
+ {
519
+ "col_type": self._col_type,
520
+ "schema": self._schema,
521
+ "table": self._table,
522
+ "flags": str(self._flags),
523
+ }
524
+ )
525
+
526
+ def _map_bytes(self) -> None:
527
+ """Map bytes."""
528
+ if self._content_type == BytesContentType.GEOMETRY:
529
+ self._col_type = ColumnType.GEOMETRY
530
+ elif self._content_type == BytesContentType.JSON:
531
+ self._col_type = ColumnType.JSON
532
+ elif self._content_type == BytesContentType.XML:
533
+ self._col_type = ColumnType.XML
534
+ elif self._is_binary:
535
+ self._col_type = ColumnType.BYTES
536
+ else:
537
+ self._col_type = ColumnType.STRING
538
+ self._is_padded = self._flags & 1
539
+
540
+ def _map_datetime(self) -> None:
541
+ """Map datetime."""
542
+ if self._length == 10:
543
+ self._col_type = ColumnType.DATE
544
+ elif self._flags & DatetimeColumnFlags.TIMESTAMP > 0:
545
+ self._col_type = ColumnType.TIMESTAMP
546
+ elif self._length >= 19:
547
+ self._col_type = ColumnType.DATETIME
548
+ else:
549
+ raise ValueError("Datetime mapping scenario unhandled")
550
+
551
+ def _map_int_type(self) -> None:
552
+ """Map int type."""
553
+ if self._length <= 4:
554
+ self._col_type = ColumnType.TINYINT
555
+ elif self._length <= 6:
556
+ self._col_type = ColumnType.SMALLINT
557
+ elif self._length <= 9:
558
+ self._col_type = ColumnType.MEDIUMINT
559
+ elif self._length <= 11:
560
+ self._col_type = ColumnType.INT
561
+ else:
562
+ self._col_type = ColumnType.BIGINT
563
+ self._number_signed = True
564
+
565
+ def _map_uint_type(self) -> None:
566
+ """Map uint type."""
567
+ if self._length <= 3:
568
+ self._col_type = ColumnType.TINYINT
569
+ elif self._length <= 5:
570
+ self._col_type = ColumnType.SMALLINT
571
+ elif self._length <= 8:
572
+ self._col_type = ColumnType.MEDIUMINT
573
+ elif self._length <= 10:
574
+ self._col_type = ColumnType.INT
575
+ else:
576
+ self._col_type = ColumnType.BIGINT
577
+ self._zero_fill = self._flags & 1
578
+
579
+ def _map_type(self) -> None:
580
+ """Map type."""
581
+ if self._proto_type == ColumnProtoType.SINT:
582
+ self._map_int_type()
583
+ elif self._proto_type == ColumnProtoType.UINT:
584
+ self._map_uint_type()
585
+ elif self._proto_type == ColumnProtoType.FLOAT:
586
+ self._col_type = ColumnType.FLOAT
587
+ self._is_number_signed = (self._flags & FloatColumnFlags.UNSIGNED) == 0
588
+ elif self._proto_type == ColumnProtoType.DECIMAL:
589
+ self._col_type = ColumnType.DECIMAL
590
+ self._is_number_signed = (self._flags & FloatColumnFlags.UNSIGNED) == 0
591
+ elif self._proto_type == ColumnProtoType.DOUBLE:
592
+ self._col_type = ColumnType.DOUBLE
593
+ self._is_number_signed = (self._flags & FloatColumnFlags.UNSIGNED) == 0
594
+ elif self._proto_type == ColumnProtoType.BYTES:
595
+ self._map_bytes()
596
+ elif self._proto_type == ColumnProtoType.TIME:
597
+ self._col_type = ColumnType.TIME
598
+ elif self._proto_type == ColumnProtoType.DATETIME:
599
+ self._map_datetime()
600
+ elif self._proto_type == ColumnProtoType.SET:
601
+ self._col_type = ColumnType.SET
602
+ elif self._proto_type == ColumnProtoType.ENUM:
603
+ self._col_type = ColumnType.ENUM
604
+ elif self._proto_type == ColumnProtoType.BIT:
605
+ self._col_type = ColumnType.BIT
606
+ else:
607
+ raise ValueError(f"Unknown column type {self._proto_type}")
608
+
609
+ @property
610
+ def schema_name(self) -> str:
611
+ """str: The schema name.
612
+
613
+ .. versionadded:: 8.0.12
614
+ """
615
+ return self._schema
616
+
617
+ @property
618
+ def table_name(self) -> str:
619
+ """str: The table name.
620
+
621
+ .. versionadded:: 8.0.12
622
+ """
623
+ return self._original_table or self._table
624
+
625
+ @property
626
+ def table_label(self) -> str:
627
+ """str: The table label.
628
+
629
+ .. versionadded:: 8.0.12
630
+ """
631
+ return self._table or self._original_table
632
+
633
+ @property
634
+ def column_name(self) -> str:
635
+ """str: The column name.
636
+
637
+ .. versionadded:: 8.0.12
638
+ """
639
+ return self._original_name or self._name
640
+
641
+ @property
642
+ def column_label(self) -> str:
643
+ """str: The column label.
644
+
645
+ .. versionadded:: 8.0.12
646
+ """
647
+ return self._name or self._original_name
648
+
649
+ @property
650
+ def type(self) -> int:
651
+ """int: The column type.
652
+
653
+ .. versionadded:: 8.0.12
654
+ """
655
+ return self._col_type
656
+
657
+ @property
658
+ def length(self) -> int:
659
+ """int. The column length.
660
+
661
+ .. versionadded:: 8.0.12
662
+ """
663
+ return self._length
664
+
665
+ @property
666
+ def fractional_digits(self) -> int:
667
+ """int: The column fractional digits.
668
+
669
+ .. versionadded:: 8.0.12
670
+ """
671
+ return self._fractional_digits
672
+
673
+ @property
674
+ def collation_name(self) -> str:
675
+ """str: The collation name.
676
+
677
+ .. versionadded:: 8.0.12
678
+ """
679
+ return self._collation_name
680
+
681
+ @property
682
+ def character_set_name(self) -> str:
683
+ """str: The character set name.
684
+
685
+ .. versionadded:: 8.0.12
686
+ """
687
+ return self._character_set_name
688
+
689
+ def get_schema_name(self) -> str:
690
+ """Returns the schema name.
691
+
692
+ Returns:
693
+ str: The schema name.
694
+ """
695
+ return self._schema
696
+
697
+ def get_table_name(self) -> str:
698
+ """Returns the table name.
699
+
700
+ Returns:
701
+ str: The table name.
702
+ """
703
+ return self._original_table or self._table
704
+
705
+ def get_table_label(self) -> str:
706
+ """Returns the table label.
707
+
708
+ Returns:
709
+ str: The table label.
710
+ """
711
+ return self._table or self._original_table
712
+
713
+ def get_column_name(self) -> str:
714
+ """Returns the column name.
715
+
716
+ Returns:
717
+ str: The column name.
718
+ """
719
+ return self._original_name or self._name
720
+
721
+ def get_column_label(self) -> str:
722
+ """Returns the column label.
723
+
724
+ Returns:
725
+ str: The column label.
726
+ """
727
+ return self._name or self._original_name
728
+
729
+ def get_proto_type(self) -> int:
730
+ """Returns the column proto type.
731
+
732
+ Returns:
733
+ int: The column proto type.
734
+ """
735
+ return self._proto_type
736
+
737
+ def get_type(self) -> int:
738
+ """Returns the column type.
739
+
740
+ Returns:
741
+ int: The column type.
742
+ """
743
+ return self._col_type
744
+
745
+ def get_length(self) -> int:
746
+ """Returns the column length.
747
+
748
+ Returns:
749
+ int: The column length.
750
+ """
751
+ return self._length
752
+
753
+ def get_fractional_digits(self) -> int:
754
+ """Returns the column fractional digits.
755
+
756
+ Returns:
757
+ int: The column fractional digits.
758
+ """
759
+ return self._fractional_digits
760
+
761
+ def get_collation_name(self) -> str:
762
+ """Returns the collation name.
763
+
764
+ Returns:
765
+ str: The collation name.
766
+ """
767
+ return self._collation_name
768
+
769
+ def get_character_set_name(self) -> str:
770
+ """Returns the character set name.
771
+
772
+ Returns:
773
+ str: The character set name.
774
+ """
775
+ return self._character_set_name
776
+
777
+ def is_number_signed(self) -> bool:
778
+ """Returns `True` if is a number signed.
779
+
780
+ Returns:
781
+ bool: Returns `True` if is a number signed.
782
+ """
783
+ return self._number_signed
784
+
785
+ def is_padded(self) -> Union[bool, int]:
786
+ """Returns `True` if is padded.
787
+
788
+ Returns:
789
+ bool: Returns `True` if is padded.
790
+ """
791
+ return self._is_padded
792
+
793
+ def is_bytes(self) -> bool:
794
+ """Returns `True` if is bytes.
795
+
796
+ Returns:
797
+ bool: Returns `True` if is bytes.
798
+ """
799
+ return self._is_bytes
800
+
801
+
802
+ class Row:
803
+ """Represents a row element returned from a SELECT query.
804
+
805
+ Args:
806
+ resultset (mysqlx.SqlResult or mysqlx.RowResult): The result set.
807
+ fields (`list`): The list of fields.
808
+ """
809
+
810
+ def __init__(
811
+ self, resultset: Union[BufferingResult, RowResult], fields: Sequence[FieldTypes]
812
+ ) -> None:
813
+ self._fields: Sequence[FieldTypes] = fields
814
+ self._resultset: Union[BufferingResult, RowResult] = resultset
815
+
816
+ def __repr__(self) -> str:
817
+ return repr(self._fields)
818
+
819
+ def __getitem__(self, index: Union[int, str]) -> Any:
820
+ """Returns the value of a column by name or index.
821
+
822
+ .. versionchanged:: 8.0.12
823
+ """
824
+ int_index = self._resultset.index_of(index) if isinstance(index, str) else index
825
+ if int_index == -1 and isinstance(index, str):
826
+ raise ValueError(f"Column name '{index}' not found")
827
+ if int_index >= len(self._fields) or int_index < 0:
828
+ raise IndexError("Index out of range")
829
+ return self._fields[int_index]
830
+
831
+ @deprecated("8.0.12")
832
+ def get_string(self, str_index: str) -> str:
833
+ """Returns the value using the column name.
834
+
835
+ Args:
836
+ str_index (str): The column name.
837
+
838
+ .. deprecated:: 8.0.12
839
+ """
840
+ int_index = self._resultset.index_of(str_index)
841
+ if int_index >= len(self._fields):
842
+ raise IndexError("Argument out of range")
843
+ if int_index == -1:
844
+ raise ValueError(f"Column name '{str_index}' not found")
845
+ return str(self._fields[int_index])
846
+
847
+
848
+ class BaseResult:
849
+ """Provides base functionality for result objects.
850
+
851
+ Args:
852
+ connection (mysqlx.connection.Connection): The Connection object.
853
+ """
854
+
855
+ def __init__(self, connection: ConnectionType) -> None:
856
+ self._connection: ConnectionType = connection
857
+ self._closed: bool = False
858
+ self._rows_affected: int = 0
859
+ self._generated_id: int = -1
860
+ self._generated_ids: List[int] = []
861
+ self._warnings: List[Dict[str, Union[int, str]]] = []
862
+
863
+ if connection is None:
864
+ self._protocol = None
865
+ else:
866
+ self._protocol = connection.protocol
867
+ connection.fetch_active_result()
868
+
869
+ def get_affected_items_count(self) -> int:
870
+ """Returns the number of affected items for the last operation.
871
+
872
+ Returns:
873
+ int: The number of affected items.
874
+ """
875
+ return self._rows_affected
876
+
877
+ def get_warnings(self) -> List[Dict[str, Union[int, str]]]:
878
+ """Returns the warnings.
879
+
880
+ Returns:
881
+ `list`: The list of warnings.
882
+ """
883
+ return self._warnings
884
+
885
+ def get_warnings_count(self) -> int:
886
+ """Returns the number of warnings.
887
+
888
+ Returns:
889
+ int: The number of warnings.
890
+ """
891
+ return len(self._warnings)
892
+
893
+ def set_closed(self, flag: bool) -> None:
894
+ """Sets if resultset fetch is done."""
895
+ self._closed = flag
896
+
897
+ def append_warning(self, level: int, code: int, msg: str) -> None:
898
+ """Append a warning.
899
+
900
+ Args:
901
+ level (int): The warning level.
902
+ code (int): The warning code.
903
+ msg (str): The warning message.
904
+ """
905
+ self._warnings.append({"level": level, "code": code, "msg": msg})
906
+
907
+ def set_generated_ids(self, generated_ids: List[int]) -> None:
908
+ """Sets the generated ids."""
909
+ self._generated_ids = generated_ids
910
+
911
+ def set_generated_insert_id(self, generated_id: int) -> None:
912
+ """Sets the generated insert id."""
913
+ self._generated_id = generated_id
914
+
915
+ def set_rows_affected(self, total: int) -> None:
916
+ """Sets the number of rows affected."""
917
+ self._rows_affected = total
918
+
919
+
920
+ class Result(BaseResult):
921
+ """Allows retrieving information about non query operations performed on
922
+ the database.
923
+
924
+ Args:
925
+ connection (mysqlx.connection.Connection): The Connection object.
926
+ ids (`list`): A list of IDs.
927
+ """
928
+
929
+ def __init__(
930
+ self,
931
+ connection: Optional[ConnectionType] = None,
932
+ ids: Optional[List[int]] = None,
933
+ ) -> None:
934
+ super().__init__(connection)
935
+ self._ids: Optional[List[int]] = ids
936
+
937
+ if connection is not None:
938
+ self._connection.close_result(self)
939
+
940
+ def get_autoincrement_value(self) -> int:
941
+ """Returns the last insert id auto generated.
942
+
943
+ Returns:
944
+ int: The last insert id.
945
+ """
946
+ return self._generated_id
947
+
948
+ @deprecated("8.0.12")
949
+ def get_document_id(self) -> Optional[int]:
950
+ """Returns ID of the last document inserted into a collection.
951
+
952
+ .. deprecated:: 8.0.12
953
+ """
954
+ if self._ids is None or len(self._ids) == 0:
955
+ return None
956
+ return self._ids[0]
957
+
958
+ @deprecated("8.0.12")
959
+ def get_generated_insert_id(self) -> int:
960
+ """Returns the generated insert id.
961
+
962
+ .. deprecated:: 8.0.12
963
+ """
964
+ return self._generated_id
965
+
966
+ def get_generated_ids(self) -> List[int]:
967
+ """Returns the generated ids."""
968
+ return self._generated_ids
969
+
970
+
971
+ class BufferingResult(BaseResult):
972
+ """Provides base functionality for buffering result objects.
973
+
974
+ Args:
975
+ connection (mysqlx.connection.Connection): The Connection object.
976
+ ids (`list`): A list of IDs.
977
+ """
978
+
979
+ def __init__(self, connection: ConnectionType) -> None:
980
+ super().__init__(connection)
981
+ self._columns: List[Column] = []
982
+ self._has_data: bool = False
983
+ self._has_more_results: bool = False
984
+ self._items: List[Union[Row, DbDoc]] = []
985
+ self._page_size: int = 0
986
+ self._position: int = -1
987
+ self._init_result()
988
+
989
+ def __getitem__(self, index: int) -> Union[Row, DbDoc]:
990
+ return self._items[index]
991
+
992
+ @property
993
+ def count(self) -> int:
994
+ """int: The total of items."""
995
+ return len(self._items)
996
+
997
+ def _init_result(self) -> None:
998
+ """Initialize the result."""
999
+ self._columns = self._connection.get_column_metadata(self)
1000
+ self._has_more_data = len(self._columns) > 0
1001
+ self._items = []
1002
+ self._page_size = 20
1003
+ self._position = -1
1004
+ self._connection.set_active_result(self if self._has_more_data else None)
1005
+
1006
+ def _read_item(self, dumping: bool) -> Optional[Union[Row, DbDoc]]:
1007
+ """Read item.
1008
+
1009
+ Args:
1010
+ dumping (bool): `True` for dumping.
1011
+
1012
+ Returns:
1013
+ :class:`mysqlx.Row`: A `Row` object.
1014
+ """
1015
+ row = self._connection.read_row(self)
1016
+ if row is None:
1017
+ return None
1018
+ item = [None] * len(row["field"])
1019
+ if not dumping:
1020
+ for key in range(len(row["field"])):
1021
+ column = self._columns[key]
1022
+ item[key] = from_protobuf(column, row["field"][key])
1023
+ return Row(self, item)
1024
+
1025
+ def _page_in_items(self) -> Union[bool, int]:
1026
+ """Reads the page items.
1027
+
1028
+ Returns:
1029
+ int: Total items read.
1030
+ """
1031
+ if self._closed:
1032
+ return False
1033
+
1034
+ count = 0
1035
+ for _ in range(self._page_size):
1036
+ item = self._read_item(False)
1037
+ if item is None:
1038
+ break
1039
+ self._items.append(item)
1040
+ count += 1
1041
+ return count
1042
+
1043
+ def index_of(self, col_name: str) -> int:
1044
+ """Returns the index of the column.
1045
+
1046
+ Returns:
1047
+ int: The index of the column.
1048
+ """
1049
+ index = 0
1050
+ for col in self._columns:
1051
+ if col.get_column_label() == col_name:
1052
+ return index
1053
+ index += 1
1054
+ return -1
1055
+
1056
+ def fetch_one(self) -> Optional[Union[Row, DbDoc]]:
1057
+ """Fetch one item.
1058
+
1059
+ Returns:
1060
+ :class:`mysqlx.Row` or :class:`mysqlx.DbDoc`: one result item.
1061
+ """
1062
+ if self._closed:
1063
+ return None
1064
+
1065
+ return self._read_item(False)
1066
+
1067
+ def fetch_all(self) -> List[Union[Row, DbDoc]]:
1068
+ """Fetch all items.
1069
+
1070
+ Returns:
1071
+ `list`: The list of items of :class:`mysqlx.DbDoc` or
1072
+ :class:`mysqlx.Row`.
1073
+ """
1074
+ while True:
1075
+ if not self._page_in_items():
1076
+ break
1077
+ return self._items
1078
+
1079
+ def set_has_data(self, flag: bool) -> None:
1080
+ """Sets if result has data.
1081
+
1082
+ Args:
1083
+ flag (bool): `True` if result has data.
1084
+ """
1085
+ self._has_data = flag
1086
+
1087
+ def set_has_more_results(self, flag: bool) -> None:
1088
+ """Sets if has more results.
1089
+
1090
+ Args:
1091
+ flag (bool): `True` if has more results.
1092
+ """
1093
+ self._has_more_results = flag
1094
+
1095
+
1096
+ class RowResult(BufferingResult):
1097
+ """Allows traversing the Row objects returned by a Table.select operation.
1098
+
1099
+ Args:
1100
+ connection (mysqlx.connection.Connection): The Connection object.
1101
+ """
1102
+
1103
+ @property
1104
+ def columns(self) -> List[Column]:
1105
+ """`list`: The list of columns."""
1106
+ return self._columns
1107
+
1108
+ def get_columns(self) -> List[Column]:
1109
+ """Returns the list of columns.
1110
+
1111
+ Returns:
1112
+ `list`: The list of columns.
1113
+
1114
+ .. versionadded:: 8.0.12
1115
+ """
1116
+ return self._columns
1117
+
1118
+
1119
+ class SqlResult(RowResult):
1120
+ """Represents a result from a SQL statement.
1121
+
1122
+ Args:
1123
+ connection (mysqlx.connection.Connection): The Connection object.
1124
+ """
1125
+
1126
+ def get_autoincrement_value(self) -> int:
1127
+ """Returns the identifier for the last record inserted.
1128
+
1129
+ Returns:
1130
+ str: The identifier of the last record inserted.
1131
+ """
1132
+ return self._generated_id
1133
+
1134
+ def next_result(self) -> bool:
1135
+ """Process the next result.
1136
+
1137
+ Returns:
1138
+ bool: Returns `True` if the fetch is done.
1139
+ """
1140
+ if self._closed:
1141
+ return False
1142
+ self._has_more_results = False
1143
+ self._init_result()
1144
+ return True
1145
+
1146
+ def has_data(self) -> bool:
1147
+ """Returns True if result has data.
1148
+
1149
+ Returns:
1150
+ bool: Returns `True` if result has data.
1151
+
1152
+ .. versionadded:: 8.0.12
1153
+ """
1154
+ return self._has_data
1155
+
1156
+
1157
+ class DocResult(BufferingResult):
1158
+ """Allows traversing the DbDoc objects returned by a Collection.find
1159
+ operation.
1160
+
1161
+ Args:
1162
+ connection (mysqlx.connection.Connection): The Connection object.
1163
+ """
1164
+
1165
+ def _read_item(self, dumping: bool) -> DbDoc:
1166
+ """Read item.
1167
+
1168
+ Args:
1169
+ dumping (bool): `True` for dumping.
1170
+
1171
+ Returns:
1172
+ :class:`mysqlx.DbDoc`: A `DbDoc` object.
1173
+ """
1174
+ row = super()._read_item(dumping)
1175
+ if row is None:
1176
+ return None
1177
+ return DbDoc(decode_from_bytes(row[0])) # type: ignore[index]