pub const GREPTIME_DB_HEADER: &str = "x-greptime-db-name";
pub const GREPTIME_AUTH_HEADER: &str = "x-greptime-auth";
pub const GREPTIME_HINTS_HEADER: &str = "x-greptime-hints";
pub const GREPTIME_RETRY_HINT_HEADER: &str = "x-greptime-err-retry-hint";
pub const GREPTIME_INNER_ERROR_HEADER: &str = "INNER_ERROR_MSG";
pub const DEFAULT_CATALOG: &str = "greptime";
pub const DEFAULT_SCHEMA: &str = "public";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MergeMode {
LastRow,
LastNonNull,
}
impl MergeMode {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::LastRow => "last_row",
Self::LastNonNull => "last_non_null",
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct WriteHints {
entries: Vec<(String, String)>,
}
impl WriteHints {
#[must_use]
pub const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
#[must_use]
pub fn with_auto_create_table(self, enabled: bool) -> Self {
self.with_hint("auto_create_table", enabled.to_string())
}
#[must_use]
pub fn with_ttl(self, ttl: impl Into<String>) -> Self {
self.with_hint("ttl", ttl)
}
#[must_use]
pub fn with_append_mode(self, enabled: bool) -> Self {
self.with_hint("append_mode", enabled.to_string())
}
#[must_use]
pub fn with_merge_mode(self, mode: MergeMode) -> Self {
self.with_hint("merge_mode", mode.as_str())
}
#[must_use]
pub fn with_physical_table(self, table: impl Into<String>) -> Self {
self.with_hint("physical_table", table)
}
#[must_use]
pub fn with_hint(self, key: impl Into<String>, value: impl Into<String>) -> Self {
match self.try_with_hint(key, value) {
Ok(hints) => hints,
Err(error) => panic!("{error}"),
}
}
pub fn try_with_hint(
mut self,
key: impl Into<String>,
value: impl Into<String>,
) -> crate::error::Result<Self> {
let key = key.into();
let value = value.into();
if key.is_empty() || !valid_component(&key) || key.contains('=') || !valid_component(&value)
{
return Err(crate::error::Error::InvalidConfig(
"hint keys must be nonempty printable ASCII without commas or equals signs; \
values must be printable ASCII without commas; surrounding whitespace is forbidden"
.to_owned(),
));
}
self.insert(key, value);
Ok(self)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn encode_header_value(&self) -> Option<String> {
if self.is_empty() {
return None;
}
let mut encoded = String::new();
for (key, value) in &self.entries {
if !encoded.is_empty() {
encoded.push(',');
}
encoded.push_str(key);
encoded.push('=');
encoded.push_str(value);
}
Some(encoded)
}
pub(crate) fn merged_with(&self, extra: &Self) -> Self {
let mut merged = self.clone();
for (key, value) in &extra.entries {
merged.insert(key.clone(), value.clone());
}
merged
}
fn insert(&mut self, key: String, value: String) {
if let Some((_, existing)) = self.entries.iter_mut().find(|(name, _)| *name == key) {
*existing = value;
} else {
self.entries.push((key, value));
}
}
}
fn valid_component(component: &str) -> bool {
component.trim() == component
&& component
.bytes()
.all(|byte| (b' '..=b'~').contains(&byte) && byte != b',')
}