Skip to content

Commit 70aba73

Browse files
authored
Merge branch 'master' into ingvar/force-net8
2 parents eaa85b1 + 3782f88 commit 70aba73

File tree

28 files changed

+1306
-339
lines changed

28 files changed

+1306
-339
lines changed

crates/bindings-csharp/BSATN.Runtime/Builtins.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -304,30 +304,30 @@ public AlgebraicType GetAlgebraicType(ITypeRegistrar registrar) =>
304304

305305
// [SpacetimeDB.Type] - we have custom representation of time in microseconds, so implementing BSATN manually
306306
public abstract partial record ScheduleAt
307-
: SpacetimeDB.TaggedEnum<(DateTimeOffset Time, TimeSpan Interval)>
307+
: SpacetimeDB.TaggedEnum<(TimeSpan Interval, DateTimeOffset Time)>
308308
{
309309
// Manual expansion of what would be otherwise generated by the [SpacetimeDB.Type] codegen.
310-
public sealed record Time(DateTimeOffset Time_) : ScheduleAt;
311-
312310
public sealed record Interval(TimeSpan Interval_) : ScheduleAt;
313311

314-
public static implicit operator ScheduleAt(DateTimeOffset time) => new Time(time);
312+
public sealed record Time(DateTimeOffset Time_) : ScheduleAt;
315313

316314
public static implicit operator ScheduleAt(TimeSpan interval) => new Interval(interval);
317315

316+
public static implicit operator ScheduleAt(DateTimeOffset time) => new Time(time);
317+
318318
public readonly partial struct BSATN : IReadWrite<ScheduleAt>
319319
{
320320
[SpacetimeDB.Type]
321321
private partial record ScheduleAtRepr
322-
: SpacetimeDB.TaggedEnum<(DateTimeOffsetRepr Time, TimeSpanRepr Interval)>;
322+
: SpacetimeDB.TaggedEnum<(TimeSpanRepr Interval, DateTimeOffsetRepr Time)>;
323323

324324
private static readonly ScheduleAtRepr.BSATN ReprBSATN = new();
325325

326326
public ScheduleAt Read(BinaryReader reader) =>
327327
ReprBSATN.Read(reader) switch
328328
{
329-
ScheduleAtRepr.Time(var timeRepr) => new Time(timeRepr.ToStd()),
330329
ScheduleAtRepr.Interval(var intervalRepr) => new Interval(intervalRepr.ToStd()),
330+
ScheduleAtRepr.Time(var timeRepr) => new Time(timeRepr.ToStd()),
331331
_ => throw new SwitchExpressionException(),
332332
};
333333

@@ -337,8 +337,8 @@ public void Write(BinaryWriter writer, ScheduleAt value)
337337
writer,
338338
value switch
339339
{
340-
Time(var time) => new ScheduleAtRepr.Time(new(time)),
341340
Interval(var interval) => new ScheduleAtRepr.Interval(new(interval)),
341+
Time(var time) => new ScheduleAtRepr.Time(new(time)),
342342
_ => throw new SwitchExpressionException(),
343343
}
344344
);
@@ -349,8 +349,8 @@ public AlgebraicType GetAlgebraicType(ITypeRegistrar registrar) =>
349349
// to avoid leaking the internal *Repr wrappers in generated SATS.
350350
new AlgebraicType.Sum(
351351
[
352-
new("Time", new AlgebraicType.U64(default)),
353352
new("Interval", new AlgebraicType.U64(default)),
353+
new("Time", new AlgebraicType.U64(default)),
354354
]
355355
);
356356
}

crates/commitlog/src/repo/fs.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use std::fs::{self, File};
2-
use std::io;
2+
use std::io::{self, Seek};
33

44
use log::{debug, warn};
55
use spacetimedb_paths::server::{CommitLogDir, SegmentFile};
66

7-
use super::{Repo, TxOffset, TxOffsetIndex, TxOffsetIndexMut};
7+
use super::{Repo, Segment, TxOffset, TxOffsetIndex, TxOffsetIndexMut};
88

99
const SEGMENT_FILE_EXT: &str = ".stdb.log";
1010

@@ -57,6 +57,19 @@ impl Fs {
5757
}
5858
}
5959

