Skip to content

Commit fbedb6a

Browse files
committed
Merge branch 'main' into fix-update-totals
2 parents 9a01eea + 5d9d0f5 commit fbedb6a

9 files changed

Lines changed: 135 additions & 37 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ jobs:
195195

196196
- name: Install cargo-nextest
197197
if: matrix.test-suite.name == 'default'
198-
uses: taiki-e/install-action@d9be7d8cda89035c9c843f78bd44d4f72d8403d4 # v2.79.7
198+
uses: taiki-e/install-action@e49978b799e49ff429d162b7a30601a569ab6538 # v2.81.1
199199
with:
200200
tool: cargo-nextest
201201

.github/workflows/ci_typos.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ jobs:
4747
with:
4848
persist-credentials: false
4949
- name: Check typos
50-
uses: crate-ci/typos@7b04f660f4ee4f048d18fd341887cf28dfbedfe2 # v1.46.3
50+
uses: crate-ci/typos@f8a58b6b53f2279f71eb605f03a4ae4d10608f45 # v1.47.0

.github/workflows/public-api.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
repo-token: ${{ secrets.GITHUB_TOKEN }}
5353

5454
- name: Install cargo-public-api
55-
uses: taiki-e/install-action@d9be7d8cda89035c9c843f78bd44d4f72d8403d4 # v2.79.7
55+
uses: taiki-e/install-action@e49978b799e49ff429d162b7a30601a569ab6538 # v2.81.1
5656
with:
5757
tool: cargo-public-api
5858

Cargo.lock

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

bindings/python/uv.lock

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

