mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-09-08 06:29:04 +00:00
* feat(event): record procedure actor Signed-off-by: WenyXu <wenymedia@gmail.com> * test: handle streamed region migration output Signed-off-by: WenyXu <wenymedia@gmail.com> * test: cover procedure actors across SQL protocols Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com>
215 lines
6.7 KiB
Rust
215 lines
6.7 KiB
Rust
// 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.
|
|
|
|
use std::future::Future;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use client::OutputData;
|
|
use common_recordbatch::RecordBatches;
|
|
use datatypes::arrow::array::AsArray;
|
|
use datatypes::arrow::datatypes::{DataType as ArrowDataType, UInt32Type};
|
|
use frontend::instance::Instance;
|
|
use servers::query_handler::sql::SqlQueryHandler;
|
|
use session::context::QueryContext;
|
|
|
|
const MAX_ATTEMPTS: usize = 60;
|
|
const POLL_INTERVAL: Duration = Duration::from_millis(250);
|
|
|
|
/// Asserts through the supplied SQL executor that a table procedure records
|
|
/// the expected actor for both submission and completion events.
|
|
pub(crate) async fn assert_procedure_actor_by_table<F, Fut>(
|
|
event_type: &str,
|
|
table_name: &str,
|
|
actor: &str,
|
|
mut execute: F,
|
|
) where
|
|
F: FnMut(String) -> Fut,
|
|
Fut: Future<Output = bool>,
|
|
{
|
|
let query = format!(
|
|
"SELECT count(DISTINCT json_get_string(procedure_trigger, 'type')) = 2 \
|
|
AND count(*) = count(CASE WHEN actor = '{actor}' THEN 1 END) AS actor_matches \
|
|
FROM greptime_private.events \
|
|
WHERE type = '{event_type}' AND table_name = '{table_name}' \
|
|
AND json_get_string(procedure_trigger, 'type') IN ('Submitted', 'Succeeded')"
|
|
);
|
|
|
|
for _ in 0..MAX_ATTEMPTS {
|
|
if execute(query.clone()).await {
|
|
return;
|
|
}
|
|
tokio::time::sleep(POLL_INTERVAL).await;
|
|
}
|
|
|
|
panic!("timed out waiting for procedure actor: {query}");
|
|
}
|
|
|
|
/// Asserts that submission and completion events record the expected actor.
|
|
pub(crate) async fn assert_procedure_actor(
|
|
instance: &Arc<Instance>,
|
|
procedure_id: &str,
|
|
actor: Option<&str>,
|
|
) {
|
|
let actor_predicate = actor.map_or_else(
|
|
|| "count(actor) = 0".to_string(),
|
|
|actor| format!("count(*) = count(CASE WHEN actor = '{actor}' THEN 1 END)"),
|
|
);
|
|
for trigger in ["Submitted", "Succeeded"] {
|
|
assert_eventually_eq(
|
|
instance,
|
|
&format!(
|
|
"SELECT count(*) > 0 AND {actor_predicate} AS actor_matches FROM greptime_private.events WHERE procedure_id = '{procedure_id}' AND json_get_string(procedure_trigger, 'type') = '{trigger}'"
|
|
),
|
|
"\
|
|
+---------------+
|
|
| actor_matches |
|
|
+---------------+
|
|
| true |
|
|
+---------------+",
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
/// Asserts that the query eventually returns exactly one event.
|
|
pub(crate) async fn assert_single_event(instance: &Arc<Instance>, query: &str) {
|
|
for _ in 0..MAX_ATTEMPTS {
|
|
if event_count_is_one(instance, query).await {
|
|
return;
|
|
}
|
|
tokio::time::sleep(POLL_INTERVAL).await;
|
|
}
|
|
|
|
panic!("timed out waiting for event: {query}");
|
|
}
|
|
|
|
/// Returns the first non-null string from `column` once the query produces one.
|
|
pub(crate) async fn find_eventually_string(
|
|
instance: &Arc<Instance>,
|
|
query: &str,
|
|
column: &str,
|
|
) -> String {
|
|
for _ in 0..MAX_ATTEMPTS {
|
|
if let Some(value) = query_first_string(instance, query, column).await {
|
|
return value;
|
|
}
|
|
tokio::time::sleep(POLL_INTERVAL).await;
|
|
}
|
|
|
|
panic!("timed out waiting for event query: {query}");
|
|
}
|
|
|
|
/// Returns the first non-null u32 from `column` once the query produces one.
|
|
pub(crate) async fn find_eventually_u32(
|
|
instance: &Arc<Instance>,
|
|
query: &str,
|
|
column: &str,
|
|
) -> u32 {
|
|
for _ in 0..MAX_ATTEMPTS {
|
|
if let Some(value) = query_first_u32(instance, query, column).await {
|
|
return value;
|
|
}
|
|
tokio::time::sleep(POLL_INTERVAL).await;
|
|
}
|
|
|
|
panic!("timed out waiting for event query: {query}");
|
|
}
|
|
|
|
/// Asserts that the query eventually returns the expected pretty-printed rows.
|
|
pub(crate) async fn assert_eventually_eq(instance: &Arc<Instance>, query: &str, expected: &str) {
|
|
let mut last_actual = None;
|
|
for _ in 0..MAX_ATTEMPTS {
|
|
last_actual = query_pretty_print(instance, query).await;
|
|
if last_actual.as_deref() == Some(expected) {
|
|
return;
|
|
}
|
|
tokio::time::sleep(POLL_INTERVAL).await;
|
|
}
|
|
|
|
panic!(
|
|
"timed out waiting for event query: {query}\nexpected:\n{expected}\nactual:\n{}",
|
|
last_actual.as_deref().unwrap_or("<query failed>")
|
|
);
|
|
}
|
|
|
|
async fn event_count_is_one(instance: &Arc<Instance>, query: &str) -> bool {
|
|
query_pretty_print(instance, query).await.as_deref()
|
|
== Some(
|
|
"\
|
|
+-------------+
|
|
| event_count |
|
|
+-------------+
|
|
| 1 |
|
|
+-------------+",
|
|
)
|
|
}
|
|
|
|
async fn query_first_string(instance: &Arc<Instance>, query: &str, column: &str) -> Option<String> {
|
|
let batches = query_record_batches(instance, query).await?;
|
|
let batch = batches.take().into_iter().next()?;
|
|
let column = batch.column_by_name(column)?;
|
|
match column.data_type() {
|
|
ArrowDataType::Utf8 => column
|
|
.as_string::<i32>()
|
|
.iter()
|
|
.next()
|
|
.flatten()
|
|
.map(ToString::to_string),
|
|
ArrowDataType::LargeUtf8 => column
|
|
.as_string::<i64>()
|
|
.iter()
|
|
.next()
|
|
.flatten()
|
|
.map(ToString::to_string),
|
|
ArrowDataType::Utf8View => column
|
|
.as_string_view()
|
|
.iter()
|
|
.next()
|
|
.flatten()
|
|
.map(ToString::to_string),
|
|
data_type => panic!("expected a string column, got {data_type:?}"),
|
|
}
|
|
}
|
|
|
|
async fn query_first_u32(instance: &Arc<Instance>, query: &str, column: &str) -> Option<u32> {
|
|
let batches = query_record_batches(instance, query).await?;
|
|
let batch = batches.take().into_iter().next()?;
|
|
batch
|
|
.column_by_name(column)?
|
|
.as_primitive::<UInt32Type>()
|
|
.iter()
|
|
.next()
|
|
.flatten()
|
|
}
|
|
|
|
async fn query_pretty_print(instance: &Arc<Instance>, query: &str) -> Option<String> {
|
|
query_record_batches(instance, query)
|
|
.await?
|
|
.pretty_print()
|
|
.ok()
|
|
}
|
|
|
|
async fn query_record_batches(instance: &Arc<Instance>, query: &str) -> Option<RecordBatches> {
|
|
let output = instance
|
|
.do_query(query, QueryContext::arc())
|
|
.await
|
|
.remove(0)
|
|
.ok()?;
|
|
let OutputData::Stream(stream) = output.data else {
|
|
return None;
|
|
};
|
|
RecordBatches::try_collect(stream).await.ok()
|
|
}
|