Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F73770
TRANSACTION_API.md
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
9 KB
Subscribers
None
TRANSACTION_API.md
View Options
#
Transaction
API
Documentation
##
Overview
The
store
library
provides
a
flexible
and
composable
transaction
system
that
allows
atomic
operations
across
multiple
stores
.
The
API
has
been
redesigned
to
be
more
generic
and
extensible
,
supporting
both
built
-
in
stores
(
RangeStore
,
NamespaceStore
)
and
custom
user
-
defined
stores
.
##
Key
Features
-
**
Composable
Transactions
**:
Mix
and
match
different
stores
in
a
single
transaction
-
**
Type
Safety
**:
Store
-
specific
transaction
contexts
prevent
API
misuse
-
**
Extensibility
**:
Easy
integration
of
custom
stores
via
the
`
TransactionProvider
`
trait
-
**
Atomic
Operations
**:
All
-
or
-
nothing
semantics
with
automatic
rollback
on
errors
-
**
Backward
Compatibility
**:
Legacy
API
remains
available
##
Core
Concepts
###
TransactionProvider
Trait
Any
store
that
wants
to
participate
in
transactions
must
implement
the
`
TransactionProvider
`
trait
:
```
rust
pub
trait
TransactionProvider
{
fn
transaction_trees
(&
self
)
->
Vec
<&
sled
::
Tree
>;
}
```
This
trait
tells
the
transaction
system
which
sled
trees
need
to
be
included
in
the
transaction
.
###
Transaction
Contexts
Each
store
type
has
its
own
transaction
context
that
provides
type
-
safe
access
to
store
operations
within
a
transaction
:
-
`
RangeTransactionContext
`
-
for
RangeStore
operations
-
`
NamespaceTransactionContext
`
-
for
NamespaceStore
operations
-
Custom
contexts
for
user
-
defined
stores
##
API
Reference
###
Basic
Transaction
API
####
Transaction
The
basic
`
Transaction
`
struct
supports
the
core
built
-
in
stores
:
```
rust
let
transaction
=
Transaction
::
new
()
.
with_range_store
(&
range_store
)
.
with_namespace_store
(&
namespace_store
)
.
with_tree
(&
metadata_tree
);
let
result
=
transaction
.
execute
(|
ctx
|
{
let
ip_bit
=
ctx
.
use_range
().
assign
(
"ip_pool"
,
"192.168.1.100"
)?;
let
reserved
=
ctx
.
use_namespace
().
reserve
(
"users"
,
"alice"
,
"data"
)?;
if
let
Some
(
tree
)
=
ctx
.
tree
(
0
)
{
tree
.
insert
(
"last_operation"
,
"user_assignment"
)?;
}
Ok
((
ip_bit
,
reserved
))
})?;
```
####
TransactionContext
Methods
-
`
use_range
()
`
-
Returns
`
RangeTransactionContext
`
for
range
operations
-
`
use_namespace
()
`
-
Returns
`
NamespaceTransactionContext
`
for
namespace
operations
-
`
tree
(
index
)
`
-
Access
additional
trees
by
index
-
`
raw_tree
(
index
)
`
-
Direct
access
to
transactional
trees
-
`
all_trees
()
`
-
Access
to
all
transactional
trees
###
Extended
Transaction
API
####
ExtendedTransaction
The
extended
API
supports
custom
stores
with
type
identification
:
```
rust
let
transaction
=
ExtendedTransaction
::
new
()
.
with_range_store
(&
range_store
)
.
with_namespace_store
(&
namespace_store
)
.
with_custom_store
(&
audit_store
,
"audit"
)
.
with_tree
(&
config_tree
);
let
result
=
transaction
.
execute
(|
ctx
|
{
let
ip_bit
=
ctx
.
use_range
().
assign
(
"ip_pool"
,
"192.168.1.100"
)?;
let
reserved
=
ctx
.
use_namespace
().
reserve
(
"users"
,
"alice"
,
"data"
)?;
// Access custom store trees by type name
if
let
Some
(
trees
)
=
ctx
.
custom_store_trees
(
"audit"
)
{
// Use audit store trees directly
}
Ok
((
ip_bit
,
reserved
))
})?;
```
####
ExtendedTransactionContext
Methods
-
`
use_range
()
`
-
Returns
`
RangeTransactionContext
`
-
`
use_namespace
()
`
-
Returns
`
NamespaceTransactionContext
`
-
`
custom_store_trees
(
type_name
)
`
-
Get
trees
for
a
custom
store
by
type
name
-
`
tree
(
index
)
`
-
Access
additional
trees
by
index
-
`
raw_tree
(
index
)
`
-
Direct
access
to
transactional
trees
-
`
store_map
()
`
-
Get
mapping
of
store
types
to
tree
indices
###
Store
-
Specific
Transaction
Contexts
####
RangeTransactionContext
```
rust
// Within a transaction
let
range_ctx
=
ctx
.
use_range
();
// Assign a value to a range
let
bit_position
=
range_ctx
.
assign
(
"ip_pool"
,
"192.168.1.100"
)?;
// Get assignment details
let
value
=
range_ctx
.
get
(
"ip_pool"
,
bit_position
)?;
```
####
NamespaceTransactionContext
```
rust
// Within a transaction
let
ns_ctx
=
ctx
.
use_namespace
();
// Reserve a key
let
reserved
=
ns_ctx
.
reserve
(
"users"
,
"alice"
,
"user_data"
)?;
// Get a value
let
value
=
ns_ctx
.
get
(
"users"
,
"alice"
)?;
// Remove a key
let
removed
=
ns_ctx
.
remove
(
"users"
,
"alice"
)?;
```
##
Creating
Custom
Stores
###
Step
1
:
Implement
TransactionProvider
```
rust
pub
struct
MyStore
{
data
:
sled
::
Tree
,
metadata
:
sled
::
Tree
,
}
impl
TransactionProvider
for
MyStore
{
fn
transaction_trees
(&
self
)
->
Vec
<&
sled
::
Tree
>
{
vec
![&
self
.
data
,
&
self
.
metadata
]
}
}
```
###
Step
2
:
Create
Transaction
Methods
Add
methods
to
your
store
that
work
within
transactions
:
```
rust
impl
MyStore
{
pub
fn
set_in_transaction
(
&
self
,
data
:
&
sled
::
transaction
::
TransactionalTree
,
metadata
:
&
sled
::
transaction
::
TransactionalTree
,
key
:
&
str
,
value
:
&
str
,
)
->
sled
::
transaction
::
ConflictableTransactionResult
<(),
()>
{
data
.
insert
(
key
,
value
)?;
metadata
.
insert
(
"last_modified"
,
&
timestamp
().
to_be_bytes
())?;
Ok
(())
}
}
```
###
Step
3
:
Create
Helper
Functions
For
the
basic
Transaction
API
,
create
helper
functions
:
```
rust
pub
fn
use_my_store_in_transaction
<
'
a
,
'
ctx
>(
ctx
:
&
'
ctx
TransactionContext
<
'
a
,
'
ctx
>,
store
:
&
'
a
MyStore
,
base_index
:
usize
,
)
->
MyTransactionHelper
<
'
a
,
'
ctx
>
{
MyTransactionHelper
{
store
,
data
:
ctx
.
raw_tree
(
base_index
).
unwrap
(),
metadata
:
ctx
.
raw_tree
(
base_index
+
1
).
unwrap
(),
}
}
```
For
the
extended
API
,
use
the
custom
store
trees
feature
:
```
rust
pub
fn
use_my_store_extended
<
'
a
,
'
ctx
>(
ctx
:
&
'
ctx
ExtendedTransactionContext
<
'
a
,
'
ctx
>,
store
:
&
'
a
MyStore
,
)
->
Option
<
MyTransactionHelper
<
'
a
,
'
ctx
>>
{
let
trees
=
ctx
.
custom_store_trees
(
"my_store"
)?;
Some
(
MyTransactionHelper
{
store
,
data
:
trees
[
0
],
metadata
:
trees
[
1
],
})
}
```
###
Step
4
:
Use
in
Transactions
```
rust
// With ExtendedTransaction
let
transaction
=
ExtendedTransaction
::
new
()
.
with_custom_store
(&
my_store
,
"my_store"
);
transaction
.
execute
(|
ctx
|
{
let
helper
=
use_my_store_extended
(
ctx
,
&
my_store
)
.
ok_or_else
(||
Error
::
StoreError
(
sled
::
Error
::
Unsupported
(
"Store not available"
.
to_string
())))?;
helper
.
set
(
"key"
,
"value"
)?;
Ok
(())
})?;
```
##
Error
Handling
All
transaction
operations
follow
Rust
'
s
`
Result
`
pattern
.
When
any
operation
within
a
transaction
returns
an
error
,
the
entire
transaction
is
automatically
rolled
back
.
###
Common
Error
Types
-
`
Error
::
StoreError
(
sled
::
Error
)
`
-
Underlying
sled
database
errors
-
`
Error
::
UndefinedRange
(
String
)
`
-
Range
not
defined
-
`
Error
::
RangeFull
(
String
)
`
-
Range
has
no
available
slots
-
`
Error
::
UndefinedNamespace
(
String
)
`
-
Namespace
not
defined
-
`
Error
::
NamespaceKeyReserved
(
String
,
String
)
`
-
Key
already
exists
in
namespace
###
Error
Handling
Best
Practices
```
rust
let
result
=
transaction
.
execute
(|
ctx
|
{
// Operations that might fail
let
bit
=
ctx
.
use_range
().
assign
(
"pool"
,
"value"
)
.
map_err
(|
e
|
{
log
::
error
!(
"Failed to assign range: {}"
,
e
);
e
})?;
Ok
(
bit
)
});
match
result
{
Ok
(
bit
)
=>
println
!(
"Successfully assigned bit: {}"
,
bit
),
Err
(
Error
::
RangeFull
(
pool
))
=>
{
println
!(
"Pool {} is full, need to expand"
,
pool
);
}
Err
(
e
)
=>
{
log
::
error
!(
"Transaction failed: {}"
,
e
);
}
}
```
##
Migration
Guide
###
From
CombinedTransaction
to
Transaction
**
Old
API
:**
```
rust
let
combined
=
CombinedTransaction
::
new
(
&
range_store
,
&
namespace_store
,
vec
![&
extra_tree
],
);
combined
.
execute
(|
ctx
|
{
ctx
.
assign_range
(
"pool"
,
"value"
)?;
ctx
.
reserve_namespace
(
"users"
,
"key"
,
"value"
)?;
ctx
.
additional_tree
(
0
)?.
insert
(
"data"
,
"value"
)?;
Ok
(())
})?;
```
**
New
API
:**
```
rust
let
transaction
=
Transaction
::
new
()
.
with_range_store
(&
range_store
)
.
with_namespace_store
(&
namespace_store
)
.
with_tree
(&
extra_tree
);
transaction
.
execute
(|
ctx
|
{
ctx
.
use_range
().
assign
(
"pool"
,
"value"
)?;
ctx
.
use_namespace
().
reserve
(
"users"
,
"key"
,
"value"
)?;
ctx
.
tree
(
0
)?.
insert
(
"data"
,
"value"
)?;
Ok
(())
})?;
```
###
Benefits
of
Migration
1
.
**
Better
Type
Safety
**:
Store
-
specific
contexts
prevent
mixing
operations
2
.
**
Clearer
API
**:
Methods
are
namespaced
by
store
type
3
.
**
Extensibility
**:
Easy
to
add
custom
stores
4
.
**
Composability
**:
Mix
and
match
stores
as
needed
##
Performance
Considerations
-
**
Transaction
Scope
**:
Keep
transactions
as
small
as
possible
-
**
Tree
Count
**:
More
trees
in
a
transaction
may
impact
performance
-
**
Contention
**:
Concurrent
transactions
on
the
same
trees
will
retry
-
**
Memory
Usage
**:
Large
transactions
hold
more
data
in
memory
##
Best
Practices
1
.
**
Single
Responsibility
**:
Each
transaction
should
have
a
clear
,
single
purpose
2
.
**
Error
Handling
**:
Always
handle
specific
error
types
appropriately
3
.
**
Testing
**:
Write
tests
that
include
rollback
scenarios
4
.
**
Documentation
**:
Document
transaction
semantics
for
custom
stores
5
.
**
Logging
**:
Add
appropriate
logging
for
transaction
operations
##
Examples
See
the
`
examples
/
`
directory
for
complete
working
examples
:
-
`
simple_custom_store
.
rs
`
-
Basic
custom
store
integration
-
`
extended_custom_store
.
rs
`
-
Advanced
patterns
(
with
lifetime
constraints
)
-
`
custom_store
.
rs
`
-
Conceptual
example
(
demonstration
only
)
##
Legacy
Support
The
original
`
CombinedTransaction
`
and
`
CombinedTransactionContext
`
types
are
still
available
for
backward
compatibility
.
However
,
new
code
should
use
the
updated
API
for
better
composability
and
type
safety
.
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Jun 9, 11:33 AM (1 d, 3 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
47643
Default Alt Text
TRANSACTION_API.md (9 KB)
Attached To
rCOLLAR collar
Event Timeline
Log In to Comment