From d16c11486134a7d862deda7dbf8b0dee5bf651c4 Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Fri, 13 Sep 2024 14:41:14 +0100 Subject: [PATCH 1/2] docs(graphql): [easy] document transaction, event filters ## Description Document the various filter options for transactions and events (and tweak the docs for object filters slightly). ## Test plan :eyes: --- crates/sui-graphql-rpc/schema.graphql | 41 ++++++++++++++++--- .../sui-graphql-rpc/src/types/event/filter.rs | 10 ++--- crates/sui-graphql-rpc/src/types/object.rs | 7 +--- .../src/types/transaction_block/filter.rs | 15 +++++++ .../snapshot_tests__schema_sdl_export.snap | 41 ++++++++++++++++--- 5 files changed, 94 insertions(+), 20 deletions(-) diff --git a/crates/sui-graphql-rpc/schema.graphql b/crates/sui-graphql-rpc/schema.graphql index b848cd8529973..f6d8946266fdc 100644 --- a/crates/sui-graphql-rpc/schema.graphql +++ b/crates/sui-graphql-rpc/schema.graphql @@ -1264,7 +1264,13 @@ type EventEdge { } input EventFilter { + """ + Filter down to events from transactions sent by this address. + """ sender: SuiAddress + """ + Filter down to the events from this transaction (given by its transaction digest). + """ transactionDigest: String """ Events emitted by a particular module. An event is emitted by a @@ -2836,11 +2842,8 @@ objects are ones whose """ input ObjectFilter { """ - This field is used to specify the type of objects that should be included in the query - results. - - Objects can be filtered by their type's package, package::module, or their fully qualified - type name. + Filter objects by their type's `package`, `package::module`, or their fully qualified type + name. Generic types can be queried by either the generic type name, e.g. `0x2::coin::Coin`, or by the full type name, such as `0x2::coin::Coin<0x2::sui::SUI>`. @@ -4288,18 +4291,46 @@ type TransactionBlockEffects { } input TransactionBlockFilter { + """ + Filter transactions by move function called. Calls can be filtered by the `package`, + `package::module`, or the `package::module::name` of their function. + """ function: String """ An input filter selecting for either system or programmable transactions. """ kind: TransactionBlockKindInput + """ + Limit to transactions that occured strictly after the given checkpoint. + """ afterCheckpoint: UInt53 + """ + Limit to transactions in the given checkpoint. + """ atCheckpoint: UInt53 + """ + Limit to transaction that occured strictly before the given checkpoint. + """ beforeCheckpoint: UInt53 + """ + Limit to transactions that were signed by the given address. + """ signAddress: SuiAddress + """ + Limit to transactions that sent an object to the given address. + """ recvAddress: SuiAddress + """ + Limit to transactions that accepted the given object as an input. + """ inputObject: SuiAddress + """ + Limit to transactions that output a versioon of this object. + """ changedObject: SuiAddress + """ + Select transactions by their digest. + """ transactionIds: [String!] } diff --git a/crates/sui-graphql-rpc/src/types/event/filter.rs b/crates/sui-graphql-rpc/src/types/event/filter.rs index 0a02a31be8969..10144d5fa8a2f 100644 --- a/crates/sui-graphql-rpc/src/types/event/filter.rs +++ b/crates/sui-graphql-rpc/src/types/event/filter.rs @@ -10,10 +10,15 @@ use async_graphql::*; #[derive(InputObject, Clone, Default)] pub(crate) struct EventFilter { + /// Filter down to events from transactions sent by this address. pub sender: Option, + + /// Filter down to the events from this transaction (given by its transaction digest). pub transaction_digest: Option, + // Enhancement (post-MVP) // after_checkpoint + // at_checkpoint // before_checkpoint /// Events emitted by a particular module. An event is emitted by a /// particular module if some function in the module is called by a @@ -36,9 +41,4 @@ pub(crate) struct EventFilter { // Enhancement (post-MVP) // pub start_time // pub end_time - - // Enhancement (post-MVP) - // pub any - // pub all - // pub not } diff --git a/crates/sui-graphql-rpc/src/types/object.rs b/crates/sui-graphql-rpc/src/types/object.rs index aa1240cb3f5d6..2991bc9e83e34 100644 --- a/crates/sui-graphql-rpc/src/types/object.rs +++ b/crates/sui-graphql-rpc/src/types/object.rs @@ -118,11 +118,8 @@ pub(crate) struct ObjectRef { /// - AND, whose ID is in `objectIds` OR whose ID and version is in `objectKeys`. #[derive(InputObject, Default, Debug, Clone, Eq, PartialEq)] pub(crate) struct ObjectFilter { - /// This field is used to specify the type of objects that should be included in the query - /// results. - /// - /// Objects can be filtered by their type's package, package::module, or their fully qualified - /// type name. + /// Filter objects by their type's `package`, `package::module`, or their fully qualified type + /// name. /// /// Generic types can be queried by either the generic type name, e.g. `0x2::coin::Coin`, or by /// the full type name, such as `0x2::coin::Coin<0x2::sui::SUI>`. diff --git a/crates/sui-graphql-rpc/src/types/transaction_block/filter.rs b/crates/sui-graphql-rpc/src/types/transaction_block/filter.rs index 29c104ac9484c..2e40d2e63ae48 100644 --- a/crates/sui-graphql-rpc/src/types/transaction_block/filter.rs +++ b/crates/sui-graphql-rpc/src/types/transaction_block/filter.rs @@ -10,20 +10,35 @@ use sui_types::base_types::SuiAddress as NativeSuiAddress; #[derive(InputObject, Debug, Default, Clone)] pub(crate) struct TransactionBlockFilter { + /// Filter transactions by move function called. Calls can be filtered by the `package`, + /// `package::module`, or the `package::module::name` of their function. pub function: Option, /// An input filter selecting for either system or programmable transactions. pub kind: Option, + + /// Limit to transactions that occured strictly after the given checkpoint. pub after_checkpoint: Option, + + /// Limit to transactions in the given checkpoint. pub at_checkpoint: Option, + + /// Limit to transaction that occured strictly before the given checkpoint. pub before_checkpoint: Option, + /// Limit to transactions that were signed by the given address. pub sign_address: Option, + + /// Limit to transactions that sent an object to the given address. pub recv_address: Option, + /// Limit to transactions that accepted the given object as an input. pub input_object: Option, + + /// Limit to transactions that output a versioon of this object. pub changed_object: Option, + /// Select transactions by their digest. pub transaction_ids: Option>, } diff --git a/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap b/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap index 7b781d6e1de02..9117563abc0a5 100644 --- a/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap +++ b/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap @@ -1268,7 +1268,13 @@ type EventEdge { } input EventFilter { + """ + Filter down to events from transactions sent by this address. + """ sender: SuiAddress + """ + Filter down to the events from this transaction (given by its transaction digest). + """ transactionDigest: String """ Events emitted by a particular module. An event is emitted by a @@ -2840,11 +2846,8 @@ objects are ones whose """ input ObjectFilter { """ - This field is used to specify the type of objects that should be included in the query - results. - - Objects can be filtered by their type's package, package::module, or their fully qualified - type name. + Filter objects by their type's `package`, `package::module`, or their fully qualified type + name. Generic types can be queried by either the generic type name, e.g. `0x2::coin::Coin`, or by the full type name, such as `0x2::coin::Coin<0x2::sui::SUI>`. @@ -4292,18 +4295,46 @@ type TransactionBlockEffects { } input TransactionBlockFilter { + """ + Filter transactions by move function called. Calls can be filtered by the `package`, + `package::module`, or the `package::module::name` of their function. + """ function: String """ An input filter selecting for either system or programmable transactions. """ kind: TransactionBlockKindInput + """ + Limit to transactions that occured strictly after the given checkpoint. + """ afterCheckpoint: UInt53 + """ + Limit to transactions in the given checkpoint. + """ atCheckpoint: UInt53 + """ + Limit to transaction that occured strictly before the given checkpoint. + """ beforeCheckpoint: UInt53 + """ + Limit to transactions that were signed by the given address. + """ signAddress: SuiAddress + """ + Limit to transactions that sent an object to the given address. + """ recvAddress: SuiAddress + """ + Limit to transactions that accepted the given object as an input. + """ inputObject: SuiAddress + """ + Limit to transactions that output a versioon of this object. + """ changedObject: SuiAddress + """ + Select transactions by their digest. + """ transactionIds: [String!] } From c6f728ba34ef053fdc87b02ab1f5e7de8195abb8 Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Fri, 13 Sep 2024 18:34:50 +0100 Subject: [PATCH 2/2] docs(graphql): explain scan limits rationale --- .../src/types/transaction_block/tx_lookups.rs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/crates/sui-graphql-rpc/src/types/transaction_block/tx_lookups.rs b/crates/sui-graphql-rpc/src/types/transaction_block/tx_lookups.rs index 940df4309a2ec..327c23c7407f9 100644 --- a/crates/sui-graphql-rpc/src/types/transaction_block/tx_lookups.rs +++ b/crates/sui-graphql-rpc/src/types/transaction_block/tx_lookups.rs @@ -1,6 +1,55 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +//! # Transaction Filter Lookup Tables +//! +//! ## Schemas +//! +//! Tables backing Transaction filters in GraphQL all follow the same rough shape: +//! +//! 1. They each get their own table, mapping the filter value to the transaction sequence number. +//! +//! 2. They also include a `sender` column, and a secondary index over the sender, filter values +//! and the transaction sequence number. +//! +//! 3. They also include a secondary index over the transaction sequence number. +//! +//! This pattern allows us to offer a simple rule for users: If you are filtering on a single +//! value, you can do so without worrying. If you want to additionally filter by the sender, that +//! is also possible, but if you want to combine any other set of filters, you need to use a "scan +//! limit". +//! +//! ## Query construction +//! +//! Queries that filter transactions work in two phases: Identify the transaction sequence numbers +//! to fetch, and then fetch their contents. Filtering all happens in the first phase: +//! +//! - Firstly filters are broken down into individual queries targeting the appropriate lookup +//! table. Each constituent query is expected to return a sorted run of transaction sequence +//! numbers. +//! +//! - If a `sender` filter is included, then it is incorporated into each constituent query, +//! leveraging their secondary indices (2), otherwise each constituent query filters only based on +//! its filter value using the primary index (1). +//! +//! - The fact that both the primary and secondary indices contain the transaction sequence number +//! help to ensure that the output from an index scan is already sorted, which avoids a +//! potentially expensive materialize and sort operation. +//! +//! - If there are multiple constituent queries, they are intersected using inner joins. Postgres +//! can occasionally pick a poor query plan for this merge, so we require that filters resulting in +//! such merges also use a "scan limit" (see below). +//! +//! ## Scan limits +//! +//! The scan limit restricts the number of transactions considered as candidates for the results. +//! It is analogous to the page size limit, which restricts the number of results returned to the +//! user, but it operates at the top of the funnel rather than the top. +//! +//! When postgres picks a poor query plan, it can end up performing a sequential scan over all +//! candidate transactions. By limiting the size of the candidate set, we bound the work done in +//! the worse case (whereas otherwise, the worst case would grow with the history of the chain). + use super::{Cursor, TransactionBlockFilter}; use crate::{ data::{pg::bytea_literal, Conn, DbConnection},