Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
/target
Justfile
Justfile
/commit.sh
/GO.sh
/DROP_DB.sh
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,34 @@

Hulykvs is a simple key-value store service implemented in Rust. It uses cockroachdb as the backend and provides a simple http api for storing and retrieving key-value pairs.

## API
## API v2
Create a key-value pair api

```POST /api2/{workspace}/{namespace}/{key}```
Stores request payload as the value for the given key in the given namespace. Existing keys will be overwritten. Returs 204 (NoContent) on sucesss.


```GET /api2/{workspace}/{namespace}/{key}```
Retrieves the value for the given key in the given namespace. Returns 404 if the key does not exist.


```DELETE /api2/{workspace}/{namespace}/{key}```
Deletes the key-value pair for the given key in the given namespace. Returns 404 if the key does not exist, 204 (NoContent) on success, 404 if the key does not exist.


```GET /api2/{workspace}/{namespace}?[prefix=<prefix>]```
Retrieves all key-value pairs in the given namespace. Optionally, a prefix can be provided to filter the results. The following structure is returned:
```json
{
"workspace": "workspace",
"namespace": "namespace",
"count": 3,
"keys": ["key1", "key2", "keyN"]
}
```
## API (old)
workspace = "defaultspace"

Create a key-value pair

```POST /api/{namespace}/{key}```
Expand All @@ -20,14 +47,14 @@ Deletes the key-value pair for the given key in the given namespace. Returns 404
Retrieves all key-value pairs in the given namespace. Optionally, a prefix can be provided to filter the results. The following structure is returned:
```json
{
"namespace": "namespace",
"namespace": "namespace",
"count": 3,
"keys": ["key1", "key2", "keyN"]
}
```

## Running
Pre-build docker images is available at: hardcoreeng/service_hulykvs:{tag}.
Pre-build docker images is available at: hardcoreeng/service_hulykvs:{tag}.

