Cache parsed `ExecuteDocument` in APQ. #919

This commit is contained in:
Sunli 2022-05-13 18:17:03 +08:00
parent b2cb673b9c
commit da725575bd
5 changed files with 37 additions and 25 deletions

View File

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Implement `OutputType` for `tokio::sync::RwLock` and `tokio::sync::Mutex`. [#896](https://github.com/async-graphql/async-graphql/pull/896)
- Bump [`uuid`](https://crates.io/crates/uuid) to `1.0.0`. [#907](https://github.com/async-graphql/async-graphql/pull/907/files)
- Add some options for exporting SDL. [#877](https://github.com/async-graphql/async-graphql/issues/877)
- Cache parsed `ExecuteDocument` in APQ. [#919](https://github.com/async-graphql/async-graphql/issues/919)
# [3.0.38] 2022-4-8

View File

@ -41,7 +41,7 @@ impl From<(usize, usize)> for Pos {
}
/// An AST node that stores its original position.
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct Positioned<T: ?Sized> {
/// The position of the node.
pub pos: Pos,

View File

@ -1,13 +1,14 @@
//! Executable document-related GraphQL types.
use async_graphql_value::{ConstValue, Name, Value};
use serde::{Deserialize, Serialize};
use super::*;
/// An executable GraphQL file or request string.
///
/// [Reference](https://spec.graphql.org/October2021/#ExecutableDocument).
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutableDocument {
/// The operations of the document.
pub operations: DocumentOperations,
@ -18,7 +19,7 @@ pub struct ExecutableDocument {
/// The operations of a GraphQL document.
///
/// There is either one anonymous operation or many named operations.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DocumentOperations {
/// The document contains a single anonymous operation.
Single(Positioned<OperationDefinition>),
@ -96,7 +97,7 @@ enum OperationsIterInner<'a> {
/// $content) { id } }`.
///
/// [Reference](https://spec.graphql.org/October2021/#OperationDefinition).
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperationDefinition {
/// The type of operation.
pub ty: OperationType,
@ -112,7 +113,7 @@ pub struct OperationDefinition {
/// `$name:String!`.
///
/// [Reference](https://spec.graphql.org/October2021/#VariableDefinition).
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VariableDefinition {
/// The name of the variable, without the preceding `$`.
pub name: Positioned<Name>,
@ -142,7 +143,7 @@ impl VariableDefinition {
/// A set of fields to be selected, for example `{ name age }`.
///
/// [Reference](https://spec.graphql.org/October2021/#SelectionSet).
#[derive(Debug, Default, Clone)]
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct SelectionSet {
/// The fields to be selected.
pub items: Vec<Positioned<Selection>>,
@ -152,7 +153,7 @@ pub struct SelectionSet {
/// inline fragment.
///
/// [Reference](https://spec.graphql.org/October2021/#Selection).
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Selection {
/// Select a single field, such as `name` or `weightKilos: weight(unit:
/// KILOGRAMS)`.
@ -188,7 +189,7 @@ impl Selection {
/// weight(unit: KILOGRAMS)`.
///
/// [Reference](https://spec.graphql.org/October2021/#Field).
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Field {
/// The optional field alias.
pub alias: Option<Positioned<Name>>,
@ -224,7 +225,7 @@ impl Field {
/// A fragment selector, such as `... userFields`.
///
/// [Reference](https://spec.graphql.org/October2021/#FragmentSpread).
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FragmentSpread {
/// The name of the fragment being selected.
pub fragment_name: Positioned<Name>,
@ -235,7 +236,7 @@ pub struct FragmentSpread {
/// An inline fragment selector, such as `... on User { name }`.
///
/// [Reference](https://spec.graphql.org/October2021/#InlineFragment).
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InlineFragment {
/// The type condition.
pub type_condition: Option<Positioned<TypeCondition>>,
@ -249,7 +250,7 @@ pub struct InlineFragment {
/// age }`.
///
/// [Reference](https://spec.graphql.org/October2021/#FragmentDefinition).
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FragmentDefinition {
/// The type this fragment operates on.
pub type_condition: Positioned<TypeCondition>,
@ -262,7 +263,7 @@ pub struct FragmentDefinition {
/// A type a fragment can apply to (`on` followed by the type).
///
/// [Reference](https://spec.graphql.org/October2021/#TypeCondition).
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypeCondition {
/// The type this fragment applies to.
pub on: Positioned<Name>,

View File

@ -17,6 +17,7 @@ use std::{
use async_graphql_value::{ConstValue, Name, Value};
pub use executable::*;
use serde::{Deserialize, Serialize};
pub use service::*;
use crate::pos::Positioned;
@ -24,7 +25,7 @@ use crate::pos::Positioned;
/// The type of an operation; `query`, `mutation` or `subscription`.
///
/// [Reference](https://spec.graphql.org/October2021/#OperationType).
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub enum OperationType {
/// A query.
Query,
@ -47,7 +48,7 @@ impl Display for OperationType {
/// A GraphQL type, for example `String` or `[String!]!`.
///
/// [Reference](https://spec.graphql.org/October2021/#Type).
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Type {
/// The base type.
pub base: BaseType,
@ -88,7 +89,7 @@ impl Display for Type {
/// A GraphQL base type, for example `String` or `[String!]`. This does not
/// include whether the type is nullable; for that see [Type](struct.Type.html).
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub enum BaseType {
/// A named type, such as `String`.
Named(Name),
@ -146,7 +147,7 @@ impl ConstDirective {
/// A GraphQL directive, such as `@deprecated(reason: "Use the other field")`.
///
/// [Reference](https://spec.graphql.org/October2021/#Directive).
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Directive {
/// The name of the directive.
pub name: Positioned<Name>,

View File

@ -2,6 +2,7 @@
use std::sync::Arc;
use async_graphql_parser::types::ExecutableDocument;
use futures_util::lock::Mutex;
use serde::Deserialize;
use sha2::{Digest, Sha256};
@ -22,15 +23,15 @@ struct PersistedQuery {
#[async_trait::async_trait]
pub trait CacheStorage: Send + Sync + Clone + 'static {
/// Load the query by `key`.
async fn get(&self, key: String) -> Option<String>;
async fn get(&self, key: String) -> Option<ExecutableDocument>;
/// Save the query by `key`.
async fn set(&self, key: String, query: String);
async fn set(&self, key: String, query: ExecutableDocument);
}
/// Memory-based LRU cache.
#[derive(Clone)]
pub struct LruCacheStorage(Arc<Mutex<lru::LruCache<String, String>>>);
pub struct LruCacheStorage(Arc<Mutex<lru::LruCache<String, ExecutableDocument>>>);
impl LruCacheStorage {
/// Creates a new LRU Cache that holds at most `cap` items.
@ -41,12 +42,12 @@ impl LruCacheStorage {
#[async_trait::async_trait]
impl CacheStorage for LruCacheStorage {
async fn get(&self, key: String) -> Option<String> {
async fn get(&self, key: String) -> Option<ExecutableDocument> {
let mut cache = self.0.lock().await;
cache.get(&key).cloned()
}
async fn set(&self, key: String, query: String) {
async fn set(&self, key: String, query: ExecutableDocument) {
let mut cache = self.0.lock().await;
cache.put(key, query);
}
@ -96,8 +97,11 @@ impl<T: CacheStorage> Extension for ApolloPersistedQueriesExtension<T> {
}
if request.query.is_empty() {
if let Some(query) = self.storage.get(persisted_query.sha256_hash).await {
Ok(Request { query, ..request })
if let Some(doc) = self.storage.get(persisted_query.sha256_hash).await {
Ok(Request {
parsed_query: Some(doc),
..request
})
} else {
Err(ServerError::new("PersistedQueryNotFound", None))
}
@ -107,8 +111,13 @@ impl<T: CacheStorage> Extension for ApolloPersistedQueriesExtension<T> {
if persisted_query.sha256_hash != sha256_hash {
Err(ServerError::new("provided sha does not match query", None))
} else {
self.storage.set(sha256_hash, request.query.clone()).await;
Ok(request)
let doc = async_graphql_parser::parse_query(&request.query)?;
self.storage.set(sha256_hash, doc.clone()).await;
Ok(Request {
query: String::new(),
parsed_query: Some(doc),
..request
})
}
}
} else {