1use std::collections::HashMap;
4use std::fmt::{self, Debug, Formatter};
5use std::sync::Arc;
6
7use parking_lot::Mutex;
8use tokio::sync::OnceCell;
9
10use crate::catalog_types::{ProjectBranchName, VersionId};
11use crate::{BranchHead, Error, Project, ResourceAddress, ResourceRef, Snapshot, Table};
12
13#[derive(Clone)]
18pub struct TableSession {
19 project: Project,
20 default_ref: ResourceRef,
21 snapshot: Snapshot,
22 branches: Arc<Mutex<HashMap<ProjectBranchName, Arc<OnceCell<Snapshot>>>>>,
23}
24
25impl Debug for TableSession {
26 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
27 formatter
28 .debug_struct("TableSession")
29 .field("project", self.project.id())
30 .field("default_ref", &self.default_ref)
31 .field("commit", &self.snapshot.commit_id())
32 .finish()
33 }
34}
35
36impl Project {
37 pub async fn table_session(&self, default_ref: &str) -> Result<TableSession, Error> {
39 TableSession::open(self.clone(), default_ref.parse()?).await
40 }
41}
42
43impl Snapshot {
44 pub fn table_session(&self) -> TableSession {
46 TableSession {
47 project: self.project().clone(),
48 default_ref: ResourceRef::Commit(self.commit_id()),
49 snapshot: self.clone(),
50 branches: Arc::new(Mutex::new(HashMap::new())),
51 }
52 }
53}
54
55impl TableSession {
56 async fn open(project: Project, default_ref: ResourceRef) -> Result<Self, Error> {
57 let snapshot = match &default_ref {
58 ResourceRef::Branch(branch) => {
59 project.branch(branch.as_str()).await?.snapshot().clone()
60 }
61 ResourceRef::Commit(commit) => project.snapshot_at(*commit),
62 ResourceRef::Version(_) => {
63 return Err(Error::invalid(
64 "a session default must be a branch or project commit, not a table version",
65 ));
66 }
67 };
68 let mut branches = HashMap::new();
69 if let ResourceRef::Branch(branch) = &default_ref {
70 branches.insert(
71 branch.clone(),
72 Arc::new(OnceCell::new_with(Some(snapshot.clone()))),
73 );
74 }
75 Ok(Self {
76 project,
77 default_ref,
78 snapshot,
79 branches: Arc::new(Mutex::new(branches)),
80 })
81 }
82
83 pub fn default_ref(&self) -> &ResourceRef {
85 &self.default_ref
86 }
87
88 pub fn snapshot(&self) -> &Snapshot {
90 &self.snapshot
91 }
92
93 pub fn head(&self) -> Option<BranchHead> {
96 BranchHead::of(&self.snapshot)
97 }
98
99 pub fn with_head(&self, head: BranchHead) -> Result<Self, Error> {
105 let ResourceRef::Branch(branch) = &self.default_ref else {
106 return Err(Error::invalid("session must select a default branch"));
107 };
108 let snapshot = head.snapshot().clone();
109 if !Arc::ptr_eq(
110 &self.project.client.connection,
111 &snapshot.project.client.connection,
112 ) || self.project.id != snapshot.project.id
113 || self.snapshot.branch_id != Some(head.branch_id())
114 {
115 return Err(Error::invalid("head must be of this session's branch"));
116 }
117 let branches = HashMap::from([(
118 branch.clone(),
119 Arc::new(OnceCell::new_with(Some(snapshot.clone()))),
120 )]);
121 Ok(Self {
122 project: self.project.clone(),
123 default_ref: self.default_ref.clone(),
124 snapshot,
125 branches: Arc::new(Mutex::new(branches)),
126 })
127 }
128
129 pub async fn refresh(&self) -> Result<Self, Error> {
131 if matches!(self.default_ref, ResourceRef::Branch(_)) {
132 Self::open(self.project.clone(), self.default_ref.clone()).await
133 } else {
134 Ok(self.snapshot.table_session())
135 }
136 }
137
138 pub async fn table(&self, address: &str) -> Result<Table, Error> {
143 let address: ResourceAddress = address.parse()?;
144 let snapshot = match address.reference() {
145 None | Some(ResourceRef::Version(_)) => self.snapshot.clone(),
146 Some(ResourceRef::Commit(commit)) => self.project.snapshot_at(*commit),
147 Some(ResourceRef::Branch(branch)) => {
148 let pin = {
149 let mut branches = self.branches.lock();
150 Arc::clone(
151 branches
152 .entry(branch.clone())
153 .or_insert_with(|| Arc::new(OnceCell::new())),
154 )
155 };
156 pin.get_or_try_init(|| async {
157 Ok::<_, Error>(
158 self.project
159 .branch(branch.as_str())
160 .await?
161 .snapshot()
162 .clone(),
163 )
164 })
165 .await?
166 .clone()
167 }
168 };
169 let mut table = snapshot.table_at_path(address.path()).await?;
170 if let Some(ResourceRef::Version(version)) = address.reference() {
171 table = self
172 .project
173 .client
174 .table_by_id(table.identity().resource_id, version.as_uuid());
175 }
176 let version = VersionId::new(table.identity().version_id).map_err(Error::invalid)?;
177 let address = address.at_version(version);
178 if let Some(base) = &mut table.base {
179 base.address = Some(address.clone());
180 }
181 table.address = Some(address);
182 Ok(table)
183 }
184}