Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 20 additions & 12 deletions bluejay-core/src/argument.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::{AsIter, Value};
use std::collections::HashMap;

pub trait Argument<const CONST: bool> {
type Value: Value<CONST>;
Expand All @@ -18,17 +17,26 @@ pub trait Arguments<const CONST: bool>: AsIter<Item = Self::Argument> {
type Argument: Argument<CONST>;

fn equivalent(optional_self: Option<&Self>, optional_other: Option<&Self>) -> bool {
let lhs: HashMap<&str, _> = optional_self
.map(|args| {
HashMap::from_iter(args.iter().map(|arg| (arg.name(), arg.value().as_ref())))
})
.unwrap_or_default();
let rhs: HashMap<&str, _> = optional_other
.map(|args| {
HashMap::from_iter(args.iter().map(|arg| (arg.name(), arg.value().as_ref())))
})
.unwrap_or_default();
lhs == rhs
match (optional_self, optional_other) {
(None, None) => true,
(None, Some(other)) => other.is_empty(),
(Some(s), None) => s.is_empty(),
(Some(s), Some(other)) => {
// For small argument lists, use O(n*m) comparison which avoids HashMap allocation
let s_count = s.len();
let o_count = other.len();
if s_count != o_count {
return false;
}
// Every arg in self must have a matching arg in other (same name, same value)
s.iter().all(|s_arg| {
other.iter().any(|o_arg| {
s_arg.name() == o_arg.name()
&& s_arg.value().as_ref() == o_arg.value().as_ref()
})
})
}
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions bluejay-validator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,9 @@ serde_json = ["dep:serde_json", "bluejay-core/serde_json"]
name = "field_selection_merging"
harness = false

[[bench]]
name = "validate"
harness = false

[lints]
workspace = true
186 changes: 186 additions & 0 deletions bluejay-validator/benches/validate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
use bluejay_parser::ast::{
definition::{DefinitionDocument, SchemaDefinition},
executable::ExecutableDocument,
Parse,
};
use bluejay_validator::executable::{document::BuiltinRulesValidator, Cache};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use std::sync::LazyLock;

const SCHEMA: &str = include_str!("../tests/test_data/executable/schema.graphql");

// A realistic query with fragments, variables, inline fragments, and nested fields
const QUERY_SIMPLE: &str = r#"
query GetDog($command: DogCommand!, $atOtherHomes: Boolean) {
dog {
name
nickname
barkVolume
doesKnowCommand(dogCommand: $command)
isHouseTrained(atOtherHomes: $atOtherHomes)
owner {
name
}
}
}
"#;

const QUERY_FRAGMENTS: &str = r#"
query GetPetWithFragments($command: DogCommand!) {
dog {
...dogFields
owner {
...humanFields
}
}
pet {
...petFields
}
}

fragment dogFields on Dog {
name
nickname
barkVolume
doesKnowCommand(dogCommand: $command)
owner {
...humanFields
}
}

fragment humanFields on Human {
name
}

fragment petFields on Pet {
name
... on Dog {
barkVolume
...dogFields
}
... on Cat {
meowVolume
}
}
"#;

const QUERY_COMPLEX: &str = r#"
query Complex($command: DogCommand!, $atOtherHomes: Boolean) {
dog {
...f1
...f2
...f3
}
pet {
...petFragment
}
}

fragment f1 on Dog {
name
nickname
barkVolume
doesKnowCommand(dogCommand: $command)
isHouseTrained(atOtherHomes: $atOtherHomes)
owner { ...humanFrag }
}

fragment f2 on Dog {
name
nickname
barkVolume
doesKnowCommand(dogCommand: $command)
isHouseTrained(atOtherHomes: $atOtherHomes)
owner { ...humanFrag }
}

fragment f3 on Dog {
name
nickname
barkVolume
doesKnowCommand(dogCommand: $command)
isHouseTrained(atOtherHomes: $atOtherHomes)
owner { ...humanFrag }
}

fragment humanFrag on Human {
name
}

fragment petFragment on Pet {
name
... on Dog {
barkVolume
nickname
...f1
}
... on Cat {
meowVolume
nickname
}
}
"#;

static DEFINITION_DOCUMENT: LazyLock<DefinitionDocument<'static>> = LazyLock::new(|| {
DefinitionDocument::parse(SCHEMA)
.result
.expect("Schema had parse errors")
});
static SCHEMA_DEFINITION: LazyLock<SchemaDefinition<'static>> =
LazyLock::new(|| SchemaDefinition::try_from(&*DEFINITION_DOCUMENT).expect("Schema had errors"));

struct QueryCase {
name: &'static str,
source: &'static str,
}

static CASES: &[QueryCase] = &[
QueryCase {
name: "simple",
source: QUERY_SIMPLE,
},
QueryCase {
name: "fragments",
source: QUERY_FRAGMENTS,
},
QueryCase {
name: "complex",
source: QUERY_COMPLEX,
},
];

fn bench_validate(c: &mut Criterion) {
let mut group = c.benchmark_group("validate");
for case in CASES {
let doc = ExecutableDocument::parse(case.source)
.result
.expect("Document had parse errors");
group.bench_with_input(BenchmarkId::from_parameter(case.name), &doc, |b, doc| {
b.iter(|| {
let cache = Cache::new(doc, &*SCHEMA_DEFINITION);
let errors: Vec<_> =
BuiltinRulesValidator::validate(doc, &*SCHEMA_DEFINITION, &cache).collect();
assert!(errors.is_empty());
});
});
}
group.finish();
}

fn bench_cache_construction(c: &mut Criterion) {
let mut group = c.benchmark_group("cache_construction");
for case in CASES {
let doc = ExecutableDocument::parse(case.source)
.result
.expect("Document had parse errors");
group.bench_with_input(BenchmarkId::from_parameter(case.name), &doc, |b, doc| {
b.iter(|| {
let _cache = Cache::new(doc, &*SCHEMA_DEFINITION);
});
});
}
group.finish();
}

criterion_group!(benches, bench_validate, bench_cache_construction);
criterion_main!(benches);
21 changes: 7 additions & 14 deletions bluejay-validator/src/executable/document/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,31 @@ use std::hash::{Hash, Hasher};

pub struct Path<'a, E: ExecutableDocument> {
root: PathRoot<'a, E>,
members: Vec<&'a E::Selection>,
}

impl<E: ExecutableDocument> Clone for Path<'_, E> {
fn clone(&self) -> Self {
Self {
root: self.root,
members: self.members.clone(),
}
*self
}
}

impl<E: ExecutableDocument> Copy for Path<'_, E> {}

impl<'a, E: ExecutableDocument> Path<'a, E> {
pub fn new(root: PathRoot<'a, E>) -> Self {
Self {
root,
members: Vec::new(),
}
Self { root }
}

pub fn root(&self) -> &PathRoot<'a, E> {
&self.root
}

pub fn with_selection(&self, selection: &'a E::Selection) -> Self {
let mut clone = self.clone();
clone.members.push(selection);
clone
pub fn with_selection(&self, _selection: &'a E::Selection) -> Self {
*self
}

pub fn members(&self) -> &[&'a E::Selection] {
&self.members
&[]
}
}

Expand Down
Loading
Loading