Skip to content

Commit 1025607

Browse files
committed
security(sql): enforce reconstructed SQL provenance
1 parent 5f64ddd commit 1025607

52 files changed

Lines changed: 1765 additions & 357 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ jobs:
6464
run: bash scripts/ci/check_calvin_determinism.sh
6565
- name: Plane-separation gate
6666
run: bash scripts/ci/check_plane_separation.sh
67+
- name: Reconstructed-SQL gate
68+
run: |
69+
python3 scripts/ci/check_reconstructed_sql.py --self-test
70+
python3 scripts/ci/check_reconstructed_sql.py
6771
- name: Install cargo-deny
6872
uses: taiki-e/install-action@v2
6973
with:
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
//! Event-Plane operations executed by the receiving Control Plane.
4+
//!
5+
//! These plans are cluster-RPC envelopes only. They must never cross the
6+
//! Control Plane → Data Plane bridge.
7+
8+
/// Cluster-routed Event-Plane operation.
9+
#[derive(
10+
Debug,
11+
Clone,
12+
PartialEq,
13+
serde::Serialize,
14+
serde::Deserialize,
15+
zerompk::ToMessagePack,
16+
zerompk::FromMessagePack,
17+
)]
18+
pub enum ClusterEventOp {
19+
/// Consume one CDC partition from the leader node's local event buffer.
20+
ConsumeStream {
21+
stream_name: String,
22+
group_name: String,
23+
partition: u32,
24+
limit: u64,
25+
},
26+
/// Publish one durable-topic message on the topic's home node.
27+
PublishTopic { topic_name: String, payload: String },
28+
}
29+
30+
#[cfg(test)]
31+
mod tests {
32+
use super::ClusterEventOp;
33+
use crate::physical_plan::{PhysicalPlan, wire};
34+
35+
#[test]
36+
fn cluster_event_plan_roundtrips_over_cluster_wire() {
37+
let plan = PhysicalPlan::ClusterEvent(ClusterEventOp::ConsumeStream {
38+
stream_name: "orders; no SQL".into(),
39+
group_name: "Analytics".into(),
40+
partition: 7,
41+
limit: 128,
42+
});
43+
let encoded = wire::encode(&plan).expect("encode typed cluster event");
44+
assert_eq!(
45+
wire::decode(&encoded).expect("decode typed cluster event"),
46+
plan
47+
);
48+
}
49+
}

nodedb-physical/src/physical_plan/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
99
pub mod array;
1010
pub mod cluster_array;
11+
pub mod cluster_event;
1112
pub mod columnar;
1213
pub mod crdt;
1314
pub mod document;
@@ -26,6 +27,7 @@ pub mod wire;
2627

2728
pub use array::{ArrayBinaryOp, ArrayOp, ArrayReducer};
2829
pub use cluster_array::ClusterArrayOp;
30+
pub use cluster_event::ClusterEventOp;
2931
pub use columnar::{ColumnarInsertIntent, ColumnarOp};
3032
pub use crdt::CrdtOp;
3133
pub use document::{
@@ -89,6 +91,9 @@ pub enum PhysicalPlan {
8991
/// Cluster-mode array operations executed by the coordinator on the
9092
/// Control Plane. Never sent to the Data Plane.
9193
ClusterArray(ClusterArrayOp),
94+
/// Event-Plane operations executed by a receiving Control Plane.
95+
/// Never sent to the Data Plane.
96+
ClusterEvent(ClusterEventOp),
9297
}
9398

9499
impl PhysicalPlan {
@@ -200,6 +205,7 @@ impl PhysicalPlan {
200205
| PhysicalPlan::Meta(_)
201206
| PhysicalPlan::Array(_)
202207
| PhysicalPlan::ClusterArray(_)
208+
| PhysicalPlan::ClusterEvent(_)
203209
| PhysicalPlan::Query(_) => false,
204210
}
205211
}
@@ -336,7 +342,8 @@ impl PhysicalPlan {
336342
| PhysicalPlan::Query(_)
337343
| PhysicalPlan::Meta(_)
338344
| PhysicalPlan::Array(_)
339-
| PhysicalPlan::ClusterArray(_) => None,
345+
| PhysicalPlan::ClusterArray(_)
346+
| PhysicalPlan::ClusterEvent(_) => None,
340347
}
341348
}
342349

