mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-07-03 20:40:37 +00:00
feat: add query frontend core skeleton
Signed-off-by: jeremyhi <fengjiachun@gmail.com>
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -11504,6 +11504,16 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "query-frontend"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.26.0"
|
||||
|
||||
@@ -62,6 +62,7 @@ members = [
|
||||
"src/promql",
|
||||
"src/puffin",
|
||||
"src/query",
|
||||
"src/query-frontend",
|
||||
"src/standalone",
|
||||
"src/servers",
|
||||
"src/session",
|
||||
@@ -328,6 +329,7 @@ plugins = { path = "src/plugins" }
|
||||
promql = { path = "src/promql" }
|
||||
puffin = { path = "src/puffin" }
|
||||
query = { path = "src/query" }
|
||||
query-frontend = { path = "src/query-frontend" }
|
||||
servers = { path = "src/servers" }
|
||||
session = { path = "src/session" }
|
||||
sql = { path = "src/sql" }
|
||||
|
||||
16
src/query-frontend/Cargo.toml
Normal file
16
src/query-frontend/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "query-frontend"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json.workspace = true
|
||||
tokio.workspace = true
|
||||
67
src/query-frontend/src/config.rs
Normal file
67
src/query-frontend/src/config.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Query frontend configuration.
|
||||
//!
|
||||
//! The default is fully disabled: constructing [`QueryFrontendConfig::default`]
|
||||
//! yields a no-op frontend so that merely wiring this crate in does not change
|
||||
//! runtime behavior. Enabling features is opt-in for later iterations.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Configuration for the query frontend.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct QueryFrontendConfig {
|
||||
/// Master switch for the query frontend. Disabled by default; when `false`
|
||||
/// the frontend must behave as a pass-through.
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
impl QueryFrontendConfig {
|
||||
/// Returns whether the query frontend is enabled.
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.enable
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_is_disabled() {
|
||||
let config = QueryFrontendConfig::default();
|
||||
assert!(!config.enable);
|
||||
assert!(!config.is_enabled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_config_deserializes_to_disabled_default() {
|
||||
// An empty object exercises the `#[serde(default)]` path and must yield
|
||||
// the disabled default.
|
||||
let config: QueryFrontendConfig = serde_json::from_str("{}").unwrap();
|
||||
assert_eq!(QueryFrontendConfig::default(), config);
|
||||
assert!(!config.is_enabled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enable_round_trips() {
|
||||
let config = QueryFrontendConfig { enable: true };
|
||||
let json = serde_json::to_string(&config).unwrap();
|
||||
let parsed: QueryFrontendConfig = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(config, parsed);
|
||||
assert!(parsed.is_enabled());
|
||||
}
|
||||
}
|
||||
67
src/query-frontend/src/executor.rs
Normal file
67
src/query-frontend/src/executor.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The boundary trait that runs the underlying query.
|
||||
//!
|
||||
//! The query frontend delegates the actual execution of a request to a
|
||||
//! [`QueryExecutor`]. The response and error types are left associated so this
|
||||
//! crate stays protocol-neutral and free of dependencies on `servers`,
|
||||
//! `frontend`, `query`, or `session`. Concrete protocol layers implement this
|
||||
//! trait over their own response/error types.
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::request::QueryFrontendRequest;
|
||||
|
||||
/// Executes a [`QueryFrontendRequest`] and produces a protocol-specific
|
||||
/// response.
|
||||
#[async_trait]
|
||||
pub trait QueryExecutor: Send + Sync {
|
||||
/// The protocol-specific successful response type.
|
||||
type Response: Send;
|
||||
/// The protocol-specific error type.
|
||||
type Error: Send;
|
||||
|
||||
/// Executes the request, returning the response or an error.
|
||||
async fn execute(&self, request: QueryFrontendRequest) -> Result<Self::Response, Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A trivial executor that echoes the request's query back, used to confirm
|
||||
/// the trait can be implemented and awaited.
|
||||
struct EchoExecutor;
|
||||
|
||||
#[async_trait]
|
||||
impl QueryExecutor for EchoExecutor {
|
||||
type Response = String;
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
request: QueryFrontendRequest,
|
||||
) -> Result<Self::Response, Self::Error> {
|
||||
Ok(request.query)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn executor_trait_is_implementable() {
|
||||
let executor = EchoExecutor;
|
||||
let request = crate::request::test_request();
|
||||
assert_eq!("up", executor.execute(request).await.unwrap());
|
||||
}
|
||||
}
|
||||
180
src/query-frontend/src/key.rs
Normal file
180
src/query-frontend/src/key.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Exact identity key for query frontend requests.
|
||||
//!
|
||||
//! The key compares two requests for *exact* equality across every field. No
|
||||
//! normalization (timestamps, query text, steps) is performed: two requests are
|
||||
//! the same only if all of `db`, `read_preference`, `query`, `start`, `end`,
|
||||
//! `step`, and `lookback` are byte-for-byte identical.
|
||||
//!
|
||||
//! The query text is retained only in memory for equality and hashing. It must
|
||||
//! never be emitted in metrics or logs.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use crate::request::QueryFrontendRequest;
|
||||
|
||||
/// Exact identity key derived from a [`QueryFrontendRequest`].
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct QueryKey {
|
||||
db: String,
|
||||
read_preference: String,
|
||||
query: String,
|
||||
start: String,
|
||||
end: String,
|
||||
step: String,
|
||||
lookback: String,
|
||||
}
|
||||
|
||||
impl QueryKey {
|
||||
/// Returns the target database, the only field safe to attach to telemetry.
|
||||
pub fn db(&self) -> &str {
|
||||
&self.db
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for QueryKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("QueryKey")
|
||||
.field("db", &self.db)
|
||||
.field("read_preference", &self.read_preference)
|
||||
.field("query", &"[REDACTED]")
|
||||
.field("start", &self.start)
|
||||
.field("end", &self.end)
|
||||
.field("step", &self.step)
|
||||
.field("lookback", &self.lookback)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&QueryFrontendRequest> for QueryKey {
|
||||
fn from(request: &QueryFrontendRequest) -> Self {
|
||||
Self {
|
||||
db: request.db.clone(),
|
||||
read_preference: request.read_preference.clone(),
|
||||
query: request.query.clone(),
|
||||
start: request.start.clone(),
|
||||
end: request.end.clone(),
|
||||
step: request.step.clone(),
|
||||
lookback: request.lookback.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::request::test_request;
|
||||
|
||||
fn base() -> QueryKey {
|
||||
test_request().key()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equal_keys_match() {
|
||||
assert_eq!(base(), test_request().key());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_field_distinguishes_the_key() {
|
||||
let request = test_request();
|
||||
|
||||
assert_ne!(
|
||||
base(),
|
||||
QueryFrontendRequest {
|
||||
db: "other".to_string(),
|
||||
..request.clone()
|
||||
}
|
||||
.key(),
|
||||
"db must be part of the identity"
|
||||
);
|
||||
assert_ne!(
|
||||
base(),
|
||||
QueryFrontendRequest {
|
||||
read_preference: "FOLLOWER".to_string(),
|
||||
..request.clone()
|
||||
}
|
||||
.key(),
|
||||
"read_preference must be part of the identity"
|
||||
);
|
||||
assert_ne!(
|
||||
base(),
|
||||
QueryFrontendRequest {
|
||||
query: "rate(up[5m])".to_string(),
|
||||
..request.clone()
|
||||
}
|
||||
.key(),
|
||||
"query must be part of the identity"
|
||||
);
|
||||
assert_ne!(
|
||||
base(),
|
||||
QueryFrontendRequest {
|
||||
start: "0".to_string(),
|
||||
..request.clone()
|
||||
}
|
||||
.key(),
|
||||
"start must be part of the identity"
|
||||
);
|
||||
assert_ne!(
|
||||
base(),
|
||||
QueryFrontendRequest {
|
||||
end: "3".to_string(),
|
||||
..request.clone()
|
||||
}
|
||||
.key(),
|
||||
"end must be part of the identity"
|
||||
);
|
||||
assert_ne!(
|
||||
base(),
|
||||
QueryFrontendRequest {
|
||||
step: "10s".to_string(),
|
||||
..request.clone()
|
||||
}
|
||||
.key(),
|
||||
"step must be part of the identity"
|
||||
);
|
||||
assert_ne!(
|
||||
base(),
|
||||
QueryFrontendRequest {
|
||||
lookback: "1m".to_string(),
|
||||
..request
|
||||
}
|
||||
.key(),
|
||||
"lookback must be part of the identity"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_from_request_keeps_every_field() {
|
||||
let request = test_request();
|
||||
assert_eq!(base(), request.key());
|
||||
assert_eq!("db", request.key().db());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_redacts_query_text() {
|
||||
let request = QueryFrontendRequest {
|
||||
query: "secret_query".to_string(),
|
||||
..test_request()
|
||||
};
|
||||
let request_debug = format!("{request:?}");
|
||||
let key_debug = format!("{:?}", request.key());
|
||||
|
||||
assert!(!request_debug.contains("secret_query"));
|
||||
assert!(request_debug.contains("[REDACTED]"));
|
||||
assert!(!key_debug.contains("secret_query"));
|
||||
assert!(key_debug.contains("[REDACTED]"));
|
||||
}
|
||||
}
|
||||
40
src/query-frontend/src/lib.rs
Normal file
40
src/query-frontend/src/lib.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Core skeleton for the GreptimeDB query frontend.
|
||||
//!
|
||||
//! The query frontend sits in front of the query path and is intended to host
|
||||
//! optimizations such as in-flight coalescing or caching in later iterations.
|
||||
//! This crate only establishes the protocol-neutral building blocks; it does
|
||||
//! **not** yet change any runtime behavior.
|
||||
//!
|
||||
//! Deliberately out of scope for this skeleton: singleflight, caching,
|
||||
//! stale-while-revalidate, TTLs, timestamp/query normalization, and response
|
||||
//! replay. The defaults are no-ops so wiring this crate in cannot alter the
|
||||
//! current request path.
|
||||
//!
|
||||
//! Module map:
|
||||
//! - [`request`]: protocol-neutral request DTO handed to the frontend.
|
||||
//! - [`key`]: exact identity key derived from a request.
|
||||
//! - [`policy`]: decides whether the frontend engages for a request.
|
||||
//! - [`config`]: configuration, disabled by default.
|
||||
//! - [`metrics`]: stable metric/label names for observability.
|
||||
//! - [`executor`]: the boundary trait that runs the underlying query.
|
||||
|
||||
pub mod config;
|
||||
pub mod executor;
|
||||
pub mod key;
|
||||
pub mod metrics;
|
||||
pub mod policy;
|
||||
pub mod request;
|
||||
35
src/query-frontend/src/metrics.rs
Normal file
35
src/query-frontend/src/metrics.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Stable metric and label names for query frontend observability.
|
||||
//!
|
||||
//! This module only declares the naming surface; it does not register or own
|
||||
//! any metric instruments. Keeping the names here lets the eventual collector
|
||||
//! and any observe-only call sites agree on identifiers. Per the key contract,
|
||||
//! only the database label is safe to emit — never the query text.
|
||||
|
||||
/// Metric name for query frontend observations.
|
||||
pub const METRIC_QUERY_FRONTEND_OBSERVATIONS: &str = "greptime_query_frontend_observations_total";
|
||||
|
||||
/// Label carrying the target database name.
|
||||
pub const LABEL_DB: &str = "db";
|
||||
|
||||
/// Label carrying the per-request outcome (see the `OUTCOME_*` constants).
|
||||
pub const LABEL_OUTCOME: &str = "outcome";
|
||||
|
||||
/// Outcome label value: request bypassed the frontend.
|
||||
pub const OUTCOME_BYPASS: &str = "bypass";
|
||||
|
||||
/// Outcome label value: request was handled by the frontend.
|
||||
pub const OUTCOME_ENGAGE: &str = "engage";
|
||||
68
src/query-frontend/src/policy.rs
Normal file
68
src/query-frontend/src/policy.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Decides whether the query frontend engages for a given request.
|
||||
//!
|
||||
//! A policy is the gate in front of any future optimization. The default
|
||||
//! [`BypassPolicy`] always bypasses, so the frontend is a pass-through until a
|
||||
//! real policy is introduced.
|
||||
|
||||
use crate::request::QueryFrontendRequest;
|
||||
|
||||
/// What the frontend should do with a request.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PolicyDecision {
|
||||
/// Hand the request straight to the underlying executor; the frontend adds
|
||||
/// no behavior.
|
||||
Bypass,
|
||||
/// Let the frontend handle the request (coalescing, caching, etc. in a
|
||||
/// later iteration).
|
||||
Engage,
|
||||
}
|
||||
|
||||
impl PolicyDecision {
|
||||
/// Returns `true` when the frontend should engage.
|
||||
pub fn is_engage(&self) -> bool {
|
||||
matches!(self, PolicyDecision::Engage)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decides, per request, whether the frontend engages.
|
||||
pub trait QueryFrontendPolicy: Send + Sync {
|
||||
/// Returns the [`PolicyDecision`] for `request`.
|
||||
fn decide(&self, request: &QueryFrontendRequest) -> PolicyDecision;
|
||||
}
|
||||
|
||||
/// Default policy that always bypasses, keeping runtime behavior unchanged.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct BypassPolicy;
|
||||
|
||||
impl QueryFrontendPolicy for BypassPolicy {
|
||||
fn decide(&self, _request: &QueryFrontendRequest) -> PolicyDecision {
|
||||
PolicyDecision::Bypass
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_policy_always_bypasses() {
|
||||
let policy = BypassPolicy;
|
||||
let request = crate::request::test_request();
|
||||
assert_eq!(PolicyDecision::Bypass, policy.decide(&request));
|
||||
assert!(!policy.decide(&request).is_engage());
|
||||
}
|
||||
}
|
||||
84
src/query-frontend/src/request.rs
Normal file
84
src/query-frontend/src/request.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Protocol-neutral request DTO consumed by the query frontend.
|
||||
//!
|
||||
//! Fields are intentionally kept as opaque strings so this crate does not
|
||||
//! depend on `servers`, `frontend`, `query`, or `session` types. Callers are
|
||||
//! responsible for translating their protocol-specific representations (for
|
||||
//! example, a PromQL range query plus its session context) into these strings
|
||||
//! verbatim. The frontend does not normalize them.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::key::QueryKey;
|
||||
|
||||
/// A single query handed to the frontend.
|
||||
///
|
||||
/// The fields mirror the inputs that uniquely identify a PromQL range query
|
||||
/// today, but the types are protocol-neutral strings so the same DTO can carry
|
||||
/// other query shapes later.
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct QueryFrontendRequest {
|
||||
/// Target database/schema name.
|
||||
pub db: String,
|
||||
/// Read preference (e.g. `LEADER`, `FOLLOWER`), as an opaque string.
|
||||
pub read_preference: String,
|
||||
/// The raw query expression.
|
||||
pub query: String,
|
||||
/// Range start, verbatim from the caller.
|
||||
pub start: String,
|
||||
/// Range end, verbatim from the caller.
|
||||
pub end: String,
|
||||
/// Range step, verbatim from the caller.
|
||||
pub step: String,
|
||||
/// Lookback delta, verbatim from the caller.
|
||||
pub lookback: String,
|
||||
}
|
||||
|
||||
impl QueryFrontendRequest {
|
||||
/// Returns the exact identity [`QueryKey`] for this request.
|
||||
pub fn key(&self) -> QueryKey {
|
||||
QueryKey::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for QueryFrontendRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("QueryFrontendRequest")
|
||||
.field("db", &self.db)
|
||||
.field("read_preference", &self.read_preference)
|
||||
.field("query", &"[REDACTED]")
|
||||
.field("start", &self.start)
|
||||
.field("end", &self.end)
|
||||
.field("step", &self.step)
|
||||
.field("lookback", &self.lookback)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn test_request() -> QueryFrontendRequest {
|
||||
QueryFrontendRequest {
|
||||
db: "db".to_string(),
|
||||
read_preference: "LEADER".to_string(),
|
||||
query: "up".to_string(),
|
||||
start: "1".to_string(),
|
||||
end: "2".to_string(),
|
||||
step: "5s".to_string(),
|
||||
lookback: "5m".to_string(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user