use alloc::rc::Rc;
use alloc::string::String;
use alloc::vec::Vec;
use core::cell::RefCell;
use crate::error::Result;
use crate::value::Value;
pub(crate) const OP_INSERT: u8 = 18;
pub(crate) const OP_UPDATE: u8 = 23;
pub(crate) const OP_DELETE: u8 = 9;
const T_INT: u8 = 1;
const T_FLOAT: u8 = 2;
const T_TEXT: u8 = 3;
const T_BLOB: u8 = 4;
const T_NULL: u8 = 5;
const T_OMIT: u8 = 0;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum ChangeOp {
Insert,
Update,
Delete,
}
#[derive(Clone, Debug)]
struct Change {
op: ChangeOp,
pk: Vec<Value>,
old: Vec<Value>,
indirect: bool,
}
fn hash_pk_value(h: u32, v: &Value) -> u32 {
match v {
Value::Null => TableChanges::hash_append(h, u32::from(T_NULL)),
Value::Integer(i) => {
let h = TableChanges::hash_append(h, u32::from(T_INT));
TableChanges::hash_i64(h, *i)
}
Value::Real(r) => {
let h = TableChanges::hash_append(h, u32::from(T_FLOAT));
TableChanges::hash_i64(h, r.to_bits() as i64)
}
Value::Text(s) => {
let h = TableChanges::hash_append(h, u32::from(T_TEXT));
TableChanges::hash_blob(h, s.as_bytes())
}
Value::Blob(b) => {
let h = TableChanges::hash_append(h, u32::from(T_BLOB));
TableChanges::hash_blob(h, b)
}
}
}
#[derive(Debug)]
struct TableChanges {
name: String,
ncol: usize,
pk_flags: Vec<u8>,
buckets: Vec<Vec<Change>>,
nentry: usize,
}
impl TableChanges {
fn new(name: String, ncol: usize, pk_flags: Vec<u8>) -> TableChanges {
TableChanges {
name,
ncol,
pk_flags,
buckets: Vec::new(),
nentry: 0,
}
}
#[inline]
fn hash_append(h: u32, add: u32) -> u32 {
(h << 3) ^ h ^ add
}
#[inline]
fn hash_i64(h: u32, i: i64) -> u32 {
let u = i as u64;
let h = Self::hash_append(h, (u & 0xFFFF_FFFF) as u32);
Self::hash_append(h, ((u >> 32) & 0xFFFF_FFFF) as u32)
}
#[inline]
fn hash_blob(mut h: u32, bytes: &[u8]) -> u32 {
for &b in bytes {
h = Self::hash_append(h, u32::from(b));
}
h
}
fn hash_pk(pk: &[Value]) -> u32 {
let mut h = 0u32;
for v in pk {
h = hash_pk_value(h, v);
}
h
}
fn bucket(&self, pk: &[Value]) -> usize {
(Self::hash_pk(pk) % self.buckets.len() as u32) as usize
}
fn maybe_grow(&mut self) {
let n = self.buckets.len();
if n == 0 || self.nentry >= n / 2 {
let new_n = if n == 0 { 256 } else { n * 2 };
let mut new_buckets: Vec<Vec<Change>> = (0..new_n).map(|_| Vec::new()).collect();
for bucket in &self.buckets {
for change in bucket {
let idx = (Self::hash_pk(&change.pk) % new_n as u32) as usize;
new_buckets[idx].insert(0, change.clone());
}
}
self.buckets = new_buckets;
}
}
fn record(&mut self, op: ChangeOp, pk: Vec<Value>, old: Vec<Value>, indirect: bool) {
self.maybe_grow();
let idx = self.bucket(&pk);
if let Some(c) = self.buckets[idx].iter_mut().find(|c| pk_eq(&c.pk, &pk)) {
if c.indirect && !indirect {
c.indirect = false;
}
return;
}
self.nentry += 1;
self.buckets[idx].insert(
0,
Change {
op,
pk,
old,
indirect,
},
);
}
fn is_empty(&self) -> bool {
self.nentry == 0
}
}
fn pk_eq(a: &[Value], b: &[Value]) -> bool {
if a.len() != b.len() {
return false;
}
a.iter().zip(b).all(|(x, y)| match (x, y) {
(Value::Null, Value::Null) => true,
(Value::Integer(i), Value::Integer(j)) => i == j,
(Value::Real(i), Value::Real(j)) => i.to_bits() == j.to_bits(),
(Value::Text(i), Value::Text(j)) => i == j,
(Value::Blob(i), Value::Blob(j)) => i == j,
_ => false,
})
}
#[derive(Debug, Default)]
pub(crate) struct SessionState {
pub(crate) enabled: bool,
attach_all: bool,
attached: Vec<String>,
indirect_mode: bool,
tables: Vec<TableChanges>,
}
impl SessionState {
#[allow(clippy::too_many_arguments)]
pub(crate) fn record(
&mut self,
table: &str,
ncol: usize,
pk_flags: &[u8],
op: ChangeOp,
pk: Vec<Value>,
old: Vec<Value>,
indirect: bool,
) {
if !self.enabled {
return;
}
let indirect = indirect || self.indirect_mode;
if !self.attach_all && !self.attached.iter().any(|t| t == table) {
return;
}
if pk.iter().any(|v| matches!(v, Value::Null)) {
return;
}
let tbl = match self.tables.iter_mut().position(|t| t.name == table) {
Some(i) => &mut self.tables[i],
None => {
self.tables.push(TableChanges::new(
String::from(table),
ncol,
pk_flags.to_vec(),
));
self.tables.last_mut().unwrap()
}
};
tbl.record(op, pk, old, indirect);
}
}
#[derive(Clone)]
pub struct Session {
pub(crate) state: Rc<RefCell<SessionState>>,
}
impl Session {
pub(crate) fn new(state: Rc<RefCell<SessionState>>) -> Session {
Session { state }
}
pub fn attach(&self) {
let mut s = self.state.borrow_mut();
s.enabled = true;
s.attach_all = true;
}
pub fn attach_table(&self, table: &str) {
let mut s = self.state.borrow_mut();
s.enabled = true;
if !s.attached.iter().any(|t| t == table) {
s.attached.push(String::from(table));
}
}
pub fn set_indirect(&self, indirect: bool) -> bool {
let mut s = self.state.borrow_mut();
s.indirect_mode = indirect;
s.indirect_mode
}
pub fn is_empty(&self) -> bool {
self.state
.borrow()
.tables
.iter()
.all(TableChanges::is_empty)
}
}
fn append_varint(out: &mut Vec<u8>, v: u64) {
if v <= 0x7f {
out.push(v as u8);
return;
}
let mut buf = [0u8; 10];
let mut n = 0;
let mut val = v;
if val & 0xff00_0000_0000_0000 != 0 {
buf[9] = (val & 0xff) as u8;
val >>= 8;
for i in (0..9).rev() {
buf[i] = ((val & 0x7f) as u8) | 0x80;
val >>= 7;
}
out.extend_from_slice(&buf[..10]);
return;
}
while val != 0 {
buf[n] = (val & 0x7f) as u8;
val >>= 7;
n += 1;
}
for i in (0..n).rev() {
let mut byte = buf[i];
if i != 0 {
byte |= 0x80;
}
out.push(byte);
}
}
fn append_value(out: &mut Vec<u8>, v: &Value) {
match v {
Value::Null => out.push(T_NULL),
Value::Integer(i) => {
out.push(T_INT);
out.extend_from_slice(&i.to_be_bytes());
}
Value::Real(r) => {
out.push(T_FLOAT);
out.extend_from_slice(&r.to_bits().to_be_bytes());
}
Value::Text(s) => {
out.push(T_TEXT);
append_varint(out, s.len() as u64);
out.extend_from_slice(s.as_bytes());
}
Value::Blob(b) => {
out.push(T_BLOB);
append_varint(out, b.len() as u64);
out.extend_from_slice(b);
}
}
}
pub(crate) fn serialize(
state: &SessionState,
read_row: impl FnMut(&str, &[Value]) -> Option<Vec<Value>>,
) -> Vec<u8> {
serialize_impl(state, false, read_row)
}
pub(crate) fn serialize_patchset(
state: &SessionState,
read_row: impl FnMut(&str, &[Value]) -> Option<Vec<Value>>,
) -> Vec<u8> {
serialize_impl(state, true, read_row)
}
fn serialize_impl(
state: &SessionState,
patchset: bool,
mut read_row: impl FnMut(&str, &[Value]) -> Option<Vec<Value>>,
) -> Vec<u8> {
let mut out = Vec::new();
for tbl in &state.tables {
if tbl.is_empty() {
continue;
}
let hdr_start = out.len();
out.push(if patchset { b'P' } else { b'T' });
append_varint(&mut out, tbl.ncol as u64);
out.extend_from_slice(&tbl.pk_flags);
out.extend_from_slice(tbl.name.as_bytes());
out.push(0);
let mut wrote_any = false;
for bucket in &tbl.buckets {
for change in bucket {
let current = read_row(&tbl.name, &change.pk);
match (change.op, current) {
(ChangeOp::Insert, Some(row)) => {
out.push(OP_INSERT);
out.push(u8::from(change.indirect));
for v in &row {
append_value(&mut out, v);
}
wrote_any = true;
}
(ChangeOp::Insert, None) => {
}
(ChangeOp::Update | ChangeOp::Delete, Some(row)) => {
if append_update(
&mut out,
&change.old,
&row,
&tbl.pk_flags,
patchset,
change.indirect,
) {
wrote_any = true;
}
}
(ChangeOp::Update | ChangeOp::Delete, None) => {
append_delete(
&mut out,
&change.old,
&tbl.pk_flags,
patchset,
change.indirect,
);
wrote_any = true;
}
}
}
}
if !wrote_any {
out.truncate(hdr_start);
}
}
out
}
fn append_delete(
out: &mut Vec<u8>,
old: &[Value],
pk_flags: &[u8],
patchset: bool,
indirect: bool,
) {
out.push(OP_DELETE);
out.push(u8::from(indirect));
for (i, v) in old.iter().enumerate() {
let is_pk = pk_flags.get(i).copied().unwrap_or(0) != 0;
if patchset {
if is_pk {
append_value(out, v);
}
} else {
append_value(out, v);
}
}
}
fn append_update(
out: &mut Vec<u8>,
old: &[Value],
new: &[Value],
pk_flags: &[u8],
patchset: bool,
indirect: bool,
) -> bool {
let start = out.len();
out.push(OP_UPDATE);
out.push(u8::from(indirect));
let ncol = old.len().min(new.len());
let mut new_rec = Vec::new();
let mut changed_any = false;
for i in 0..ncol {
let is_pk = pk_flags.get(i).copied().unwrap_or(0) != 0;
let changed = old[i] != new[i];
if changed {
changed_any = true;
}
if !patchset {
if changed || is_pk {
append_value(out, &old[i]);
} else {
out.push(T_OMIT);
}
}
if changed || (patchset && is_pk) {
append_value(&mut new_rec, &new[i]);
} else {
new_rec.push(T_OMIT);
}
}
if !changed_any {
out.truncate(start);
return false;
}
out.extend_from_slice(&new_rec);
true
}
#[derive(Debug, Clone)]
pub(crate) struct ChangeRecord {
pub(crate) op: ChangeOp,
pub(crate) old: Vec<Option<Value>>,
pub(crate) new: Vec<Option<Value>>,
}
#[derive(Debug, Clone)]
pub(crate) struct TableChangeset {
pub(crate) name: String,
pub(crate) ncol: usize,
pub(crate) pk_flags: Vec<u8>,
pub(crate) changes: Vec<ChangeRecord>,
}
struct Reader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
fn new(data: &'a [u8]) -> Reader<'a> {
Reader { data, pos: 0 }
}
fn eof(&self) -> bool {
self.pos >= self.data.len()
}
fn u8(&mut self) -> Result<u8> {
let b = *self
.data
.get(self.pos)
.ok_or_else(|| corrupt("unexpected end of changeset"))?;
self.pos += 1;
Ok(b)
}
fn peek(&self) -> Result<u8> {
self.data
.get(self.pos)
.copied()
.ok_or_else(|| corrupt("unexpected end of changeset"))
}
fn take(&mut self, n: usize) -> Result<&'a [u8]> {
let end = self
.pos
.checked_add(n)
.filter(|e| *e <= self.data.len())
.ok_or_else(|| corrupt("truncated changeset field"))?;
let s = &self.data[self.pos..end];
self.pos = end;
Ok(s)
}
fn varint(&mut self) -> Result<u64> {
let mut result: u64 = 0;
for i in 0..9 {
let byte = self.u8()?;
if i == 8 {
result = (result << 8) | u64::from(byte);
return Ok(result);
}
result = (result << 7) | u64::from(byte & 0x7f);
if byte & 0x80 == 0 {
return Ok(result);
}
}
Ok(result)
}
fn value(&mut self) -> Result<Option<Value>> {
let t = self.u8()?;
match t {
T_OMIT => Ok(None),
T_NULL => Ok(Some(Value::Null)),
T_INT => {
let bytes = self.take(8)?;
let mut a = [0u8; 8];
a.copy_from_slice(bytes);
Ok(Some(Value::Integer(i64::from_be_bytes(a))))
}
T_FLOAT => {
let bytes = self.take(8)?;
let mut a = [0u8; 8];
a.copy_from_slice(bytes);
Ok(Some(Value::Real(f64::from_bits(u64::from_be_bytes(a)))))
}
T_TEXT => {
let n = self.varint()? as usize;
let bytes = self.take(n)?;
let s = core::str::from_utf8(bytes)
.map_err(|_| corrupt("non-UTF-8 text in changeset"))?;
Ok(Some(Value::Text(String::from(s).into())))
}
T_BLOB => {
let n = self.varint()? as usize;
let bytes = self.take(n)?;
Ok(Some(Value::Blob(bytes.to_vec())))
}
other => Err(corrupt(&alloc::format!(
"unknown changeset value type {other}"
))),
}
}
fn record(&mut self, ncol: usize) -> Result<Vec<Option<Value>>> {
let mut out = Vec::with_capacity(ncol);
for _ in 0..ncol {
out.push(self.value()?);
}
Ok(out)
}
fn pk_only_record(&mut self, ncol: usize, pk_flags: &[u8]) -> Result<Vec<Option<Value>>> {
let mut out = alloc::vec![None; ncol];
for (i, slot) in out.iter_mut().enumerate().take(ncol) {
if pk_flags.get(i).copied().unwrap_or(0) != 0 {
*slot = self.value()?;
}
}
Ok(out)
}
}
fn corrupt(msg: &str) -> crate::error::Error {
crate::error::Error::Corrupt(alloc::format!("changeset: {msg}"))
}
pub(crate) fn parse_changeset(data: &[u8]) -> Result<Vec<TableChangeset>> {
let mut r = Reader::new(data);
let mut tables: Vec<TableChangeset> = Vec::new();
let mut patchset = false;
while !r.eof() {
let marker = r.peek()?;
match marker {
b'T' | b'P' => {
patchset = marker == b'P';
r.u8()?; let ncol = r.varint()? as usize;
if ncol == 0 {
return Err(corrupt("table has zero columns"));
}
let mut pk_flags = Vec::with_capacity(ncol);
for _ in 0..ncol {
pk_flags.push(r.u8()?);
}
let start = r.pos;
loop {
let b = r.u8()?;
if b == 0 {
break;
}
}
let name_bytes = &r.data[start..r.pos - 1];
let name = String::from(
core::str::from_utf8(name_bytes)
.map_err(|_| corrupt("non-UTF-8 table name"))?,
);
tables.push(TableChangeset {
name,
ncol,
pk_flags,
changes: Vec::new(),
});
}
OP_INSERT | OP_UPDATE | OP_DELETE => {
let tbl = tables
.last_mut()
.ok_or_else(|| corrupt("change record before any table header"))?;
let ncol = tbl.ncol;
let pk_flags = tbl.pk_flags.clone();
let op = r.u8()?;
let _indirect = r.u8()?;
let rec = match op {
OP_INSERT => ChangeRecord {
op: ChangeOp::Insert,
old: Vec::new(),
new: r.record(ncol)?,
},
OP_DELETE if patchset => {
let old = r.pk_only_record(ncol, &pk_flags)?;
ChangeRecord {
op: ChangeOp::Delete,
old,
new: Vec::new(),
}
}
OP_DELETE => ChangeRecord {
op: ChangeOp::Delete,
old: r.record(ncol)?,
new: Vec::new(),
},
_ if patchset => {
let new = r.record(ncol)?;
let mut old = alloc::vec![None; ncol];
for (i, flag) in pk_flags.iter().enumerate().take(ncol) {
if *flag != 0 {
old[i] = new[i].clone();
}
}
ChangeRecord {
op: ChangeOp::Update,
old,
new,
}
}
_ => {
let old = r.record(ncol)?;
let new = r.record(ncol)?;
ChangeRecord {
op: ChangeOp::Update,
old,
new,
}
}
};
tbl.changes.push(rec);
}
other => {
return Err(corrupt(&alloc::format!(
"unexpected marker byte {other:#x}"
)));
}
}
}
Ok(tables)
}
fn serial_len(a: &[u8]) -> Result<usize> {
let e = *a.first().ok_or_else(|| corrupt("truncated record field"))?;
match e {
0x00 | 0xFF | T_NULL => Ok(1),
T_INT | T_FLOAT => {
if a.len() < 9 {
return Err(corrupt("truncated int/float field"));
}
Ok(9)
}
T_TEXT | T_BLOB => {
let mut r = Reader::new(a);
r.pos = 1;
let n = r.varint()? as usize;
let nvar = r.pos - 1;
r.pos
.checked_add(n)
.filter(|e| *e <= a.len())
.ok_or_else(|| corrupt("truncated text/blob field"))?;
Ok(1 + nvar + n)
}
other => Err(corrupt(&alloc::format!(
"unknown changeset value type {other}"
))),
}
}
fn split_record(a: &[u8], ncol: usize) -> Result<(Vec<&[u8]>, usize)> {
let mut fields = Vec::with_capacity(ncol);
let mut off = 0usize;
for _ in 0..ncol {
let n = serial_len(&a[off..])?;
fields.push(&a[off..off + n]);
off += n;
}
Ok((fields, off))
}
struct TableHdr {
ncol: usize,
pk_flags: Vec<u8>,
name: Vec<u8>,
raw: Vec<u8>,
}
fn read_table_hdr(r: &mut Reader<'_>) -> Result<TableHdr> {
let start = r.pos;
let t = r.u8()?;
debug_assert_eq!(t, b'T');
let ncol = r.varint()? as usize;
if ncol == 0 {
return Err(corrupt("table has zero columns"));
}
let mut pk_flags = Vec::with_capacity(ncol);
for _ in 0..ncol {
pk_flags.push(r.u8()?);
}
let name_start = r.pos;
loop {
if r.u8()? == 0 {
break;
}
}
let name = r.data[name_start..r.pos - 1].to_vec();
let raw = r.data[start..r.pos].to_vec();
Ok(TableHdr {
ncol,
pk_flags,
name,
raw,
})
}
pub(crate) fn invert(changeset: &[u8]) -> Result<Vec<u8>> {
let mut r = Reader::new(changeset);
let mut out = Vec::with_capacity(changeset.len());
let mut ncol = 0usize;
let mut pk_flags: Vec<u8> = Vec::new();
while !r.eof() {
match r.peek()? {
b'T' => {
let hdr = read_table_hdr(&mut r)?;
out.extend_from_slice(&hdr.raw);
ncol = hdr.ncol;
pk_flags = hdr.pk_flags;
}
op @ (OP_INSERT | OP_DELETE) => {
r.u8()?;
let indirect = r.u8()?;
let (_, consumed) = split_record(&r.data[r.pos..], ncol)?;
let rec = r.take(consumed)?;
out.push(if op == OP_INSERT {
OP_DELETE
} else {
OP_INSERT
});
out.push(indirect);
out.extend_from_slice(rec);
}
OP_UPDATE => {
r.u8()?;
let indirect = r.u8()?;
let (old, n_old) = {
let (f, n) = split_record(&r.data[r.pos..], ncol)?;
(f.into_iter().map(<[u8]>::to_vec).collect::<Vec<_>>(), n)
};
r.pos += n_old;
let (new, n_new) = {
let (f, n) = split_record(&r.data[r.pos..], ncol)?;
(f.into_iter().map(<[u8]>::to_vec).collect::<Vec<_>>(), n)
};
r.pos += n_new;
out.push(OP_UPDATE);
out.push(indirect);
for i in 0..ncol {
let is_pk = pk_flags.get(i).copied().unwrap_or(0) != 0;
out.extend_from_slice(if is_pk { &old[i] } else { &new[i] });
}
for (i, field) in old.iter().enumerate().take(ncol) {
let is_pk = pk_flags.get(i).copied().unwrap_or(0) != 0;
if is_pk {
out.push(T_OMIT);
} else {
out.extend_from_slice(field);
}
}
}
other => {
return Err(corrupt(&alloc::format!(
"unexpected marker byte {other:#x}"
)));
}
}
}
Ok(out)
}
#[derive(Clone)]
struct ConcatChange {
op: u8,
indirect: u8,
record: Vec<u8>,
}
struct ConcatTable {
ncol: usize,
pk_flags: Vec<u8>,
name: Vec<u8>,
hdr: Vec<u8>,
buckets: Vec<Vec<ConcatChange>>,
nentry: usize,
}
impl ConcatTable {
fn new(hdr: &TableHdr) -> ConcatTable {
ConcatTable {
ncol: hdr.ncol,
pk_flags: hdr.pk_flags.clone(),
name: hdr.name.clone(),
hdr: hdr.raw.clone(),
buckets: Vec::new(),
nentry: 0,
}
}
fn hash(&self, record: &[u8], nbucket: usize) -> Result<usize> {
let mut h = 0u32;
let mut off = 0usize;
for i in 0..self.ncol {
let n = serial_len(&record[off..])?;
let field = &record[off..off + n];
if self.pk_flags.get(i).copied().unwrap_or(0) != 0 {
let ty = field[0];
h = TableChanges::hash_append(h, u32::from(ty));
match ty {
T_INT | T_FLOAT => {
let mut a = [0u8; 8];
a.copy_from_slice(&field[1..9]);
h = TableChanges::hash_i64(h, i64::from_be_bytes(a));
}
T_TEXT | T_BLOB => {
let mut rr = Reader::new(field);
rr.pos = 1;
let nb = rr.varint()? as usize;
h = TableChanges::hash_blob(h, &field[rr.pos..rr.pos + nb]);
}
_ => {}
}
}
off += n;
}
Ok((h % nbucket as u32) as usize)
}
fn pk_equal(&self, a: &[u8], b: &[u8]) -> Result<bool> {
let mut oa = 0usize;
let mut ob = 0usize;
for i in 0..self.ncol {
let na = serial_len(&a[oa..])?;
let nb = serial_len(&b[ob..])?;
if self.pk_flags.get(i).copied().unwrap_or(0) != 0
&& (na != nb || a[oa..oa + na] != b[ob..ob + nb])
{
return Ok(false);
}
oa += na;
ob += nb;
}
Ok(true)
}
fn maybe_grow(&mut self) -> Result<()> {
let n = self.buckets.len();
if n == 0 || self.nentry >= n / 2 {
let new_n = if n == 0 { 256 } else { n * 2 };
let mut nb: Vec<Vec<ConcatChange>> = (0..new_n).map(|_| Vec::new()).collect();
for bucket in &self.buckets {
for change in bucket {
let idx = self.hash(&change.record, new_n)?;
nb[idx].insert(0, change.clone());
}
}
self.buckets = nb;
}
Ok(())
}
fn add(&mut self, op: u8, indirect: u8, record: Vec<u8>) -> Result<()> {
self.maybe_grow()?;
let nbucket = self.buckets.len();
let idx = self.hash(&record, nbucket)?;
let existing = {
let mut found = None;
for (j, c) in self.buckets[idx].iter().enumerate() {
if self.pk_equal(&c.record, &record)? {
found = Some(j);
break;
}
}
found.map(|j| {
self.nentry -= 1;
self.buckets[idx].remove(j)
})
};
let merged = self.merge(existing, op, indirect, record)?;
if let Some(c) = merged {
self.buckets[idx].insert(0, c);
self.nentry += 1;
}
Ok(())
}
fn merge(
&self,
existing: Option<ConcatChange>,
op2: u8,
indirect2: u8,
rec2: Vec<u8>,
) -> Result<Option<ConcatChange>> {
let Some(pexist) = existing else {
return Ok(Some(ConcatChange {
op: op2,
indirect: indirect2,
record: rec2,
}));
};
let op1 = pexist.op;
if (op1 == OP_INSERT && op2 == OP_INSERT)
|| (op1 == OP_UPDATE && op2 == OP_INSERT)
|| (op1 == OP_DELETE && op2 == OP_UPDATE)
|| (op1 == OP_DELETE && op2 == OP_DELETE)
{
return Ok(Some(pexist));
}
if op1 == OP_INSERT && op2 == OP_DELETE {
return Ok(None);
}
let indirect = u8::from(indirect2 != 0 && pexist.indirect != 0);
let (op, record) = if op1 == OP_INSERT {
debug_assert!(op2 == OP_UPDATE);
let (_, half) = split_record(&rec2, self.ncol)?;
let new_part = &rec2[half..];
let mut out = Vec::new();
self.merge_record(&mut out, &pexist.record, new_part)?;
(OP_INSERT, out)
} else if op1 == OP_DELETE {
debug_assert!(op2 == OP_INSERT);
let mut out = Vec::new();
let ok = self.merge_update(&mut out, &pexist.record, None, &rec2, None)?;
if !ok {
return Ok(None);
}
(OP_UPDATE, out)
} else if op2 == OP_UPDATE {
debug_assert!(op1 == OP_UPDATE);
let (_, half_e) = split_record(&pexist.record, self.ncol)?;
let (exist_old, exist_new) = pexist.record.split_at(half_e);
let (_, half_i) = split_record(&rec2, self.ncol)?;
let (in_old, in_new) = rec2.split_at(half_i);
let mut out = Vec::new();
let ok =
self.merge_update(&mut out, in_old, Some(exist_old), exist_new, Some(in_new))?;
if !ok {
return Ok(None);
}
(OP_UPDATE, out)
} else {
debug_assert!(op1 == OP_UPDATE && op2 == OP_DELETE);
let (_, half1) = split_record(&pexist.record, self.ncol)?;
let old1 = &pexist.record[..half1];
let mut out = Vec::new();
self.merge_record(&mut out, &rec2, old1)?;
(OP_DELETE, out)
};
Ok(Some(ConcatChange {
op,
indirect,
record,
}))
}
fn merge_record(&self, out: &mut Vec<u8>, left: &[u8], right: &[u8]) -> Result<()> {
let mut lo = 0usize;
let mut ro = 0usize;
for _ in 0..self.ncol {
let nl = serial_len(&left[lo..])?;
let nr = serial_len(&right[ro..])?;
if right[ro] != 0 {
out.extend_from_slice(&right[ro..ro + nr]);
} else {
out.extend_from_slice(&left[lo..lo + nl]);
}
lo += nl;
ro += nr;
}
Ok(())
}
fn merge_update(
&self,
out: &mut Vec<u8>,
old1: &[u8],
old2: Option<&[u8]>,
new1: &[u8],
new2: Option<&[u8]>,
) -> Result<bool> {
let start = out.len();
let mut co1 = 0usize;
let mut co2 = 0usize;
let mut cn1 = 0usize;
let mut cn2 = 0usize;
let mut required = false;
for i in 0..self.ncol {
let old = merge_value(old1, &mut co1, old2, &mut co2)?;
let new = merge_value(new1, &mut cn1, new2, &mut cn2)?;
let is_pk = self.pk_flags.get(i).copied().unwrap_or(0) != 0;
if is_pk || old != new {
if !is_pk {
required = true;
}
out.extend_from_slice(old);
} else {
out.push(T_OMIT);
}
}
if !required {
out.truncate(start);
return Ok(false);
}
let mut co1 = 0usize;
let mut co2 = 0usize;
let mut cn1 = 0usize;
let mut cn2 = 0usize;
for i in 0..self.ncol {
let old = merge_value(old1, &mut co1, old2, &mut co2)?;
let new = merge_value(new1, &mut cn1, new2, &mut cn2)?;
let is_pk = self.pk_flags.get(i).copied().unwrap_or(0) != 0;
if is_pk || old == new {
out.push(T_OMIT);
} else {
out.extend_from_slice(new);
}
}
Ok(true)
}
}
fn merge_value<'a>(
one: &'a [u8],
co: &mut usize,
two: Option<&'a [u8]>,
ct: &mut usize,
) -> Result<&'a [u8]> {
let n1 = serial_len(&one[*co..])?;
let f1 = &one[*co..*co + n1];
let mut ret: Option<&[u8]> = None;
if let Some(two) = two {
let n2 = serial_len(&two[*ct..])?;
let f2 = &two[*ct..*ct + n2];
if f2[0] != 0 {
ret = Some(f2);
}
*ct += n2;
}
*co += n1;
Ok(ret.unwrap_or(f1))
}
fn concat_absorb(changeset: &[u8], tables: &mut Vec<ConcatTable>) -> Result<()> {
let mut r = Reader::new(changeset);
let mut cur: Option<usize> = None;
while !r.eof() {
match r.peek()? {
b'T' => {
let hdr = read_table_hdr(&mut r)?;
let idx = tables.iter().position(|t| t.name == hdr.name);
cur = Some(match idx {
Some(i) => {
if tables[i].ncol != hdr.ncol || tables[i].pk_flags != hdr.pk_flags {
return Err(corrupt("incompatible table definition in concat"));
}
i
}
None => {
tables.push(ConcatTable::new(&hdr));
tables.len() - 1
}
});
}
op @ (OP_INSERT | OP_DELETE | OP_UPDATE) => {
let ti = cur.ok_or_else(|| corrupt("change record before any table header"))?;
let ncol = tables[ti].ncol;
r.u8()?;
let indirect = r.u8()?;
let nfields = if op == OP_UPDATE { ncol * 2 } else { ncol };
let (_, consumed) = split_record(&r.data[r.pos..], nfields)?;
let record = r.take(consumed)?.to_vec();
tables[ti].add(op, indirect, record)?;
}
other => {
return Err(corrupt(&alloc::format!(
"unexpected marker byte {other:#x}"
)));
}
}
}
Ok(())
}
pub(crate) fn concat(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {
let mut tables: Vec<ConcatTable> = Vec::new();
concat_absorb(a, &mut tables)?;
concat_absorb(b, &mut tables)?;
let mut out = Vec::new();
for tbl in &tables {
if tbl.nentry == 0 {
continue;
}
out.extend_from_slice(&tbl.hdr);
for bucket in &tbl.buckets {
for change in bucket {
out.push(change.op);
out.push(change.indirect);
out.extend_from_slice(&change.record);
}
}
}
Ok(out)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ConflictType {
Data,
NotFound,
Conflict,
Constraint,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConflictAction {
Omit,
Replace,
Abort,
}
#[derive(Debug, Clone, Copy)]
pub struct Changeset;
impl Changeset {
pub fn invert(changeset: &[u8]) -> Result<Vec<u8>> {
invert(changeset)
}
pub fn concat(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {
concat(a, b)
}
}
pub(crate) struct RebaseEntry {
pub(crate) table: String,
pub(crate) ncol: usize,
pub(crate) pk_flags: Vec<u8>,
pub(crate) op: ChangeOp,
pub(crate) replace: bool,
pub(crate) values: Vec<Option<Value>>,
}
pub(crate) fn serialize_rebase(entries: &[RebaseEntry]) -> Vec<u8> {
let mut out = Vec::new();
let mut seen: Vec<&str> = Vec::new();
for e in entries {
if !seen.iter().any(|t| *t == e.table) {
seen.push(&e.table);
out.push(b'T');
append_varint(&mut out, e.ncol as u64);
out.extend_from_slice(&e.pk_flags);
out.extend_from_slice(e.table.as_bytes());
out.push(0);
}
out.push(if e.op == ChangeOp::Delete {
OP_DELETE
} else {
OP_INSERT
});
out.push(u8::from(e.replace));
for v in &e.values {
match v {
Some(v) => append_value(&mut out, v),
None => out.push(T_OMIT),
}
}
}
out
}
#[derive(Clone, Debug, PartialEq)]
enum RField {
Undefined,
Replaced,
Val(Value),
}
#[derive(Clone)]
struct RebaseChange {
op: ChangeOp,
replace: bool,
fields: Vec<RField>,
}
struct RebaseHashTable {
name: String,
ncol: usize,
pk_flags: Vec<u8>,
rows: Vec<(Vec<Value>, RebaseChange)>,
}
#[derive(Default)]
pub struct Rebaser {
tables: Vec<RebaseHashTable>,
}
impl Rebaser {
pub fn new() -> Rebaser {
Rebaser { tables: Vec::new() }
}
pub fn configure(&mut self, rebase: &[u8]) -> Result<()> {
let mut r = Reader::new(rebase);
let mut cur: Option<usize> = None;
while !r.eof() {
let marker = r.peek()?;
match marker {
b'T' => {
r.u8()?;
let ncol = r.varint()? as usize;
if ncol == 0 {
return Err(corrupt("rebase table has zero columns"));
}
let mut pk_flags = Vec::with_capacity(ncol);
for _ in 0..ncol {
pk_flags.push(r.u8()?);
}
let start = r.pos;
loop {
if r.u8()? == 0 {
break;
}
}
let name = String::from(
core::str::from_utf8(&r.data[start..r.pos - 1])
.map_err(|_| corrupt("non-UTF-8 table name"))?,
);
cur = Some(match self.tables.iter().position(|t| t.name == name) {
Some(i) => i,
None => {
self.tables.push(RebaseHashTable {
name,
ncol,
pk_flags,
rows: Vec::new(),
});
self.tables.len() - 1
}
});
}
OP_INSERT | OP_DELETE => {
let op = if marker == OP_DELETE {
ChangeOp::Delete
} else {
ChangeOp::Insert
};
r.u8()?;
let replace = r.u8()? != 0;
let ti = cur.ok_or_else(|| corrupt("rebase record before table header"))?;
let (ncol, pk_flags) = {
let t = &self.tables[ti];
(t.ncol, t.pk_flags.clone())
};
let raw = r.record(ncol)?;
let mut pk = Vec::new();
for (i, v) in raw.iter().enumerate() {
if pk_flags.get(i).copied().unwrap_or(0) != 0 {
pk.push(v.clone().unwrap_or(Value::Null));
}
}
let fields: Vec<RField> = raw
.into_iter()
.enumerate()
.map(|(i, v)| match v {
None => RField::Undefined,
Some(val) => {
if replace && pk_flags.get(i).copied().unwrap_or(0) == 0 {
RField::Replaced
} else {
RField::Val(val)
}
}
})
.collect();
let newc = RebaseChange {
op,
replace,
fields,
};
self.merge_into(ti, pk, newc);
}
other => {
return Err(corrupt(&alloc::format!("bad rebase op byte {other}")));
}
}
}
Ok(())
}
fn merge_into(&mut self, ti: usize, pk: Vec<Value>, newc: RebaseChange) {
let pk_flags = self.tables[ti].pk_flags.clone();
let t = &mut self.tables[ti];
let Some(slot) = t.rows.iter_mut().find(|(k, _)| pk_eq(k, &pk)) else {
t.rows.push((pk, newc));
return;
};
let existing = &slot.1;
if existing.op == ChangeOp::Delete && existing.replace {
return;
}
let mut fields = Vec::with_capacity(existing.fields.len());
for (i, ef) in existing.fields.iter().enumerate() {
let non_pk = pk_flags.get(i).copied().unwrap_or(0) == 0;
let nf = newc.fields.get(i).cloned().unwrap_or(RField::Undefined);
let f = if *ef == RField::Replaced || (non_pk && newc.replace) {
RField::Replaced
} else if nf == RField::Undefined {
ef.clone()
} else {
nf
};
fields.push(f);
}
slot.1 = RebaseChange {
op: newc.op,
replace: newc.replace || existing.replace,
fields,
};
}
pub fn rebase(&self, changeset: &[u8]) -> Result<Vec<u8>> {
let tables = parse_changeset(changeset)?;
let mut out = Vec::new();
for tbl in &tables {
if tbl.changes.is_empty() {
continue;
}
out.push(b'T');
append_varint(&mut out, tbl.ncol as u64);
out.extend_from_slice(&tbl.pk_flags);
out.extend_from_slice(tbl.name.as_bytes());
out.push(0);
let rt = self.tables.iter().find(|t| t.name == tbl.name);
for change in &tbl.changes {
if let Some(bytes) = self.rebase_one(tbl, rt, change) {
out.extend_from_slice(&bytes);
}
}
}
Ok(out)
}
fn rebase_one(
&self,
tbl: &TableChangeset,
rt: Option<&RebaseHashTable>,
change: &ChangeRecord,
) -> Option<Vec<u8>> {
let key_src = match change.op {
ChangeOp::Insert => &change.new,
_ => &change.old,
};
let mut pk = Vec::new();
for (i, v) in key_src.iter().enumerate() {
if tbl.pk_flags.get(i).copied().unwrap_or(0) != 0 {
pk.push(v.clone().unwrap_or(Value::Null));
}
}
let matched = rt
.and_then(|t| t.rows.iter().find(|(k, _)| pk_eq(k, &pk)))
.map(|(_, c)| c);
let Some(c) = matched else {
return Some(self.emit_unchanged(tbl, change));
};
match change.op {
ChangeOp::Insert => {
if c.op == ChangeOp::Insert {
if c.replace {
None } else {
let old = fields_to_record(&c.fields);
Some(emit_update_raw(&old, &change.new))
}
} else {
Some(self.emit_unchanged(tbl, change))
}
}
ChangeOp::Update => {
if c.op == ChangeOp::Delete {
if c.replace {
None
} else {
let merged = record_merge_fields(&change.new, &c.fields);
Some(emit_insert(&merged))
}
} else {
emit_partial_update(&tbl.pk_flags, &change.old, &change.new, &c.fields)
}
}
ChangeOp::Delete => {
if c.op == ChangeOp::Insert {
let merged = record_merge_local(&c.fields, &change.old);
Some(emit_delete(&merged))
} else {
None
}
}
}
}
fn emit_unchanged(&self, tbl: &TableChangeset, change: &ChangeRecord) -> Vec<u8> {
match change.op {
ChangeOp::Insert => emit_insert(&change.new),
ChangeOp::Delete => emit_delete(&change.old),
ChangeOp::Update => emit_update(&tbl.pk_flags, &change.old, &change.new),
}
}
}
fn fields_to_record(fields: &[RField]) -> Vec<Option<Value>> {
fields
.iter()
.map(|f| match f {
RField::Val(v) => Some(v.clone()),
_ => None,
})
.collect()
}
fn record_merge_fields(local: &[Option<Value>], fields: &[RField]) -> Vec<Option<Value>> {
(0..local.len().max(fields.len()))
.map(|i| {
let l = local.get(i).cloned().flatten();
match l {
Some(v) => Some(v),
None => match fields.get(i) {
Some(RField::Val(v)) => Some(v.clone()),
_ => None,
},
}
})
.collect()
}
fn record_merge_local(fields: &[RField], local: &[Option<Value>]) -> Vec<Option<Value>> {
(0..fields.len().max(local.len()))
.map(|i| match fields.get(i) {
Some(RField::Val(v)) => Some(v.clone()),
_ => local.get(i).cloned().flatten(),
})
.collect()
}
fn emit_partial_update(
pk_flags: &[u8],
old: &[Option<Value>],
new: &[Option<Value>],
fields: &[RField],
) -> Option<Vec<u8>> {
let ncol = pk_flags.len();
let mut rebased_old: Vec<Option<Value>> = Vec::with_capacity(ncol);
let mut b_data = false;
for i in 0..ncol {
let is_pk = pk_flags.get(i).copied().unwrap_or(0) != 0;
let f = fields.get(i).unwrap_or(&RField::Undefined);
let o = old.get(i).cloned().flatten();
if is_pk || matches!(f, RField::Undefined) {
if !is_pk && o.is_some() {
b_data = true;
}
rebased_old.push(o);
} else if let RField::Val(v) = f {
if o.is_some() {
b_data = true;
rebased_old.push(Some(v.clone()));
} else {
rebased_old.push(None);
}
} else {
rebased_old.push(None);
}
}
if !b_data {
return None;
}
let mut rebased_new: Vec<Option<Value>> = Vec::with_capacity(ncol);
for i in 0..ncol {
let is_pk = pk_flags.get(i).copied().unwrap_or(0) != 0;
let replaced = matches!(fields.get(i), Some(RField::Replaced));
if is_pk || !replaced {
rebased_new.push(new.get(i).cloned().flatten());
} else {
rebased_new.push(None);
}
}
Some(emit_update_raw(&rebased_old, &rebased_new))
}
fn write_record(out: &mut Vec<u8>, rec: &[Option<Value>]) {
for v in rec {
match v {
Some(v) => append_value(out, v),
None => out.push(T_OMIT),
}
}
}
fn emit_insert(new: &[Option<Value>]) -> Vec<u8> {
let mut out = Vec::new();
out.push(OP_INSERT);
out.push(0);
write_record(&mut out, new);
out
}
fn emit_delete(old: &[Option<Value>]) -> Vec<u8> {
let mut out = Vec::new();
out.push(OP_DELETE);
out.push(0);
write_record(&mut out, old);
out
}
fn emit_update(pk_flags: &[u8], old: &[Option<Value>], new: &[Option<Value>]) -> Vec<u8> {
let ncol = old.len().min(new.len());
let mut o = Vec::with_capacity(ncol);
let mut n = Vec::with_capacity(ncol);
for i in 0..ncol {
let is_pk = pk_flags.get(i).copied().unwrap_or(0) != 0;
let changed = old[i] != new[i];
o.push(if changed || is_pk {
old[i].clone()
} else {
None
});
n.push(if changed { new[i].clone() } else { None });
}
emit_update_raw(&o, &n)
}
fn emit_update_raw(old: &[Option<Value>], new: &[Option<Value>]) -> Vec<u8> {
let mut out = Vec::new();
out.push(OP_UPDATE);
out.push(0);
write_record(&mut out, old);
write_record(&mut out, new);
out
}
const _: () = {
fn assert_clone<T: Clone>() {}
fn checks() {
assert_clone::<Session>();
}
let _ = checks;
};
const _: () = {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
fn assert_copy<T: Copy>() {}
fn checks() {
assert_send::<Changeset>();
assert_sync::<Changeset>();
assert_copy::<Changeset>();
}
let _ = checks;
};
#[cfg(test)]
mod tests {
use super::*;
fn hex(b: &[u8]) -> String {
let mut s = String::new();
for byte in b {
s.push_str(&alloc::format!("{byte:02x}"));
}
s
}
#[test]
fn insert_matches_oracle() {
let mut st = SessionState {
enabled: true,
attach_all: true,
attached: Vec::new(),
indirect_mode: false,
tables: Vec::new(),
};
st.record(
"t",
2,
&[1, 0],
ChangeOp::Insert,
alloc::vec![Value::Integer(1)],
alloc::vec![Value::Integer(1), Value::Null],
false,
);
let out = serialize(&st, |_, pk| {
assert_eq!(pk, [Value::Integer(1)]);
Some(alloc::vec![Value::Integer(1), Value::Integer(2)])
});
assert_eq!(
hex(&out),
"5402010074001200010000000000000001010000000000000002"
);
}
#[test]
fn composite_insert_matches_oracle() {
let mut st = SessionState {
enabled: true,
attach_all: true,
attached: Vec::new(),
indirect_mode: false,
tables: Vec::new(),
};
st.record(
"t",
3,
&[1, 2, 0],
ChangeOp::Insert,
alloc::vec![Value::Integer(1), Value::Integer(2)],
alloc::vec![Value::Integer(1), Value::Integer(2), Value::Null],
false,
);
let out = serialize(&st, |_, pk| {
assert_eq!(pk, [Value::Integer(1), Value::Integer(2)]);
Some(alloc::vec![
Value::Integer(1),
Value::Integer(2),
Value::Integer(3)
])
});
assert_eq!(
hex(&out),
"540301020074001200010000000000000001\
010000000000000002010000000000000003"
);
}
#[test]
fn patchset_insert_matches_oracle() {
let mut st = SessionState {
enabled: true,
attach_all: true,
attached: Vec::new(),
indirect_mode: false,
tables: Vec::new(),
};
st.record(
"t",
2,
&[1, 0],
ChangeOp::Insert,
alloc::vec![Value::Integer(1)],
alloc::vec![Value::Integer(1), Value::Null],
false,
);
let out = serialize_patchset(&st, |_, _| {
Some(alloc::vec![Value::Integer(1), Value::Integer(2)])
});
assert_eq!(
hex(&out),
"5002010074001200010000000000000001010000000000000002"
);
}
#[test]
fn patchset_update_matches_oracle() {
let mut st = SessionState {
enabled: true,
attach_all: true,
attached: Vec::new(),
indirect_mode: false,
tables: Vec::new(),
};
st.record(
"t",
3,
&[1, 0, 0],
ChangeOp::Update,
alloc::vec![Value::Integer(1)],
alloc::vec![Value::Integer(1), Value::Integer(2), Value::Integer(3)],
false,
);
let out = serialize_patchset(&st, |_, _| {
Some(alloc::vec![
Value::Integer(1),
Value::Integer(20),
Value::Integer(3)
])
});
assert_eq!(
hex(&out),
"50030100007400170001000000000000000101000000000000001400"
);
}
#[test]
fn patchset_delete_matches_oracle() {
let mut st = SessionState {
enabled: true,
attach_all: true,
attached: Vec::new(),
indirect_mode: false,
tables: Vec::new(),
};
st.record(
"t",
3,
&[1, 0, 0],
ChangeOp::Delete,
alloc::vec![Value::Integer(1)],
alloc::vec![Value::Integer(1), Value::Integer(2), Value::Integer(3)],
false,
);
let out = serialize_patchset(&st, |_, _| None);
assert_eq!(hex(&out), "500301000074000900010000000000000001");
}
#[test]
fn patchset_composite_delete_matches_oracle() {
let mut st = SessionState {
enabled: true,
attach_all: true,
attached: Vec::new(),
indirect_mode: false,
tables: Vec::new(),
};
st.record(
"t",
3,
&[1, 2, 0],
ChangeOp::Delete,
alloc::vec![Value::Integer(1), Value::Integer(2)],
alloc::vec![Value::Integer(1), Value::Integer(2), Value::Integer(3)],
false,
);
let out = serialize_patchset(&st, |_, _| None);
assert_eq!(
hex(&out),
"500301020074000900010000000000000001010000000000000002"
);
}
#[test]
fn patchset_composite_update_matches_oracle() {
let mut st = SessionState {
enabled: true,
attach_all: true,
attached: Vec::new(),
indirect_mode: false,
tables: Vec::new(),
};
st.record(
"t",
3,
&[1, 2, 0],
ChangeOp::Update,
alloc::vec![Value::Integer(1), Value::Integer(2)],
alloc::vec![Value::Integer(1), Value::Integer(2), Value::Integer(3)],
false,
);
let out = serialize_patchset(&st, |_, _| {
Some(alloc::vec![
Value::Integer(1),
Value::Integer(2),
Value::Integer(30)
])
});
assert_eq!(
hex(&out),
"50030102007400170001000000000000000101000000000000000201000000000000001e"
);
}
#[test]
fn parse_patchset_normalizes_records() {
let update = "50030100007400170001000000000000000101000000000000001400";
let bytes = from_hex(update);
let tables = parse_changeset(&bytes).unwrap();
assert_eq!(tables.len(), 1);
let t = &tables[0];
assert_eq!(t.ncol, 3);
assert_eq!(t.pk_flags, alloc::vec![1, 0, 0]);
let c = &t.changes[0];
assert_eq!(c.op, ChangeOp::Update);
assert_eq!(
c.new,
alloc::vec![Some(Value::Integer(1)), Some(Value::Integer(20)), None]
);
assert_eq!(c.old, alloc::vec![Some(Value::Integer(1)), None, None]);
let del = "500301000074000900010000000000000001";
let tables = parse_changeset(&from_hex(del)).unwrap();
let c = &tables[0].changes[0];
assert_eq!(c.op, ChangeOp::Delete);
assert_eq!(c.old, alloc::vec![Some(Value::Integer(1)), None, None]);
}
fn from_hex(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
}