diff --git a/Cargo.lock b/Cargo.lock index 0fb070fb49d..9294af3a43f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2130,7 +2130,6 @@ dependencies = [ [[package]] name = "grovedb" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=f89e03e4e0ac12aa2feea5c94b38c09f4909facc#f89e03e4e0ac12aa2feea5c94b38c09f4909facc" dependencies = [ "axum", "bincode", @@ -2161,7 +2160,6 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=f89e03e4e0ac12aa2feea5c94b38c09f4909facc#f89e03e4e0ac12aa2feea5c94b38c09f4909facc" dependencies = [ "integer-encoding", "intmap", @@ -2171,7 +2169,6 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=f89e03e4e0ac12aa2feea5c94b38c09f4909facc#f89e03e4e0ac12aa2feea5c94b38c09f4909facc" dependencies = [ "grovedb-costs", "hex", @@ -2183,7 +2180,6 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=f89e03e4e0ac12aa2feea5c94b38c09f4909facc#f89e03e4e0ac12aa2feea5c94b38c09f4909facc" dependencies = [ "bincode", "blake3", @@ -2206,7 +2202,6 @@ dependencies = [ [[package]] name = "grovedb-path" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=f89e03e4e0ac12aa2feea5c94b38c09f4909facc#f89e03e4e0ac12aa2feea5c94b38c09f4909facc" dependencies = [ "hex", ] @@ -2214,7 +2209,6 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=f89e03e4e0ac12aa2feea5c94b38c09f4909facc#f89e03e4e0ac12aa2feea5c94b38c09f4909facc" dependencies = [ "blake3", "grovedb-costs", @@ -2233,7 +2227,6 @@ dependencies = [ [[package]] name = "grovedb-version" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=f89e03e4e0ac12aa2feea5c94b38c09f4909facc#f89e03e4e0ac12aa2feea5c94b38c09f4909facc" dependencies = [ "thiserror 2.0.11", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2242,7 +2235,6 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=f89e03e4e0ac12aa2feea5c94b38c09f4909facc#f89e03e4e0ac12aa2feea5c94b38c09f4909facc" dependencies = [ "hex", "itertools 0.14.0", @@ -2251,7 +2243,6 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=f89e03e4e0ac12aa2feea5c94b38c09f4909facc#f89e03e4e0ac12aa2feea5c94b38c09f4909facc" dependencies = [ "serde", "serde_with 3.9.0", diff --git a/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json b/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json index 80d20a3f775..5376a1c3ceb 100644 --- a/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json +++ b/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json @@ -378,6 +378,10 @@ }, "required": ["resolution"], "additionalProperties": false + }, + "count": { + "type": "boolean", + "description": "Enables count operations on the index. Adds extra costs for documents storage" } }, "required": [ diff --git a/packages/rs-dpp/src/data_contract/document_type/index/mod.rs b/packages/rs-dpp/src/data_contract/document_type/index/mod.rs index 3987591ba93..f2bf8d78814 100644 --- a/packages/rs-dpp/src/data_contract/document_type/index/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/index/mod.rs @@ -294,6 +294,8 @@ pub struct Index { pub null_searchable: bool, /// Contested indexes are useful when a resource is considered valuable pub contested_index: Option, + /// Enables count operations on the index + pub count: bool, } impl Index { @@ -468,6 +470,7 @@ impl TryFrom<&[(Value, Value)]> for Index { let mut name = None; let mut contested_index = None; let mut index_properties: Vec = Vec::new(); + let mut count = false; for (key_value, value_value) in index_type_value_map { let key = key_value.to_str()?; @@ -603,6 +606,13 @@ impl TryFrom<&[(Value, Value)]> for Index { index_properties.push(index_property); } } + "count" => { + // This is a boolean value + // If it is not a boolean value, then it is an error + if value_value.is_bool() { + count = value_value.as_bool().expect("confirmed as bool"); + } + } _ => { return Err(DataContractError::ValueWrongType( "unexpected property name".to_string(), @@ -626,6 +636,7 @@ impl TryFrom<&[(Value, Value)]> for Index { unique, null_searchable, contested_index, + count, }) } } diff --git a/packages/rs-dpp/src/data_contract/document_type/index/random_index.rs b/packages/rs-dpp/src/data_contract/document_type/index/random_index.rs index bad9be1a883..71b5b0bbb5b 100644 --- a/packages/rs-dpp/src/data_contract/document_type/index/random_index.rs +++ b/packages/rs-dpp/src/data_contract/document_type/index/random_index.rs @@ -60,6 +60,7 @@ impl Index { unique, null_searchable: true, contested_index: None, + count: false, }) } } diff --git a/packages/rs-dpp/src/data_contract/document_type/index_level/mod.rs b/packages/rs-dpp/src/data_contract/document_type/index_level/mod.rs index e2c6a132750..135309d5cfb 100644 --- a/packages/rs-dpp/src/data_contract/document_type/index_level/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/index_level/mod.rs @@ -35,6 +35,8 @@ pub struct IndexLevelTypeInfo { pub should_insert_with_all_null: bool, /// The index type pub index_type: IndexType, + /// Is this index countable. Uses sum trees to enable count operations + pub count: bool, } impl IndexType { @@ -214,6 +216,7 @@ impl IndexLevel { current_level.has_index_with_type = Some(IndexLevelTypeInfo { should_insert_with_all_null: index.null_searchable, index_type, + count: index.count, }); } } @@ -282,6 +285,7 @@ mod tests { unique: false, null_searchable: true, contested_index: None, + count: false, }]; let old_index_structure = @@ -309,6 +313,7 @@ mod tests { unique: false, null_searchable: true, contested_index: None, + count: false, }]; let new_indices = vec![ @@ -321,6 +326,7 @@ mod tests { unique: false, null_searchable: true, contested_index: None, + count: false, }, Index { name: "test2".to_string(), @@ -331,6 +337,7 @@ mod tests { unique: false, null_searchable: true, contested_index: None, + count: false, }, ]; @@ -367,6 +374,7 @@ mod tests { unique: false, null_searchable: true, contested_index: None, + count: false, }, Index { name: "test2".to_string(), @@ -377,6 +385,7 @@ mod tests { unique: false, null_searchable: true, contested_index: None, + count: false, }, ]; @@ -389,6 +398,7 @@ mod tests { unique: false, null_searchable: true, contested_index: None, + count: false, }]; let old_index_structure = @@ -423,6 +433,7 @@ mod tests { unique: false, null_searchable: true, contested_index: None, + count: false, }]; let new_indices = vec![Index { @@ -440,6 +451,7 @@ mod tests { unique: false, null_searchable: true, contested_index: None, + count: false, }]; let old_index_structure = @@ -480,6 +492,7 @@ mod tests { unique: false, null_searchable: true, contested_index: None, + count: false, }]; let new_indices = vec![Index { @@ -491,6 +504,7 @@ mod tests { unique: false, null_searchable: true, contested_index: None, + count: false, }]; let old_index_structure = diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index b6908320de0..b3ffcef41a7 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "f89e03e4e0ac12aa2feea5c94b38c09f4909facc", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "f89e03e4e0ac12aa2feea5c94b38c09f4909facc", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "f89e03e4e0ac12aa2feea5c94b38c09f4909facc" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "f89e03e4e0ac12aa2feea5c94b38c09f4909facc", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "f89e03e4e0ac12aa2feea5c94b38c09f4909facc" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "f89e03e4e0ac12aa2feea5c94b38c09f4909facc" } +grovedb = { path = "../../../grovedb/grovedb", optional = true, default-features = false } +grovedb-costs = { path = "../../../grovedb/costs", optional = true } +grovedb-path = { path = "../../../grovedb/path" } +grovedb-storage = { path = "../../../grovedb/storage", optional = true } +grovedb-version = { path = "../../../grovedb/grovedb-version" } +grovedb-epoch-based-storage-flags = { path = "../../../grovedb/grovedb-epoch-based-storage-flags" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/drive/document/insert/add_reference_for_index_level_for_contract_operations/v0/mod.rs b/packages/rs-drive/src/drive/document/insert/add_reference_for_index_level_for_contract_operations/v0/mod.rs index a65c3c9e110..a0aa609f169 100644 --- a/packages/rs-drive/src/drive/document/insert/add_reference_for_index_level_for_contract_operations/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/insert/add_reference_for_index_level_for_contract_operations/v0/mod.rs @@ -48,6 +48,7 @@ impl Drive { if all_fields_null && !index_type.should_insert_with_all_null { return Ok(()); } + // unique indexes will be stored under key "0" // non-unique indices should have a tree at key "0" that has all elements based off of primary key if !index_type.index_type.is_unique() || any_fields_null { @@ -57,12 +58,19 @@ impl Drive { let path_key_info = key_path_info.add_path_info(index_path_info.clone()); + // if index is countable, we should use count trees, so we can get the count of elements + let reference_tree_type = if index_type.count { + TreeType::CountTree + } else { + TreeType::NormalTree + }; + let apply_type = if estimated_costs_only_with_layer_info.is_none() { BatchInsertTreeApplyType::StatefulBatchInsertTree } else { BatchInsertTreeApplyType::StatelessBatchInsertTree { in_tree_type: TreeType::NormalTree, - tree_type: TreeType::NormalTree, + tree_type: reference_tree_type, flags_len: storage_flags .map(|s| s.serialized_size()) .unwrap_or_default(), @@ -75,7 +83,7 @@ impl Drive { // a contested resource index self.batch_insert_empty_tree_if_not_exists( path_key_info, - TreeType::NormalTree, + reference_tree_type, *storage_flags, apply_type, transaction, @@ -94,7 +102,7 @@ impl Drive { estimated_costs_only_with_layer_info.insert( index_path_info.clone().convert_to_key_info_path(), EstimatedLayerInformation { - tree_type: TreeType::NormalTree, + tree_type: reference_tree_type, estimated_layer_count: PotentiallyAtMaxElements, estimated_layer_sizes: AllReference( DEFAULT_HASH_SIZE_U8, diff --git a/packages/rs-drive/tests/query_tests.rs b/packages/rs-drive/tests/query_tests.rs index c0980cbad1e..41e1a457f93 100644 --- a/packages/rs-drive/tests/query_tests.rs +++ b/packages/rs-drive/tests/query_tests.rs @@ -269,6 +269,81 @@ pub fn setup_family_tests( (drive, contract) } +#[cfg(feature = "server")] +/// Inserts the test "family" contract and adds `count` documents containing randomly named people to it. +pub fn setup_countable_family_tests( + count: u32, + seed: u64, + platform_version: &PlatformVersion, +) -> (Drive, DataContract) { + let drive_config = DriveConfig::default(); + + let drive = setup_drive(Some(drive_config), None); + + let db_transaction = drive.grove.start_transaction(); + + // Create contracts tree + let mut batch = GroveDbOpBatch::new(); + + add_init_contracts_structure_operations(&mut batch); + + drive + .grove_apply_batch(batch, false, Some(&db_transaction), &platform_version.drive) + .expect("expected to create contracts tree successfully"); + + // setup code + let contract = test_helpers::setup_contract( + &drive, + "tests/supporting_files/contract/family/family-contract-countable.json", + None, + None, + None::, + Some(&db_transaction), + Some(platform_version), + ); + + let people = Person::random_people(count, seed); + for person in people { + let value = serde_json::to_value(person).expect("serialized person"); + let document_cbor = cbor_serializer::serializable_value_to_cbor(&value, Some(0)) + .expect("expected to serialize to cbor"); + let document = Document::from_cbor(document_cbor.as_slice(), None, None, platform_version) + .expect("document should be properly deserialized"); + + let document_type = contract + .document_type_for_name("person") + .expect("expected to get document type"); + + let storage_flags = Some(Cow::Owned(StorageFlags::SingleEpoch(0))); + + drive + .add_document_for_contract( + DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentRefInfo((&document, storage_flags)), + owner_id: None, + }, + contract: &contract, + document_type, + }, + true, + BlockInfo::genesis(), + true, + Some(&db_transaction), + platform_version, + None, + ) + .expect("document should be inserted"); + } + drive + .grove + .commit_transaction(db_transaction) + .unwrap() + .expect("transaction should be committed"); + + (drive, contract) +} + #[cfg(feature = "server")] /// Same as `setup_family_tests` but with null values in the documents. pub fn setup_family_tests_with_nulls(count: u32, seed: u64) -> (Drive, DataContract) { @@ -1174,8 +1249,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 32, 210, 24, 196, 148, 43, 20, 34, 0, 116, 183, 136, 32, 210, 163, 183, 214, 6, 152, - 86, 46, 45, 88, 13, 23, 41, 37, 70, 129, 119, 211, 12, + 52, 186, 238, 222, 182, 44, 21, 157, 245, 92, 246, 208, 252, 54, 235, 135, 213, 57, + 149, 174, 152, 61, 63, 115, 248, 135, 161, 79, 245, 251, 56, 229, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -2488,8 +2563,8 @@ mod tests { assert_eq!( root_hash.as_slice(), vec![ - 251, 69, 177, 93, 128, 236, 106, 87, 205, 123, 80, 61, 44, 107, 186, 193, 22, 192, - 239, 7, 107, 110, 97, 197, 59, 245, 26, 12, 63, 91, 248, 231 + 110, 0, 64, 166, 240, 60, 119, 196, 122, 11, 154, 52, 255, 157, 95, 48, 182, 170, + 58, 40, 37, 186, 247, 154, 242, 152, 109, 71, 193, 151, 81, 32 ], ); } @@ -2509,8 +2584,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 59, 253, 119, 177, 148, 100, 153, 121, 228, 238, 250, 185, 103, 53, 113, 8, 30, 192, - 75, 150, 153, 2, 24, 109, 93, 91, 97, 75, 106, 35, 29, 252, + 54, 6, 179, 188, 181, 97, 161, 198, 92, 12, 185, 60, 151, 219, 115, 50, 51, 248, 81, + 106, 10, 89, 183, 126, 179, 14, 72, 251, 234, 175, 4, 161, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -3823,8 +3898,8 @@ mod tests { assert_eq!( root_hash.as_slice(), vec![ - 5, 92, 86, 251, 178, 238, 8, 246, 80, 139, 148, 81, 135, 108, 57, 197, 114, 102, - 219, 71, 50, 0, 47, 252, 106, 157, 118, 30, 128, 199, 55, 126, + 210, 86, 49, 150, 167, 157, 51, 144, 168, 56, 23, 102, 241, 71, 99, 154, 100, 242, + 18, 114, 80, 148, 15, 143, 54, 73, 38, 185, 167, 199, 99, 136 ], ); } @@ -3974,8 +4049,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 59, 253, 119, 177, 148, 100, 153, 121, 228, 238, 250, 185, 103, 53, 113, 8, 30, 192, - 75, 150, 153, 2, 24, 109, 93, 91, 97, 75, 106, 35, 29, 252, + 54, 6, 179, 188, 181, 97, 161, 198, 92, 12, 185, 60, 151, 219, 115, 50, 51, 248, 81, + 106, 10, 89, 183, 126, 179, 14, 72, 251, 234, 175, 4, 161, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -4549,8 +4624,8 @@ mod tests { // Make sure the state is deterministic let expected_app_hash = vec![ - 59, 253, 119, 177, 148, 100, 153, 121, 228, 238, 250, 185, 103, 53, 113, 8, 30, 192, - 75, 150, 153, 2, 24, 109, 93, 91, 97, 75, 106, 35, 29, 252, + 54, 6, 179, 188, 181, 97, 161, 198, 92, 12, 185, 60, 151, 219, 115, 50, 51, 248, 81, + 106, 10, 89, 183, 126, 179, 14, 72, 251, 234, 175, 4, 161, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -6992,4 +7067,59 @@ mod tests { assert_eq!(query_result.documents().len(), 1); } + + #[cfg(feature = "server")] + #[test] + fn test_count_regular_index() { + let platform_version = PlatformVersion::latest(); + + let (drive, contract) = setup_countable_family_tests(6, 15, platform_version); + + let db_transaction = drive.grove.start_transaction(); + + let root_hash = drive + .grove + .root_hash(Some(&db_transaction), &platform_version.drive.grove_version) + .unwrap() + .expect("there is always a root hash"); + + // A query getting all elements by firstName + + let query_value = platform_value!({ + "where": [ + ["age", ">=", 1] + ], + "orderBy": [ + ["age", "asc"] + ] + }); + + let person_document_type = contract + .document_type_for_name("person") + .expect("contract should have a person document type"); + + let query = DriveDocumentQuery::from_value( + query_value, + &contract, + person_document_type, + &drive.config, + ) + .expect("query should be built"); + + let (proof, _) = query + .execute_with_proof(&drive, None, None, platform_version) + .expect("we should be able to a proof"); + + dbg!(hex::encode(proof)); + + // assert_eq!(root_hash, proof_root_hash); + } +} + +#[cfg(feature = "server")] +#[test] +#[ignore] +fn pwd() { + let working_dir = std::env::current_dir().unwrap(); + println!("{}", working_dir.display()); } diff --git a/packages/rs-drive/tests/supporting_files/contract/family/family-contract-countable.json b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-countable.json new file mode 100644 index 00000000000..96f73d1b8ad --- /dev/null +++ b/packages/rs-drive/tests/supporting_files/contract/family/family-contract-countable.json @@ -0,0 +1,74 @@ +{ + "$format_version": "0", + "id": "94zNLp7A1ZcYG3Egqf2YmQk4DQr9P8D543GwXyCJRz4", + "ownerId": "AcYUCSvAmUwryNsQqkqqD1o3BnFuzepGtR3Mhh2swLk6", + "version": 1, + "documentSchemas": { + "person": { + "type": "object", + "indices": [ + { + "properties": [ + { + "firstName": "asc" + }, + { + "lastName": "asc" + } + ], + "count": true + }, + { + "properties": [ + { + "firstName": "asc" + }, + { + "middleName": "asc" + }, + { + "lastName": "asc" + } + ], + "unique": true, + "count": true + }, + { + "properties": [ + { + "age": "asc" + } + ], + "count": true + } + ], + "properties": { + "age": { + "type": "integer", + "position": 0 + }, + "firstName": { + "type": "string", + "maxLength": 50, + "position": 1 + }, + "middleName": { + "type": "string", + "maxLength": 50, + "position": 2 + }, + "lastName": { + "type": "string", + "maxLength": 50, + "position": 3 + } + }, + "required": [ + "firstName", + "lastName", + "age" + ], + "additionalProperties": false + } + } +} diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 6692c09e017..cbb8d36b9f0 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "1.0.63" } bincode = { version = "=2.0.0-rc.3" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "f89e03e4e0ac12aa2feea5c94b38c09f4909facc" } +grovedb-version = { path = "../../../grovedb/grovedb-version" } once_cell = "1.19.0" [features]