|
| 1 | +import struct |
1 | 2 | import json |
2 | 3 | import sqlparse |
3 | 4 | import re |
@@ -27,6 +28,58 @@ def convert_bytes(obj): |
27 | 28 | return obj |
28 | 29 |
|
29 | 30 |
|
| 31 | +def parse_mysql_point(binary): |
| 32 | + """ |
| 33 | + Parses the binary representation of a MySQL POINT data type |
| 34 | + and returns a tuple (x, y) representing the coordinates. |
| 35 | +
|
| 36 | + :param binary: The binary data representing the POINT. |
| 37 | + :return: A tuple (x, y) with the coordinate values. |
| 38 | + """ |
| 39 | + if binary is None: |
| 40 | + return 0, 0 |
| 41 | + |
| 42 | + if len(binary) == 21: |
| 43 | + # No SRID. Proceed as per WKB POINT |
| 44 | + # Read the byte order |
| 45 | + byte_order = binary[0] |
| 46 | + if byte_order == 0: |
| 47 | + endian = '>' |
| 48 | + elif byte_order == 1: |
| 49 | + endian = '<' |
| 50 | + else: |
| 51 | + raise ValueError("Invalid byte order in WKB POINT") |
| 52 | + # Read the WKB Type |
| 53 | + wkb_type = struct.unpack(endian + 'I', binary[1:5])[0] |
| 54 | + if wkb_type != 1: # WKB type 1 means POINT |
| 55 | + raise ValueError("Not a WKB POINT type") |
| 56 | + # Read X and Y coordinates |
| 57 | + x = struct.unpack(endian + 'd', binary[5:13])[0] |
| 58 | + y = struct.unpack(endian + 'd', binary[13:21])[0] |
| 59 | + elif len(binary) == 25: |
| 60 | + # With SRID included |
| 61 | + # First 4 bytes are the SRID |
| 62 | + srid = struct.unpack('>I', binary[0:4])[0] # SRID is big-endian |
| 63 | + # Next byte is byte order |
| 64 | + byte_order = binary[4] |
| 65 | + if byte_order == 0: |
| 66 | + endian = '>' |
| 67 | + elif byte_order == 1: |
| 68 | + endian = '<' |
| 69 | + else: |
| 70 | + raise ValueError("Invalid byte order in WKB POINT") |
| 71 | + # Read the WKB Type |
| 72 | + wkb_type = struct.unpack(endian + 'I', binary[5:9])[0] |
| 73 | + if wkb_type != 1: # WKB type 1 means POINT |
| 74 | + raise ValueError("Not a WKB POINT type") |
| 75 | + # Read X and Y coordinates |
| 76 | + x = struct.unpack(endian + 'd', binary[9:17])[0] |
| 77 | + y = struct.unpack(endian + 'd', binary[17:25])[0] |
| 78 | + else: |
| 79 | + raise ValueError("Invalid binary length for WKB POINT") |
| 80 | + return (x, y) |
| 81 | + |
| 82 | + |
30 | 83 | def strip_sql_name(name): |
31 | 84 | name = name.strip() |
32 | 85 | if name.startswith('`'): |
@@ -64,9 +117,11 @@ def __init__(self, db_replicator: 'DbReplicator' = None): |
64 | 117 | self.db_replicator = db_replicator |
65 | 118 |
|
66 | 119 | def convert_type(self, mysql_type, parameters): |
67 | | - |
68 | 120 | is_unsigned = 'unsigned' in parameters.lower() |
69 | 121 |
|
| 122 | + if mysql_type == 'point': |
| 123 | + return 'Tuple(x Float32, y Float32)' |
| 124 | + |
70 | 125 | if mysql_type == 'int': |
71 | 126 | if is_unsigned: |
72 | 127 | return 'UInt32' |
@@ -146,6 +201,8 @@ def convert_field_type(self, mysql_type, mysql_parameters): |
146 | 201 | mysql_parameters = mysql_parameters.lower() |
147 | 202 | not_null = 'not null' in mysql_parameters |
148 | 203 | clickhouse_type = self.convert_type(mysql_type, mysql_parameters) |
| 204 | + if 'Tuple' in clickhouse_type: |
| 205 | + not_null = True |
149 | 206 | if not not_null: |
150 | 207 | clickhouse_type = f'Nullable({clickhouse_type})' |
151 | 208 | return clickhouse_type |
@@ -197,6 +254,9 @@ def convert_record(self, mysql_record, mysql_field_types, clickhouse_field_types |
197 | 254 | if 'UInt64' in clickhouse_field_type and clickhouse_field_value < 0: |
198 | 255 | clickhouse_field_value = 18446744073709551616 + clickhouse_field_value |
199 | 256 |
|
| 257 | + if 'point' in mysql_field_type: |
| 258 | + clickhouse_field_value = parse_mysql_point(clickhouse_field_value) |
| 259 | + |
200 | 260 | clickhouse_record.append(clickhouse_field_value) |
201 | 261 | return tuple(clickhouse_record) |
202 | 262 |
|
|
0 commit comments