Skip to content

Commit 2a23ced

Browse files
committed
rebase with main
1 parent 4e50143 commit 2a23ced

6 files changed

Lines changed: 217 additions & 94 deletions

File tree

Cargo.lock

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ object_store = { version = "0.13.1", features = [
2626
"azure",
2727
"gcp",
2828
] }
29+
datafusion-proto = "53.1.0"
2930
parquet = "58.0.0"
3031

3132
# Web server and HTTP-related

src/metrics/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,18 @@ pub static TOTAL_QUERY_CALLS_BY_DATE: Lazy<IntCounterVec> = Lazy::new(|| {
257257
.expect("metric can be created")
258258
});
259259

260+
pub static TOTAL_FILES_SCANNED_IN_HOTTIER_BY_DATE: Lazy<IntCounterVec> = Lazy::new(|| {
261+
IntCounterVec::new(
262+
Opts::new(
263+
"total_files_scanned_in_hottier_by_date",
264+
"Total files scanned in hottier by date",
265+
)
266+
.namespace(METRICS_NAMESPACE),
267+
&["stream", "date", "tenant_id"],
268+
)
269+
.expect("metric can be created")
270+
});
271+
260272
pub static TOTAL_FILES_SCANNED_IN_QUERY_BY_DATE: Lazy<IntCounterVec> = Lazy::new(|| {
261273
IntCounterVec::new(
262274
Opts::new(
@@ -683,6 +695,17 @@ pub fn increment_files_scanned_in_query_by_date(count: u64, date: &str, tenant_i
683695
.inc_by(count);
684696
}
685697

698+
pub fn increment_files_scanned_in_hottier_by_date(
699+
count: u64,
700+
date: &str,
701+
tenant_id: &str,
702+
stream_name: &str,
703+
) {
704+
TOTAL_FILES_SCANNED_IN_HOTTIER_BY_DATE
705+
.with_label_values(&[stream_name, date, tenant_id])
706+
.inc_by(count);
707+
}
708+
686709
pub fn increment_bytes_scanned_in_query_by_date(bytes: u64, date: &str, tenant_id: &str) {
687710
TOTAL_BYTES_SCANNED_IN_QUERY_BY_DATE
688711
.with_label_values(&[date, tenant_id])

src/query/mod.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ type QueryResult = Result<(Either<Vec<RecordBatch>, BoxedBatchStream>, Vec<Strin
9898
// Lazy::new(|| Query::create_session_context(PARSEABLE.storage()));
9999
pub static SCHEMA_PROVIDER: OnceCell<Box<dyn ParseableSchemaProvider>> = OnceCell::new();
100100

101+
/// Additional physical optimizer rules registered by enterprise/plugins.
102+
/// Must be populated BEFORE `QUERY_SESSION_STATE` is first accessed.
103+
pub static ADDITIONAL_PHYSICAL_OPTIMIZER_RULES: Lazy<
104+
RwLock<Vec<Arc<dyn datafusion::physical_optimizer::PhysicalOptimizerRule + Send + Sync>>>,
105+
> = Lazy::new(|| RwLock::new(Vec::new()));
106+
101107
pub static QUERY_SESSION_STATE: Lazy<SessionState> =
102108
Lazy::new(|| Query::create_session_state(PARSEABLE.storage()));
103109

@@ -280,11 +286,19 @@ impl Query {
280286
.parquet
281287
.schema_force_view_types = true;
282288

283-
SessionStateBuilder::new()
289+
let mut builder = SessionStateBuilder::new()
284290
.with_default_features()
285291
.with_config(config)
286-
.with_runtime_env(runtime)
287-
.build()
292+
.with_runtime_env(runtime);
293+
294+
// Append any additional physical optimizer rules (e.g., enterprise partial agg pushdown)
295+
if let Ok(rules) = ADDITIONAL_PHYSICAL_OPTIMIZER_RULES.read() {
296+
for rule in rules.iter() {
297+
builder = builder.with_physical_optimizer_rule(Arc::clone(rule));
298+
}
299+
}
300+
301+
builder.build()
288302
}
289303

290304
/// this function returns the result of the query
@@ -316,6 +330,8 @@ impl Query {
316330
return Ok((Either::Left(vec![]), fields));
317331
}
318332

333+
let ctx = QUERY_SESSION.get_ctx();
334+
319335
let plan = ctx.state().create_physical_plan(df.logical_plan()).await?;
320336

321337
let results = if !is_streaming {

src/query/stream_schema_provider.rs

Lines changed: 109 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ use crate::{
5656
},
5757
event::DEFAULT_TIMESTAMP_KEY,
5858
hottier::HotTierManager,
59-
metrics::{QUERY_CACHE_HIT, increment_files_scanned_in_query_by_date},
59+
metrics::{
60+
QUERY_CACHE_HIT, increment_files_scanned_in_hottier_by_date,
61+
increment_files_scanned_in_query_by_date,
62+
},
6063
option::Mode,
6164
parseable::{DEFAULT_TENANT, PARSEABLE, STREAM_EXISTS},
6265
storage::{ObjectStorage, ObjectStoreFormat},
@@ -205,6 +208,13 @@ impl StandardTableProvider {
205208
.await
206209
.map_err(|err| DataFusionError::External(Box::new(err)))?;
207210

211+
increment_files_scanned_in_hottier_by_date(
212+
hot_tier_files.len() as u64,
213+
&chrono::Utc::now().date_naive().to_string(),
214+
self.tenant_id.as_deref().unwrap_or(DEFAULT_TENANT),
215+
&self.stream,
216+
);
217+
208218
let hot_tier_files: Vec<File> = hot_tier_files
209219
.into_iter()
210220
.map(|mut file| {
@@ -352,101 +362,108 @@ impl StandardTableProvider {
352362
&self,
353363
manifest_files: Vec<File>,
354364
) -> (Vec<Vec<PartitionedFile>>, datafusion::common::Statistics) {
355-
let target_partition: usize = num_cpus::get();
356-
let mut partitioned_files = Vec::from_iter((0..target_partition).map(|_| Vec::new()));
357-
let mut column_statistics = HashMap::<String, Option<TypedStatistics>>::new();
358-
let mut count = 0;
359-
let mut file_count = 0u64;
360-
for (index, file) in manifest_files
361-
.into_iter()
362-
.enumerate()
363-
.map(|(x, y)| (x % target_partition, y))
365+
partitioned_files(&self.schema, &self.tenant_id, manifest_files)
366+
}
367+
}
368+
369+
#[inline(always)]
370+
pub fn partitioned_files(
371+
schema: &SchemaRef,
372+
tenant_id: &Option<String>,
373+
manifest_files: Vec<File>,
374+
) -> (Vec<Vec<PartitionedFile>>, datafusion::common::Statistics) {
375+
let target_partition: usize = num_cpus::get();
376+
let mut partitioned_files = Vec::from_iter((0..target_partition).map(|_| Vec::new()));
377+
let mut column_statistics = HashMap::<String, Option<TypedStatistics>>::new();
378+
let mut count = 0;
379+
let mut file_count = 0u64;
380+
for (index, file) in manifest_files
381+
.into_iter()
382+
.enumerate()
383+
.map(|(x, y)| (x % target_partition, y))
384+
{
385+
#[allow(unused_mut)]
386+
let File {
387+
mut file_path,
388+
num_rows,
389+
columns,
390+
..
391+
} = file;
392+
393+
// Track billing metrics for files scanned in query
394+
file_count += 1;
395+
396+
// object_store::path::Path doesn't automatically deal with Windows path separators
397+
// to do that, we are using from_absolute_path() which takes into consideration the underlying filesystem
398+
// before sending the file path to PartitionedFile
399+
// the github issue- https://github.com/parseablehq/parseable/issues/824
400+
// For some reason, the `from_absolute_path()` doesn't work for macos, hence the ugly solution
401+
// TODO: figure out an elegant solution to this
402+
#[cfg(windows)]
364403
{
365-
#[allow(unused_mut)]
366-
let File {
367-
mut file_path,
368-
num_rows,
369-
columns,
370-
..
371-
} = file;
372-
373-
// Track billing metrics for files scanned in query
374-
file_count += 1;
375-
376-
// object_store::path::Path doesn't automatically deal with Windows path separators
377-
// to do that, we are using from_absolute_path() which takes into consideration the underlying filesystem
378-
// before sending the file path to PartitionedFile
379-
// the github issue- https://github.com/parseablehq/parseable/issues/824
380-
// For some reason, the `from_absolute_path()` doesn't work for macos, hence the ugly solution
381-
// TODO: figure out an elegant solution to this
382-
#[cfg(windows)]
383-
{
384-
if PARSEABLE.storage.name() == "drive" {
385-
file_path = object_store::path::Path::from_absolute_path(file_path)
386-
.unwrap()
387-
.to_string();
388-
}
404+
if PARSEABLE.storage.name() == "drive" {
405+
file_path = object_store::path::Path::from_absolute_path(file_path)
406+
.unwrap()
407+
.to_string();
389408
}
390-
let pf = PartitionedFile::new(file_path, file.file_size);
391-
partitioned_files[index].push(pf);
392-
393-
columns.into_iter().for_each(|col| {
394-
column_statistics
395-
.entry(col.name)
396-
.and_modify(|x| {
397-
if let Some((stats, col_stats)) = x.as_ref().cloned().zip(col.stats.clone())
398-
{
399-
// update() returns None on type mismatch (e.g. column
400-
// historically written as both Utf8 and Timestamp(ms)).
401-
// Dropping to None here makes the planner skip min/max
402-
// pushdown for this column instead of crashing the worker.
403-
*x = stats.update(col_stats);
404-
}
405-
})
406-
.or_insert_with(|| col.stats.as_ref().cloned());
407-
});
408-
count += num_rows;
409409
}
410-
let statistics = self
411-
.schema
412-
.fields()
413-
.iter()
414-
.map(|field| {
415-
column_statistics
416-
.get(field.name())
417-
.and_then(|stats| stats.as_ref())
418-
.and_then(|stats| stats.clone().min_max_as_scalar(field.data_type()))
419-
.map(|(min, max)| datafusion::common::ColumnStatistics {
420-
null_count: Precision::Absent,
421-
max_value: Precision::Exact(max),
422-
min_value: Precision::Exact(min),
423-
distinct_count: Precision::Absent,
424-
sum_value: Precision::Absent,
425-
byte_size: Precision::Absent,
426-
})
427-
.unwrap_or_default()
428-
})
429-
.collect();
410+
let pf = PartitionedFile::new(file_path, file.file_size);
411+
partitioned_files[index].push(pf);
412+
413+
columns.into_iter().for_each(|col| {
414+
column_statistics
415+
.entry(col.name)
416+
.and_modify(|x| {
417+
if let Some((stats, col_stats)) = x.as_ref().cloned().zip(col.stats.clone()) {
418+
// update() returns None on type mismatch (e.g. column
419+
// historically written as both Utf8 and Timestamp(ms)).
420+
// Dropping to None here makes the planner skip min/max
421+
// pushdown for this column instead of crashing the worker.
422+
*x = stats.update(col_stats);
423+
}
424+
})
425+
.or_insert_with(|| col.stats.as_ref().cloned());
426+
});
427+
count += num_rows;
428+
}
429+
let statistics = schema
430+
.fields()
431+
.iter()
432+
.map(|field| {
433+
column_statistics
434+
.get(field.name())
435+
.and_then(|stats| stats.as_ref())
436+
.and_then(|stats| stats.clone().min_max_as_scalar(field.data_type()))
437+
.map(|(min, max)| datafusion::common::ColumnStatistics {
438+
null_count: Precision::Absent,
439+
max_value: Precision::Exact(max),
440+
min_value: Precision::Exact(min),
441+
distinct_count: Precision::Absent,
442+
sum_value: Precision::Absent,
443+
byte_size: Precision::Absent,
444+
})
445+
.unwrap_or_default()
446+
})
447+
.collect();
430448

431-
let statistics = datafusion::common::Statistics {
432-
num_rows: Precision::Exact(count as usize),
433-
total_byte_size: Precision::Absent,
434-
column_statistics: statistics,
435-
};
449+
let statistics = datafusion::common::Statistics {
450+
num_rows: Precision::Exact(count as usize),
451+
total_byte_size: Precision::Absent,
452+
column_statistics: statistics,
453+
};
436454

437-
// Track billing metrics for query scan
438-
let current_date = chrono::Utc::now().date_naive().to_string();
439-
increment_files_scanned_in_query_by_date(
440-
file_count,
441-
&current_date,
442-
self.tenant_id.as_deref().unwrap_or(DEFAULT_TENANT),
443-
);
455+
// Track billing metrics for query scan
456+
let current_date = chrono::Utc::now().date_naive().to_string();
457+
increment_files_scanned_in_query_by_date(
458+
file_count,
459+
&current_date,
460+
tenant_id.as_deref().unwrap_or(DEFAULT_TENANT),
461+
);
444462

445-
(partitioned_files, statistics)
446-
}
463+
(partitioned_files, statistics)
447464
}
448465

449-
async fn collect_from_snapshot(
466+
pub async fn collect_from_snapshot(
450467
snapshot: &Snapshot,
451468
time_filters: &[PartialTimeFilter],
452469
filters: &[Expr],
@@ -683,7 +700,8 @@ impl TableProvider for StandardTableProvider {
683700
}
684701
}
685702

686-
fn reversed_mem_table(
703+
#[inline(always)]
704+
pub fn reversed_mem_table(
687705
mut records: Vec<RecordBatch>,
688706
schema: Arc<Schema>,
689707
) -> Result<MemTable, DataFusionError> {
@@ -863,7 +881,7 @@ pub fn is_within_staging_window(time_filters: &[PartialTimeFilter]) -> bool {
863881
!has_upper_bound
864882
}
865883

866-
fn expr_in_boundary(filter: &Expr) -> bool {
884+
pub fn expr_in_boundary(filter: &Expr) -> bool {
867885
let Expr::BinaryExpr(binexpr) = filter else {
868886
return false;
869887
};
@@ -881,7 +899,7 @@ fn expr_in_boundary(filter: &Expr) -> bool {
881899
)
882900
}
883901

884-
fn extract_timestamp_bound(
902+
pub fn extract_timestamp_bound(
885903
binexpr: &BinaryExpr,
886904
time_partition: &Option<String>,
887905
) -> Option<(Operator, NaiveDateTime)> {

0 commit comments

Comments
 (0)