blob: 82febf8b88796f2713ea9e975585e7576180d298 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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::collections::HashSet;
use std::io::{Error as IoError, ErrorKind};
use crate::{
load_parquet_table, load_query_files, open_catalog_session, parse_number_selection,
register_parquet_tables, run_query_file, write_report, BenchmarkReport, Command,
ExistingTablePolicy, QueryRunConfig, SourceKind, TPCDS_TABLES,
};
type BoxError = Box<dyn std::error::Error + Send + Sync>;
pub async fn execute_command(command: Command) -> Result<(), BoxError> {
match command {
Command::Load(args) => {
let runtime = args.runtime.to_config().map_err(invalid_input)?;
let tables = select_tables(args.tables.as_deref())?;
let session = open_catalog_session(&runtime, &args.warehouse, &args.database).await?;
let policy: ExistingTablePolicy = args.if_exists.into();
for table in tables {
println!("Loading {table} ...");
let result = load_parquet_table(&session, &args.data, table, policy).await?;
println!(
" status={:?} rows={} elapsed_ms={}",
result.status, result.rows, result.elapsed_ms
);
}
Ok(())
}
Command::Run(args) => {
args.validate().map_err(invalid_input)?;
let runtime = args.runtime.to_config().map_err(invalid_input)?;
let session = open_catalog_session(&runtime, &args.warehouse, &args.database).await?;
if args.source == SourceKind::Parquet {
let tables = select_tables(args.tables.as_deref())?;
let data = args
.data
.as_deref()
.ok_or_else(|| invalid_input("--data is required when --source=parquet"))?;
register_parquet_tables(&session, data, &tables).await?;
}
let query_numbers =
parse_number_selection(args.query.as_deref(), 1, 99).map_err(invalid_input)?;
let query_files =
load_query_files(&args.queries, &query_numbers).map_err(invalid_input)?;
let query_run = QueryRunConfig {
warmup_iterations: args.warmup,
measured_iterations: args.iterations,
};
let mut query_results = Vec::with_capacity(query_files.len());
for query in &query_files {
println!("Running q{} ...", query.number);
let result = run_query_file(&session, query, &query_run).await;
for iteration in &result.iterations {
if let Some(error) = &iteration.error {
println!(
" iteration={} failed after {:.3} ms: {error}",
iteration.iteration, iteration.total_ms
);
} else {
println!(
" iteration={} total_ms={:.3} rows={} spilled_bytes={}",
iteration.iteration,
iteration.total_ms,
iteration.output_rows,
iteration.metrics.spilled_bytes
);
}
}
query_results.push(result);
}
let report = BenchmarkReport::new(
args.source,
runtime,
query_run,
args.warehouse.display().to_string(),
args.data
.as_ref()
.map(|path| path.display().to_string())
.unwrap_or_default(),
args.queries.display().to_string(),
args.database,
query_results,
);
write_report(&report, &args.output)?;
println!("Report written to {}", args.output.display());
if report.has_failures() {
return Err(IoError::other(format!(
"one or more queries failed; see {}",
args.output.display()
))
.into());
}
Ok(())
}
}
}
fn select_tables(selection: Option<&str>) -> Result<Vec<&'static str>, BoxError> {
let Some(selection) = selection else {
return Ok(TPCDS_TABLES.to_vec());
};
let selected = selection
.split(',')
.map(str::trim)
.filter(|name| !name.is_empty())
.collect::<HashSet<_>>();
if selected.is_empty() {
return Ok(TPCDS_TABLES.to_vec());
}
if let Some(unknown) = selected.iter().find(|name| !TPCDS_TABLES.contains(name)) {
return Err(invalid_input(format!("unknown TPC-DS table '{unknown}'")));
}
Ok(TPCDS_TABLES
.iter()
.copied()
.filter(|name| selected.contains(name))
.collect())
}
fn invalid_input(message: impl Into<String>) -> BoxError {
IoError::new(ErrorKind::InvalidInput, message.into()).into()
}