Skip to content

Commit 40dfc1e

Browse files
committed
Make partition key optional
Signed-off-by: Ryan Levick <[email protected]>
1 parent b9ae38c commit 40dfc1e

File tree

3 files changed

+77
-31
lines changed

3 files changed

+77
-31
lines changed

crates/key-value-azure/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ use store::{
88

99
/// A key-value store that uses Azure Cosmos as the backend.
1010
pub struct AzureKeyValueStore {
11-
app_id: String,
11+
app_id: Option<String>,
1212
}
1313

1414
impl AzureKeyValueStore {
1515
/// Creates a new `AzureKeyValueStore`.
16-
pub fn new(app_id: String) -> Self {
16+
///
17+
/// When `app_id` is provided, the store will a partition key of `$app_id/$store_name`,
18+
/// otherwise the partition key will be `id`.
19+
pub fn new(app_id: Option<String>) -> Self {
1720
Self { app_id }
1821
}
1922
}

crates/key-value-azure/src/store.rs

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::sync::{Arc, Mutex};
1313

1414
pub struct KeyValueAzureCosmos {
1515
client: CollectionClient,
16-
app_id: String,
16+
app_id: Option<String>,
1717
}
1818

1919
/// Azure Cosmos Key / Value runtime config literal options for authentication
@@ -72,7 +72,7 @@ impl KeyValueAzureCosmos {
7272
database: String,
7373
container: String,
7474
auth_options: KeyValueAzureCosmosAuthOptions,
75-
app_id: String,
75+
app_id: Option<String>,
7676
) -> Result<Self> {
7777
let token = match auth_options {
7878
KeyValueAzureCosmosAuthOptions::RuntimeConfigValues(config) => {
@@ -97,7 +97,7 @@ impl StoreManager for KeyValueAzureCosmos {
9797
async fn get(&self, name: &str) -> Result<Arc<dyn Store>, Error> {
9898
Ok(Arc::new(AzureCosmosStore {
9999
client: self.client.clone(),
100-
partition_key: format!("{}/{}", self.app_id, name),
100+
partition_key: self.app_id.as_ref().map(|i| format!("{i}/{name}")),
101101
}))
102102
}
103103

@@ -117,7 +117,10 @@ impl StoreManager for KeyValueAzureCosmos {
117117
#[derive(Clone)]
118118
struct AzureCosmosStore {
119119
client: CollectionClient,
120-
partition_key: String,
120+
/// An optional partition key to use for all operations.
121+
///
122+
/// If the partition key is not set, the store will use `/id` as the partition key.
123+
partition_key: Option<String>,
121124
}
122125

123126
#[async_trait]
@@ -161,15 +164,7 @@ impl Store for AzureCosmosStore {
161164
}
162165

163166
async fn get_many(&self, keys: Vec<String>) -> Result<Vec<(String, Option<Vec<u8>>)>, Error> {
164-
let in_clause: String = keys
165-
.into_iter()
166-
.map(|k| format!("'{}'", k))
167-
.collect::<Vec<String>>()
168-
.join(", ");
169-
let stmt = Query::new(format!(
170-
"SELECT * FROM c WHERE c.id IN ({}) AND partition_key='{}'",
171-
in_clause, self.partition_key
172-
));
167+
let stmt = Query::new(self.get_in_query(keys));
173168
let query = self
174169
.client
175170
.query_documents(stmt)
@@ -243,7 +238,19 @@ struct CompareAndSwap {
243238
client: CollectionClient,
244239
bucket_rep: u32,
245240
etag: Mutex<Option<String>>,
246-
partition_key: String,
241+
partition_key: Option<String>,
242+
}
243+
244+
impl CompareAndSwap {
245+
fn get_query(&self) -> String {
246+
let mut query = format!("SELECT * FROM c WHERE c.id='{}'", self.key);
247+
self.append_partition_key(&mut query);
248+
query
249+
}
250+
251+
fn append_partition_key(&self, query: &mut String) {
252+
append_partition_key_condition(query, self.partition_key.as_deref());
253+
}
247254
}
248255

249256
#[async_trait]
@@ -253,10 +260,7 @@ impl Cas for CompareAndSwap {
253260
async fn current(&self) -> Result<Option<Vec<u8>>, Error> {
254261
let mut stream = self
255262
.client
256-
.query_documents(Query::new(format!(
257-
"SELECT * FROM c WHERE c.id='{}' and c.partition_key='{}'",
258-
self.key, self.partition_key
259-
)))
263+
.query_documents(Query::new(self.get_query()))
260264
.query_cross_partition(true)
261265
.max_item_count(1)
262266
.into_stream::<Pair>();
@@ -287,7 +291,11 @@ impl Cas for CompareAndSwap {
287291
/// `swap` updates the value for the key using the etag saved in the `current` function for
288292
/// optimistic concurrency.
289293
async fn swap(&self, value: Vec<u8>) -> Result<(), SwapError> {
290-
let pk = PartitionKey::from(&self.partition_key);
294+
let pk = PartitionKey::from(
295+
self.partition_key
296+
.as_deref()
297+
.unwrap_or_else(|| self.key.as_str()),
298+
);
291299
let pair = Pair {
292300
id: self.key.clone(),
293301
value,
@@ -334,10 +342,7 @@ impl AzureCosmosStore {
334342
async fn get_pair(&self, key: &str) -> Result<Option<Pair>, Error> {
335343
let query = self
336344
.client
337-
.query_documents(Query::new(format!(
338-
"SELECT * FROM c WHERE c.id='{}' AND c.partition_key='{}'",
339-
key, self.partition_key
340-
)))
345+
.query_documents(Query::new(self.get_query(key)))
341346
.query_cross_partition(true)
342347
.max_item_count(1);
343348

@@ -356,7 +361,7 @@ impl AzureCosmosStore {
356361
async fn get_keys(&self) -> Result<Vec<String>, Error> {
357362
let query = self
358363
.client
359-
.query_documents(Query::new("SELECT * FROM c".to_string()))
364+
.query_documents(Query::new(self.get_keys_query()))
360365
.query_cross_partition(true);
361366
let mut res = Vec::new();
362367

@@ -368,19 +373,59 @@ impl AzureCosmosStore {
368373

369374
Ok(res)
370375
}
376+
377+
fn get_query(&self, key: &str) -> String {
378+
let mut query = format!("SELECT * FROM c WHERE c.id='{}'", key);
379+
self.append_partition_key(&mut query);
380+
query
381+
}
382+
383+
fn get_keys_query(&self) -> String {
384+
let mut query = "SELECT * FROM c".to_owned();
385+
self.append_partition_key(&mut query);
386+
query
387+
}
388+
389+
fn get_in_query(&self, keys: Vec<String>) -> String {
390+
let in_clause: String = keys
391+
.into_iter()
392+
.map(|k| format!("'{}'", k))
393+
.collect::<Vec<String>>()
394+
.join(", ");
395+
396+
let mut query = format!("SELECT * FROM c WHERE c.id IN ({})", in_clause);
397+
self.append_partition_key(&mut query);
398+
query
399+
}
400+
401+
fn append_partition_key(&self, query: &mut String) {
402+
append_partition_key_condition(query, self.partition_key.as_deref());
403+
}
404+
}
405+
406+
/// Appends an option partition key condition to the query.
407+
fn append_partition_key_condition(query: &mut String, partition_key: Option<&str>) {
408+
if let Some(pk) = partition_key {
409+
query.push_str(" AND c.partition_key='");
410+
query.push_str(pk);
411+
query.push('\'')
412+
}
371413
}
372414

373415
#[derive(Serialize, Deserialize, Clone, Debug)]
374416
pub struct Pair {
375417
pub id: String,
376418
pub value: Vec<u8>,
377-
pub partition_key: String,
419+
#[serde(skip_serializing_if = "Option::is_none")]
420+
pub partition_key: Option<String>,
378421
}
379422

380423
impl CosmosEntity for Pair {
381424
type Entity = String;
382425

383426
fn partition_key(&self) -> Self::Entity {
384-
self.partition_key.clone()
427+
self.partition_key
428+
.clone()
429+
.unwrap_or_else(|| self.id.clone())
385430
}
386431
}

crates/runtime-config/src/lib.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,9 +403,7 @@ pub fn key_value_config_resolver(
403403
.register_store_type(spin_key_value_redis::RedisKeyValueStore::new())
404404
.unwrap();
405405
key_value
406-
.register_store_type(spin_key_value_azure::AzureKeyValueStore::new(
407-
"MY_APP".to_owned(),
408-
))
406+
.register_store_type(spin_key_value_azure::AzureKeyValueStore::new(None))
409407
.unwrap();
410408
key_value
411409
.register_store_type(spin_key_value_aws::AwsDynamoKeyValueStore::new())

0 commit comments

Comments
 (0)