blob: e56f76f9581d753f7563cc256c50231039ad42de [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.
"""Vector parameter resolution shared by primary-key and Data Evolution indexes."""
from dataclasses import dataclass
from typing import Optional
from pypaimon.common.options.core_options import CoreOptions
from pypaimon.common.options.options import Options
@dataclass(frozen=True)
class VectorIndexOptions:
index_type: Optional[str]
options: Options
metric: str
@staticmethod
def columns(options, index_type=None):
core = CoreOptions(options)
if index_type is not None:
declared = core.global_index_columns(index_type)
return declared if declared else [column for column in VectorIndexOptions._legacy_columns(options)
if VectorIndexOptions._legacy_index_type(options, column) == index_type]
types = VectorIndexOptions._index_types(core)
if types:
return [column for kind in types for column in VectorIndexOptions.columns(options, kind)]
return VectorIndexOptions._legacy_columns(options)
@staticmethod
def _legacy_columns(options):
columns = options.to_map().get('pk-vector.index.columns')
return [] if columns is None else [column.strip() for column in str(columns).split(',')]
@staticmethod
def columns_option_key(options):
core = CoreOptions(options)
types = VectorIndexOptions._index_types(core)
if not types:
return 'pk-vector.index.columns'
return ', '.join(core.global_index_columns_option_key(kind) for kind in types)
@staticmethod
def _index_types(core):
return [kind for kind in core.global_index_types() if CoreOptions._is_vector_index_type(kind)]
@staticmethod
def _legacy_index_type(options, column):
value = options.to_map().get('fields.%s.pk-vector.index.type' % column)
return None if value is None else str(value).lower().strip()
@staticmethod
def resolve(options, index_type, column, *, default_metric=True):
values = dict(options.to_map())
if index_type is None:
core = CoreOptions(options)
declared = [kind for kind in VectorIndexOptions._index_types(core)
if column in core.global_index_columns(kind)]
index_type = ((declared[0] if len(declared) == 1 else None) if declared
else VectorIndexOptions._legacy_index_type(options, column))
if index_type is None:
return VectorIndexOptions(
None, Options(values),
_normalize_metric(values.get('fields.%s.pk-vector.distance.metric' % column)))
prefix = index_type + '.'
field_prefix = 'fields.%s.' % column
aliases = (field_prefix + 'distance.metric', field_prefix + 'metric',
prefix + 'distance.metric', prefix + 'metric')
metric = next((values[key] for key in (
field_prefix + 'distance.metric', field_prefix + 'metric',
field_prefix + 'pk-vector.distance.metric', prefix + 'distance.metric',
prefix + 'metric', 'distance.metric', 'metric') if values.get(key) is not None), None)
# Resolve aliases after generic merging, so COSINE/cosine cannot be
# mistaken for conflicting generic strings when options are resolved twice.
for key in aliases:
values.pop(key, None)
resolved = CoreOptions(Options(values)).global_index_options(index_type, column).to_map()
metric = next((resolved[key] for key in aliases if resolved.get(key) is not None), metric)
if metric is not None or default_metric:
normalized = _normalize_metric(metric)
for key in aliases:
resolved[key] = normalized
return VectorIndexOptions(index_type, Options(resolved), _normalize_metric(metric))
def _normalize_metric(metric):
return str('inner_product' if metric is None else metric).lower().replace('-', '_')