You can use the following command to run the image locally:
```bash
Expand Down
8 changes: 8 additions & 0 deletions commmit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

clear

#git checkout -b feature/db_uuid_migrate
git add .
#git commit -m "Add API v2 with workspace"
#git push origin feature/workspace-support
1 change: 1 addition & 0 deletions hulykvs/hulykvs
6 changes: 6 additions & 0 deletions hulykvs_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ bb8-postgres = "0.9.0"
md5 = "0.7.0"
jsonwebtoken = "9.3.1"
size = { version = "0.5.0", features = ["serde"] }
uuid = { version = "1.7", features = ["v4"] }
regex = "1.10"

[[bin]]
name = "hulykvs"
path = "src/main.rs"

[build-dependencies]
regex = "1"

49 changes: 49 additions & 0 deletions hulykvs_server/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::{env, fs, path::Path};
use regex::Regex;

fn main() {

let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let root = Path::new(&manifest_dir);

// UUID from TOML

let config_path = root.join("src/config/default.toml");
let text = fs::read_to_string(&config_path).expect("Cannot read default.toml");

let uuid_line = text
.lines()
.find(|line| line.trim_start().starts_with("default_workspace_uuid"))
.expect("default_workspace_uuid not found");

let uuid = uuid_line
.split('=')
.nth(1)
.expect("No = in line")
.trim()
.trim_matches('"')
.to_string();

let migration_path = root.join("etc/migrations/V2__workspace_uuid.sql");

let original_sql = fs::read_to_string(&migration_path).expect("Cannot read migration");

// UUID
let re = Regex::new(
r"UUID NOT NULL DEFAULT '([0-9a-fA-F-]{36})'",
).unwrap();

let patched_sql = re.replace_all(&original_sql, |caps: &regex::Captures| {
if &caps[1] != uuid {
caps[0].replace(&caps[1], &uuid)
} else {
caps[0].to_string()
}
});

if patched_sql != original_sql {
fs::write(&migration_path, patched_sql.as_ref())
.expect("Failed to write patched migration");
println!("cargo:warning=Patched migration V2 with UUID: {}", uuid);
}
}
1 change: 1 addition & 0 deletions hulykvs_server/etc/migrations/V2__workspace_uuid.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE kvs ADD COLUMN workspace UUID NOT NULL DEFAULT 'bb793ec1-252e-4419-8998-7f220ababdfa';
1 change: 1 addition & 0 deletions hulykvs_server/etc/migrations/V3__workspace_uuid.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE kvs ALTER PRIMARY KEY USING COLUMNS (workspace, namespace, key);
1 change: 1 addition & 0 deletions hulykvs_server/etc/migrations/V4__workspace_uuid.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE kvs ALTER COLUMN workspace DROP DEFAULT;
2 changes: 2 additions & 0 deletions hulykvs_server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub struct Config {
pub db_scheme: String,

pub payload_size_limit: size::Size,

pub default_workspace_uuid: String,
}

pub static CONFIG: LazyLock<Config> = LazyLock::new(|| {
Expand Down
3 changes: 3 additions & 0 deletions hulykvs_server/src/config/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ db_connection = "postgresql://root@huly.local:26257/defaultdb?sslmode=disable"
db_scheme = "hulykvs"

payload_size_limit = "2mb"

default_workspace_uuid = "bb793ec1-252e-4419-8998-7f220ababdfa"

24 changes: 12 additions & 12 deletions hulykvs_server/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ pub async fn get(
let connection = pool.get().await?;

let statement = r#"
select value from kvs where namespace=$1 and key=$2
select value from kvs where workspace=$1 and namespace=$2 and key=$3
"#;

let result = connection.query(statement, &[&nsstr, &keystr]).await?;
let result = connection.query(statement, &[&"defaultspace", &nsstr, &keystr]).await?;

let response = match result.as_slice() {
[] => HttpResponse::NotFound().finish(),
Expand Down Expand Up @@ -76,16 +76,16 @@ pub async fn post(
let md5 = md5::compute(&body);

let statement = r#"
insert into kvs(namespace, key, md5, value)
values($1, $2, $3, $4)
on conflict(namespace, key)
insert into kvs(workspace, namespace, key, md5, value)
values($1, $2, $3, $4, $5)
on conflict(workspace, workspace, namespace, key)
do update set
md5=excluded.md5,
value=excluded.value
"#;

connection
.execute(statement, &[&nsstr, &keystr, &&md5[..], &&body[..]])
.execute(statement, &[&"defaultspace", &nsstr, &keystr, &&md5[..], &&body[..]])
.await?;

Ok(HttpResponse::NoContent().finish())
Expand All @@ -111,10 +111,10 @@ pub async fn delete(
let connection = pool.get().await?;

let statement = r#"
delete from kvs where namespace=$1 and key=$2
delete from kvs where workspace=$1 and namespace=$2 and key=$3
"#;

let response = match connection.execute(statement, &[&nsstr, &keystr]).await? {
let response = match connection.execute(statement, &[&"defaultspace", &nsstr, &keystr]).await? {
1 => HttpResponse::NoContent(),
0 => HttpResponse::NotFound(),
_ => panic!("multiple rows deleted, unique constraint is probably violated"),
Expand Down Expand Up @@ -157,16 +157,16 @@ pub async fn list(
let response = if let Some(prefix) = &query.prefix {
let pattern = format!("{}%", prefix);
let statement = r#"
select key from kvs where namespace=$1 and key like $2
select key from kvs where workspace=$1 and namespace=$2 and key like $3
"#;

connection.query(statement, &[&nsstr, &pattern]).await?
connection.query(statement, &[&"defaultspace",&nsstr, &pattern]).await?
} else {
let statement = r#"
select key from kvs where namespace=$1
select key from kvs where workspace=$1 and namespace=$2
"#;

connection.query(statement, &[&nsstr]).await?
connection.query(statement, &[&"defaultspace",&nsstr]).await?
};

let count = response.len();
Expand Down
Loading