use super::gsi::IndexWrite;
use crate::errors::{DynoxideError, Result};
use crate::storage::TableMetadata;
use crate::storage_backend::{IndexWriteOp, StorageBackend};
use crate::types::{Item, KeyType, LocalSecondaryIndex};
use std::collections::HashMap;
pub type LsiDef = super::gsi::IndexDef;
pub fn lsi_to_def(lsi: &LocalSecondaryIndex) -> Result<LsiDef> {
let pk_attr = lsi
.key_schema
.iter()
.find(|k| k.key_type == KeyType::HASH)
.map(|k| k.attribute_name.clone())
.ok_or_else(|| DynoxideError::InternalServerError("LSI missing HASH key".to_string()))?;
let sk_attr = lsi
.key_schema
.iter()
.find(|k| k.key_type == KeyType::RANGE)
.map(|k| k.attribute_name.clone());
let hash_attrs = vec![pk_attr.clone()];
let range_attrs = sk_attr.iter().cloned().collect();
Ok(LsiDef {
index_name: lsi.index_name.clone(),
pk_attr,
sk_attr,
hash_attrs,
range_attrs,
projection_type: lsi.projection.projection_type.clone().unwrap_or_default(),
non_key_attributes: lsi.projection.non_key_attributes.clone(),
})
}
pub fn parse_lsi_defs(meta: &TableMetadata) -> Result<Vec<LsiDef>> {
if meta.lsi_definitions.is_some() {
crate::bench_counters::record(&crate::bench_counters::INDEX_DEFS_PARSES);
}
let lsis: Vec<LocalSecondaryIndex> = match meta.lsi_definitions.as_ref() {
Some(json) => serde_json::from_str(json)
.map_err(|e| DynoxideError::InternalServerError(format!("Bad LSI JSON: {e}")))?,
None => return Ok(Vec::new()),
};
lsis.iter().map(lsi_to_def).collect()
}
pub fn parse_lsi_key_schema(
meta: &TableMetadata,
index_name: &str,
) -> Result<(String, Option<String>)> {
let lsi_defs = parse_lsi_defs(meta)?;
let lsi = lsi_defs
.into_iter()
.find(|l| l.index_name == index_name)
.ok_or_else(|| {
DynoxideError::ValidationException(format!(
"The table does not have the specified index: {index_name}"
))
})?;
Ok((lsi.pk_attr, lsi.sk_attr))
}
pub async fn maintain_lsis_after_write<S: StorageBackend>(
storage: &S,
meta: &TableMetadata,
target: &IndexWrite<'_>,
item: &Item,
) -> Result<HashMap<String, f64>> {
let lsi_defs = parse_lsi_defs(meta)?;
maintain_lsis_after_write_with_defs(storage, &lsi_defs, target, item).await
}
pub async fn maintain_lsis_after_write_with_defs<S: StorageBackend>(
storage: &S,
lsi_defs: &[LsiDef],
target: &IndexWrite<'_>,
item: &Item,
) -> Result<HashMap<String, f64>> {
let want_capacity = crate::types::capacity_wanted(target.capacity_mode);
let mut lsi_units: HashMap<String, f64> = HashMap::new();
let mut ops: Vec<IndexWriteOp> = Vec::new();
for lsi in lsi_defs {
ops.push(IndexWriteOp::DeleteLsi {
table_name: target.table_name.to_string(),
index_name: lsi.index_name.clone(),
base_pk: target.pk.to_string(),
base_sk: target.sk.to_string(),
});
let entry = super::index_capacity::entry_for(item, lsi, target.pk_attr, target.sk_attr);
if let Some(ref entry) = entry {
let (lsi_pk, lsi_sk) = entry.key.clone();
let item_json = serde_json::to_string(&entry.projected)
.map_err(|e| DynoxideError::InternalServerError(e.to_string()))?;
ops.push(IndexWriteOp::InsertLsi {
table_name: target.table_name.to_string(),
index_name: lsi.index_name.clone(),
pk: lsi_pk,
sk: lsi_sk,
base_pk: target.pk.to_string(),
base_sk: target.sk.to_string(),
item_json,
});
}
if want_capacity
&& let Some(units) = super::index_capacity::index_write_units_for(
target.old_item,
entry,
lsi,
target.pk_attr,
target.sk_attr,
)
{
lsi_units.insert(lsi.index_name.clone(), units);
}
}
storage.apply_index_writes(&ops).await?;
Ok(lsi_units)
}
pub async fn maintain_lsis_after_delete<S: StorageBackend>(
storage: &S,
meta: &TableMetadata,
target: &IndexWrite<'_>,
) -> Result<HashMap<String, f64>> {
let lsi_defs = parse_lsi_defs(meta)?;
maintain_lsis_after_delete_with_defs(storage, &lsi_defs, target).await
}
pub async fn maintain_lsis_after_delete_with_defs<S: StorageBackend>(
storage: &S,
lsi_defs: &[LsiDef],
target: &IndexWrite<'_>,
) -> Result<HashMap<String, f64>> {
let want_capacity = crate::types::capacity_wanted(target.capacity_mode);
let mut lsi_units: HashMap<String, f64> = HashMap::new();
let mut ops: Vec<IndexWriteOp> = Vec::new();
for lsi in lsi_defs {
ops.push(IndexWriteOp::DeleteLsi {
table_name: target.table_name.to_string(),
index_name: lsi.index_name.clone(),
base_pk: target.pk.to_string(),
base_sk: target.sk.to_string(),
});
if want_capacity
&& let Some(units) = super::index_capacity::index_write_units(
target.old_item,
None,
lsi,
target.pk_attr,
target.sk_attr,
)
{
lsi_units.insert(lsi.index_name.clone(), units);
}
}
storage.apply_index_writes(&ops).await?;
Ok(lsi_units)
}