blob: 840626a3e27eba42e1e0ae43b0eb41541fcff4a7 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Physical MAP BLOB key encoding shared by Python readers and writers."""
import datetime
import struct
from decimal import Decimal as BigDecimal
from typing import Optional
from pypaimon.data.decimal import Decimal
from pypaimon.schema.data_types import AtomicType, DataType
_EPOCH_DATE = datetime.date(1970, 1, 1)
class MapBlobKeySerializer:
def __init__(self, type_name: str, struct_format: Optional[str] = None):
self._type_name = type_name
self._struct_format = struct_format
self.fixed_length = -1 if struct_format is None else struct.calcsize(struct_format)
def serialize(self, key) -> bytes:
if self._struct_format is None:
if not isinstance(key, str):
raise ValueError(
f"MAP<X, BLOB> {self._type_name} key must be a string."
)
return key.encode('utf-8')
if not isinstance(key, int) or isinstance(key, bool):
raise ValueError(
f"MAP<X, BLOB> {self._type_name} key must be an integer."
)
try:
return struct.pack(self._struct_format, key)
except struct.error as error:
raise ValueError(
f"MAP<X, BLOB> {self._type_name} key is out of range: {key}."
) from error
def deserialize(self, data: bytes):
if self._struct_format is None:
try:
return data.decode('utf-8')
except UnicodeDecodeError as error:
raise ValueError("Invalid MAP<X, BLOB> string key.") from error
if len(data) != self.fixed_length:
raise ValueError(
f"Expected {self.fixed_length} key bytes, but found {len(data)}."
)
return struct.unpack(self._struct_format, data)[0]
class BooleanMapBlobKeySerializer(MapBlobKeySerializer):
def __init__(self):
self.fixed_length = 1
def serialize(self, key) -> bytes:
if not isinstance(key, bool):
raise ValueError("MAP<X, BLOB> BOOLEAN key must be a boolean.")
return b'\x01' if key else b'\x00'
def deserialize(self, data: bytes):
if len(data) != self.fixed_length:
raise ValueError(
f"Expected {self.fixed_length} key bytes, but found {len(data)}."
)
if data[0] == 0:
return False
if data[0] == 1:
return True
raise ValueError("Invalid MAP<X, BLOB> boolean key.")
class BinaryMapBlobKeySerializer(MapBlobKeySerializer):
def __init__(self, type_name: str):
self._type_name = type_name
self.fixed_length = -1
def serialize(self, key) -> bytes:
if not isinstance(key, bytes):
raise ValueError(
f"MAP<X, BLOB> {self._type_name} key must be bytes."
)
return key
def deserialize(self, data: bytes):
return data
class DecimalMapBlobKeySerializer(MapBlobKeySerializer):
def __init__(self, type_name: str, precision: int, scale: int):
self._type_name = type_name
self._precision = precision
self._scale = scale
self.fixed_length = 8 if Decimal.is_compact_precision(precision) else -1
def serialize(self, key) -> bytes:
if not isinstance(key, BigDecimal):
raise ValueError(
f"MAP<X, BLOB> {self._type_name} key must be a decimal.Decimal."
)
decimal = Decimal.from_big_decimal(key, self._precision, self._scale)
if decimal is None:
raise ValueError(
f"MAP<X, BLOB> {self._type_name} key exceeds declared precision."
)
if decimal.is_compact():
return struct.pack('<q', decimal.to_unscaled_long())
return decimal.to_unscaled_bytes()
def deserialize(self, data: bytes):
if self.fixed_length >= 0:
if len(data) != self.fixed_length:
raise ValueError(
f"Expected {self.fixed_length} key bytes, but found {len(data)}."
)
decimal = Decimal.from_unscaled_long(
struct.unpack('<q', data)[0],
self._precision,
self._scale,
)
else:
if not data:
raise ValueError("Invalid MAP<X, BLOB> decimal key.")
decimal = Decimal.from_unscaled_bytes(
data,
self._precision,
self._scale,
)
if decimal is None:
raise ValueError(
"MAP<X, BLOB> decimal key exceeds declared precision."
)
return decimal.to_big_decimal()
class DateMapBlobKeySerializer(MapBlobKeySerializer):
def __init__(self):
self.fixed_length = 4
def serialize(self, key) -> bytes:
if isinstance(key, datetime.datetime) or not isinstance(key, datetime.date):
raise ValueError("MAP<X, BLOB> DATE key must be a datetime.date.")
try:
return struct.pack('<i', (key - _EPOCH_DATE).days)
except struct.error as error:
raise ValueError(f"MAP<X, BLOB> DATE key is out of range: {key}.") from error
def deserialize(self, data: bytes):
if len(data) != self.fixed_length:
raise ValueError(
f"Expected {self.fixed_length} key bytes, but found {len(data)}."
)
return _EPOCH_DATE + datetime.timedelta(days=struct.unpack('<i', data)[0])
class TimeMapBlobKeySerializer(MapBlobKeySerializer):
def __init__(self, type_name: str):
self._type_name = type_name
self.fixed_length = 4
def serialize(self, key) -> bytes:
if not isinstance(key, datetime.time):
raise ValueError(
f"MAP<X, BLOB> {self._type_name} key must be a datetime.time."
)
millis = (
(key.hour * 3600 + key.minute * 60 + key.second) * 1000
+ key.microsecond // 1000
)
return struct.pack('<i', millis)
def deserialize(self, data: bytes):
if len(data) != self.fixed_length:
raise ValueError(
f"Expected {self.fixed_length} key bytes, but found {len(data)}."
)
millis = struct.unpack('<i', data)[0]
seconds, millis = divmod(millis, 1000)
minutes, second = divmod(seconds, 60)
hour, minute = divmod(minutes, 60)
try:
return datetime.time(hour, minute, second, millis * 1000)
except ValueError as error:
raise ValueError("Invalid MAP<X, BLOB> TIME key.") from error
def create_map_blob_key_serializer(data_type: DataType) -> MapBlobKeySerializer:
if not isinstance(data_type, AtomicType):
raise ValueError(f"Unsupported key type for MAP<X, BLOB>: {data_type}")
type_name = data_type.type.upper()
if type_name == 'TINYINT':
return MapBlobKeySerializer(type_name, '<b')
if type_name == 'SMALLINT':
return MapBlobKeySerializer(type_name, '<h')
if type_name in ('INT', 'INTEGER'):
return MapBlobKeySerializer(type_name, '<i')
if type_name == 'BIGINT':
return MapBlobKeySerializer(type_name, '<q')
if type_name == 'BOOLEAN':
return BooleanMapBlobKeySerializer()
if type_name.startswith('DECIMAL'):
precision, scale = Decimal.extract_decimal_precision_scale(type_name)
return DecimalMapBlobKeySerializer(type_name, precision, scale)
if type_name == 'DATE':
return DateMapBlobKeySerializer()
if type_name == 'TIME' or type_name.startswith('TIME('):
return TimeMapBlobKeySerializer(type_name)
if (
type_name == 'BYTES'
or type_name.startswith('BINARY')
or type_name.startswith('VARBINARY')
):
return BinaryMapBlobKeySerializer(type_name)
if type_name == 'STRING' or type_name.startswith('CHAR') or type_name.startswith('VARCHAR'):
return MapBlobKeySerializer(type_name)
raise ValueError(f"Unsupported key type for MAP<X, BLOB>: {data_type}")