use std::sync::Arc;
use rstest::rstest;
use test_utils::add_commit;
use crate::engine::sync::SyncEngine;
#[cfg(feature = "declarative-plans")]
use crate::engine::test_delegating::DelegatingEngine;
use crate::object_store::memory::InMemory;
use crate::{Engine, Snapshot};
const ONE_COLUMN_SCHEMA_STRING: &str =
r#"{"type":"struct","fields":[{"name":"id","type":"long","nullable":true,"metadata":{}}]}"#;
const TWO_COLUMN_SCHEMA_STRING: &str = r#"{"type":"struct","fields":[{"name":"id","type":"long","nullable":true,"metadata":{}},{"name":"name","type":"string","nullable":true,"metadata":{}}]}"#;
fn checkpoint_commit(version: i64, features: &[&str], schema_string: &str) -> String {
serde_json::json!({ "checkpoint": [
{ "checkpointMetadata": { "version": version } },
{ "contentRoot": { "path": "metadata/root.parquet", "sizeInBytes": 1, "version": version } },
{ "protocol": {
"minReaderVersion": 3, "minWriterVersion": 7,
"readerFeatures": features, "writerFeatures": features,
} },
{ "metaData": {
"id": "test-table",
"format": { "provider": "parquet", "options": {} },
"schemaString": schema_string,
"partitionColumns": [],
"configuration": {},
} },
] })
.to_string()
}
fn metadata_commit(schema_string: &str) -> String {
serde_json::json!({ "metaData": {
"id": "test-table",
"format": { "provider": "parquet", "options": {} },
"schemaString": schema_string,
"partitionColumns": [],
"configuration": {},
} })
.to_string()
}
fn protocol_commit(min_reader_version: i64, min_writer_version: i64) -> String {
serde_json::json!({ "protocol": {
"minReaderVersion": min_reader_version,
"minWriterVersion": min_writer_version,
} })
.to_string()
}
fn non_plan_engine(store: Arc<InMemory>) -> impl Engine {
let engine = SyncEngine::new_with_store(store);
#[cfg(feature = "declarative-plans")]
let engine = DelegatingEngine::new(Arc::new(engine)).without_plan_executor();
engine
}
#[tokio::test]
async fn test_load_resolves_pm_from_manifest_commit_checkpoint_action() {
check_manifest_commit_checkpoint(non_plan_engine).await;
#[cfg(feature = "declarative-plans")]
check_manifest_commit_checkpoint(|store| SyncEngine::new_with_store(store)).await;
}
async fn check_manifest_commit_checkpoint<E: Engine>(make_engine: impl FnOnce(Arc<InMemory>) -> E) {
let store = Arc::new(InMemory::new());
let table_root = url::Url::parse("memory:///").unwrap();
add_commit(
table_root.as_str(),
store.as_ref(),
0,
checkpoint_commit(0, &["adaptiveMetadata-preview"], ONE_COLUMN_SCHEMA_STRING),
)
.await
.unwrap();
let engine = make_engine(store);
let snapshot = Snapshot::builder_for(table_root).build(&engine).unwrap();
assert_eq!(snapshot.version(), 0);
assert!(snapshot.schema().field("id").is_some());
}
#[rstest]
#[case::newer_checkpoint_beats_older_pm(
format!("{}\n{}", protocol_commit(1, 2), metadata_commit(ONE_COLUMN_SCHEMA_STRING)),
checkpoint_commit(1, &["adaptiveMetadata-preview"], TWO_COLUMN_SCHEMA_STRING),
2,
1
)]
#[case::newer_checkpoint_beats_older_metadata(
metadata_commit(ONE_COLUMN_SCHEMA_STRING),
checkpoint_commit(1, &["adaptiveMetadata-preview"], TWO_COLUMN_SCHEMA_STRING),
2,
1
)]
#[case::newer_metadata_beats_older_checkpoint(
checkpoint_commit(0, &["adaptiveMetadata-preview"], ONE_COLUMN_SCHEMA_STRING),
metadata_commit(TWO_COLUMN_SCHEMA_STRING),
2,
1
)]
#[case::newer_checkpoint_protocol_wins(
checkpoint_commit(0, &["adaptiveMetadata-preview"], ONE_COLUMN_SCHEMA_STRING),
checkpoint_commit(1, &["adaptiveMetadata-preview", "deletionVectors"], ONE_COLUMN_SCHEMA_STRING),
1,
2
)]
#[tokio::test]
async fn resolve_pm_newest_action_wins(
#[case] v0: String,
#[case] v1: String,
#[case] expected_fields: usize,
#[case] expected_reader_features: usize,
) {
assert_newest_pm_wins(v0, v1, expected_fields, expected_reader_features).await;
}
async fn assert_newest_pm_wins(
v0: String,
v1: String,
expected_fields: usize,
expected_reader_features: usize,
) {
build_and_check_pm(
&v0,
&v1,
expected_fields,
expected_reader_features,
non_plan_engine,
)
.await;
#[cfg(feature = "declarative-plans")]
build_and_check_pm(
&v0,
&v1,
expected_fields,
expected_reader_features,
|store| SyncEngine::new_with_store(store),
)
.await;
}
async fn build_and_check_pm<E: Engine>(
v0: &str,
v1: &str,
expected_fields: usize,
expected_reader_features: usize,
make_engine: impl FnOnce(Arc<InMemory>) -> E,
) {
let store = Arc::new(InMemory::new());
let table_root = url::Url::parse("memory:///").unwrap();
add_commit(table_root.as_str(), store.as_ref(), 0, v0.to_string())
.await
.unwrap();
add_commit(table_root.as_str(), store.as_ref(), 1, v1.to_string())
.await
.unwrap();
let engine = make_engine(store);
let snapshot = Snapshot::builder_for(table_root).build(&engine).unwrap();
assert_eq!(snapshot.version(), 1);
assert_eq!(
snapshot.schema().num_fields(),
expected_fields,
"resolved metaData should have {expected_fields} column(s)"
);
let protocol = snapshot.table_configuration().protocol();
assert_eq!(
protocol.min_reader_version(),
3,
"AMT protocol is reader v3"
);
assert_eq!(
protocol.reader_features().map_or(0, |f| f.len()),
expected_reader_features,
"resolved protocol should have {expected_reader_features} reader feature(s)"
);
}
#[tokio::test]
async fn test_lagging_checkpoint_ranks_by_checkpoint_version() {
assert_lagging_checkpoint_loses_to_gap_commit(non_plan_engine).await;
#[cfg(feature = "declarative-plans")]
assert_lagging_checkpoint_loses_to_gap_commit(|store| SyncEngine::new_with_store(store)).await;
}
async fn assert_lagging_checkpoint_loses_to_gap_commit<E: Engine>(
make_engine: impl FnOnce(Arc<InMemory>) -> E,
) {
let store = Arc::new(InMemory::new());
let table_root = url::Url::parse("memory:///").unwrap();
add_commit(
table_root.as_str(),
store.as_ref(),
0,
checkpoint_commit(0, &["adaptiveMetadata-preview"], ONE_COLUMN_SCHEMA_STRING),
)
.await
.unwrap();
add_commit(
table_root.as_str(),
store.as_ref(),
1,
metadata_commit(TWO_COLUMN_SCHEMA_STRING),
)
.await
.unwrap();
add_commit(
table_root.as_str(),
store.as_ref(),
2,
checkpoint_commit(0, &["adaptiveMetadata-preview"], ONE_COLUMN_SCHEMA_STRING),
)
.await
.unwrap();
let engine = make_engine(store);
let snapshot = Snapshot::builder_for(table_root).build(&engine).unwrap();
assert_eq!(snapshot.version(), 2);
let schema = snapshot.schema();
assert!(schema.field("name").is_some());
assert_eq!(schema.num_fields(), 2);
}