diff --git a/crates/sui-graphql-e2e-tests/tests/consistency/objects_pagination.exp b/crates/sui-graphql-e2e-tests/tests/consistency/objects_pagination.exp index b8c70b12f3849..ddcee8d2d97d9 100644 --- a/crates/sui-graphql-e2e-tests/tests/consistency/objects_pagination.exp +++ b/crates/sui-graphql-e2e-tests/tests/consistency/objects_pagination.exp @@ -580,7 +580,56 @@ Response: { "data": { "objects_at_version": { "objects": { - "nodes": [] + "nodes": [ + { + "version": 3, + "contents": { + "type": { + "repr": "0x7fa8cc064aa9d56a46411e3e0e5878a6cd740a4edabe2cb3c28ec32d6e314db4::M1::Object" + }, + "json": { + "id": "0x5cbc81679b7c3fe2763d6129f752abacd0787935b4a081c341e1cebc571446d5", + "value": "0" + } + } + }, + { + "version": 6, + "contents": { + "type": { + "repr": "0x7fa8cc064aa9d56a46411e3e0e5878a6cd740a4edabe2cb3c28ec32d6e314db4::M1::Object" + }, + "json": { + "id": "0x81b0f4f8040edbdd1778dac36f4a6c7ebd4d2689c5f9e28ea916ab3542ea6c5e", + "value": "3" + } + } + }, + { + "version": 5, + "contents": { + "type": { + "repr": "0x7fa8cc064aa9d56a46411e3e0e5878a6cd740a4edabe2cb3c28ec32d6e314db4::M1::Object" + }, + "json": { + "id": "0xb94dc19e08bf7ea2999f22ae8db6ef903d2ace4a08e99d40867064ca10abdf07", + "value": "2" + } + } + }, + { + "version": 4, + "contents": { + "type": { + "repr": "0x7fa8cc064aa9d56a46411e3e0e5878a6cd740a4edabe2cb3c28ec32d6e314db4::M1::Object" + }, + "json": { + "id": "0xcf941059092fa42fb8bc3c184adaf83083856daa002de4f4aaadfc1e79cfa26a", + "value": "1" + } + } + } + ] } } } diff --git a/crates/sui-graphql-e2e-tests/tests/consistency/objects_pagination.move b/crates/sui-graphql-e2e-tests/tests/consistency/objects_pagination.move index d14bb99d49bcf..af3b0b24e1fb7 100644 --- a/crates/sui-graphql-e2e-tests/tests/consistency/objects_pagination.move +++ b/crates/sui-graphql-e2e-tests/tests/consistency/objects_pagination.move @@ -264,7 +264,7 @@ module Test::M1 { } //# run-graphql -# No longer accessible, as there are more recent versions owned by address B. +# Historical lookups will still return results at version. { objects_at_version: address(address: "@{A}") { objects( diff --git a/crates/sui-graphql-e2e-tests/tests/consistency/performance/many_objects.exp b/crates/sui-graphql-e2e-tests/tests/consistency/performance/many_objects.exp new file mode 100644 index 0000000000000..eefb75332d5db --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/consistency/performance/many_objects.exp @@ -0,0 +1,484 @@ +processed 13 tasks + +init: +A: object(0,0), B: object(0,1) + +task 1 'publish'. lines 12-39: +created: object(1,0) +mutated: object(0,2) +gas summary: computation_cost: 1000000, storage_cost: 6118000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 41-41: +created: object(2,0), object(2,1), object(2,2), object(2,3), object(2,4), object(2,5), object(2,6), object(2,7), object(2,8), object(2,9), object(2,10), object(2,11), object(2,12), object(2,13), object(2,14), object(2,15), object(2,16), object(2,17), object(2,18), object(2,19), object(2,20), object(2,21), object(2,22), object(2,23), object(2,24), object(2,25), object(2,26), object(2,27), object(2,28), object(2,29), object(2,30), object(2,31), object(2,32), object(2,33), object(2,34), object(2,35), object(2,36), object(2,37), object(2,38), object(2,39), object(2,40), object(2,41), object(2,42), object(2,43), object(2,44), object(2,45), object(2,46), object(2,47), object(2,48), object(2,49), object(2,50), object(2,51), object(2,52), object(2,53), object(2,54), object(2,55), object(2,56), object(2,57), object(2,58), object(2,59), object(2,60), object(2,61), object(2,62), object(2,63), object(2,64), object(2,65), object(2,66), object(2,67), object(2,68), object(2,69), object(2,70), object(2,71), object(2,72), object(2,73), object(2,74), object(2,75), object(2,76), object(2,77), object(2,78), object(2,79), object(2,80), object(2,81), object(2,82), object(2,83), object(2,84), object(2,85), object(2,86), object(2,87), object(2,88), object(2,89), object(2,90), object(2,91), object(2,92), object(2,93), object(2,94), object(2,95), object(2,96), object(2,97), object(2,98), object(2,99), object(2,100), object(2,101), object(2,102), object(2,103), object(2,104), object(2,105), object(2,106), object(2,107), object(2,108), object(2,109), object(2,110), object(2,111), object(2,112), object(2,113), object(2,114), object(2,115), object(2,116), object(2,117), object(2,118), object(2,119), object(2,120), object(2,121), object(2,122), object(2,123), object(2,124), object(2,125), object(2,126), object(2,127), object(2,128), object(2,129), object(2,130), object(2,131), object(2,132), object(2,133), object(2,134), object(2,135), object(2,136), object(2,137), object(2,138), object(2,139), object(2,140), object(2,141), object(2,142), object(2,143), object(2,144), object(2,145), object(2,146), object(2,147), object(2,148), object(2,149), object(2,150), object(2,151), object(2,152), object(2,153), object(2,154), object(2,155), object(2,156), object(2,157), object(2,158), object(2,159), object(2,160), object(2,161), object(2,162), object(2,163), object(2,164), object(2,165), object(2,166), object(2,167), object(2,168), object(2,169), object(2,170), object(2,171), object(2,172), object(2,173), object(2,174), object(2,175), object(2,176), object(2,177), object(2,178), object(2,179), object(2,180), object(2,181), object(2,182), object(2,183), object(2,184), object(2,185), object(2,186), object(2,187), object(2,188), object(2,189), object(2,190), object(2,191), object(2,192), object(2,193), object(2,194), object(2,195), object(2,196), object(2,197), object(2,198), object(2,199), object(2,200), object(2,201), object(2,202), object(2,203), object(2,204), object(2,205), object(2,206), object(2,207), object(2,208), object(2,209), object(2,210), object(2,211), object(2,212), object(2,213), object(2,214), object(2,215), object(2,216), object(2,217), object(2,218), object(2,219), object(2,220), object(2,221), object(2,222), object(2,223), object(2,224), object(2,225), object(2,226), object(2,227), object(2,228), object(2,229), object(2,230), object(2,231), object(2,232), object(2,233), object(2,234), object(2,235), object(2,236), object(2,237), object(2,238), object(2,239), object(2,240), object(2,241), object(2,242), object(2,243), object(2,244), object(2,245), object(2,246), object(2,247), object(2,248), object(2,249), object(2,250), object(2,251), object(2,252), object(2,253), object(2,254), object(2,255), object(2,256), object(2,257), object(2,258), object(2,259), object(2,260), object(2,261), object(2,262), object(2,263), object(2,264), object(2,265), object(2,266), object(2,267), object(2,268), object(2,269), object(2,270), object(2,271), object(2,272), object(2,273), object(2,274), object(2,275), object(2,276), object(2,277), object(2,278), object(2,279), object(2,280), object(2,281), object(2,282), object(2,283), object(2,284), object(2,285), object(2,286), object(2,287), object(2,288), object(2,289), object(2,290), object(2,291), object(2,292), object(2,293), object(2,294), object(2,295), object(2,296), object(2,297), object(2,298), object(2,299), object(2,300), object(2,301), object(2,302), object(2,303), object(2,304), object(2,305), object(2,306), object(2,307), object(2,308), object(2,309), object(2,310), object(2,311), object(2,312), object(2,313), object(2,314), object(2,315), object(2,316), object(2,317), object(2,318), object(2,319), object(2,320), object(2,321), object(2,322), object(2,323), object(2,324), object(2,325), object(2,326), object(2,327), object(2,328), object(2,329), object(2,330), object(2,331), object(2,332), object(2,333), object(2,334), object(2,335), object(2,336), object(2,337), object(2,338), object(2,339), object(2,340), object(2,341), object(2,342), object(2,343), object(2,344), object(2,345), object(2,346), object(2,347), object(2,348), object(2,349), object(2,350), object(2,351), object(2,352), object(2,353), object(2,354), object(2,355), object(2,356), object(2,357), object(2,358), object(2,359), object(2,360), object(2,361), object(2,362), object(2,363), object(2,364), object(2,365), object(2,366), object(2,367), object(2,368), object(2,369), object(2,370), object(2,371), object(2,372), object(2,373), object(2,374), object(2,375), object(2,376), object(2,377), object(2,378), object(2,379), object(2,380), object(2,381), object(2,382), object(2,383), object(2,384), object(2,385), object(2,386), object(2,387), object(2,388), object(2,389), object(2,390), object(2,391), object(2,392), object(2,393), object(2,394), object(2,395), object(2,396), object(2,397), object(2,398), object(2,399), object(2,400), object(2,401), object(2,402), object(2,403), object(2,404), object(2,405), object(2,406), object(2,407), object(2,408), object(2,409), object(2,410), object(2,411), object(2,412), object(2,413), object(2,414), object(2,415), object(2,416), object(2,417), object(2,418), object(2,419), object(2,420), object(2,421), object(2,422), object(2,423), object(2,424), object(2,425), object(2,426), object(2,427), object(2,428), object(2,429), object(2,430), object(2,431), object(2,432), object(2,433), object(2,434), object(2,435), object(2,436), object(2,437), object(2,438), object(2,439), object(2,440), object(2,441), object(2,442), object(2,443), object(2,444), object(2,445), object(2,446), object(2,447), object(2,448), object(2,449), object(2,450), object(2,451), object(2,452), object(2,453), object(2,454), object(2,455), object(2,456), object(2,457), object(2,458), object(2,459), object(2,460), object(2,461), object(2,462), object(2,463), object(2,464), object(2,465), object(2,466), object(2,467), object(2,468), object(2,469), object(2,470), object(2,471), object(2,472), object(2,473), object(2,474), object(2,475), object(2,476), object(2,477), object(2,478), object(2,479), object(2,480), object(2,481), object(2,482), object(2,483), object(2,484), object(2,485), object(2,486), object(2,487), object(2,488), object(2,489), object(2,490), object(2,491), object(2,492), object(2,493), object(2,494), object(2,495), object(2,496), object(2,497), object(2,498), object(2,499) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 658388000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3 'create-checkpoint'. lines 43-43: +Checkpoint created: 2 + +task 4 'run-graphql'. lines 45-86: +Response: { + "data": { + "last_2": { + "nodes": [ + { + "version": 2, + "asMoveObject": { + "owner": { + "owner": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + } + }, + "contents": { + "json": { + "id": "0xffb8fcef6b804e51b3ac9d1d3f789472ed397e764e74c1132ca271e4ac14b4b6", + "value": "245" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + }, + { + "version": 2, + "asMoveObject": { + "owner": { + "owner": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + } + }, + "contents": { + "json": { + "id": "0xffe237fc32ab9a718a6c50c761074cbbde5fb691394e147c466de8b0592849df", + "value": "83" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + } + ] + }, + "last_4_objs_owned_by_A": { + "objects": { + "nodes": [ + { + "owner": { + "owner": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + } + }, + "contents": { + "json": { + "id": "0xfe634b417989c12e4e72daf427d78a361863e9940a3d9c5e138f4fcefb9d7ce5", + "value": "231" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + }, + { + "owner": { + "owner": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + } + }, + "contents": { + "json": { + "id": "0xfe965a0c18f5cb4b1dbe5a982a12ac7ce17fbae1a9b3b7ee474232fd464d2a04", + "value": "438" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + }, + { + "owner": { + "owner": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + } + }, + "contents": { + "json": { + "id": "0xffb8fcef6b804e51b3ac9d1d3f789472ed397e764e74c1132ca271e4ac14b4b6", + "value": "245" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + }, + { + "owner": { + "owner": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + } + }, + "contents": { + "json": { + "id": "0xffe237fc32ab9a718a6c50c761074cbbde5fb691394e147c466de8b0592849df", + "value": "83" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + ] + } + } + } +} + +task 5 'transfer-object'. lines 88-88: +mutated: object(0,0), object(2,499) +gas summary: computation_cost: 1000000, storage_cost: 2302800, storage_rebate: 2279772, non_refundable_storage_fee: 23028 + +task 6 'transfer-object'. lines 90-90: +mutated: object(0,0), object(2,498) +gas summary: computation_cost: 1000000, storage_cost: 2302800, storage_rebate: 2279772, non_refundable_storage_fee: 23028 + +task 7 'transfer-object'. lines 92-92: +mutated: object(0,0), object(2,497) +gas summary: computation_cost: 1000000, storage_cost: 2302800, storage_rebate: 2279772, non_refundable_storage_fee: 23028 + +task 8 'view-object'. lines 94-94: +Owner: Account Address ( B ) +Version: 4 +Contents: Test::M1::Object {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,498)}}, value: 245u64} + +task 9 'view-object'. lines 96-96: +Owner: Account Address ( B ) +Version: 5 +Contents: Test::M1::Object {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,497)}}, value: 438u64} + +task 10 'create-checkpoint'. lines 98-98: +Checkpoint created: 3 + +task 11 'run-graphql'. lines 100-142: +Response: { + "data": { + "last_3": { + "nodes": [ + { + "version": 5, + "asMoveObject": { + "owner": { + "owner": { + "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" + } + }, + "contents": { + "json": { + "id": "0xfe965a0c18f5cb4b1dbe5a982a12ac7ce17fbae1a9b3b7ee474232fd464d2a04", + "value": "438" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + }, + { + "version": 4, + "asMoveObject": { + "owner": { + "owner": { + "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" + } + }, + "contents": { + "json": { + "id": "0xffb8fcef6b804e51b3ac9d1d3f789472ed397e764e74c1132ca271e4ac14b4b6", + "value": "245" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + }, + { + "version": 3, + "asMoveObject": { + "owner": { + "owner": { + "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" + } + }, + "contents": { + "json": { + "id": "0xffe237fc32ab9a718a6c50c761074cbbde5fb691394e147c466de8b0592849df", + "value": "83" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + } + ] + }, + "last_obj_owned_by_A": { + "objects": { + "nodes": [ + { + "version": 2, + "owner": { + "owner": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + } + }, + "contents": { + "json": { + "id": "0xfe634b417989c12e4e72daf427d78a361863e9940a3d9c5e138f4fcefb9d7ce5", + "value": "231" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + ] + } + } + } +} + +task 12 'run-graphql'. lines 144-248: +Response: { + "data": { + "a": { + "asMoveObject": { + "owner": { + "owner": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + } + }, + "contents": { + "json": { + "id": "0xffe237fc32ab9a718a6c50c761074cbbde5fb691394e147c466de8b0592849df", + "value": "83" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + }, + "b": { + "asMoveObject": { + "owner": { + "owner": { + "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" + } + }, + "contents": { + "json": { + "id": "0xffe237fc32ab9a718a6c50c761074cbbde5fb691394e147c466de8b0592849df", + "value": "83" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + }, + "objects_a": { + "nodes": [ + { + "asMoveObject": { + "owner": { + "owner": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + } + }, + "contents": { + "json": { + "id": "0xfe965a0c18f5cb4b1dbe5a982a12ac7ce17fbae1a9b3b7ee474232fd464d2a04", + "value": "438" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + }, + { + "asMoveObject": { + "owner": { + "owner": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + } + }, + "contents": { + "json": { + "id": "0xffb8fcef6b804e51b3ac9d1d3f789472ed397e764e74c1132ca271e4ac14b4b6", + "value": "245" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + }, + { + "asMoveObject": { + "owner": { + "owner": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + } + }, + "contents": { + "json": { + "id": "0xffe237fc32ab9a718a6c50c761074cbbde5fb691394e147c466de8b0592849df", + "value": "83" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + } + ] + }, + "objects_b": { + "nodes": [ + { + "asMoveObject": { + "owner": { + "owner": { + "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" + } + }, + "contents": { + "json": { + "id": "0xfe965a0c18f5cb4b1dbe5a982a12ac7ce17fbae1a9b3b7ee474232fd464d2a04", + "value": "438" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + }, + { + "asMoveObject": { + "owner": { + "owner": { + "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" + } + }, + "contents": { + "json": { + "id": "0xffb8fcef6b804e51b3ac9d1d3f789472ed397e764e74c1132ca271e4ac14b4b6", + "value": "245" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + }, + { + "asMoveObject": { + "owner": { + "owner": { + "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" + } + }, + "contents": { + "json": { + "id": "0xffe237fc32ab9a718a6c50c761074cbbde5fb691394e147c466de8b0592849df", + "value": "83" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + } + ] + }, + "owned_by_b": { + "objects": { + "nodes": [ + { + "version": 1, + "owner": { + "owner": { + "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" + } + }, + "contents": { + "json": { + "id": "0xd41dc489aa9cb88f0fd03b478e2185585acdc62e452b533cbb9733dc6e9c9389", + "balance": { + "value": "300000000000000" + } + }, + "type": { + "repr": "0x0000000000000000000000000000000000000000000000000000000000000002::coin::Coin<0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI>" + } + } + }, + { + "version": 5, + "owner": { + "owner": { + "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" + } + }, + "contents": { + "json": { + "id": "0xfe965a0c18f5cb4b1dbe5a982a12ac7ce17fbae1a9b3b7ee474232fd464d2a04", + "value": "438" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + }, + { + "version": 4, + "owner": { + "owner": { + "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" + } + }, + "contents": { + "json": { + "id": "0xffb8fcef6b804e51b3ac9d1d3f789472ed397e764e74c1132ca271e4ac14b4b6", + "value": "245" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + }, + { + "version": 3, + "owner": { + "owner": { + "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" + } + }, + "contents": { + "json": { + "id": "0xffe237fc32ab9a718a6c50c761074cbbde5fb691394e147c466de8b0592849df", + "value": "83" + }, + "type": { + "repr": "0xbb23730728f32620f18fca1a0b54b8c3dcbad08bee7008e9715a4a321500204f::M1::Object" + } + } + } + ] + } + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/consistency/performance/many_objects.move b/crates/sui-graphql-e2e-tests/tests/consistency/performance/many_objects.move new file mode 100644 index 0000000000000..67d207473bdf6 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/consistency/performance/many_objects.move @@ -0,0 +1,248 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Transfer 500 objects to A. The first graphql query fetches the last 4 objects owned by A. Then +// transfer the last 3 objects from A to B. Make a graphql query for the `last: 1` - this is to test +// that we return the next valid result even if the first `limit` rows that match the filtering +// criteria are then invalidated by a newer version of the matched object. We set `last: 1` but +// transfer the last 3 objects because we increase the limit by 2 behind the scenes. + +//# init --addresses Test=0x0 --accounts A B --simulator + +//# publish +module Test::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::TxContext; + use sui::transfer; + + struct Object has key, store { + id: UID, + value: u64, + } + + struct Ledger has key, store { + id: UID, + object_ids: vector, + } + + public entry fun create_many(recipient: address, ctx: &mut TxContext) { + let i = 0; + while (i < 500) { + transfer::public_transfer( + + Object { id: object::new(ctx), value: i }, + recipient + ); + i = i + 1; + } + } +} + +//# run Test::M1::create_many --sender A --args @A + +//# create-checkpoint 2 + +//# run-graphql +{ + last_2: objects(last: 2, filter: {type: "@{Test}"}) { + nodes { + version + asMoveObject { + owner { + ... on AddressOwner { + owner { + address + } + } + } + contents { + json + type { + repr + } + } + } + } + } + last_4_objs_owned_by_A: address(address: "@{A}") { + objects(last: 4) { + nodes { + owner { + ... on AddressOwner { + owner { + address + } + } + } + contents { + json + type { + repr + } + } + } + } + } +} + +//# transfer-object 2,499 --sender A --recipient B + +//# transfer-object 2,498 --sender A --recipient B + +//# transfer-object 2,497 --sender A --recipient B + +//# view-object 2,498 + +//# view-object 2,497 + +//# create-checkpoint + +//# run-graphql +{ + last_3: objects(last: 3, filter: {type: "@{Test}"}) { + nodes { + version + asMoveObject { + owner { + ... on AddressOwner { + owner { + address + } + } + } + contents { + json + type { + repr + } + } + } + } + } + last_obj_owned_by_A: address(address: "@{A}") { + objects(last: 1) { + nodes { + version + owner { + ... on AddressOwner { + owner { + address + } + } + } + contents { + json + type { + repr + } + } + } + } + } +} + +//# run-graphql +# Test that we correctly return the object at version, both for the `object` and `objects` +# resolvers. +{ + a: object(address: "@{obj_2_499}", version: 2) { + asMoveObject { + owner { + ... on AddressOwner { + owner { + address + } + } + } + contents { + json + type { + repr + } + } + } + } + b: object(address: "@{obj_2_499}", version: 3) { + asMoveObject { + owner { + ... on AddressOwner { + owner { + address + } + } + } + contents { + json + type { + repr + } + } + } + } + objects_a: objects(filter: {objectKeys: [ + {objectId: "@{obj_2_499}", version: 2}, + {objectId: "@{obj_2_498}", version: 2}, + {objectId: "@{obj_2_497}", version: 2}, + ]}) { + nodes { + asMoveObject { + owner { + ... on AddressOwner { + owner { + address + } + } + } + contents { + json + type { + repr + } + } + } + } + } + objects_b: objects(filter: {objectKeys: [ + {objectId: "@{obj_2_499}", version: 3}, + {objectId: "@{obj_2_498}", version: 4}, + {objectId: "@{obj_2_497}", version: 5}, + ]}) { + nodes { + asMoveObject { + owner { + ... on AddressOwner { + owner { + address + } + } + } + contents { + json + type { + repr + } + } + } + } + } + owned_by_b: address(address: "@{B}") { + objects { + nodes { + version + owner { + ... on AddressOwner { + owner { + address + } + } + } + contents { + json + type { + repr + } + } + } + } + } +} diff --git a/crates/sui-graphql-rpc/src/consistency.rs b/crates/sui-graphql-rpc/src/consistency.rs index ebd2bb7c2c298..bf0c5370b246a 100644 --- a/crates/sui-graphql-rpc/src/consistency.rs +++ b/crates/sui-graphql-rpc/src/consistency.rs @@ -3,19 +3,23 @@ use async_graphql::connection::CursorType; use serde::{Deserialize, Serialize}; +use sui_indexer::models_v2::objects::StoredHistoryObject; use crate::data::Conn; use crate::raw_query::RawQuery; use crate::types::checkpoint::Checkpoint; -use crate::types::cursor::JsonCursor; +use crate::types::cursor::{JsonCursor, Page}; +use crate::types::object::Cursor; use crate::{filter, query}; +#[derive(Copy, Clone)] pub(crate) enum View { /// Return objects that fulfill the filtering criteria, even if there are more recent versions - /// of the object within the checkpoint range + /// of the object within the checkpoint range. This is used for lookups such as by `object_id` + /// and `version`. Historical, /// Return objects that fulfill the filtering criteria and are the most recent version within - /// the checkpoint range + /// the checkpoint range. Consistent, } @@ -58,67 +62,130 @@ impl Checkpointed for JsonCursor { /// Constructs a `RawQuery` against the `objects_snapshot` and `objects_history` table to fetch /// objects that satisfy some filtering criteria `filter_fn` within the provided checkpoint range -/// `lhs` and `rhs`. If the `view` parameter is set to `Consistent`, the query additionally filters -/// out objects that satisfy the provided filters, but are not the most recent version of the object -/// within the checkpoint range. If the view parameter is set to `Historical`, this final filter is -/// not applied. -pub(crate) fn build_objects_query(view: View, lhs: i64, rhs: i64, filter_fn: F) -> RawQuery -where - F: Fn(RawQuery) -> RawQuery, -{ - // Construct the filtered inner query - apply the same filtering criteria to both - // objects_snapshot and objects_history tables. - let mut snapshot_objs = query!(r#"SELECT * FROM objects_snapshot"#); - snapshot_objs = filter_fn(snapshot_objs); - - // Additionally filter objects_history table for results between the available range, or - // checkpoint_viewed_at, if provided. - let mut history_objs = query!(r#"SELECT * FROM objects_history"#); - history_objs = filter_fn(history_objs); - history_objs = filter!( - history_objs, +/// `lhs` and `rhs`. The `objects_snapshot` table contains the latest versions of objects up to a +/// checkpoint sequence number, and `objects_history` captures changes after that, so a query to +/// both tables is necessary to handle these object states: +/// 1) In snapshot, not in history - occurs when an object gets snapshotted and then has not been +/// modified since +/// 2) In history, not in snapshot - occurs when a new object is created +/// 3) In snapshot and in history - occurs when an object is snapshotted and further modified +/// +/// Additionally, even among objects that satisfy the filtering criteria, it is possible that there +/// is a yet more recent version of the object within the checkpoint range, such as when the owner +/// of an object changes. The `LEFT JOIN` against the `objects_history` table handles this and +/// scenario 3. Note that the implementation applies the `LEFT JOIN` to each inner query in +/// conjunction with the `page`'s cursor and limit. If this was instead done once at the end, the +/// query would be drastically inefficient as we would be dealing with a large number of rows from +/// `objects_snapshot`, and potentially `objects_history` as the checkpoint range grows. Instead, +/// the `LEFT JOIN` and limit applied on the inner queries work in conjunction to make the final +/// query noticeably more efficient. The former serves as a filter, and the latter reduces the +/// number of rows that the database needs to work with. +/// +/// However, not all queries require this `LEFT JOIN`, such as when no filtering criteria is +/// specified, or if the filter is a lookup at a specific `object_id` and `object_version`. This is +/// controlled by the `view` parameter. If the `view` parameter is set to `Consistent`, this filter +/// is applied, otherwise if the `view` parameter is set to `Historical`, this filter is not +/// applied. +/// +/// Finally, the two queries are merged together with `UNION ALL`. We use `UNION ALL` instead of +/// `UNION`; the latter incurs significant overhead as it additionally de-duplicates records from +/// both sources. This dedupe is unnecessary, since we have the fragment `SELECT DISTINCT ON +/// (object_id) ... ORDER BY object_id, object_version DESC`. This is also redundant for the most +/// part, due to the invariant that the `objects_history` captures changes that occur after +/// `objects_snapshot`, but it's a safeguard to handle any possible overlap during snapshot +/// creation. +pub(crate) fn build_objects_query( + view: View, + lhs: i64, + rhs: i64, + page: &Page, + filter_fn: impl Fn(RawQuery) -> RawQuery, +) -> RawQuery { + // Subquery to be used in `LEFT JOIN` against the inner queries for more recent object versions + let mut newer = query!("SELECT object_id, object_version FROM objects_history"); + newer = filter!( + newer, format!(r#"checkpoint_sequence_number BETWEEN {} AND {}"#, lhs, rhs) ); - // Combine the two queries, and select the most recent version of each object. The result set is - // the most recent version of objects from `objects_snapshot` and `objects_history` that match - // the filter criteria. - let candidates = query!( - r#"SELECT DISTINCT ON (object_id) * FROM (({}) UNION ({})) o"#, - snapshot_objs, - history_objs - ) - .order_by("object_id") - .order_by("object_version DESC"); + let mut snapshot_objs_inner = query!("SELECT * FROM objects_snapshot"); + snapshot_objs_inner = filter_fn(snapshot_objs_inner); - // The following conditions ensure that the version of object matching our filters is the latest - // version at the checkpoint we are viewing at. If the filter includes version constraints (an - // `object_keys` field), then this extra check is not required (it will filter out correct - // results). - match view { + let mut snapshot_objs = match view { View::Consistent => { - let mut newer = query!("SELECT object_id, object_version FROM objects_history"); - newer = filter!( - newer, + // The `LEFT JOIN` serves as a filter to remove objects that have a more recent version + let mut snapshot_objs = query!( + r#"SELECT candidates.* FROM ({}) candidates + LEFT JOIN ({}) newer + ON (candidates.object_id = newer.object_id AND candidates.object_version < newer.object_version)"#, + snapshot_objs_inner, + newer.clone() + ); + snapshot_objs = filter!(snapshot_objs, "newer.object_version IS NULL"); + snapshot_objs + } + View::Historical => { + // The cursor pagination logic refers to the table with the `candidates` alias + query!( + "SELECT candidates.* FROM ({}) candidates", + snapshot_objs_inner + ) + } + }; + + // Always apply cursor pagination and limit to constrain the number of rows returned, ensure + // that the inner queries are in step, and to handle the scenario where a user provides more + // `objectKeys` than allowed by the maximum page size. + snapshot_objs = page.apply::(snapshot_objs); + + // Similar to the snapshot query, construct the filtered inner query for the history table. + let mut history_objs_inner = query!("SELECT * FROM objects_history"); + history_objs_inner = filter_fn(history_objs_inner); + + let mut history_objs = match view { + View::Consistent => { + // Additionally bound the inner `objects_history` query by the checkpoint range + history_objs_inner = filter!( + history_objs_inner, format!(r#"checkpoint_sequence_number BETWEEN {} AND {}"#, lhs, rhs) ); - let query = query!( - r#"SELECT candidates.* - FROM ({}) candidates - LEFT JOIN ({}) newer - ON ( - candidates.object_id = newer.object_id - AND candidates.object_version < newer.object_version - )"#, - candidates, + + let mut history_objs = query!( + r#"SELECT candidates.* FROM ({}) candidates + LEFT JOIN ({}) newer + ON (candidates.object_id = newer.object_id AND candidates.object_version < newer.object_version)"#, + history_objs_inner, newer ); - filter!(query, "newer.object_version IS NULL") + history_objs = filter!(history_objs, "newer.object_version IS NULL"); + history_objs } View::Historical => { - query!("SELECT * FROM ({}) candidates", candidates) + // The cursor pagination logic refers to the table with the `candidates` alias + query!( + "SELECT candidates.* FROM ({}) candidates", + history_objs_inner + ) } - } + }; + + // Always apply cursor pagination and limit to constrain the number of rows returned, ensure + // that the inner queries are in step, and to handle the scenario where a user provides more + // `objectKeys` than allowed by the maximum page size. + history_objs = page.apply::(history_objs); + + // Combine the two queries, and select the most recent version of each object. The result set is + // the most recent version of objects from `objects_snapshot` and `objects_history` that match + // the filter criteria. + let query = query!( + r#"SELECT DISTINCT ON (object_id) * FROM (({}) UNION ALL ({})) candidates"#, + snapshot_objs, + history_objs + ) + .order_by("object_id") + .order_by("object_version DESC"); + + query!("SELECT * FROM ({}) candidates", query) } /// Given a `checkpoint_viewed_at` representing the checkpoint sequence number when the query was diff --git a/crates/sui-graphql-rpc/src/types/balance.rs b/crates/sui-graphql-rpc/src/types/balance.rs index 64ad222e85f7a..319c601686e1c 100644 --- a/crates/sui-graphql-rpc/src/types/balance.rs +++ b/crates/sui-graphql-rpc/src/types/balance.rs @@ -211,7 +211,7 @@ fn balance_query(address: SuiAddress, coin_type: Option, lhs: i64, rhs: // Combine the two queries, and select the most recent version of each object. let candidates = query!( - r#"SELECT DISTINCT ON (object_id) * FROM (({}) UNION ({})) o"#, + r#"SELECT DISTINCT ON (object_id) * FROM (({}) UNION ALL ({})) o"#, snapshot_objs, history_objs ) diff --git a/crates/sui-graphql-rpc/src/types/coin.rs b/crates/sui-graphql-rpc/src/types/coin.rs index 0aba5f5a8aba1..01ec509872f7d 100644 --- a/crates/sui-graphql-rpc/src/types/coin.rs +++ b/crates/sui-graphql-rpc/src/types/coin.rs @@ -315,7 +315,7 @@ impl Coin { let result = page.paginate_raw_query::( conn, rhs, - coins_query(coin_type, owner, lhs as i64, rhs as i64), + coins_query(coin_type, owner, lhs as i64, rhs as i64, &page), )?; Ok(Some((result, rhs))) @@ -371,8 +371,17 @@ impl TryFrom<&MoveObject> for Coin { } } -fn coins_query(coin_type: TypeTag, owner: Option, lhs: i64, rhs: i64) -> RawQuery { - build_objects_query(View::Consistent, lhs, rhs, move |query| { +/// Constructs a raw query to fetch objects from the database. Since there are no point lookups for +/// the coin query, objects are filtered out if they satisfy the criteria but have a later version +/// in the same checkpoint. +fn coins_query( + coin_type: TypeTag, + owner: Option, + lhs: i64, + rhs: i64, + page: &Page, +) -> RawQuery { + build_objects_query(View::Consistent, lhs, rhs, page, move |query| { apply_filter(query, &coin_type, owner) }) } diff --git a/crates/sui-graphql-rpc/src/types/cursor.rs b/crates/sui-graphql-rpc/src/types/cursor.rs index 51b5bc0234df2..16c6bcdcba7d4 100644 --- a/crates/sui-graphql-rpc/src/types/cursor.rs +++ b/crates/sui-graphql-rpc/src/types/cursor.rs @@ -328,18 +328,7 @@ impl Page { T: Send + RawPaginated + FromSqlRow + 'static, { let new_query = move || { - let mut query = query.clone(); - if let Some(after) = self.after() { - query = T::filter_ge(after, query); - } - - if let Some(before) = self.before() { - query = T::filter_le(before, query); - } - - query = T::order(self.is_from_front(), query); - - query = query.limit(self.limit() as i64 + 2); + let query = self.apply::(query.clone()); query.into_boxed() }; @@ -445,6 +434,23 @@ impl Page { (prev, next, results) } + + pub(crate) fn apply(&self, mut query: RawQuery) -> RawQuery + where + T: RawPaginated, + { + if let Some(after) = self.after() { + query = T::filter_ge(after, query); + } + + if let Some(before) = self.before() { + query = T::filter_le(before, query); + } + + query = T::order(self.is_from_front(), query); + + query.limit(self.limit() as i64 + 2) + } } #[Scalar(name = "String", visible = false)] diff --git a/crates/sui-graphql-rpc/src/types/dynamic_field.rs b/crates/sui-graphql-rpc/src/types/dynamic_field.rs index cccc06e452eaf..279f38a2c8019 100644 --- a/crates/sui-graphql-rpc/src/types/dynamic_field.rs +++ b/crates/sui-graphql-rpc/src/types/dynamic_field.rs @@ -345,7 +345,7 @@ fn dynamic_fields_query( // Combine the two queries, and select the most recent version of each object. let candidates = query!( - r#"SELECT DISTINCT ON (object_id) * FROM (({}) UNION ({})) o"#, + r#"SELECT DISTINCT ON (object_id) * FROM (({}) UNION ALL ({})) o"#, snapshot_objs, history_objs ) diff --git a/crates/sui-graphql-rpc/src/types/object.rs b/crates/sui-graphql-rpc/src/types/object.rs index 9dd67a8ac6dba..9a40a05d64df4 100644 --- a/crates/sui-graphql-rpc/src/types/object.rs +++ b/crates/sui-graphql-rpc/src/types/object.rs @@ -22,8 +22,7 @@ use super::transaction_block; use super::transaction_block::TransactionBlockFilter; use super::type_filter::{ExactTypeFilter, TypeFilter}; use super::{owner::Owner, sui_address::SuiAddress, transaction_block::TransactionBlock}; -use crate::consistency::Checkpointed; -use crate::consistency::{build_objects_query, consistent_range, View}; +use crate::consistency::{build_objects_query, consistent_range, Checkpointed, View}; use crate::context_data::package_cache::PackageCache; use crate::data::{self, Db, DbConnection, QueryExecutor}; use crate::error::Error; @@ -755,7 +754,7 @@ impl Object { let result = page.paginate_raw_query::( conn, rhs, - objects_query(&filter, lhs as i64, rhs as i64), + objects_query(&filter, lhs as i64, rhs as i64, &page), )?; Ok(Some((result, rhs))) @@ -1213,6 +1212,10 @@ impl ObjectFilter { query } + + pub(crate) fn has_filters(&self) -> bool { + self != &Default::default() + } } impl HistoricalObjectCursor { @@ -1358,14 +1361,19 @@ pub(crate) async fn deserialize_move_struct( Ok((struct_tag, move_struct)) } -fn objects_query(filter: &ObjectFilter, lhs: i64, rhs: i64) -> RawQuery { - let view = if filter.object_keys.is_some() { +/// Constructs a raw query to fetch objects from the database. Objects are filtered out if they +/// satisfy the criteria but have a later version in the same checkpoint. If object keys are +/// provided, or no filters are specified at all, then this final condition is not applied. +fn objects_query(filter: &ObjectFilter, lhs: i64, rhs: i64, page: &Page) -> RawQuery +where +{ + let view = if filter.object_keys.is_some() || !filter.has_filters() { View::Historical } else { View::Consistent }; - build_objects_query(view, lhs, rhs, move |query| filter.apply(query)) + build_objects_query(view, lhs, rhs, page, move |query| filter.apply(query)) } #[cfg(test)] diff --git a/crates/sui-graphql-rpc/src/types/type_filter.rs b/crates/sui-graphql-rpc/src/types/type_filter.rs index a6c2795a738a8..2bec5eb3fee8c 100644 --- a/crates/sui-graphql-rpc/src/types/type_filter.rs +++ b/crates/sui-graphql-rpc/src/types/type_filter.rs @@ -130,7 +130,7 @@ impl TypeFilter { let generic_pattern = format!("{}<%", tag.to_canonical_display(/* with_prefix */ true)); - let statement = field.to_string() + " = {} OR " + field + " LIKE {}"; + let statement = format!("({field} = {{}} OR {field} LIKE {{}})"); query = filter!(query, statement, exact_pattern, generic_pattern); } diff --git a/crates/sui-transactional-test-runner/src/test_adapter.rs b/crates/sui-transactional-test-runner/src/test_adapter.rs index 538249f79c77c..9c9adb88ab3f9 100644 --- a/crates/sui-transactional-test-runner/src/test_adapter.rs +++ b/crates/sui-transactional-test-runner/src/test_adapter.rs @@ -582,7 +582,7 @@ impl<'a> MoveTestAdapter<'a> for SuiTestAdapter<'a> { let cluster = self.cluster.as_ref().unwrap(); let highest_checkpoint = self.executor.get_latest_checkpoint_sequence_number()?; cluster - .wait_for_checkpoint_catchup(highest_checkpoint, Duration::from_secs(30)) + .wait_for_checkpoint_catchup(highest_checkpoint, Duration::from_secs(60)) .await; cluster