From 59a1c2ae2e6ef5e4be3c596fe78504affc810ee4 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Mon, 13 Oct 2025 13:12:33 +0300 Subject: [PATCH] Disallow joining more than 63 tables Returns an error instead of panicing --- core/translate/optimizer/mod.rs | 6 +++++ core/translate/plan.rs | 5 ++++ .../query_processing/test_read_path.rs | 23 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/core/translate/optimizer/mod.rs b/core/translate/optimizer/mod.rs index f053e7e7e..81dde810c 100644 --- a/core/translate/optimizer/mod.rs +++ b/core/translate/optimizer/mod.rs @@ -187,6 +187,12 @@ fn optimize_table_access( order_by: &mut Vec<(Box, SortOrder)>, group_by: &mut Option, ) -> Result>> { + if table_references.joined_tables().len() > TableReferences::MAX_JOINED_TABLES { + crate::bail_parse_error!( + "Only up to {} tables can be joined", + TableReferences::MAX_JOINED_TABLES + ); + } let access_methods_arena = RefCell::new(Vec::new()); let maybe_order_target = compute_order_target(order_by, group_by.as_mut()); let constraints_per_table = diff --git a/core/translate/plan.rs b/core/translate/plan.rs index 21fa84b69..2aef7d507 100644 --- a/core/translate/plan.rs +++ b/core/translate/plan.rs @@ -583,6 +583,11 @@ pub struct TableReferences { } impl TableReferences { + /// The maximum number of tables that can be joined together in a query. + /// This limit is arbitrary, although we currently use a u128 to represent the [crate::translate::planner::TableMask], + /// which can represent up to 128 tables. + /// Even at 63 tables we currently cannot handle the optimization performantly, hence the arbitrary cap. + pub const MAX_JOINED_TABLES: usize = 63; pub fn new( joined_tables: Vec, outer_query_refs: Vec, diff --git a/tests/integration/query_processing/test_read_path.rs b/tests/integration/query_processing/test_read_path.rs index 98874283b..5044be19d 100644 --- a/tests/integration/query_processing/test_read_path.rs +++ b/tests/integration/query_processing/test_read_path.rs @@ -897,3 +897,26 @@ fn test_multiple_connections_visibility() -> anyhow::Result<()> { assert_eq!(rows, vec![vec![rusqlite::types::Value::Integer(2)]]); Ok(()) } + +#[test] +/// Test that we can only join up to 63 tables, and trying to join more should fail with an error instead of panicing. +fn test_max_joined_tables_limit() { + let tmp_db = TempDatabase::new("test_max_joined_tables_limit", false); + let conn = tmp_db.connect_limbo(); + + // Create 64 tables + for i in 0..64 { + conn.execute(&format!("CREATE TABLE t{} (id INTEGER)", i)).unwrap(); + } + + // Try to join 64 tables - should fail + let mut sql = String::from("SELECT * FROM t0"); + for i in 1..64 { + sql.push_str(&format!(" JOIN t{} ON t{}.id = t0.id", i, i)); + } + + let Err(LimboError::ParseError(result)) = conn.prepare(&sql) else { + panic!("Expected an error but got no error"); + }; + assert!(result.contains("Only up to 63 tables can be joined")); +}