Skip to main content

uqa_sql/schema/indexes/
vectors.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! SQL vector-index target identity, key type, and existing-index checks.
8use crate::{
9    ast::{ColumnType, CreateIndex},
10    SQLError,
11};
12pub trait VectorIndexCatalog {
13    fn resolve_table_name(&self, name: &str) -> Result<Option<String>, SQLError>;
14    fn column_type(&self, table: &str, column: &str) -> Result<Option<ColumnType>, SQLError>;
15    fn vector_index_names(&self, table: &str, column: &str) -> Result<Vec<String>, SQLError>;
16}
17pub struct VectorIndexTarget<'a> {
18    pub table: String,
19    pub fields: Vec<(&'a str, u32)>,
20}
21pub fn resolve_vector_index_target<'a>(
22    catalog: &dyn VectorIndexCatalog,
23    statement: &'a CreateIndex,
24    access_method: &str,
25) -> Result<VectorIndexTarget<'a>, SQLError> {
26    let table = catalog
27        .resolve_table_name(&statement.table)?
28        .ok_or_else(|| {
29            SQLError::Unsupported(format!(
30                "CREATE INDEX USING {access_method}: relation `{}` does not exist",
31                statement.table
32            ))
33        })?;
34    let mut fields = Vec::with_capacity(statement.columns.len());
35    for key in &statement.columns {
36        let column = super::keys::require_column_key(key, access_method)?;
37        let dimensions = match catalog.column_type(&table, column)? {
38            Some(ColumnType::Vector(dim) | ColumnType::Tensor(dim)) => dim,
39            Some(other) => {
40                return Err(SQLError::Unsupported(format!(
41                    "CREATE INDEX USING {access_method} requires VECTOR or TENSOR column `{column}`, got {other:?}"
42                )));
43            }
44            None => {
45                return Err(SQLError::Unsupported(format!(
46                    "CREATE INDEX USING {access_method}: column `{table}`.`{column}` does not exist"
47                )));
48            }
49        };
50        let existing = catalog.vector_index_names(&table, column)?;
51        if !existing.is_empty() {
52            return Err(SQLError::Unsupported(format!(
53                "CREATE INDEX USING {access_method}: `{table}`.`{column}` already has physical vector index `{}`",
54                existing.join("`, `")
55            )));
56        }
57        fields.push((column, dimensions));
58    }
59    Ok(VectorIndexTarget { table, fields })
60}