Data Management With ReductStore
ReductStore provides a set of methods to manage data in the database. This guide provides an overview of typical data management operations in ReductStore and explains how to perform them using the ReductStore SDKs or the HTTP API.
Changing Labels​
ReductStore doesn't allow you to change the contents of records, but it does allow you to change the labels of existing records. You can add, remove or update labels for a single record or for a batch of records to avoid HTTP requests overhead.
note
You should send a label as an empty string to remove it.
- Python
- JavaScript
- Rust
- C++
- cURL
import time
import asyncio
from reduct import Client, Bucket, Batch
async def main():
# Create a client instance, then get or create a bucket
async with Client("http://127.0.0.1:8383", api_token="my-token") as client:
bucket: Bucket = await client.create_bucket("my-bucket", exist_ok=True)
# Send some records to the "py-example" entry with labels
ts = time.time()
await bucket.write(
"py-example",
b"Some binary data",
ts,
labels={"key1": "value1", "key2": "value2"},
)
await bucket.write(
"py-example",
b"Some binary data",
ts + 1,
labels={"key1": "value1", "key2": "value2"},
)
# Update labels of a record: remove "key2" and update "key1"
await bucket.update("py-example", ts, labels={"key1": "new-value", "key2": ""})
async with bucket.read("py-example", ts) as record:
assert record.labels["key1"] == "new-value"
assert "key2" not in record.labels
# Update labels in a batch
batch = Batch()
batch.add(ts, labels={"key1": "new-value", "key2": ""})
batch.add(ts + 1, labels={"key3": "value3"})
errors = await bucket.update_batch("py-example", batch)
assert not errors
if __name__ == "__main__":
import asyncio
asyncio.run(main())
import { Client } from "reduct-js";
import assert from "node:assert";
// Create a client instance, then get or create a bucket
const client = new Client("http://127.0.0.1:8383", { apiToken: "my-token" });
const bucket = await client.getOrCreateBucket("bucket");
// Send some records to the "entry" entry with labels
const timestamp = BigInt(Date.now()) * 1000n;
let record = await bucket.beginWrite("entry-1", {
ts: timestamp,
labels: { key1: "value1", key2: "value2" },
});
await record.write("Some binary data");
record = await bucket.beginWrite("entry-1", {
ts: timestamp + 1000_000n,
labels: { key1: "value1", key2: "value2" },
});
await record.write("Some more binary data");
// Update labels of a record: remove "key2" and update "key1"
await bucket.update("entry-1", timestamp, { key1: "new-value", key2: "" });
record = await bucket.beginRead("entry-1", timestamp, true); // only labels
assert(record.labels["key1"] === "new-value");
assert(record.labels["key2"] === undefined);
// Update labels in a batch
const batch = await bucket.beginUpdateBatch("entry-1");
batch.addOnlyLabels(timestamp, { label1: "value1", label2: "" });
batch.addOnlyLabels(timestamp + 1000_000n, { label3: "value3" });
const errors = await batch.write();
assert(errors.size === 0);
use std::time::{Duration, SystemTime};
use bytes::Bytes;
use reduct_rs::{RecordBuilder, ReductClient, ReductError};
use tokio;
#[tokio::main]
async fn main() -> Result<(), ReductError> {
// Create a client instance, then get or create a bucket
let client = ReductClient::builder()
.url("http://127.0.0.1:8383")
.api_token("my-token")
.build();
let bucket = client.create_bucket("test").exist_ok(true).send().await?;
// Send some records to the "rs-example" entry with labels "key1=value1" and "key2=value2
let timestamp = SystemTime::now();
bucket
.write_record("rs-example")
.timestamp(timestamp)
.add_label("key1", "value1")
.add_label("key2", "value2")
.data(Bytes::from("Some binary data"))
.send()
.await?;
bucket
.write_record("rs-example")
.timestamp(timestamp + Duration::from_secs(1))
.add_label("key1", "value1")
.add_label("key2", "value2")
.data(Bytes::from("Some more binary data"))
.send()
.await?;
// Update labels of a record: remove "key2" and update "key1"
bucket
.update_record("rs-example")
.timestamp(timestamp)
.remove_label("key2")
.update_label("key1", "value3")
.send()
.await?;
let record = bucket.read_record("rs-example").timestamp(timestamp).send().await?;
assert_eq!(record.labels().get("key1"), Some(&"value3".to_string()));
assert_eq!(record.labels().get("key2"), None);
// Update labels in a batch
let record1 = RecordBuilder::new()
.timestamp(timestamp)
.add_label("key1", "value1")
.add_label("key2", "") // Remove label "key2"
.build();
let record2 = RecordBuilder::new()
.timestamp(timestamp + Duration::from_secs(1))
.add_label("key1", "value1")
.add_label("key2", "") // Remove label "key2"
.build();
let errors = bucket.update_batch("rs-example")
.add_records(vec![record1, record2])
.send()
.await?;
assert_eq!(errors.len(), 0);
Ok(())
}
#include <reduct/client.h>
#include <cassert>
#include <chrono>
using reduct::IBucket;
using reduct::IClient;
using reduct::Error;
using std::chrono_literals::operator ""s;
int main() {
// Create a client instance, then get or create a bucket
auto client = IClient::Build("http://127.0.0.1:8383", {.api_token="my-token"});
auto [bucket, create_err] = client->GetOrCreateBucket("my-bucket");
assert(create_err == Error::kOk);
// Send some records to the "entry" entry with labels
IBucket::Time ts = IBucket::Time::clock::now();
auto err = bucket->Write("cpp-example", {
.timestamp = ts,
.labels = {{"key1", "value1"},
{"key2", "value2"}}
}, [](auto rec) {
rec->WriteAll("Some binary data");
});
assert(err == Error::kOk);
err = bucket->Write("cpp-example", {
.timestamp = ts + 1s,
.labels = {{"key1", "value1"},
{"key2", "value2"}}
}, [](auto rec) {
rec->WriteAll("Some binary data");
});
assert(err == Error::kOk);
// Update labels of a record: remove "key2" and update "key1"
err = bucket->Update("cpp-example", {
.timestamp = ts,
.labels = {{"key1", "value3"},
{"key2", ""}}
});
assert(err == Error::kOk);
err = bucket->Read("cpp-example", ts, [](auto rec) {
assert(rec.labels["key1"] == "value3");
assert(rec.labels["key2"] == "");
return true;
});
assert(err == Error::kOk);
// Update labels in a batch
auto [record_errors, http_err] = bucket->UpdateBatch("cpp-example", [ts](auto batch) {
batch->AddOnlyLabels(ts, {{"key1", "value4"}});
batch->AddOnlyLabels(ts + 1s, {{"key1", "value4"}});
});
assert(http_err == Error::kOk);
assert(record_errors.empty());
return 0;
}
#!/bin/bash
set -e -x
API_PATH="http://127.0.0.1:8383/api/v1"
AUTH_HEADER="Authorization: Bearer my-token"
# Send a record with labels a
TIME=`date +%s000000`
curl -d "Some binary data" \
-H "${AUTH_HEADER}" \
-H "x-reduct-label-key1: value1" \
-H "x-reduct-label-key2: value2" \
-H "Content-Type: plain/text" \
-X POST -a ${API_PATH}/b/example-bucket/entry_1?ts=${TIME}
# Update the "key1" label to "new_value1" and remove the "key2" label
curl -H "${AUTH_HEADER}" \
-H "x-reduct-label-key1: new_value1" \
-H "x-reduct-label-key2: " \
-X PATCH -a ${API_PATH}/b/example-bucket/entry_1/labels
Deleting Data​
Currently, you can delete data per bucket or per entry by using the Client SDKs, Reduct CLI or HTTP API:
- Python
- JavaScript
- Rust
- C++
- CLI
- cURL
from reduct import Client, Bucket
async def main():
# Create a client with the base URL and API token
async with Client("http://localhost:8383", api_token="my-token") as client:
# Get bucket to remove
bucket: Bucket = await client.get_bucket("bucket-to-remove")
# Delete only entry with name "example-entry"
await bucket.remove_entry("example-entry")
# Remove entire bucket
await bucket.remove()
if __name__ == "__main__":
import asyncio
asyncio.run(main())
import { Client } from "reduct-js";
import assert from "node:assert";
// Create a new client with the server URL and an API token
const client = new Client("http://127.0.0.1:8383", { apiToken: "my-token" });
// Get bucket to remove
const bucket = await client.getBucket("bucket-to-remove");
// Delete only entry with name "example-entry"
await bucket.removeEntry("example-entry");
// Remove entire bucket
await bucket.remove();
use reduct_rs::{ErrorCode, ReductClient, ReductError};
use tokio;
#[tokio::main]
async fn main() -> Result<(), ReductError> {
// Create a new client with the API URL and API token
let client = ReductClient::builder()
.url("http://127.0.0.1:8383")
.api_token("my-token")
.build();
// Get bucket to remove
let bucket = client.get_bucket("bucket-to-remove").await?;
// Delete only entry with name "example-entry"
bucket.remove_entry("example-entry").await?;
// Remove entire bucket
bucket.remove().await?;
Ok(())
}
#include <reduct/client.h>
#include <iostream>
#include <cassert>
using reduct::IBucket;
using reduct::IClient;
using reduct::Error;
int main() {
// Create a client with the server URL
auto client = IClient::Build("http://127.0.0.1:8383", {
.api_token = "my-token"
});
// Get bucket to remove
auto [bucket, get_err] = client->GetBucket("bucket-to-remove");
assert(get_err == Error::kOk);
// Delete only entry with name "example-entry"
auto remove_entry_err = bucket->RemoveEntry("example-entry");
assert(remove_entry_err == Error::kOk);
// Remove entire bucket
auto remove_err = bucket->Remove();
assert(remove_err == Error::kOk);
}
reduct-cli alias add local -L http://localhost:8383 -t "my-token"
# Delete only entry with name "example-entry"
reduct-cli bucket rm local/bucket-to-remove --only-entries example-entry
# Delete entire bucket without confirmation
reduct-cli bucket rm local/bucket-to-remove -y
#!/bin/bash
set -e -x
API_PATH="http://127.0.0.1:8383/api/v1"
AUTH_HEADER="Authorization: Bearer my-token"
# Delete only entry with name "example-entry"
curl -X DELETE \
-H "${AUTH_HEADER}" \
-a "${API_PATH}"/b/bucket-to-remove/example-entry
# Delete entire bucket
curl -X DELETE \
-H "${AUTH_HEADER}" \
-a "${API_PATH}"/b/bucket-to-remove