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 target = resolve_vector_index_columns(catalog, statement, access_method)?;
27    for (column, _) in &target.fields {
28        let existing = catalog.vector_index_names(&target.table, column)?;
29        if !existing.is_empty() {
30            return Err(SQLError::Unsupported(format!(
31                "CREATE INDEX USING {access_method}: `{}`.`{column}` already has physical vector index `{}`",
32                target.table, existing.join("`, `")
33            )));
34        }
35    }
36    Ok(target)
37}
38
39/// Validate column declarations without consulting physical index occupancy.
40pub fn resolve_vector_index_columns<'a>(
41    catalog: &dyn VectorIndexCatalog,
42    statement: &'a CreateIndex,
43    access_method: &str,
44) -> Result<VectorIndexTarget<'a>, SQLError> {
45    let table = catalog
46        .resolve_table_name(&statement.table)?
47        .ok_or_else(|| {
48            SQLError::Unsupported(format!(
49                "CREATE INDEX USING {access_method}: relation `{}` does not exist",
50                statement.table
51            ))
52        })?;
53    let mut fields = Vec::with_capacity(statement.columns.len());
54    for key in &statement.columns {
55        let column = super::keys::require_column_key(key, access_method)?;
56        let dimensions = match catalog.column_type(&table, column)? {
57            Some(ColumnType::Vector(dim) | ColumnType::Tensor(dim)) => dim,
58            Some(other) => {
59                return Err(SQLError::Unsupported(format!(
60                    "CREATE INDEX USING {access_method} requires VECTOR or TENSOR column `{column}`, got {other:?}"
61                )));
62            }
63            None => {
64                return Err(SQLError::Unsupported(format!(
65                    "CREATE INDEX USING {access_method}: column `{table}`.`{column}` does not exist"
66                )));
67            }
68        };
69        fields.push((column, dimensions));
70    }
71    Ok(VectorIndexTarget { table, fields })
72}