Cache parsed `ExecuteDocument` in APQ. #919
This commit is contained in:
parent
b2cb673b9c
commit
da725575bd
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue