Skip to content

Commit 934f9fd

Browse files
delete branch functionality for postgres and sqlite
1 parent 0cf1e8b commit 934f9fd

3 files changed

Lines changed: 192 additions & 1 deletion

File tree

pangolin/pangolin_store/src/postgres/branches.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ impl PostgresStore {
8383
if result.rows_affected() == 0 {
8484
return Err(anyhow::anyhow!("Branch not found"));
8585
}
86+
87+
// Also delete assets associated with this branch
88+
sqlx::query("DELETE FROM assets WHERE tenant_id = $1 AND catalog_name = $2 AND branch = $3")
89+
.bind(tenant_id)
90+
.bind(catalog_name)
91+
.bind(&name)
92+
.execute(&self.pool)
93+
.await?;
94+
8695
Ok(())
8796
}
8897

pangolin/pangolin_store/src/sqlite/branches.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,25 @@ impl SqliteStore {
7171
}
7272

7373
pub async fn delete_branch(&self, tenant_id: Uuid, catalog_name: &str, name: String) -> Result<()> {
74-
sqlx::query("DELETE FROM branches WHERE tenant_id = ? AND catalog_name = ? AND name = ?")
74+
let result = sqlx::query("DELETE FROM branches WHERE tenant_id = ? AND catalog_name = ? AND name = ?")
7575
.bind(tenant_id.to_string())
7676
.bind(catalog_name)
7777
.bind(&name)
7878
.execute(&self.pool)
7979
.await?;
80+
81+
if result.rows_affected() == 0 {
82+
return Err(anyhow::anyhow!("Branch '{}' not found", name));
83+
}
84+
85+
// Also delete assets associated with this branch
86+
sqlx::query("DELETE FROM assets WHERE tenant_id = ? AND catalog_name = ? AND branch = ?")
87+
.bind(tenant_id.to_string())
88+
.bind(catalog_name)
89+
.bind(&name)
90+
.execute(&self.pool)
91+
.await?;
92+
8093
Ok(())
8194
}
8295