nodedb-physical/src/physical_plan/routing.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ pub fn plan_contains_cluster_partitioned_leaf(plan: &PhysicalPlan) -> bool {
117117
| PhysicalPlan::Spatial(_)
118118
| PhysicalPlan::Crdt(_)
119119
| PhysicalPlan::Meta(_)
120+
| PhysicalPlan::ClusterEvent(_)
120121
| PhysicalPlan::Query(QueryOp::ProviderScan { .. })
121122
| PhysicalPlan::Query(QueryOp::PartialAggregate { .. })
122123
| PhysicalPlan::Query(QueryOp::PartialAggregateState { .. })

nodedb-sql/src/ddl_ast/parse/collection/column_list.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,12 @@ pub(super) fn extract_column_pairs(body: &str) -> Result<Vec<(String, String)>,
6464
/// Heuristic: does the first token in the paren body look like a WITH-clause
6565
/// key rather than a column name+type?
6666
fn is_with_clause_inner(upper_inner: &str) -> bool {
67-
let first_tok = upper_inner.split_whitespace().next().unwrap_or("");
67+
let first_tok = upper_inner
68+
.split(|character: char| character.is_whitespace() || character == '=')
69+
.next()
70+
.unwrap_or("");
6871
matches!(
69-
first_tok.trim_end_matches(['=', '\'']),
72+
first_tok,
7073
"ENGINE"
7174
| "PROFILE"
7275
| "VECTOR_FIELD"
@@ -319,6 +322,15 @@ fn parse_col_token(token: &str) -> Result<Option<(String, String)>, SqlError> {
319322
mod tests {
320323
use super::*;
321324

325+
#[test]
326+
fn with_clause_without_spaces_is_not_a_column_list() {
327+
assert!(
328+
extract_column_pairs("WITH (engine='document_schemaless')")
329+
.expect("WITH options")
330+
.is_empty()
331+
);
332+
}
333+
322334
#[test]
323335
fn generated_clause_after_unicode_type_preserves_original_offsets() {
324336
let parsed = parse_col_token("slug CUSTOMffff GENERATED ALWAYS AS (lower(name))")

nodedb/src/control/clone/resolver/rewrite.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,8 @@ pub fn rewrite_plan_for_source(
289289
| PhysicalPlan::Query(_)
290290
| PhysicalPlan::Meta(_)
291291
| PhysicalPlan::Array(_)
292-
| PhysicalPlan::ClusterArray(_) => Ok(None),
292+
| PhysicalPlan::ClusterArray(_)
293+
| PhysicalPlan::ClusterEvent(_) => Ok(None),
293294
}
294295
}
295296

@@ -322,7 +323,8 @@ pub(super) fn extract_collection_from_plan(plan: &PhysicalPlan) -> Option<&str>
322323
| PhysicalPlan::Query(_)
323324
| PhysicalPlan::Meta(_)
324325
| PhysicalPlan::Array(_)
325-
| PhysicalPlan::ClusterArray(_) => None,
326+
| PhysicalPlan::ClusterArray(_)
327+
| PhysicalPlan::ClusterEvent(_) => None,
326328
}
327329
}
328330

nodedb/src/control/cluster/calvin/scheduler/driver/core/routing.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ pub(crate) fn plan_vshard_in_database(plan: &PhysicalPlan, database_id: Database
7777
// Cluster-fanned-out array ops are handled entirely by the
7878
// Control-Plane `ArrayCoordinator` and never dispatched to the Data
7979
// Plane (see `data/executor/dispatch/visitor.rs`'s `unreachable!`).
80-
PhysicalPlan::ClusterArray(_) => PlanRouting::ControlPlaneOnly,
80+
PhysicalPlan::ClusterArray(_) | PhysicalPlan::ClusterEvent(_) => {
81+
PlanRouting::ControlPlaneOnly
82+
}
8183
// Reads / query operators / metadata ops: `is_write_plan` already
8284
// excludes every variant of these four families upstream.
8385
PhysicalPlan::Text(_) => PlanRouting::NotAWrite,

0 commit comments

Comments
 (0)