crates/catalog/glue/src/catalog.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use iceberg_storage_opendal::OpenDalStorageFactory;
4040
use crate::error::{from_aws_build_error, from_aws_sdk_error};
4141
use crate::utils::{
4242
convert_to_database, convert_to_glue_table, convert_to_namespace, create_sdk_config,
43-
get_default_table_location, get_metadata_location, validate_namespace,
43+
get_default_table_location, get_metadata_location, is_iceberg_table, validate_namespace,
4444
};
4545
use crate::{
4646
AWS_ACCESS_KEY_ID, AWS_REGION_NAME, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, with_catalog_id,
@@ -504,9 +504,21 @@ impl Catalog for GlueCatalog {
504504
}
505505

506506
let db_name = validate_namespace(namespace)?;
507-
let table_list = self.list_tables(namespace).await?;
508507

509-
if !table_list.is_empty() {
508+
// Check for ANY Glue table in the database, not just Iceberg tables.
509+
// Glue's `delete_database` will fail if any table (Iceberg or not) is
510+
// still present, and `list_tables` only returns Iceberg tables, so we
511+
// query Glue directly here.
512+
let builder = self
513+
.client
514+
.0
515+
.get_tables()
516+
.database_name(&db_name)
517+
.max_results(1);
518+
let builder = with_catalog_id!(builder, self.config);
519+
let resp = builder.send().await.map_err(from_aws_sdk_error)?;
520+
521+
if !resp.table_list().is_empty() {
510522
return Err(Error::new(
511523
ErrorKind::DataInvalid,
512524
format!("Database with name: {} is not empty", &db_name),
@@ -521,12 +533,16 @@ impl Catalog for GlueCatalog {
521533
Ok(())
522534
}
523535

524-
/// Asynchronously lists all tables within a specified namespace.
536+
/// Asynchronously lists all Iceberg tables within a specified namespace.
537+
///
538+
/// Glue databases may contain a mix of Iceberg and non-Iceberg tables
539+
/// (e.g. plain Hive tables). Only tables whose `table_type` parameter is
540+
/// set to `ICEBERG` (case-insensitive) are returned
525541
///
526542
/// # Returns
527543
/// A `Result<Vec<TableIdent>>`, which is:
528544
/// - `Ok(vec![...])` containing a vector of `TableIdent` instances, each
529-
/// representing a table within the specified namespace.
545+
/// representing an Iceberg table within the specified namespace.
530546
/// - `Err(...)` if an error occurs during namespace validation or while
531547
/// querying the database.
532548
async fn list_tables(&self, namespace: &NamespaceIdent) -> Result<Vec<TableIdent>> {
@@ -551,6 +567,7 @@ impl Catalog for GlueCatalog {
551567
let tables: Vec<_> = resp
552568
.table_list()
553569
.iter()
570+
.filter(|tbl| is_iceberg_table(&tbl.parameters))
554571
.map(|tbl| TableIdent::new(namespace.clone(), tbl.name().to_string()))
555572
.collect();
556573

crates/catalog/glue/src/utils.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,17 @@ pub(crate) fn get_default_table_location(
226226
}
227227
}
228228

229+
/// Returns `true` if the given Glue table is an Iceberg table.
230+
///
231+
/// Iceberg tables are identified by the `table_type=ICEBERG` parameter
232+
/// (case-insensitive).
233+
pub(crate) fn is_iceberg_table(parameters: &Option<HashMap<String, String>>) -> bool {
234+
parameters
235+
.as_ref()
236+
.and_then(|p| p.get(TABLE_TYPE))
237+
.is_some_and(|v| v.eq_ignore_ascii_case(ICEBERG))
238+
}
239+
229240
/// Get metadata location from `GlueTable` parameters
230241
pub(crate) fn get_metadata_location(
231242
parameters: &Option<HashMap<String, String>>,
@@ -302,6 +313,36 @@ mod tests {
302313
Ok(())
303314
}
304315

316+
#[test]
317+
fn test_is_iceberg_table() {
318+
// table_type=ICEBERG -> iceberg
319+
let params_table_type = Some(HashMap::from([(
320+
TABLE_TYPE.to_string(),
321+
ICEBERG.to_string(),
322+
)]));
323+
assert!(is_iceberg_table(&params_table_type));
324+
325+
// table_type is case-insensitive
326+
let params_table_type_lower = Some(HashMap::from([(
327+
TABLE_TYPE.to_string(),
328+
"iceberg".to_string(),
329+
)]));
330+
assert!(is_iceberg_table(&params_table_type_lower));
331+
332+
// Plain Hive table -> not iceberg
333+
let params_hive = Some(HashMap::from([(
334+
TABLE_TYPE.to_string(),
335+
"EXTERNAL_TABLE".to_string(),
336+
)]));
337+
assert!(!is_iceberg_table(&params_hive));
338+
339+
// No parameters at all -> not iceberg
340+
assert!(!is_iceberg_table(&None));
341+
342+
// Empty parameters -> not iceberg
343+
assert!(!is_iceberg_table(&Some(HashMap::new())));
344+
}
345+
305346
#[test]
306347
fn test_convert_to_glue_table() -> Result<()> {
307348
let table_name = "my_table".to_string();

crates/catalog/sql/src/catalog.rs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,11 @@ impl SqlCatalog {
257257
"StorageFactory must be provided for SqlCatalog. Use `with_storage_factory` to configure it.",
258258
)
259259
})?;
260-
let fileio = FileIOBuilder::new(factory).build();
260+
// Forward catalog props so storage-backend keys reach the FileIO.
261+
// Unrecognized keys are ignored by backends.
262+
let fileio = FileIOBuilder::new(factory)
263+
.with_props(config.props.clone())
264+
.build();
261265

262266
install_default_drivers();
263267
let max_connections: u32 = config
@@ -598,7 +602,7 @@ impl Catalog for SqlCatalog {
598602
let mut tx = self.connection.begin().await.map_err(from_sqlx_error)?;
599603
let update_stmt = format!(
600604
"UPDATE {NAMESPACE_TABLE_NAME} SET {NAMESPACE_FIELD_PROPERTY_VALUE} = ?
601-
WHERE {CATALOG_FIELD_CATALOG_NAME} = ?
605+
WHERE {CATALOG_FIELD_CATALOG_NAME} = ?
602606
AND {NAMESPACE_FIELD_NAME} = ?
603607
AND {NAMESPACE_FIELD_PROPERTY_KEY} = ?"
604608
);
@@ -689,7 +693,7 @@ impl Catalog for SqlCatalog {
689693
WHERE {CATALOG_FIELD_TABLE_NAMESPACE} = ?
690694
AND {CATALOG_FIELD_CATALOG_NAME} = ?
691695
AND (
692-
{CATALOG_FIELD_RECORD_TYPE} = '{CATALOG_FIELD_TABLE_RECORD_TYPE}'
696+
{CATALOG_FIELD_RECORD_TYPE} = '{CATALOG_FIELD_TABLE_RECORD_TYPE}'
693697
OR {CATALOG_FIELD_RECORD_TYPE} IS NULL
694698
)",
695699
),
@@ -728,7 +732,7 @@ impl Catalog for SqlCatalog {
728732
AND {CATALOG_FIELD_CATALOG_NAME} = ?
729733
AND {CATALOG_FIELD_TABLE_NAME} = ?
730734
AND (
731-
{CATALOG_FIELD_RECORD_TYPE} = '{CATALOG_FIELD_TABLE_RECORD_TYPE}'
735+
{CATALOG_FIELD_RECORD_TYPE} = '{CATALOG_FIELD_TABLE_RECORD_TYPE}'
732736
OR {CATALOG_FIELD_RECORD_TYPE} IS NULL
733737
)"
734738
),
@@ -755,7 +759,7 @@ impl Catalog for SqlCatalog {
755759
AND {CATALOG_FIELD_TABLE_NAME} = ?
756760
AND {CATALOG_FIELD_TABLE_NAMESPACE} = ?
757761
AND (
758-
{CATALOG_FIELD_RECORD_TYPE} = '{CATALOG_FIELD_TABLE_RECORD_TYPE}'
762+
{CATALOG_FIELD_RECORD_TYPE} = '{CATALOG_FIELD_TABLE_RECORD_TYPE}'
759763
OR {CATALOG_FIELD_RECORD_TYPE} IS NULL
760764
)"
761765
),
@@ -791,7 +795,7 @@ impl Catalog for SqlCatalog {
791795
AND {CATALOG_FIELD_TABLE_NAME} = ?
792796
AND {CATALOG_FIELD_TABLE_NAMESPACE} = ?
793797
AND (
794-
{CATALOG_FIELD_RECORD_TYPE} = '{CATALOG_FIELD_TABLE_RECORD_TYPE}'
798+
{CATALOG_FIELD_RECORD_TYPE} = '{CATALOG_FIELD_TABLE_RECORD_TYPE}'
795799
OR {CATALOG_FIELD_RECORD_TYPE} IS NULL
796800
)"
797801
),
@@ -1180,6 +1184,33 @@ mod tests {
11801184
new_sql_catalog(warehouse_loc.clone(), Some("iceberg")).await;
11811185
}
11821186

1187+
// Regression test: storage-backend props set on the catalog must reach
1188+
// the FileIO; otherwise authenticated backends fail with 401s on writes.
1189+
#[tokio::test]
1190+
async fn test_storage_props_propagate_to_file_io() {
1191+
let sql_lite_uri = format!("sqlite:{}", temp_path());
1192+
sqlx::Sqlite::create_database(&sql_lite_uri).await.unwrap();
1193+
let warehouse_location = temp_path();
1194+
1195+
let catalog = SqlCatalogBuilder::default()
1196+
.with_storage_factory(Arc::new(LocalFsStorageFactory))
1197+
.load(
1198+
"iceberg",
1199+
HashMap::from_iter([
1200+
(SQL_CATALOG_PROP_URI.to_string(), sql_lite_uri),
1201+
(SQL_CATALOG_PROP_WAREHOUSE.to_string(), warehouse_location),
1202+
("s3.region".to_string(), "us-east-1".to_string()),
1203+
("hf.token".to_string(), "hf_test_token".to_string()),
1204+
]),
1205+
)
1206+
.await
1207+
.unwrap();
1208+
1209+
let props = catalog.fileio.config().props();
1210+
assert_eq!(props.get("s3.region"), Some(&"us-east-1".to_string()));
1211+
assert_eq!(props.get("hf.token"), Some(&"hf_test_token".to_string()));
1212+
}
1213+
11831214
#[tokio::test]
11841215
async fn test_builder_method() {
11851216
let sql_lite_uri = format!("sqlite:{}", temp_path());

0 commit comments

Comments
 (0)