Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Model Name Scanning #965

Merged
merged 36 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
41942cb
reenabled index and added interface with keys
Sep 9, 2023
83ed6f6
getting by key
Sep 9, 2023
6ff7a9b
bidirectional reference for keys and debug
Sep 9, 2023
fe6191d
changed panic to assert
Sep 9, 2023
2a8baab
commented out test
Sep 9, 2023
a76b990
a placeholder for key deletion
Sep 11, 2023
8df343d
using index in database
Sep 11, 2023
60dcf56
using new api in world
Sep 11, 2023
7b60981
increased gas budget for failing tests
Sep 11, 2023
2039dd9
simple test entities in world
Sep 11, 2023
94f042d
fix tests
neotheprogramist Sep 11, 2023
8f631e9
delete .vscode
neotheprogramist Sep 11, 2023
ba47ffd
add .vscode to .gitignore
neotheprogramist Sep 11, 2023
5eafbae
disable indexing by default
neotheprogramist Sep 11, 2023
af8781e
fix tests
neotheprogramist Sep 11, 2023
8f87c55
doc comments
matzayonc Sep 11, 2023
525335b
added new test cases
verzotokumpel Sep 13, 2023
9dca483
improved some tests
verzotokumpel Sep 13, 2023
4f3b9ae
updating indexer to use layouts
matzayonc Sep 25, 2023
b9d704a
run with CAIRO_FIX_TESTS=1
neotheprogramist Sep 25, 2023
5604bf1
clippy
neotheprogramist Sep 25, 2023
8a73c21
updated test_entities for new api
matzayonc Sep 25, 2023
1ae23a2
rebase and comments
matzayonc Sep 28, 2023
e0581c7
Update crates/dojo-core/src/database/index.cairo
matzayonc Sep 28, 2023
9091d4a
ci trigger
matzayonc Sep 28, 2023
c0b60c2
CAIRO_FIX_TESTS=1
neotheprogramist Sep 28, 2023
d24f6cc
Remove partitions
tarrencev Sep 28, 2023
ccb83cb
Propose api
tarrencev Sep 28, 2023
4173a06
made future interface compile
Oct 2, 2023
5ff7ae5
enabled adding to index and simple test
Oct 3, 2023
995aecd
update after rebase
Oct 3, 2023
f43c363
stripping unneeded
Oct 3, 2023
a53f71b
test fixes
Oct 3, 2023
c499667
Merge branch 'dojoengine:main' into 8-index-by-name
neotheprogramist Oct 3, 2023
bc1eafa
launch json fix
Oct 3, 2023
8fa6213
removed keys_layout
Oct 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 20 additions & 25 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in 'dojo-world'",
"cargo": {
"args": [
"test",
"--no-run",
"--package=dojo-world",
"--lib"
],
"filter": {
"name": "dojo-world",
"kind": "lib"
}
},
"args": ["migration::compile_moves"],
"cwd": "${workspaceFolder}/crates/dojo-world"
},
]
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in 'dojo-world'",
"cargo": {
"args": ["test", "--no-run", "--package=dojo-world", "--lib"],
"filter": {
"name": "dojo-world",
"kind": "lib"
}
},
"args": ["migration::compile_moves"],
"cwd": "${workspaceFolder}/crates/dojo-world"
}
]
}
53 changes: 38 additions & 15 deletions crates/dojo-core/src/database.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ mod utils;
#[cfg(test)]
mod utils_test;

use index::WhereCondition;

