create-feltdb 0.11.8 → 0.11.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package-versions.js +1 -1
- package/dist/server-source/crates/feltdb/src/lib.rs +363 -2
- package/dist/server-source/crates/feltdb/src/read_basis.rs +257 -0
- package/dist/server-source/crates/feltdb/src/state_contract.rs +442 -73
- package/dist/server-source/crates/feltdb/src/workload_diagnostics.rs +18 -4
- package/dist/server-source/crates/feltdb-server/src/main.rs +198 -10
- package/package.json +1 -1
- package/dist/server-source/crates/feltdb-wasm/test.db +0 -0
- package/dist/server-source/crates/feltdb-wasm/test_wasm.db.lock +0 -0
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
//! fell back to scan without updating these fields.
|
|
22
22
|
|
|
23
23
|
use crate::{
|
|
24
|
+
AtomicMutation, AtomicPrecondition, FeltDb, FlowError, PreconditionFailure, RecordPrecondition,
|
|
25
|
+
StoredRow,
|
|
24
26
|
application::{ApplicationManifest, ApplicationRevision, FieldDefinition},
|
|
25
27
|
policy_evaluation::{
|
|
26
28
|
Actor, AuthorizationState, PolicyEvaluator, PolicySubject, RecordAuthorizationContext,
|
|
27
29
|
},
|
|
28
|
-
AtomicMutation, AtomicPrecondition, FeltDb, FlowError, PreconditionFailure, RecordPrecondition,
|
|
29
|
-
StoredRow,
|
|
30
30
|
};
|
|
31
31
|
use serde::{Deserialize, Serialize};
|
|
32
32
|
use serde_json::{Map, Value};
|
|
@@ -625,7 +625,7 @@ pub fn begin_query_read(
|
|
|
625
625
|
state_namespace: &str,
|
|
626
626
|
authorization: AuthorizationContext,
|
|
627
627
|
) -> Result<ReadContext, StateFailure> {
|
|
628
|
-
|
|
628
|
+
build_read_context(db, schema, state_namespace, authorization, Vec::new(), true)
|
|
629
629
|
}
|
|
630
630
|
|
|
631
631
|
fn begin_read_internal(
|
|
@@ -649,6 +649,24 @@ fn begin_read_internal(
|
|
|
649
649
|
} else {
|
|
650
650
|
Vec::new()
|
|
651
651
|
};
|
|
652
|
+
build_read_context(
|
|
653
|
+
db,
|
|
654
|
+
schema,
|
|
655
|
+
state_namespace,
|
|
656
|
+
authorization,
|
|
657
|
+
snapshot_rows,
|
|
658
|
+
!capture_snapshot,
|
|
659
|
+
)
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
fn build_read_context(
|
|
663
|
+
db: &FeltDb,
|
|
664
|
+
schema: &StateSchema,
|
|
665
|
+
state_namespace: &str,
|
|
666
|
+
authorization: AuthorizationContext,
|
|
667
|
+
snapshot_rows: Vec<StoredRow>,
|
|
668
|
+
live_query: bool,
|
|
669
|
+
) -> Result<ReadContext, StateFailure> {
|
|
652
670
|
Ok(ReadContext {
|
|
653
671
|
application_id: schema.application_id.clone(),
|
|
654
672
|
revision_id: schema.revision_id.clone(),
|
|
@@ -662,10 +680,131 @@ fn begin_read_internal(
|
|
|
662
680
|
state_namespace: state_namespace.into(),
|
|
663
681
|
authorization,
|
|
664
682
|
snapshot_rows,
|
|
665
|
-
live_query
|
|
683
|
+
live_query,
|
|
666
684
|
})
|
|
667
685
|
}
|
|
668
686
|
|
|
687
|
+
fn namespace_capability(state_namespace: &str, collection: &str) -> String {
|
|
688
|
+
format!("{state_namespace}:{collection}")
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
fn membership_collection_name(schema: &StateSchema) -> Option<String> {
|
|
692
|
+
AuthorizationState::new(Arc::new(schema.clone()), "", Arc::new(Vec::new()))
|
|
693
|
+
.find_membership_collection()
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
fn member_policy_snapshot_rows(
|
|
697
|
+
db: &FeltDb,
|
|
698
|
+
schema: &StateSchema,
|
|
699
|
+
state_namespace: &str,
|
|
700
|
+
) -> Result<Vec<StoredRow>, StateFailure> {
|
|
701
|
+
let Some(collection) = membership_collection_name(schema) else {
|
|
702
|
+
return Ok(Vec::new());
|
|
703
|
+
};
|
|
704
|
+
db.list_collection(&namespace_capability(state_namespace, &collection))
|
|
705
|
+
.map_err(StateFailure::storage)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
fn load_reference_rows(
|
|
709
|
+
db: &FeltDb,
|
|
710
|
+
state_namespace: &str,
|
|
711
|
+
values: &[Value],
|
|
712
|
+
references: &[QueryReference],
|
|
713
|
+
) -> Result<BTreeMap<(String, String), Value>, StateFailure> {
|
|
714
|
+
let mut rows = BTreeMap::new();
|
|
715
|
+
let mut requested = BTreeMap::<String, BTreeSet<String>>::new();
|
|
716
|
+
for reference in references {
|
|
717
|
+
for id in values
|
|
718
|
+
.iter()
|
|
719
|
+
.filter_map(|value| value.get(&reference.field).and_then(Value::as_str))
|
|
720
|
+
{
|
|
721
|
+
requested
|
|
722
|
+
.entry(reference.collection.clone())
|
|
723
|
+
.or_default()
|
|
724
|
+
.insert(id.to_string());
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
for (collection, ids) in requested {
|
|
728
|
+
let capability = namespace_capability(state_namespace, &collection);
|
|
729
|
+
let loaded = db
|
|
730
|
+
.get_collection_records(&capability, ids.iter().map(String::as_str))
|
|
731
|
+
.map_err(StateFailure::storage)?;
|
|
732
|
+
let present = loaded
|
|
733
|
+
.into_iter()
|
|
734
|
+
.map(|row| (row.key, row.value))
|
|
735
|
+
.collect::<BTreeMap<_, _>>();
|
|
736
|
+
for id in ids {
|
|
737
|
+
rows.insert(
|
|
738
|
+
(collection.clone(), id.clone()),
|
|
739
|
+
present.get(&id).cloned().unwrap_or(Value::Null),
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
Ok(rows)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
fn transaction_snapshot_rows(
|
|
747
|
+
db: &FeltDb,
|
|
748
|
+
schema: &StateSchema,
|
|
749
|
+
request: &TransactionRequest,
|
|
750
|
+
uniform_write_policy: Option<&PolicySubject>,
|
|
751
|
+
collection_write_policies: Option<&BTreeMap<String, PolicySubject>>,
|
|
752
|
+
) -> Result<Vec<StoredRow>, StateFailure> {
|
|
753
|
+
let namespace = request
|
|
754
|
+
.state_namespace
|
|
755
|
+
.as_deref()
|
|
756
|
+
.unwrap_or(&request.application_id);
|
|
757
|
+
let mut full_collections = BTreeSet::new();
|
|
758
|
+
let mut records = BTreeSet::new();
|
|
759
|
+
for condition in &request.preconditions {
|
|
760
|
+
if let TransactionPrecondition::CollectionVersion { collection, .. } = condition {
|
|
761
|
+
full_collections.insert(collection.clone());
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
let membership_collection = membership_collection_name(schema);
|
|
765
|
+
for operation in &request.operations {
|
|
766
|
+
let substrate = is_substrate_collection(&operation.collection);
|
|
767
|
+
records.insert((operation.collection.clone(), operation.id.clone()));
|
|
768
|
+
let write_policy = collection_write_policies
|
|
769
|
+
.and_then(|policies| policies.get(&operation.collection))
|
|
770
|
+
.or(uniform_write_policy);
|
|
771
|
+
if matches!(write_policy, Some(&PolicySubject::Member)) {
|
|
772
|
+
if let Some(collection) = &membership_collection {
|
|
773
|
+
full_collections.insert(collection.clone());
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if substrate || matches!(operation.kind, TransactionOperationKind::Delete) {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
let definition = collection(schema, &operation.collection)?;
|
|
780
|
+
if definition.indexes.iter().any(|index| index.unique) {
|
|
781
|
+
full_collections.insert(operation.collection.clone());
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
let mut snapshot = Vec::new();
|
|
786
|
+
let mut materialized = BTreeSet::new();
|
|
787
|
+
for collection in &full_collections {
|
|
788
|
+
snapshot.extend(
|
|
789
|
+
db.list_collection(&namespace_capability(namespace, collection))
|
|
790
|
+
.map_err(StateFailure::storage)?,
|
|
791
|
+
);
|
|
792
|
+
materialized.insert(collection.clone());
|
|
793
|
+
}
|
|
794
|
+
for (collection, key) in records {
|
|
795
|
+
if materialized.contains(&collection) {
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
if let Some(row) = db
|
|
799
|
+
.get_collection_record(&namespace_capability(namespace, &collection), &key)
|
|
800
|
+
.map_err(StateFailure::storage)?
|
|
801
|
+
{
|
|
802
|
+
snapshot.push(row);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
Ok(snapshot)
|
|
806
|
+
}
|
|
807
|
+
|
|
669
808
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
670
809
|
#[serde(tag = "operator", rename_all = "camelCase")]
|
|
671
810
|
pub enum QueryFilter {
|
|
@@ -1231,14 +1370,39 @@ pub fn execute_query(
|
|
|
1231
1370
|
"read context does not match schema",
|
|
1232
1371
|
));
|
|
1233
1372
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1373
|
+
// Substrate collections carry no application schema: their records are
|
|
1374
|
+
// validated by the substrate that writes them, and they are read only
|
|
1375
|
+
// under `composition:read`.
|
|
1376
|
+
let substrate = is_substrate_collection(&query.collection);
|
|
1377
|
+
let substrate_definition;
|
|
1378
|
+
let definition = if substrate {
|
|
1379
|
+
if !query.references.is_empty() {
|
|
1380
|
+
return Err(StateFailure::new(
|
|
1381
|
+
"INVALID_QUERY",
|
|
1382
|
+
"substrate collections do not expand references",
|
|
1383
|
+
));
|
|
1384
|
+
}
|
|
1385
|
+
substrate_definition = CollectionSchema {
|
|
1386
|
+
name: query.collection.clone(),
|
|
1387
|
+
version: 1,
|
|
1388
|
+
fields: vec![],
|
|
1389
|
+
indexes: vec![],
|
|
1390
|
+
};
|
|
1391
|
+
&substrate_definition
|
|
1392
|
+
} else {
|
|
1393
|
+
collection(schema, &query.collection)?
|
|
1394
|
+
};
|
|
1395
|
+
let readable = if substrate {
|
|
1396
|
+
context.authorization.can("composition:read")
|
|
1397
|
+
} else {
|
|
1398
|
+
context.authorization.can("state:read")
|
|
1399
|
+
&& (context.authorization.readable_collections.is_empty()
|
|
1400
|
+
|| context
|
|
1401
|
+
.authorization
|
|
1402
|
+
.readable_collections
|
|
1403
|
+
.contains(&query.collection))
|
|
1404
|
+
};
|
|
1405
|
+
if !readable {
|
|
1242
1406
|
return Err(StateFailure::new(
|
|
1243
1407
|
"AUTHORIZATION_DENIED",
|
|
1244
1408
|
"collection is not readable",
|
|
@@ -1271,7 +1435,7 @@ pub fn execute_query(
|
|
|
1271
1435
|
for reference in &query.references {
|
|
1272
1436
|
requested.insert(reference.field.clone());
|
|
1273
1437
|
}
|
|
1274
|
-
if requested.iter().any(|f| !available.contains(f)) {
|
|
1438
|
+
if !substrate && requested.iter().any(|f| !available.contains(f)) {
|
|
1275
1439
|
return Err(StateFailure::new(
|
|
1276
1440
|
"UNKNOWN_FIELD",
|
|
1277
1441
|
"query references an unknown field",
|
|
@@ -1341,14 +1505,10 @@ pub fn execute_query(
|
|
|
1341
1505
|
}
|
|
1342
1506
|
}
|
|
1343
1507
|
|
|
1344
|
-
let
|
|
1345
|
-
|
|
1346
|
-
query_snapshot = _db
|
|
1347
|
-
.state_rows_for_namespace(&context.state_namespace)
|
|
1348
|
-
.map_err(StateFailure::storage)?;
|
|
1349
|
-
&query_snapshot
|
|
1508
|
+
let policy_snapshot = if matches!(read_policy, Some(PolicySubject::Member)) {
|
|
1509
|
+
member_policy_snapshot_rows(_db, schema, &context.state_namespace)?
|
|
1350
1510
|
} else {
|
|
1351
|
-
|
|
1511
|
+
Vec::new()
|
|
1352
1512
|
};
|
|
1353
1513
|
|
|
1354
1514
|
// Detect unfiltered COUNT(*) for maintained cardinality optimization
|
|
@@ -1433,7 +1593,10 @@ pub fn execute_query(
|
|
|
1433
1593
|
(false, None, None)
|
|
1434
1594
|
};
|
|
1435
1595
|
let used_index = planned_index && context.live_query;
|
|
1596
|
+
// Substrate collections declare no indexes and already live inside the
|
|
1597
|
+
// application's tenant-scoped namespace; their bounded queries are exempt.
|
|
1436
1598
|
if context.live_query
|
|
1599
|
+
&& !substrate
|
|
1437
1600
|
&& query.filter.as_ref().is_some_and(contains_managed_scope)
|
|
1438
1601
|
&& !used_index
|
|
1439
1602
|
{
|
|
@@ -1452,7 +1615,8 @@ pub fn execute_query(
|
|
|
1452
1615
|
let execution_before = crate::query_execution_diagnostics::counters();
|
|
1453
1616
|
let index_lookup_start = Instant::now();
|
|
1454
1617
|
let selected_rows = if !context.live_query {
|
|
1455
|
-
|
|
1618
|
+
context
|
|
1619
|
+
.snapshot_rows
|
|
1456
1620
|
.iter()
|
|
1457
1621
|
.filter(|row| {
|
|
1458
1622
|
row.capability == capability
|
|
@@ -1523,22 +1687,26 @@ pub fn execute_query(
|
|
|
1523
1687
|
.map(|id| Actor::new(id))
|
|
1524
1688
|
};
|
|
1525
1689
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1690
|
+
let auth_state = matches!(policy, &PolicySubject::Member).then(|| {
|
|
1691
|
+
AuthorizationState::new(
|
|
1692
|
+
Arc::new(schema.clone()),
|
|
1693
|
+
context.state_namespace.clone(),
|
|
1694
|
+
Arc::new(policy_snapshot.clone()),
|
|
1695
|
+
)
|
|
1696
|
+
});
|
|
1532
1697
|
|
|
1533
1698
|
values.retain(|record_value| {
|
|
1534
1699
|
if let Some(record_id) = record_value.get("_id").and_then(Value::as_str) {
|
|
1535
|
-
let record_context = RecordAuthorizationContext {
|
|
1700
|
+
let mut record_context = RecordAuthorizationContext {
|
|
1536
1701
|
actor: actor.clone(),
|
|
1537
1702
|
collection: query.collection.clone(),
|
|
1538
1703
|
record_id: record_id.to_string(),
|
|
1539
1704
|
record_value: record_value.clone(),
|
|
1540
|
-
authorization_state:
|
|
1705
|
+
authorization_state: None,
|
|
1541
1706
|
};
|
|
1707
|
+
if let Some(state) = &auth_state {
|
|
1708
|
+
record_context.authorization_state = Some(state.clone());
|
|
1709
|
+
}
|
|
1542
1710
|
|
|
1543
1711
|
PolicyEvaluator::evaluate_record(policy.clone(), &record_context).is_ok()
|
|
1544
1712
|
} else {
|
|
@@ -1547,19 +1715,18 @@ pub fn execute_query(
|
|
|
1547
1715
|
});
|
|
1548
1716
|
}
|
|
1549
1717
|
|
|
1718
|
+
let reference_rows =
|
|
1719
|
+
load_reference_rows(_db, &context.state_namespace, &values, &query.references)?;
|
|
1550
1720
|
for value in &mut values {
|
|
1551
1721
|
for reference in &query.references {
|
|
1552
1722
|
let id = value.get(&reference.field).and_then(Value::as_str);
|
|
1553
1723
|
let related = id
|
|
1554
1724
|
.and_then(|id| {
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
&& row.key == id
|
|
1559
|
-
&& !row.deleted
|
|
1560
|
-
})
|
|
1725
|
+
reference_rows
|
|
1726
|
+
.get(&(reference.collection.clone(), id.to_string()))
|
|
1727
|
+
.filter(|value| !value.is_null())
|
|
1561
1728
|
})
|
|
1562
|
-
.map(|
|
|
1729
|
+
.map(|value| projected(value, &reference.projection))
|
|
1563
1730
|
.unwrap_or(Value::Null);
|
|
1564
1731
|
if let Some(object) = value.as_object_mut() {
|
|
1565
1732
|
object.insert(reference.as_name.clone(), related);
|
|
@@ -1826,6 +1993,98 @@ pub struct TransactionRequest {
|
|
|
1826
1993
|
pub operations: Vec<TransactionOperation>,
|
|
1827
1994
|
#[serde(default)]
|
|
1828
1995
|
pub preconditions: Vec<TransactionPrecondition>,
|
|
1996
|
+
/// Read bases this authority issued for this state namespace
|
|
1997
|
+
/// (`capture_read_basis`), re-evaluated inside the commit lock.
|
|
1998
|
+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
1999
|
+
pub fences: Vec<crate::ReadBasis>,
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
/// Collections the composition substrate keeps inside an application's state
|
|
2003
|
+
/// namespace. They are not part of the application contract: their records
|
|
2004
|
+
/// are validated by the substrate, and reading or writing them requires the
|
|
2005
|
+
/// dedicated `composition:read` / `composition:write` capabilities rather
|
|
2006
|
+
/// than `state:*`. Every other `_feltdb` name remains unwritable here.
|
|
2007
|
+
pub const SUBSTRATE_COLLECTIONS: &[&str] = &[
|
|
2008
|
+
"_feltdb.CompositionContract",
|
|
2009
|
+
"_feltdb.Composition",
|
|
2010
|
+
"_feltdb.CompositionVersion",
|
|
2011
|
+
"_feltdb.CompositionTopology",
|
|
2012
|
+
"_feltdb.StateOwnership",
|
|
2013
|
+
"_feltdb.StateDependency",
|
|
2014
|
+
"_feltdb.StateChange",
|
|
2015
|
+
"_feltdb.StateAttribution",
|
|
2016
|
+
"_feltdb.StateObservation",
|
|
2017
|
+
"_feltdb.StateProposal",
|
|
2018
|
+
"_feltdb.StateImpact",
|
|
2019
|
+
"_feltdb.Intent",
|
|
2020
|
+
"_feltdb.IntentEvent",
|
|
2021
|
+
"_feltdb.ProposalEvent",
|
|
2022
|
+
];
|
|
2023
|
+
|
|
2024
|
+
pub fn is_substrate_collection(name: &str) -> bool {
|
|
2025
|
+
SUBSTRATE_COLLECTIONS.contains(&name)
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
/// Capture a read basis over an application state namespace.
|
|
2029
|
+
///
|
|
2030
|
+
/// Every collection the read set names must be one the caller may read in
|
|
2031
|
+
/// full: an application collection readable under `state:read` whose read
|
|
2032
|
+
/// policy does not depend on the record, or a substrate collection under
|
|
2033
|
+
/// `composition:read`. A token is a digest over every row it covers, so a
|
|
2034
|
+
/// basis over rows the caller could not read would disclose them; such a
|
|
2035
|
+
/// read set is refused, never digested.
|
|
2036
|
+
pub fn capture_read_basis(
|
|
2037
|
+
db: &FeltDb,
|
|
2038
|
+
schema: &StateSchema,
|
|
2039
|
+
state_namespace: &str,
|
|
2040
|
+
reads: &crate::ReadSet,
|
|
2041
|
+
readable: &dyn Fn(&str) -> bool,
|
|
2042
|
+
) -> Result<crate::ReadBasis, StateFailure> {
|
|
2043
|
+
let reads = reads
|
|
2044
|
+
.normalised()
|
|
2045
|
+
.map_err(|error| StateFailure::new("VALIDATION_FAILED", &error.to_string()))?;
|
|
2046
|
+
authorize_read_set(schema, &reads, readable)?;
|
|
2047
|
+
db.read_basis_in(
|
|
2048
|
+
&reads,
|
|
2049
|
+
&crate::ReadLayout::Namespace(state_namespace.to_string()),
|
|
2050
|
+
)
|
|
2051
|
+
.map_err(StateFailure::storage)
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
/// The refusal for a read basis over a collection the caller cannot read in full.
|
|
2055
|
+
pub fn read_basis_denied(collection: &str) -> StateFailure {
|
|
2056
|
+
let mut failure = StateFailure::new(
|
|
2057
|
+
"AUTHORIZATION_DENIED",
|
|
2058
|
+
"a read basis may only cover collections the caller can read in full",
|
|
2059
|
+
);
|
|
2060
|
+
failure.resource = Some(collection.to_string());
|
|
2061
|
+
failure
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
fn authorize_read_set(
|
|
2065
|
+
schema: &StateSchema,
|
|
2066
|
+
reads: &crate::ReadSet,
|
|
2067
|
+
readable: &dyn Fn(&str) -> bool,
|
|
2068
|
+
) -> Result<(), StateFailure> {
|
|
2069
|
+
let collections = reads
|
|
2070
|
+
.records
|
|
2071
|
+
.iter()
|
|
2072
|
+
.map(|record| record.collection.as_str())
|
|
2073
|
+
.chain(
|
|
2074
|
+
reads
|
|
2075
|
+
.predicates
|
|
2076
|
+
.iter()
|
|
2077
|
+
.map(|predicate| predicate.collection.as_str()),
|
|
2078
|
+
);
|
|
2079
|
+
for name in collections {
|
|
2080
|
+
if !is_substrate_collection(name) {
|
|
2081
|
+
collection(schema, name)?;
|
|
2082
|
+
}
|
|
2083
|
+
if !readable(name) {
|
|
2084
|
+
return Err(read_basis_denied(name));
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
Ok(())
|
|
1829
2088
|
}
|
|
1830
2089
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
1831
2090
|
pub struct TransactionAudit {
|
|
@@ -2103,10 +2362,12 @@ fn validate_record(collection: &CollectionSchema, value: &mut Value) -> Result<(
|
|
|
2103
2362
|
}
|
|
2104
2363
|
}
|
|
2105
2364
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
{
|
|
2365
|
+
// `__version` and `id` are record identity, carried by every client
|
|
2366
|
+
// record whether or not the contract declares them; a declared `id` is
|
|
2367
|
+
// validated by its declaration above.
|
|
2368
|
+
if object.keys().any(|name| {
|
|
2369
|
+
name != "__version" && name != "id" && !collection.fields.iter().any(|f| &f.name == name)
|
|
2370
|
+
}) {
|
|
2110
2371
|
return Err(StateFailure::new(
|
|
2111
2372
|
"VALIDATION_FAILED",
|
|
2112
2373
|
"record contains an unknown field",
|
|
@@ -2162,17 +2423,34 @@ fn execute_transaction_with_policies(
|
|
|
2162
2423
|
"state:write is required",
|
|
2163
2424
|
));
|
|
2164
2425
|
}
|
|
2165
|
-
let canonical =
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2426
|
+
let canonical = if request.fences.is_empty() {
|
|
2427
|
+
serde_json::to_vec(&(
|
|
2428
|
+
request.tenant_id.as_str(),
|
|
2429
|
+
request.application_id.as_str(),
|
|
2430
|
+
request.revision_id.as_str(),
|
|
2431
|
+
request.schema_version,
|
|
2432
|
+
request.state_namespace.as_deref(),
|
|
2433
|
+
request.causal_parent,
|
|
2434
|
+
&request.authorization.subject,
|
|
2435
|
+
&request.preconditions,
|
|
2436
|
+
&request.operations,
|
|
2437
|
+
))
|
|
2438
|
+
} else {
|
|
2439
|
+
// Fences are part of what the transaction means, so they bind its
|
|
2440
|
+
// identity. Unfenced transactions hash exactly as they always have.
|
|
2441
|
+
serde_json::to_vec(&(
|
|
2442
|
+
request.tenant_id.as_str(),
|
|
2443
|
+
request.application_id.as_str(),
|
|
2444
|
+
request.revision_id.as_str(),
|
|
2445
|
+
request.schema_version,
|
|
2446
|
+
request.state_namespace.as_deref(),
|
|
2447
|
+
request.causal_parent,
|
|
2448
|
+
&request.authorization.subject,
|
|
2449
|
+
&request.preconditions,
|
|
2450
|
+
&request.operations,
|
|
2451
|
+
&request.fences,
|
|
2452
|
+
))
|
|
2453
|
+
}
|
|
2176
2454
|
.map_err(StateFailure::storage)?;
|
|
2177
2455
|
let transaction_id = request
|
|
2178
2456
|
.transaction_id
|
|
@@ -2252,9 +2530,13 @@ fn execute_transaction_with_policies(
|
|
|
2252
2530
|
.as_deref()
|
|
2253
2531
|
.unwrap_or(&request.application_id)
|
|
2254
2532
|
.to_string();
|
|
2255
|
-
let snapshot =
|
|
2256
|
-
|
|
2257
|
-
|
|
2533
|
+
let snapshot = transaction_snapshot_rows(
|
|
2534
|
+
db,
|
|
2535
|
+
schema,
|
|
2536
|
+
request,
|
|
2537
|
+
uniform_write_policy,
|
|
2538
|
+
collection_write_policies,
|
|
2539
|
+
)?;
|
|
2258
2540
|
// Record preconditions are collected here and evaluated by the store,
|
|
2259
2541
|
// inside the same lock as the writes. Evaluating them against `snapshot`
|
|
2260
2542
|
// alongside the counters below would reintroduce exactly the gap they
|
|
@@ -2371,7 +2653,22 @@ fn execute_transaction_with_policies(
|
|
|
2371
2653
|
"collection is not writable",
|
|
2372
2654
|
));
|
|
2373
2655
|
}
|
|
2374
|
-
|
|
2656
|
+
// Substrate collections are validated by the substrate that owns them,
|
|
2657
|
+
// and written only under the dedicated capability.
|
|
2658
|
+
let substrate = is_substrate_collection(&operation.collection);
|
|
2659
|
+
if substrate && !request.authorization.can("composition:write") {
|
|
2660
|
+
let mut failure = StateFailure::new(
|
|
2661
|
+
"AUTHORIZATION_DENIED",
|
|
2662
|
+
"composition:write is required for substrate collections",
|
|
2663
|
+
);
|
|
2664
|
+
failure.resource = Some(operation.collection.clone());
|
|
2665
|
+
return Err(failure);
|
|
2666
|
+
}
|
|
2667
|
+
let definition = if substrate {
|
|
2668
|
+
None
|
|
2669
|
+
} else {
|
|
2670
|
+
Some(collection(schema, &operation.collection)?)
|
|
2671
|
+
};
|
|
2375
2672
|
let capability = format!(
|
|
2376
2673
|
"{}:{}",
|
|
2377
2674
|
request
|
|
@@ -2392,7 +2689,8 @@ fn execute_transaction_with_policies(
|
|
|
2392
2689
|
// Per-operation record authorization
|
|
2393
2690
|
let write_policy = collection_write_policies
|
|
2394
2691
|
.and_then(|policies| policies.get(&operation.collection))
|
|
2395
|
-
.or(uniform_write_policy)
|
|
2692
|
+
.or(uniform_write_policy)
|
|
2693
|
+
.filter(|_| !substrate);
|
|
2396
2694
|
if let Some(policy) = write_policy {
|
|
2397
2695
|
let actor = if request.authorization.subject.is_empty()
|
|
2398
2696
|
|| request.authorization.subject == ":"
|
|
@@ -2458,7 +2756,16 @@ fn execute_transaction_with_policies(
|
|
|
2458
2756
|
return Err(StateFailure::new("CONFLICT", "record already exists"));
|
|
2459
2757
|
}
|
|
2460
2758
|
let mut value = operation.value.clone();
|
|
2461
|
-
|
|
2759
|
+
match definition {
|
|
2760
|
+
Some(definition) => validate_record(definition, &mut value)?,
|
|
2761
|
+
None if value.is_object() => {}
|
|
2762
|
+
None => {
|
|
2763
|
+
return Err(StateFailure::new(
|
|
2764
|
+
"VALIDATION_FAILED",
|
|
2765
|
+
"record must be an object",
|
|
2766
|
+
));
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2462
2769
|
Some(value)
|
|
2463
2770
|
}
|
|
2464
2771
|
TransactionOperationKind::Update => {
|
|
@@ -2474,7 +2781,9 @@ fn execute_transaction_with_policies(
|
|
|
2474
2781
|
.expect("existing object")
|
|
2475
2782
|
.insert(k.clone(), v.clone());
|
|
2476
2783
|
}
|
|
2477
|
-
|
|
2784
|
+
if let Some(definition) = definition {
|
|
2785
|
+
validate_record(definition, &mut value)?;
|
|
2786
|
+
}
|
|
2478
2787
|
Some(value)
|
|
2479
2788
|
}
|
|
2480
2789
|
TransactionOperationKind::Delete => {
|
|
@@ -2509,6 +2818,7 @@ fn execute_transaction_with_policies(
|
|
|
2509
2818
|
.unwrap_or(&request.application_id),
|
|
2510
2819
|
definition.name
|
|
2511
2820
|
);
|
|
2821
|
+
let mut referenced_records = BTreeMap::new();
|
|
2512
2822
|
for ((cap, key), value) in overlay
|
|
2513
2823
|
.iter()
|
|
2514
2824
|
.filter(|((cap, _), value)| cap == &capability && value.is_some())
|
|
@@ -2525,16 +2835,29 @@ fn execute_transaction_with_policies(
|
|
|
2525
2835
|
.unwrap_or(&request.application_id),
|
|
2526
2836
|
target
|
|
2527
2837
|
);
|
|
2528
|
-
let present =
|
|
2529
|
-
.get(&(target_capability.clone(), reference.into()))
|
|
2530
|
-
|
|
2531
|
-
.
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2838
|
+
let present = if let Some(value) =
|
|
2839
|
+
overlay.get(&(target_capability.clone(), reference.into()))
|
|
2840
|
+
{
|
|
2841
|
+
value.is_some()
|
|
2842
|
+
} else if snapshot.iter().any(|row| {
|
|
2843
|
+
row.capability == target_capability
|
|
2844
|
+
&& row.key == reference
|
|
2845
|
+
&& !row.deleted
|
|
2846
|
+
}) {
|
|
2847
|
+
true
|
|
2848
|
+
} else {
|
|
2849
|
+
let cache_key = (target_capability.clone(), reference.to_string());
|
|
2850
|
+
if let Some(present) = referenced_records.get(&cache_key) {
|
|
2851
|
+
*present
|
|
2852
|
+
} else {
|
|
2853
|
+
let present = db
|
|
2854
|
+
.get_collection_record(&target_capability, reference)
|
|
2855
|
+
.map_err(StateFailure::storage)?
|
|
2856
|
+
.is_some();
|
|
2857
|
+
referenced_records.insert(cache_key, present);
|
|
2858
|
+
present
|
|
2859
|
+
}
|
|
2860
|
+
};
|
|
2538
2861
|
if !present {
|
|
2539
2862
|
let mut failure = StateFailure::new(
|
|
2540
2863
|
"VALIDATION_FAILED",
|
|
@@ -2583,12 +2906,14 @@ fn execute_transaction_with_policies(
|
|
|
2583
2906
|
}
|
|
2584
2907
|
}
|
|
2585
2908
|
let commit = db
|
|
2586
|
-
.
|
|
2909
|
+
.apply_atomic_transaction_fenced_in(
|
|
2587
2910
|
&storage_transaction_id,
|
|
2588
2911
|
Some(&payload_hash),
|
|
2589
2912
|
request.causal_parent,
|
|
2590
2913
|
&preconditions,
|
|
2591
2914
|
&record_preconditions,
|
|
2915
|
+
&crate::ReadLayout::Namespace(namespace.clone()),
|
|
2916
|
+
&request.fences,
|
|
2592
2917
|
&mutations,
|
|
2593
2918
|
Some(serde_json::json!({"transaction_id":transaction_id.clone(),"subject":request.authorization.subject.clone(),"tenant":request.tenant_id.clone(),"application":request.application_id.clone(),"revision":request.revision_id.clone(),"schema_version":request.schema_version,"operations":request.operations.len(),"authorization_decisions":["state:write allowed"],"causal_parent":request.causal_parent,"result":"committed"})),
|
|
2594
2919
|
)
|
|
@@ -2625,6 +2950,12 @@ fn execute_transaction_with_policies(
|
|
|
2625
2950
|
return failure;
|
|
2626
2951
|
}
|
|
2627
2952
|
let text = error.to_string();
|
|
2953
|
+
if text.contains("INVALID_READ_SET:") {
|
|
2954
|
+
// A malformed or foreign basis is a caller error, never a lost race.
|
|
2955
|
+
let mut failure = StateFailure::new("VALIDATION_FAILED", &text);
|
|
2956
|
+
failure.transaction = Some(transaction_id.clone());
|
|
2957
|
+
return failure;
|
|
2958
|
+
}
|
|
2628
2959
|
if text.contains("PRECONDITION_FAILED") {
|
|
2629
2960
|
let mut failure = StateFailure::new("PRECONDITION_FAILED", &text);
|
|
2630
2961
|
failure.transaction = Some(transaction_id.clone());
|
|
@@ -2923,6 +3254,7 @@ mod tests {
|
|
|
2923
3254
|
if_version: None,
|
|
2924
3255
|
},
|
|
2925
3256
|
],
|
|
3257
|
+
fences: vec![],
|
|
2926
3258
|
};
|
|
2927
3259
|
let result = execute_transaction(&db, &schema, &request, None).unwrap();
|
|
2928
3260
|
assert_eq!(result.state_after, 2);
|
|
@@ -2974,6 +3306,7 @@ mod tests {
|
|
|
2974
3306
|
value: serde_json::json!({ "title": title }),
|
|
2975
3307
|
if_version: None,
|
|
2976
3308
|
}],
|
|
3309
|
+
fences: vec![],
|
|
2977
3310
|
},
|
|
2978
3311
|
None,
|
|
2979
3312
|
)
|
|
@@ -3122,6 +3455,7 @@ mod tests {
|
|
|
3122
3455
|
value: serde_json::json!({ "title": "First" }),
|
|
3123
3456
|
if_version: None,
|
|
3124
3457
|
}],
|
|
3458
|
+
fences: vec![],
|
|
3125
3459
|
},
|
|
3126
3460
|
None,
|
|
3127
3461
|
)
|
|
@@ -3160,6 +3494,7 @@ mod tests {
|
|
|
3160
3494
|
if_version: None,
|
|
3161
3495
|
},
|
|
3162
3496
|
],
|
|
3497
|
+
fences: vec![],
|
|
3163
3498
|
};
|
|
3164
3499
|
assert_eq!(
|
|
3165
3500
|
execute_transaction(&db, &schema, &request, None)
|
|
@@ -3194,6 +3529,7 @@ mod tests {
|
|
|
3194
3529
|
value: serde_json::json!({"title":title,"status":status}),
|
|
3195
3530
|
if_version: None,
|
|
3196
3531
|
}],
|
|
3532
|
+
fences: vec![],
|
|
3197
3533
|
},
|
|
3198
3534
|
None,
|
|
3199
3535
|
)
|
|
@@ -3285,6 +3621,7 @@ mod tests {
|
|
|
3285
3621
|
value: serde_json::json!({"title":"First"}),
|
|
3286
3622
|
if_version: None,
|
|
3287
3623
|
}],
|
|
3624
|
+
fences: vec![],
|
|
3288
3625
|
};
|
|
3289
3626
|
let first = execute_transaction(&db, &schema, &request, None).unwrap();
|
|
3290
3627
|
let duplicate = execute_transaction(&db, &schema, &request, None).unwrap();
|
|
@@ -3324,6 +3661,7 @@ mod tests {
|
|
|
3324
3661
|
value: serde_json::json!({"title":"First"}),
|
|
3325
3662
|
if_version: None,
|
|
3326
3663
|
}],
|
|
3664
|
+
fences: vec![],
|
|
3327
3665
|
};
|
|
3328
3666
|
execute_transaction(&db, &schema, &request, None).unwrap();
|
|
3329
3667
|
request.transaction_id = Some("stale-parent".into());
|
|
@@ -3361,6 +3699,7 @@ mod tests {
|
|
|
3361
3699
|
value: serde_json::json!({"title":"Original"}),
|
|
3362
3700
|
if_version: None,
|
|
3363
3701
|
}],
|
|
3702
|
+
fences: vec![],
|
|
3364
3703
|
};
|
|
3365
3704
|
{
|
|
3366
3705
|
let db = crate::open(&path).unwrap();
|
|
@@ -3417,6 +3756,7 @@ mod tests {
|
|
|
3417
3756
|
value: serde_json::json!({"title":format!("{app}-{record}")}),
|
|
3418
3757
|
if_version: None,
|
|
3419
3758
|
}],
|
|
3759
|
+
fences: vec![],
|
|
3420
3760
|
};
|
|
3421
3761
|
(schema, request)
|
|
3422
3762
|
}
|
|
@@ -3501,6 +3841,7 @@ mod tests {
|
|
|
3501
3841
|
value: serde_json::json!({"title":id}),
|
|
3502
3842
|
if_version: None,
|
|
3503
3843
|
}],
|
|
3844
|
+
fences: vec![],
|
|
3504
3845
|
};
|
|
3505
3846
|
execute_transaction(&db, &schema, &insert("one"), None).unwrap();
|
|
3506
3847
|
let context = begin_read(&db, &schema, "app", auth()).unwrap();
|
|
@@ -3561,6 +3902,7 @@ mod tests {
|
|
|
3561
3902
|
if_version: None,
|
|
3562
3903
|
},
|
|
3563
3904
|
],
|
|
3905
|
+
fences: vec![],
|
|
3564
3906
|
};
|
|
3565
3907
|
execute_transaction(&db, &schema, &request, None).unwrap();
|
|
3566
3908
|
}
|
|
@@ -3594,6 +3936,7 @@ mod tests {
|
|
|
3594
3936
|
if_version: None,
|
|
3595
3937
|
})
|
|
3596
3938
|
.collect(),
|
|
3939
|
+
fences: vec![],
|
|
3597
3940
|
};
|
|
3598
3941
|
execute_transaction(&db, &schema, &request, None).unwrap();
|
|
3599
3942
|
let context = begin_query_read(&db, &schema, "app", auth()).unwrap();
|
|
@@ -3657,6 +4000,7 @@ mod tests {
|
|
|
3657
4000
|
if_version: None,
|
|
3658
4001
|
})
|
|
3659
4002
|
.collect(),
|
|
4003
|
+
fences: vec![],
|
|
3660
4004
|
};
|
|
3661
4005
|
execute_transaction(&db, &schema, &request, None).unwrap();
|
|
3662
4006
|
let context = begin_read(&db, &schema, "app", auth()).unwrap();
|
|
@@ -3719,6 +4063,7 @@ mod tests {
|
|
|
3719
4063
|
value: serde_json::json!({"title": "Test", "status": "open"}),
|
|
3720
4064
|
if_version: None,
|
|
3721
4065
|
}],
|
|
4066
|
+
fences: vec![],
|
|
3722
4067
|
};
|
|
3723
4068
|
execute_transaction(&db, &schema, &request, None).unwrap();
|
|
3724
4069
|
let context = begin_query_read(&db, &schema, "app", auth()).unwrap();
|
|
@@ -3779,6 +4124,7 @@ mod tests {
|
|
|
3779
4124
|
if_version: None,
|
|
3780
4125
|
})
|
|
3781
4126
|
.collect(),
|
|
4127
|
+
fences: vec![],
|
|
3782
4128
|
};
|
|
3783
4129
|
execute_transaction(&db, &schema, &request, None).unwrap();
|
|
3784
4130
|
let context = begin_query_read(&db, &schema, "app", auth()).unwrap();
|
|
@@ -3841,6 +4187,7 @@ mod tests {
|
|
|
3841
4187
|
authorization: auth(),
|
|
3842
4188
|
preconditions: vec![],
|
|
3843
4189
|
operations,
|
|
4190
|
+
fences: vec![],
|
|
3844
4191
|
},
|
|
3845
4192
|
None,
|
|
3846
4193
|
)
|
|
@@ -3867,10 +4214,11 @@ mod tests {
|
|
|
3867
4214
|
let first_cursor = loop {
|
|
3868
4215
|
let page = execute_query(&db, &schema, &context, &query, None).unwrap();
|
|
3869
4216
|
assert!(page.records.len() <= 25);
|
|
3870
|
-
assert!(
|
|
3871
|
-
.records
|
|
3872
|
-
|
|
3873
|
-
|
|
4217
|
+
assert!(
|
|
4218
|
+
page.records
|
|
4219
|
+
.iter()
|
|
4220
|
+
.all(|record| record["status"] == "tenant-a")
|
|
4221
|
+
);
|
|
3874
4222
|
ids.extend(
|
|
3875
4223
|
page.records
|
|
3876
4224
|
.iter()
|
|
@@ -3946,6 +4294,7 @@ mod tests {
|
|
|
3946
4294
|
value: serde_json::json!({ "title": "First", "status": "open" }),
|
|
3947
4295
|
if_version: None,
|
|
3948
4296
|
}],
|
|
4297
|
+
fences: vec![],
|
|
3949
4298
|
},
|
|
3950
4299
|
None,
|
|
3951
4300
|
)
|
|
@@ -4126,6 +4475,7 @@ mod tests {
|
|
|
4126
4475
|
authorization: auth(),
|
|
4127
4476
|
preconditions: vec![],
|
|
4128
4477
|
operations,
|
|
4478
|
+
fences: vec![],
|
|
4129
4479
|
},
|
|
4130
4480
|
None,
|
|
4131
4481
|
)
|
|
@@ -4188,6 +4538,7 @@ mod tests {
|
|
|
4188
4538
|
value: serde_json::json!({"tenant_id":"tenant-a"}),
|
|
4189
4539
|
if_version: None,
|
|
4190
4540
|
}],
|
|
4541
|
+
fences: vec![],
|
|
4191
4542
|
},
|
|
4192
4543
|
None,
|
|
4193
4544
|
)
|
|
@@ -4322,6 +4673,7 @@ mod tests {
|
|
|
4322
4673
|
authorization: auth_ctx.clone(),
|
|
4323
4674
|
preconditions: vec![],
|
|
4324
4675
|
operations,
|
|
4676
|
+
fences: vec![],
|
|
4325
4677
|
};
|
|
4326
4678
|
execute_transaction(&db, &schema, &request, None).unwrap();
|
|
4327
4679
|
}
|
|
@@ -4505,6 +4857,7 @@ mod tests {
|
|
|
4505
4857
|
if_version: None,
|
|
4506
4858
|
}],
|
|
4507
4859
|
preconditions: vec![],
|
|
4860
|
+
fences: vec![],
|
|
4508
4861
|
};
|
|
4509
4862
|
|
|
4510
4863
|
let result = execute_transaction(&db, &schema, &request, None);
|
|
@@ -4543,6 +4896,7 @@ mod tests {
|
|
|
4543
4896
|
if_version: None,
|
|
4544
4897
|
}],
|
|
4545
4898
|
preconditions: vec![],
|
|
4899
|
+
fences: vec![],
|
|
4546
4900
|
};
|
|
4547
4901
|
execute_transaction(&db, &schema, &insert_request, None).unwrap();
|
|
4548
4902
|
|
|
@@ -4612,6 +4966,7 @@ mod tests {
|
|
|
4612
4966
|
if_version: None,
|
|
4613
4967
|
}],
|
|
4614
4968
|
preconditions: vec![],
|
|
4969
|
+
fences: vec![],
|
|
4615
4970
|
};
|
|
4616
4971
|
|
|
4617
4972
|
let result = execute_transaction(&db, &schema, &request, None);
|
|
@@ -4643,6 +4998,7 @@ mod tests {
|
|
|
4643
4998
|
if_version: None,
|
|
4644
4999
|
}],
|
|
4645
5000
|
preconditions: vec![],
|
|
5001
|
+
fences: vec![],
|
|
4646
5002
|
};
|
|
4647
5003
|
execute_transaction(&db, &schema, &insert_request, None).unwrap();
|
|
4648
5004
|
|
|
@@ -4716,6 +5072,7 @@ mod tests {
|
|
|
4716
5072
|
if_version: None,
|
|
4717
5073
|
}],
|
|
4718
5074
|
preconditions: vec![],
|
|
5075
|
+
fences: vec![],
|
|
4719
5076
|
};
|
|
4720
5077
|
|
|
4721
5078
|
let result = execute_transaction(&db, &schema, &request, None);
|
|
@@ -4750,6 +5107,7 @@ mod tests {
|
|
|
4750
5107
|
if_version: None,
|
|
4751
5108
|
}],
|
|
4752
5109
|
preconditions: vec![],
|
|
5110
|
+
fences: vec![],
|
|
4753
5111
|
};
|
|
4754
5112
|
let initial_result = execute_transaction(&db, &schema, &insert_initial, None).unwrap();
|
|
4755
5113
|
let state_before_denied_tx = initial_result.state_after;
|
|
@@ -4783,6 +5141,7 @@ mod tests {
|
|
|
4783
5141
|
if_version: None,
|
|
4784
5142
|
}],
|
|
4785
5143
|
preconditions: vec![],
|
|
5144
|
+
fences: vec![],
|
|
4786
5145
|
};
|
|
4787
5146
|
|
|
4788
5147
|
let denied_result = execute_transaction(&db, &schema, &denied_request, None);
|
|
@@ -4848,6 +5207,7 @@ mod tests {
|
|
|
4848
5207
|
if_version: None,
|
|
4849
5208
|
}],
|
|
4850
5209
|
preconditions: vec![],
|
|
5210
|
+
fences: vec![],
|
|
4851
5211
|
};
|
|
4852
5212
|
|
|
4853
5213
|
let result = execute_transaction(&db, &schema, &write_request, None);
|
|
@@ -4893,6 +5253,7 @@ mod tests {
|
|
|
4893
5253
|
if_version: None,
|
|
4894
5254
|
}],
|
|
4895
5255
|
preconditions: vec![],
|
|
5256
|
+
fences: vec![],
|
|
4896
5257
|
};
|
|
4897
5258
|
|
|
4898
5259
|
let _ = execute_transaction(&db, &schema, &insert_request, None);
|
|
@@ -5005,6 +5366,7 @@ mod tests {
|
|
|
5005
5366
|
if_version: None,
|
|
5006
5367
|
}],
|
|
5007
5368
|
preconditions: vec![],
|
|
5369
|
+
fences: vec![],
|
|
5008
5370
|
};
|
|
5009
5371
|
|
|
5010
5372
|
let result = execute_transaction(&db, &schema, &write_request, None);
|
|
@@ -5048,6 +5410,7 @@ mod tests {
|
|
|
5048
5410
|
if_version: None,
|
|
5049
5411
|
}],
|
|
5050
5412
|
preconditions: vec![],
|
|
5413
|
+
fences: vec![],
|
|
5051
5414
|
};
|
|
5052
5415
|
|
|
5053
5416
|
let result = execute_transaction(&db, &schema, &write_request, None);
|
|
@@ -5095,6 +5458,7 @@ mod tests {
|
|
|
5095
5458
|
if_version: None,
|
|
5096
5459
|
}],
|
|
5097
5460
|
preconditions: vec![],
|
|
5461
|
+
fences: vec![],
|
|
5098
5462
|
};
|
|
5099
5463
|
|
|
5100
5464
|
let _ = execute_transaction(&db, &schema, &insert_in_org2, None);
|
|
@@ -5130,6 +5494,7 @@ mod tests {
|
|
|
5130
5494
|
if_version: Some(0),
|
|
5131
5495
|
}],
|
|
5132
5496
|
preconditions: vec![],
|
|
5497
|
+
fences: vec![],
|
|
5133
5498
|
};
|
|
5134
5499
|
|
|
5135
5500
|
let result = execute_transaction(&db, &schema, &cross_org_mutation, None);
|
|
@@ -5211,6 +5576,7 @@ mod tests {
|
|
|
5211
5576
|
},
|
|
5212
5577
|
],
|
|
5213
5578
|
preconditions: vec![],
|
|
5579
|
+
fences: vec![],
|
|
5214
5580
|
};
|
|
5215
5581
|
|
|
5216
5582
|
execute_transaction(&db, &schema, &insert_records, None).expect("insert should succeed");
|
|
@@ -5304,6 +5670,7 @@ mod tests {
|
|
|
5304
5670
|
if_version: None,
|
|
5305
5671
|
}],
|
|
5306
5672
|
preconditions: vec![],
|
|
5673
|
+
fences: vec![],
|
|
5307
5674
|
};
|
|
5308
5675
|
|
|
5309
5676
|
execute_transaction(&db, &schema, &insert_request, None).expect("insert should succeed");
|
|
@@ -5395,6 +5762,7 @@ mod tests {
|
|
|
5395
5762
|
},
|
|
5396
5763
|
],
|
|
5397
5764
|
preconditions: vec![],
|
|
5765
|
+
fences: vec![],
|
|
5398
5766
|
};
|
|
5399
5767
|
|
|
5400
5768
|
execute_transaction(&db, &schema, &insert_records, None).expect("insert should succeed");
|
|
@@ -5482,6 +5850,7 @@ mod tests {
|
|
|
5482
5850
|
authorization: admin_context,
|
|
5483
5851
|
operations: ops,
|
|
5484
5852
|
preconditions: vec![],
|
|
5853
|
+
fences: vec![],
|
|
5485
5854
|
};
|
|
5486
5855
|
|
|
5487
5856
|
execute_transaction(&db, &schema, &insert_request, None).expect("insert should succeed");
|