scripts/test_delete_branch.sh

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "🧪 Testing delete_branch with cascade delete across all backends..."
5+
6+
# Colors
7+
GREEN='\033[0;32m'
8+
BLUE='\033[0;34m'
9+
RED='\033[0;31m'
10+
NC='\033[0m'
11+
12+
# Test function for each backend
13+
test_backend() {
14+
local backend=$1
15+
local db_url=$2
16+
17+
echo -e "${BLUE}Testing $backend backend...${NC}"
18+
19+
# Create test database/file
20+
case $backend in
21+
"postgres")
22+
docker-compose -f docker-compose.db-test.yml up -d postgres
23+
sleep 5
24+
;;
25+
"sqlite")
26+
rm -f /tmp/test_delete_branch.db
27+
;;
28+
"mongo")
29+
docker-compose -f docker-compose.db-test.yml up -d mongo
30+
sleep 5
31+
;;
32+
esac
33+
34+
# Run Rust test
35+
cd pangolin/pangolin_store
36+
37+
cat > /tmp/test_delete_branch_${backend}.rs << 'EOF'
38+
use pangolin_store::*;
39+
use pangolin_core::model::{Branch, BranchType, Asset, AssetType};
40+
use uuid::Uuid;
41+
42+
#[tokio::test]
43+
async fn test_delete_branch_cascade() {
44+
let tenant_id = Uuid::new_v4();
45+
let catalog_name = "test_catalog";
46+
let branch_name = "test_branch";
47+
48+
// Create store based on backend
49+
let store: Box<dyn CatalogStore> = match std::env::var("TEST_BACKEND").unwrap().as_str() {
50+
"postgres" => {
51+
let url = std::env::var("DATABASE_URL").unwrap();
52+
Box::new(PostgresStore::new(&url).await.unwrap())
53+
},
54+
"sqlite" => {
55+
let url = std::env::var("DATABASE_URL").unwrap();
56+
let store = SqliteStore::new(&url).await.unwrap();
57+
let schema = include_str!("../../sql/sqlite_schema.sql");
58+
store.apply_schema(schema).await.unwrap();
59+
Box::new(store)
60+
},
61+
"mongo" => {
62+
let url = std::env::var("DATABASE_URL").unwrap();
63+
let db_name = std::env::var("MONGO_DB_NAME").unwrap_or("test".to_string());
64+
Box::new(MongoStore::new(&url, &db_name).await.unwrap())
65+
},
66+
_ => panic!("Unknown backend")
67+
};
68+
69+
// Create branch
70+
let branch = Branch {
71+
name: branch_name.to_string(),
72+
head_commit_id: None,
73+
branch_type: BranchType::Experimental,
74+
assets: vec![],
75+
};
76+
store.create_branch(tenant_id, catalog_name, branch).await.unwrap();
77+
78+
// Create assets in the branch
79+
for i in 0..3 {
80+
let asset = Asset {
81+
id: Uuid::new_v4(),
82+
name: format!("test_asset_{}", i),
83+
asset_type: AssetType::IcebergTable,
84+
metadata_location: Some(format!("s3://bucket/table_{}", i)),
85+
schema: None,
86+
properties: std::collections::HashMap::new(),
87+
};
88+
store.create_asset(
89+
tenant_id,
90+
catalog_name,
91+
Some(branch_name.to_string()),
92+
vec!["test_ns".to_string()],
93+
asset
94+
).await.unwrap();
95+
}
96+
97+
// Verify assets exist
98+
let assets_before = store.list_assets(
99+
tenant_id,
100+
catalog_name,
101+
Some(branch_name.to_string()),
102+
vec!["test_ns".to_string()]
103+
).await.unwrap();
104+
assert_eq!(assets_before.len(), 3, "Should have 3 assets before delete");
105+
106+
// Delete branch
107+
store.delete_branch(tenant_id, catalog_name, branch_name.to_string()).await.unwrap();
108+
109+
// Verify branch is deleted
110+
let branch_result = store.get_branch(tenant_id, catalog_name, branch_name.to_string()).await.unwrap();
111+
assert!(branch_result.is_none(), "Branch should be deleted");
112+
113+
// Verify assets are cascade deleted
114+
let assets_after = store.list_assets(
115+
tenant_id,
116+
catalog_name,
117+
Some(branch_name.to_string()),
118+
vec!["test_ns".to_string()]
119+
).await.unwrap();
120+
assert_eq!(assets_after.len(), 0, "Assets should be cascade deleted");
121+
122+
println!("✅ {} backend: delete_branch with cascade delete works!", std::env::var("TEST_BACKEND").unwrap());
123+
}
124+
EOF
125+
126+
# Run test
127+
export TEST_BACKEND=$backend
128+
export DATABASE_URL=$db_url
129+
export MONGO_DB_NAME="test_delete_branch"
130+
131+
cargo test --test delete_branch_test -- --nocapture || {
132+
echo -e "${RED}$backend test failed${NC}"
133+
cd ../..
134+
return 1
135+
}
136+
137+
cd ../..
138+
echo -e "${GREEN}$backend test passed${NC}"
139+
140+
# Cleanup
141+
case $backend in
142+
"postgres")
143+
docker-compose -f docker-compose.db-test.yml down postgres
144+
;;
145+
"sqlite")
146+
rm -f /tmp/test_delete_branch.db
147+
;;
148+
"mongo")
149+
docker-compose -f docker-compose.db-test.yml down mongo
150+
;;
151+
esac
152+
}
153+
154+
# Test all backends
155+
echo "Testing Postgres..."
156+
test_backend "postgres" "postgresql://testuser:testpass@localhost:5432/testdb"
157+
158+
echo ""
159+
echo "Testing SQLite..."
160+
test_backend "sqlite" "sqlite:///tmp/test_delete_branch.db"
161+
162+
echo ""
163+
echo "Testing MongoDB..."
164+
test_backend "mongo" "mongodb://testuser:testpass@localhost:27017"
165+
166+
echo ""
167+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
168+
echo -e "${GREEN}✅ All backends pass delete_branch cascade delete test!${NC}"
169+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"

0 commit comments

Comments
 (0)