char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
78f4748
main
1use std:: collections:: BTreeMap ; 2use std:: io; 3 4use super ::{ Decision , Store , check_keyspace_name, parse_stamp}; 5 6// Bounds on one batch so a hostile or buggy guest cannot make a single 7// commit arbitrarily large. Bytes count keyspace names, keys and values. 8pub const MAX_BATCH_OPS : usize =1_000 ; 9pub const MAX_BATCH_BYTES : usize =4 <<20 ; 10 11# [ derive ( Debug , Clone , PartialEq , Eq )] 12pub enum BatchOp { 13Check { 14keyspace : String , 15key : Vec < u8 >, 16expected : Option < String >, 17}, 18Set { 19keyspace : String , 20key : Vec < u8 >, 21value : Vec < u8 >, 22}, 23Delete { 24keyspace : String , 25key : Vec < u8 >, 26}, 27} 28 29impl BatchOp { 30pub fn keyspace ( & self ) ->& str { 31match self { 32BatchOp :: Check { keyspace, ..} 33 |BatchOp :: Set { keyspace, ..} 34 |BatchOp :: Delete { keyspace, ..} => keyspace, 35} 36} 37} 38 39fn invalid ( message : & str ) -> io:: Error { 40 io:: Error :: new ( io:: ErrorKind :: InvalidInput , message) 41} 42 43impl Store { 44pub fn atomic_commit ( & self , ops : & [ BatchOp ]) -> io:: Result <( bool , Option < String >)> { 45if ops. len () >MAX_BATCH_OPS { 46return Err ( invalid ( "Atomic batch exceeds operation limit" )); 47} 48let mut bytes =0 ; 49for opin ops{ 50check_keyspace_name ( op. keyspace ()) ?; 51 bytes += op. keyspace (). len () 52 +match op{ 53BatchOp :: Check { key, ..} |BatchOp :: Delete { key, ..} => key. len (), 54BatchOp :: Set { key, value, ..} => key. len () + value. len (), 55}; 56// Malformed stamps are caller error, not a quiet check failure. 57if let BatchOp :: Check { expected : Some ( stamp), ..} = op{ 58parse_stamp ( stamp) ?; 59} 60} 61if bytes >MAX_BATCH_BYTES { 62return Err ( invalid ( "Atomic batch exceeds size limit" )); 63} 64if ops. is_empty () { 65return Ok (( true , None )); 66} 67self . write_tx ( |tx|{ 68// Checks observe pre-commit state and never register keyspaces: a 69// missing registry row means the entry is absent. 70for opin ops{ 71if let BatchOp :: Check { keyspace, key, expected} = op{ 72let current = tx. get ( keyspace, key. as_slice ()) ?; 73let pass =match ( & current, expected) { 74( None , None ) =>true , 75( Some ( _), None ) =>false , 76( None , Some ( _)) =>false , 77// Both sides are canonical renderings (parse_stamp 78// accepted `want` above), so string equality suffices. 79( Some (( _, have)), Some ( want)) => have == want, 80}; 81if !pass{ 82// Abort drops the transaction uncommitted: a failed 83// batch changes nothing, not even metadata. 84return Ok ( Decision :: Abort (( false , None ))); 85} 86} 87} 88let mut mutations: BTreeMap <( & str , & [ u8 ]), Option < & [ u8 ]>> =BTreeMap :: new (); 89for opin ops{ 90match op{ 91BatchOp :: Check { ..} =>{} 92BatchOp :: Set { keyspace, key, value} =>{ 93 mutations 94. insert (( keyspace. as_str (), key. as_slice ()), Some ( value. as_slice ())); 95} 96BatchOp :: Delete { keyspace, key} =>{ 97 mutations. insert (( keyspace. as_str (), key. as_slice ()), None ); 98} 99} 100} 101// A check-only batch commits nothing and burns no versionstamp. 102if mutations. is_empty () { 103return Ok ( Decision :: Abort (( true , None ))); 104} 105for (( keyspace, key), mutation) in & mutations{ 106match mutation{ 107Some ( value) =>{ 108 tx. put ( keyspace, key, value) ?; 109} 110None =>{ 111 tx. delete ( keyspace, key) ?; 112} 113} 114} 115let stamp =match tx. stamp () { 116Some ( stamp) => stamp, 117// Every mutation missed: the batch still consumes a commit. 118None => tx. allocate_stamp () ?, 119}; 120Ok ( Decision :: Commit (( true , Some ( stamp)))) 121}) 122} 123}