fn get(
class_hash: starknet::ClassHash, table: felt252, key: felt252, offset: u8, length: usize, layout: Span<u8>
) -> Span<felt252> {
Expand All @@ -35,30 +37,51 @@ fn set(
storage::set_many(0, keys.span(), offset, value, layout);
}

fn set_with_index(
class_hash: starknet::ClassHash, table: felt252, key: felt252, offset: u8, value: Span<felt252>, layout: Span<u8>
) {
set(class_hash, table, key, offset, value, layout);
index::create(0, table, key);
}

fn del(class_hash: starknet::ClassHash, table: felt252, key: felt252) {
index::delete(0, table, key);
}

// returns a tuple of spans, first contains the entity IDs,
// second the deserialized entities themselves
fn all(
class_hash: starknet::ClassHash, model: felt252, partition: felt252, length: usize, layout: Span<u8>
// Query all entities that meet a criteria. If no index is defined,
// Returns a tuple of spans, first contains the entity IDs,
// second the deserialized entities themselves.
fn scan(
class_hash: starknet::ClassHash, model: felt252, where: Option<WhereCondition>, values_length: usize, values_layout: Span<u8>
) -> (Span<felt252>, Span<Span<felt252>>) {
let table = {
if partition == 0.into() {
model
} else {
match where {
Option::Some(clause) => {
let mut serialized = ArrayTrait::new();
model.serialize(ref serialized);
partition.serialize(ref serialized);
let hash = poseidon_hash_span(serialized.span());
hash.into()
clause.key.serialize(ref serialized);
let index = poseidon_hash_span(serialized.span());

let all_ids = index::get_by_key(0, index, clause.value);
(all_ids.span(), get_by_ids(class_hash, index, all_ids.span(), values_length, values_layout))
},

// If no `where` clause is defined, we return all values.
Option::None(_) => {
let all_ids = index::query(0, model, Option::None);
(all_ids, get_by_ids(class_hash, model, all_ids, values_length, values_layout))
}
};
}
}

let all_ids = index::get(0, table);
let mut ids = all_ids.span();
/// Returns entries on the given ids.
/// # Arguments
/// * `class_hash` - The class hash of the contract.
/// * `table` - The table to get the entries from.
/// * `all_ids` - The ids of the entries to get.
/// * `length` - The length of the entries.
fn get_by_ids(class_hash: starknet::ClassHash, table: felt252, all_ids: Span<felt252>, length: u32, layout: Span<u8>) -> Span<Span<felt252>> {
let mut entities: Array<Span<felt252>> = ArrayTrait::new();
let mut ids = all_ids;
loop {
match ids.pop_front() {
Option::Some(id) => {
Expand All @@ -70,7 +93,7 @@ fn all(
entities.append(value);
},
Option::None(_) => {
break (all_ids.span(), entities.span());
break entities.span();
}
};
}
Expand Down
111 changes: 87 additions & 24 deletions crates/dojo-core/src/database/index.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ use serde::Serde;

use dojo::database::storage;

#[derive(Copy, Drop)]
struct WhereCondition {
key: felt252,
value: felt252,
}

fn create(address_domain: u32, index: felt252, id: felt252) {
if exists(address_domain, index, id) {
return ();
Expand All @@ -18,6 +24,12 @@ fn create(address_domain: u32, index: felt252, id: felt252) {
storage::set(address_domain, build_index_key(index, index_len), id);
}

/// Deletes an entry from the main index, as well as from each of the keys.
/// # Arguments
/// * address_domain - The address domain to write to.
/// * index - The index to write to.
/// * id - The id of the entry.
/// # Returns
fn delete(address_domain: u32, index: felt252, id: felt252) {
if !exists(address_domain, index, id) {
return ();
Expand All @@ -42,50 +54,101 @@ fn exists(address_domain: u32, index: felt252, id: felt252) -> bool {
storage::get(address_domain, build_index_item_key(index, id)) != 0
}

fn get(address_domain: u32, index: felt252) -> Array<felt252> {
fn query(address_domain: u32, table: felt252, where: Option<WhereCondition>) -> Span<felt252> {
let mut res = ArrayTrait::new();

let index_len_key = build_index_len_key(index);
let index_len = storage::get(address_domain, index_len_key);
match where {
Option::Some(clause) => {
let mut serialized = ArrayTrait::new();
table.serialize(ref serialized);
clause.key.serialize(ref serialized);
let index = poseidon_hash_span(serialized.span());

let index_len_key = build_index_len_key(index);
let index_len = storage::get(address_domain, index_len_key);
let mut idx = 0;

loop {
if idx == index_len {
break ();
}
let id = storage::get(address_domain, build_index_key(index, idx));
res.append(id);
}
},

// If no `where` clause is defined, we return all values.
Option::None(_) => {
let index_len_key = build_index_len_key(table);
let index_len = storage::get(address_domain, index_len_key);
let mut idx = 0;

loop {
if idx == index_len {
break ();
}

res.append(storage::get(address_domain, build_index_key(table, idx)));
idx += 1;
};
}
}

res.span()
}

/// Returns all the entries that hold a given key
/// # Arguments
/// * address_domain - The address domain to write to.
/// * index - The index to read from.
/// * key - The key return values from.
fn get_by_key(address_domain: u32, index: felt252, key: felt252) -> Array<felt252> {
let mut res = ArrayTrait::new();
let specific_len_key = build_index_specific_key_len(index, key);
let index_len = storage::get(address_domain, specific_len_key);

let mut idx = 0;

loop {
if idx == index_len {
break ();
}

res.append(storage::get(address_domain, build_index_key(index, idx)));
let specific_key = build_index_specific_key(index, key, idx);
let id = storage::get(address_domain, specific_key);
res.append(id);

idx += 1;
};

res
}

fn index_key_prefix() -> Array<felt252> {
let mut prefix = ArrayTrait::new();
prefix.append('dojo_index');
prefix
}

fn build_index_len_key(index: felt252) -> Span<felt252> {
let mut index_len_key = index_key_prefix();
index_len_key.append('index_lens');
index_len_key.append(index);
index_len_key.span()
array!['dojo_index_lens', index].span()
}

fn build_index_key(index: felt252, idx: felt252) -> Span<felt252> {
let mut key = index_key_prefix();
key.append('indexes');
key.append(index);
key.append(idx);
key.span()
array!['dojo_indexes', index, idx].span()
}

fn build_index_item_key(index: felt252, id: felt252) -> Span<felt252> {
let mut index_len_key = index_key_prefix();
index_len_key.append('index_ids');
index_len_key.append(index);
index_len_key.append(id);
index_len_key.span()
array!['dojo_index_ids', index, id].span()
}

/// Key for a length of index for a given key.
/// # Arguments
/// * index - The index to write to.
/// * key - The key to write.
fn build_index_specific_key_len(index: felt252, key: felt252) -> Span<felt252> {
array!['dojo_index_key_len', index, key].span()
}

/// Key for an index of a given key.
/// # Arguments
/// * index - The index to write to.
/// * key - The key to write.
/// * idx - The position in the index.
fn build_index_specific_key(index: felt252, key: felt252, idx: felt252) -> Span<felt252> {
array!['dojo_index_key', index, key, idx].span()
}
21 changes: 12 additions & 9 deletions crates/dojo-core/src/database/index_test.cairo
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
use array::ArrayTrait;
use traits::Into;
use debug::PrintTrait;
use option::OptionTrait;


use dojo::database::index;

#[test]
#[available_gas(2000000)]
fn test_index_entity() {
let no_query = index::get(0, 69);
let no_query = index::query(0, 69, Option::None(()));
assert(no_query.len() == 0, 'entity indexed');

index::create(0, 69, 420);
let query = index::get(0, 69);
let query = index::query(0, 69, Option::None(()));
assert(query.len() == 1, 'entity not indexed');
assert(*query.at(0) == 420, 'entity value incorrect');

index::create(0, 69, 420);
let noop_query = index::get(0, 69);
let noop_query = index::query(0, 69, Option::None(()));
assert(noop_query.len() == 1, 'index should be noop');

index::create(0, 69, 1337);
let two_query = index::get(0, 69);
let two_query = index::query(0, 69, Option::None(()));
assert(two_query.len() == 2, 'index should have two query');
assert(*two_query.at(1) == 1337, 'entity value incorrect');
}
Expand All @@ -28,7 +31,7 @@ fn test_index_entity() {
#[available_gas(2000000)]
fn test_entity_delete_basic() {
index::create(0, 69, 420);
let query = index::get(0, 69);
let query = index::query(0, 69, Option::None(()));
assert(query.len() == 1, 'entity not indexed');
assert(*query.at(0) == 420, 'entity value incorrect');

Expand All @@ -37,7 +40,7 @@ fn test_entity_delete_basic() {
index::delete(0, 69, 420);

assert(!index::exists(0, 69, 420), 'entity should not exist');
let no_query = index::get(0, 69);
let no_query = index::query(0, 69, Option::None(()));
assert(no_query.len() == 0, 'index should have no query');
}

Expand All @@ -48,10 +51,10 @@ fn test_entity_query_delete_shuffle() {
index::create(0, table, 10);
index::create(0, table, 20);
index::create(0, table, 30);
assert(index::get(0, table).len() == 3, 'wrong size');
assert(index::query(0, table, Option::None(())).len() == 3, 'wrong size');

index::delete(0, table, 10);
let entities = index::get(0, table);
let entities = index::query(0, table, Option::None(()));
assert(entities.len() == 2, 'wrong size');
assert(*entities.at(0) == 30, 'idx 0 not 30');
assert(*entities.at(1) == 20, 'idx 1 not 20');
Expand All @@ -60,6 +63,6 @@ fn test_entity_query_delete_shuffle() {
#[test]
#[available_gas(20000000)]
fn test_entity_query_delete_non_existing() {
assert(index::get(0, 69).len() == 0, 'table len != 0');
assert(index::query(0, 69, Option::None(())).len() == 0, 'table len != 0');
index::delete(0, 69, 999); // deleting non-existing should not panic
}
29 changes: 15 additions & 14 deletions crates/dojo-core/src/database_test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use starknet::syscalls::deploy_syscall;
use starknet::class_hash::{Felt252TryIntoClassHash, ClassHash};
use dojo::world::{IWorldDispatcher};
use dojo::executor::executor;
use dojo::database::{get, set, del, all};
use dojo::database::{get, set, set_with_index, del, scan};
use dojo::database::index::WhereCondition;

#[test]
#[available_gas(1000000)]
Expand Down Expand Up @@ -118,19 +119,19 @@ fn test_database_del() {

#[test]
#[available_gas(10000000)]
fn test_database_all() {
let mut even = ArrayTrait::new();
even.append(0x2);
even.append(0x4);

let mut odd = ArrayTrait::new();
odd.append(0x1);
odd.append(0x3);
fn test_database_scan() {
let even = array![2, 4].span();
let odd = array![1, 3].span();
let layout = array![251, 251].span();

let class_hash: starknet::ClassHash = executor::TEST_CLASS_HASH.try_into().unwrap();
set(class_hash, 'table', 'even', 0, even.span(), array![251, 251].span());
set(class_hash, 'table', 'odd', 0, odd.span(), array![251, 251].span());

let base = starknet::storage_base_address_from_felt252('table');
let (keys, values) = all(class_hash, 'table', 0, 2, array![251, 251].span());
set_with_index(class_hash, 'table', 'even', 0, even, layout);
set_with_index(class_hash, 'table', 'odd', 0, odd, layout);

let (keys, values) = scan(class_hash, 'table', Option::None(()), 2, layout);
assert(keys.len() == 2, 'Wrong number of keys!');
assert(values.len() == 2, 'Wrong number of values!');
assert(*keys.at(0) == 'even', 'Wrong key at index 0!');
assert(*(*values.at(0)).at(0) == 2, 'Wrong value at index 0!');
assert(*(*values.at(0)).at(1) == 4, 'Wrong value at index 1!');
}
Loading
Loading