60+
impl Segment for File {
61+
fn segment_len(&mut self) -> io::Result<u64> {
62+
let old_pos = self.stream_position()?;
63+
let len = self.seek(io::SeekFrom::End(0))?;
64+
// If we're already at the end of the file, avoid seeking.
65+
if old_pos != len {
66+
self.seek(io::SeekFrom::Start(old_pos))?;
67+
}
68+
69+
Ok(len)
70+
}
71+
}
72+
6073
impl Repo for Fs {
6174
type Segment = File;
6275

crates/commitlog/src/repo/mem.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ impl Segment {
2828
pub fn len(&self) -> usize {
2929
self.buf.read().unwrap().len()
3030
}
31+
3132
pub fn is_empty(&self) -> bool {
3233
self.len() == 0
3334
}
@@ -39,6 +40,12 @@ impl From<SharedBytes> for Segment {
3940
}
4041
}
4142

43+
impl super::Segment for Segment {
44+
fn segment_len(&mut self) -> io::Result<u64> {
45+
Ok(Segment::len(self) as u64)
46+
}
47+
}
48+
4249
impl FileLike for Segment {
4350
fn fsync(&mut self) -> io::Result<()> {
4451
Ok(())
@@ -118,8 +125,10 @@ impl Repo for Memory {
118125
let mut inner = self.0.write().unwrap();
119126
match inner.entry(offset) {
120127
btree_map::Entry::Occupied(entry) => {
121-
if entry.get().read().unwrap().len() == 0 {
122-
Ok(Segment::from(Arc::clone(entry.get())))
128+
let entry = entry.get();
129+
let read_guard = entry.read().unwrap();
130+
if read_guard.len() == 0 {
131+
Ok(Segment::from(Arc::clone(entry)))
123132
} else {
124133
Err(io::Error::new(
125134
io::ErrorKind::AlreadyExists,

crates/commitlog/src/repo/mod.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,28 @@ pub type TxOffset = u64;
2222
pub type TxOffsetIndexMut = IndexFileMut<TxOffset>;
2323
pub type TxOffsetIndex = IndexFile<TxOffset>;
2424

25+
pub trait Segment: FileLike + io::Read + io::Write + io::Seek + Send + Sync {
26+
/// Determine the length in bytes of the segment.
27+
///
28+
/// This method does not rely on metadata `fsync`, and may use up to three
29+
/// `seek` operations.
30+
///
31+
/// If the method returns successfully, the seek position before the call is
32+
/// restored. However, if it returns an error, the seek position is
33+
/// unspecified.
34+
//
35+
// TODO: Replace with `Seek::stream_len` if / when stabilized:
36+
// https://github.com/rust-lang/rust/issues/59359
37+
fn segment_len(&mut self) -> io::Result<u64>;
38+
}
39+
2540
/// A repository of log segments.
2641
///
2742
/// This is mainly an internal trait to allow testing against an in-memory
2843
/// representation.
2944
pub trait Repo: Clone {
3045
/// The type of log segments managed by this repo, which must behave like a file.
31-
type Segment: io::Read + io::Write + FileLike + io::Seek + Send + Sync + 'static;
46+
type Segment: Segment + 'static;
3247

3348
/// Create a new segment with the minimum transaction offset `offset`.
3449
///

crates/commitlog/src/tests/partial.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use log::debug;
99

1010
use crate::{
1111
commitlog, error, payload,
12-
repo::{self, Repo},
12+
repo::{self, Repo, Segment},
1313
segment::FileLike,
1414
tests::helpers::enable_logging,
1515
Commit, Encode, Options, DEFAULT_LOG_FORMAT_VERSION,
@@ -160,6 +160,12 @@ struct ShortSegment {
160160
max_len: u64,
161161
}
162162

163+
impl Segment for ShortSegment {
164+
fn segment_len(&mut self) -> io::Result<u64> {
165+
self.inner.segment_len()
166+
}
167+
}
168+
163169
impl FileLike for ShortSegment {
164170
fn fsync(&mut self) -> std::io::Result<()> {
165171
self.inner.fsync()

crates/core/src/db/datastore/locking_tx_datastore/mut_tx.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,15 +399,35 @@ impl MutTxId {
399399
_ => unimplemented!(),
400400
};
401401
// Create and build the index.
402+
//
403+
// Ensure adding the index does not cause a unique constraint violation due to
404+
// the existing rows having the same value for some column(s).
402405
let mut insert_index = table.new_index(index.index_id, &columns, is_unique)?;
403-
insert_index.build_from_rows(&columns, table.scan_rows(blob_store))?;
406+
let mut build_from_rows = |table: &Table, bs: &dyn BlobStore| -> Result<()> {
407+
if let Some(violation) = insert_index.build_from_rows(&columns, table.scan_rows(bs))? {
408+
let violation = table
409+
.get_row_ref(bs, violation)
410+
.expect("row came from scanning the table")
411+
.project(&columns)
412+
.expect("`cols` should consist of valid columns for this table");
413+
return Err(IndexError::from(table.build_error_unique(&insert_index, &columns, violation)).into());
414+
}
415+
Ok(())
416+
};
417+
build_from_rows(table, blob_store)?;
404418
// NOTE: Also add all the rows in the already committed table to the index.
419+
//
405420
// FIXME: Is this correct? Index scan iterators (incl. the existing `Locking` versions)
406421
// appear to assume that a table's index refers only to rows within that table,
407422
// and does not handle the case where a `TxState` index refers to `CommittedState` rows.
408-
if let Some(committed_table) = commit_table {
409-
insert_index.build_from_rows(&columns, committed_table.scan_rows(commit_blob_store))?;
423+
//
424+
// TODO(centril): An alternative here is to actually add this index to `CommittedState`,
425+
// pretending that it was already committed, and recording this pretense.
426+
// Then, we can roll that back on a failed tx.
427+
if let Some(commit_table) = commit_table {
428+
build_from_rows(commit_table, commit_blob_store)?;
410429
}
430+
411431
table.indexes.insert(columns.clone(), insert_index);
412432
// Associate `index_id -> (table_id, col_list)` for fast lookup.
413433
idx_map.insert(index_id, (table_id, columns.clone()));

0 commit comments

Comments
 (0)