1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
//! Immutable table versions.
use std::fmt::{self, Debug, Formatter};
use std::sync::Arc;
use uuid::Uuid;
use crate::api::tasks::{
TableMaintenanceDefinition, TaskDefinition, TaskReadTarget, TaskReadTargetLatestOnBranch,
};
use crate::arrow_input::IntoArrowStream;
use crate::catalog_types::{ProjectBranchId, ProjectCommitId, ProjectId, ResourceId};
use crate::client::Connection;
use crate::commit::BindingMutation;
use crate::registration::register;
use crate::scandal::types::{PreparedWriteReceipt, TableWriteResult};
use crate::task_handle::submit;
use crate::{
Error, Expression, Project, ResourceAddress, Run, Scan, ScanOptions, TaskHandle, WorkItem,
};
/// Stable identity of one immutable table version.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct TableIdentity {
/// Arbor resource identifier.
pub resource_id: Uuid,
/// Exact immutable version identifier.
pub version_id: Uuid,
}
/// An exact immutable table version.
///
/// Writing a table produces its next version without publishing it: nothing changes in the
/// Catalog until a [`Commit`](crate::Commit) stages the new version and publishes.
#[derive(Clone)]
pub struct Table {
pub(crate) connection: Arc<Connection>,
identity: TableIdentity,
pub(crate) base: Option<CatalogBase>,
pub(crate) address: Option<ResourceAddress>,
}
/// The version a Catalog commit binds for a table's resource. Versions written from that
/// table keep it, so a commit on that commit's branch can swap it for the newest one.
#[derive(Clone)]
pub(crate) struct CatalogBase {
pub(crate) project_id: ProjectId,
pub(crate) commit_id: ProjectCommitId,
/// The branch that commit was pinned from, or `None` for an exact-commit snapshot.
pub(crate) branch_id: Option<ProjectBranchId>,
pub(crate) version_id: Uuid,
/// The path that resolved the bound version, when it was opened by path.
pub(crate) address: Option<ResourceAddress>,
}
/// Rows deleted from a table, and the version without them.
#[derive(Clone, Debug)]
pub struct DeletedRows {
/// The version without the deleted rows; the original table when none matched.
pub table: Table,
/// How many visible rows were deleted.
pub rows: u64,
}
impl Debug for Table {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Table")
.field("identity", &self.identity)
.finish()
}
}
impl Project {
/// Write the first version of a new table from `input`.
///
/// The table has no name until [`Commit::create_table`](crate::Commit::create_table)
/// binds it and the commit publishes.
pub async fn write_table(&self, input: impl IntoArrowStream) -> Result<Table, Error> {
let result = self
.client
.connection
.tables()?
.create(self.id.as_str(), input.into_write_input()?)
.await?;
Ok(self
.client
.table_by_id(result.resource_id, result.version_id))
}
}
impl Table {
pub(crate) fn new(connection: Arc<Connection>, identity: TableIdentity) -> Self {
Self {
connection,
identity,
base: None,
address: None,
}
}
/// Exact immutable identity selected by this handle.
pub fn identity(&self) -> TableIdentity {
self.identity
}
/// Canonical `path@v<version>` provenance, when opened through a resource path.
/// Tables opened by IDs, and versions written from a table, have no resource path.
pub fn resolved_address(&self) -> Option<&ResourceAddress> {
self.address.as_ref()
}
/// Submit maintenance of this table's latest version on the branch it was opened from.
///
/// Maintenance compacts and reclusters, publishing each step as a new version; its
/// result lists them. Returns `None` when maintenance of this table and branch is
/// already waiting to run.
pub async fn maintain(&self, run: Run) -> Result<Option<TaskHandle>, Error> {
let (project_id, branch_id) = self
.base
.as_ref()
.and_then(|base| Some((&base.project_id, base.branch_id?)))
.ok_or_else(|| Error::invalid("maintaining a table needs one opened from a branch"))?;
let definition = TaskDefinition::TableMaintenance(TableMaintenanceDefinition {
read_target: TaskReadTarget::LatestOnBranch(TaskReadTargetLatestOnBranch {
branch_id: branch_id.as_uuid(),
}),
table_resource_id: self.identity.resource_id,
});
submit(
&self.connection.tasks,
project_id.as_str().into(),
definition,
None,
run,
)
.await
}
/// Open a scan with fixed projection, predicate, schema, and execution binding.
///
/// Metadata is resolved once. Batches are read when its streams are polled.
pub async fn scan(&self, options: ScanOptions) -> Result<Scan, Error> {
let mut scan = self
.connection
.tables()?
.open_scan(self.identity, options)
.await?;
scan.address = self.address.clone();
Ok(scan)
}
/// Write the next version of this table: its rows followed by `input`.
pub async fn append(&self, input: impl IntoArrowStream) -> Result<Table, Error> {
self.write(input, false).await
}
/// Write the next version of this table with only the rows of `input`.
pub async fn overwrite(&self, input: impl IntoArrowStream) -> Result<Table, Error> {
self.write(input, true).await
}
/// Write the next version of this table without the visible rows matching `filter`, or
/// without any rows when there is no filter.
///
/// Retained rows keep their stable row IDs. When no row matches, nothing is written and
/// the result holds this table. Unsupported predicates fail before anything is written.
pub async fn delete_rows(&self, filter: Option<Expression>) -> Result<DeletedRows, Error> {
let scan = self
.scan(ScanOptions {
columns: Some(Vec::new()),
filter: filter.clone(),
..Default::default()
})
.await?;
let work = scan
.splits()
.iter()
.enumerate()
.map(|(split, item)| WorkItem {
split,
rows: item.rows.clone(),
})
.collect::<Vec<_>>();
let rows = scan.count_rows(&work).await?.into_iter().sum();
if rows == 0 {
return Ok(DeletedRows {
table: self.clone(),
rows,
});
}
let result = self
.connection
.tables()?
.delete(self.identity, filter, rows)
.await?;
Ok(DeletedRows {
table: self.successor(result)?,
rows,
})
}
/// Register a version of this table that a write prepared but whose registration outcome
/// was unknown ([`Error::RegistrationUnknown`]), without uploading its rows again.
pub async fn recover_write(&self, receipt: PreparedWriteReceipt) -> Result<Table, Error> {
if receipt.resource_id != self.identity.resource_id
|| receipt.base_version != Some(self.identity.version_id)
{
return Err(Error::invalid("receipt was not written from this table"));
}
let result = register(&self.connection.arbor, receipt).await?;
self.successor(result)
}
/// The binding that selected this table's Catalog base, to remove from the project root.
pub(crate) fn unbinding(&self) -> Result<BindingMutation, Error> {
let (parent, name) = self
.base
.as_ref()
.and_then(|base| base.address.as_ref())
.ok_or_else(|| Error::invalid("table was not opened by path"))?
.root_binding()?;
Ok(BindingMutation {
parent,
name,
expected: Some(ResourceId::new(self.identity.resource_id).map_err(Error::invalid)?),
replacement: None,
})
}
async fn write(&self, input: impl IntoArrowStream, overwrite: bool) -> Result<Self, Error> {
let result = self
.connection
.tables()?
.write(self.identity, input.into_write_input()?, overwrite)
.await?;
self.successor(result)
}
/// The version `result` wrote from this table, keeping this table's Catalog base.
pub(crate) fn successor(&self, result: TableWriteResult) -> Result<Self, Error> {
if result.resource_id != self.identity.resource_id {
return Err(Error::invalid("write changed resource identity"));
}
Ok(Self {
connection: Arc::clone(&self.connection),
identity: TableIdentity {
resource_id: result.resource_id,
version_id: result.version_id,
},
base: self.base.clone(),
address: None,
})
}
}