Repository: apollographql/apollo-rs
Pull request #838 (2024-02-16 22:25:45)

Remove Salsa database for 1.2× ~ 2× validation speed
See changelog


Patch:
diff --git a/crates/apollo-compiler/CHANGELOG.md b/crates/apollo-compiler/CHANGELOG.md
index f3f4ee2..4ed6eb9 100644
--- a/crates/apollo-compiler/CHANGELOG.md
+++ b/crates/apollo-compiler/CHANGELOG.md
@@ -17,6 +17,34 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 ## Maintenance
 ## Documentation-->
 
+# [x.x.x] (unreleased) - 2024-mm-dd
+
+## BREAKING
+
+- **Move `NodeLocation` and `FileId` from `apollo_compiler::validation` to the crate root - [SimonSapin], [pull/838]**
+
+## Maintenance
+
+- **Remove Salsa database for 1.2× ~ 2× validation speed - [SimonSapin], [pull/838]**
+  We were not taking advantage of caching it provides.
+  Additionally, validation uses `Schema` and `ExecutableDocument` directly
+  rather than converting them to AST.
+  ```
+  $ cargo bench --bench multi-source
+  supergraph parse_and_validate
+                        time:   [398.09 µs 399.32 µs 400.82 µs]
+                        change: [-19.966% -19.418% -18.864%] (p = 0.00 < 0.05)
+                        Performance has improved.
+  simple_query parse_and_validate
+                        time:   [12.097 µs 12.104 µs 12.113 µs]
+                        change: [-52.467% -52.282% -52.109%] (p = 0.00 < 0.05)
+                        Performance has improved.
+  ```
+
+[SimonSapin]: https://github.com/SimonSapin]
+[pull/838]: https://github.com/apollographql/apollo-rs/pull/838
+
+
 # [1.0.0-beta.13](https://crates.io/crates/apollo-compiler/1.0.0-beta.13) - 2024-02-14
 
 > This release includes a critical fix to overflow protection in validation.
diff --git a/crates/apollo-compiler/Cargo.toml b/crates/apollo-compiler/Cargo.toml
index a50dee9..3bbcb4a 100644
--- a/crates/apollo-compiler/Cargo.toml
+++ b/crates/apollo-compiler/Cargo.toml
@@ -22,7 +22,6 @@ apollo-parser = { path = "../apollo-parser", version = "0.7.6" }
 ariadne = { version = "0.4.0", features = ["auto-color"] }
 indexmap = "2.0.0"
 rowan = "0.15.5"
-salsa = "0.16.1"
 serde = { version = "1.0", features = ["derive"] }
 serde_json_bytes = { version = "0.2.2", features = ["preserve_order"] }
 thiserror = "1.0.31"
diff --git a/crates/apollo-compiler/benches/multi_source.rs b/crates/apollo-compiler/benches/multi_source.rs
index aad36d2..1f75d60 100644
--- a/crates/apollo-compiler/benches/multi_source.rs
+++ b/crates/apollo-compiler/benches/multi_source.rs
@@ -1,59 +1,43 @@
+use apollo_compiler::ast::Document;
 use apollo_compiler::ExecutableDocument;
 use apollo_compiler::Schema;
 use criterion::*;
 
-fn compile(schema: &str, query: &str) {
-    let schema = Schema::parse_and_validate(schema, "schema.graphql").unwrap();
-    let doc = ExecutableDocument::parse(&schema, query, "query.graphql").unwrap();
+fn parse_ast(schema: &str, query: &str) {
+    let schema = Document::parse(schema, "schema.graphql").unwrap();
+    let doc = Document::parse(query, "query.graphql").unwrap();
     black_box((schema, doc));
 }
 
-fn compile_and_validate(schema: &str, query: &str) {
+fn parse_and_validate(schema: &str, query: &str) {
     let schema = Schema::parse_and_validate(schema, "schema.graphql").unwrap();
     let doc = ExecutableDocument::parse_and_validate(&schema, query, "query.graphql").unwrap();
     black_box((schema, doc));
 }
 
-fn bench_simple_query_compiler(c: &mut Criterion) {
+fn bench_simple_query(c: &mut Criterion) {
     let query = include_str!("testdata/simple_query.graphql");
     let schema = include_str!("testdata/simple_schema.graphql");
 
-    c.bench_function("simple_query_compiler", move |b| {
-        b.iter(|| compile(schema, query))
+    c.bench_function("simple_query parse_ast", move |b| {
+        b.iter(|| parse_ast(schema, query))
     });
-}
-
-fn bench_simple_query_compiler_with_validation(c: &mut Criterion) {
-    let query = include_str!("testdata/simple_query.graphql");
-    let schema = include_str!("testdata/simple_schema.graphql");
-
-    c.bench_function("simple_query_compiler_with_validation", move |b| {
-        b.iter(|| compile_and_validate(schema, query))
+    c.bench_function("simple_query parse_and_validate", move |b| {
+        b.iter(|| parse_and_validate(schema, query))
     });
 }
 
-fn bench_compiler_supergraph(c: &mut Criterion) {
+fn bench_supergraph(c: &mut Criterion) {
     let schema = include_str!("testdata/supergraph.graphql");
     let query = include_str!("testdata/supergraph_query.graphql");
 
-    c.bench_function("supergraph_compiler", move |b| {
-        b.iter(|| compile(schema, query))
+    c.bench_function("supergraph parse_ast", move |b| {
+        b.iter(|| parse_ast(schema, query))
     });
-}
-fn bench_compiler_supergraph_with_validation(c: &mut Criterion) {
-    let schema = include_str!("testdata/supergraph.graphql");
-    let query = include_str!("testdata/supergraph_query.graphql");
-
-    c.bench_function("supergraph_compiler_with_validation", move |b| {
-        b.iter(|| compile_and_validate(schema, query))
+    c.bench_function("supergraph parse_and_validate", move |b| {
+        b.iter(|| parse_and_validate(schema, query))
     });
 }
 
-criterion_group!(
-    benches,
-    bench_compiler_supergraph,
-    bench_compiler_supergraph_with_validation,
-    bench_simple_query_compiler,
-    bench_simple_query_compiler_with_validation
-);
+criterion_group!(benches, bench_supergraph, bench_simple_query);
 criterion_main!(benches);
diff --git a/crates/apollo-compiler/src/ast/from_cst.rs b/crates/apollo-compiler/src/ast/from_cst.rs
index adacb11..21a03d9 100644
--- a/crates/apollo-compiler/src/ast/from_cst.rs
+++ b/crates/apollo-compiler/src/ast/from_cst.rs
@@ -1,8 +1,8 @@
 use crate::ast;
 use crate::ast::Document;
 use crate::validation::FileId;
-use crate::validation::NodeLocation;
 use crate::Node;
+use crate::NodeLocation;
 use crate::SourceMap;
 use apollo_parser::cst;
 use apollo_parser::cst::CstNode;
diff --git a/crates/apollo-compiler/src/ast/impls.rs b/crates/apollo-compiler/src/ast/impls.rs
index 86311a6..4d597d0 100644
--- a/crates/apollo-compiler/src/ast/impls.rs
+++ b/crates/apollo-compiler/src/ast/impls.rs
@@ -470,51 +470,30 @@ impl DirectiveDefinition {
 impl SchemaDefinition {
     serialize_method!();
 }
-impl Extensible for SchemaDefinition {
-    type Extension = SchemaExtension;
-}
 
 impl ScalarTypeDefinition {
     serialize_method!();
 }
-impl Extensible for ScalarTypeDefinition {
-    type Extension = ScalarTypeExtension;
-}
 
 impl ObjectTypeDefinition {
     serialize_method!();
 }
-impl Extensible for ObjectTypeDefinition {
-    type Extension = ObjectTypeExtension;
-}
 
 impl InterfaceTypeDefinition {
     serialize_method!();
 }
-impl Extensible for InterfaceTypeDefinition {
-    type Extension = InterfaceTypeExtension;
-}
 
 impl UnionTypeDefinition {
     serialize_method!();
 }
-impl Extensible for UnionTypeDefinition {
-    type Extension = UnionTypeExtension;
-}
 
 impl EnumTypeDefinition {
     serialize_method!();
 }
-impl Extensible for EnumTypeDefinition {
-    type Extension = EnumTypeExtension;
-}
 
 impl InputObjectTypeDefinition {
     serialize_method!();
 }
-impl Extensible for InputObjectTypeDefinition {
-    type Extension = InputObjectTypeExtension;
-}
 
 impl SchemaExtension {
     serialize_method!();
@@ -1840,62 +1819,3 @@ impl fmt::Debug for InvalidNameError {
         fmt::Display::fmt(self, f)
     }
 }
-
-impl<T: Extensible> TypeWithExtensions<T> {
-    /// Iterate over elements of the base definition and its extensions.
-    pub fn iter_all<'a, Item, DefIter, ExtIter>(
-        &'a self,
-        mut map_definition: impl FnMut(&'a Node<T>) -> DefIter + 'a,
-        map_extension: impl FnMut(&'a Node<T::Extension>) -> ExtIter + 'a,
-    ) -> impl Iterator<Item = Item> + 'a
-    where
-        Item: 'a,
-        DefIter: Iterator<Item = Item> + 'a,
-        ExtIter: Iterator<Item = Item> + 'a,
-    {
-        map_definition(&self.definition).chain(self.extensions.iter().flat_map(map_extension))
-    }
-}
-
-macro_rules! iter_extensible_method {
-    ($property:ident, $ty:ty) => {
-        pub fn $property(&self) -> impl Iterator<Item = &'_ $ty> + '_ {
-            self.iter_all(
-                |definition| definition.$property.iter(),
-                |extension| extension.$property.iter(),
-            )
-        }
-    };
-}
-
-impl TypeWithExtensions<SchemaDefinition> {
-    iter_extensible_method!(directives, Node<Directive>);
-    iter_extensible_method!(root_operations, Node<(OperationType, NamedType)>);
-}
-
-impl TypeWithExtensions<ObjectTypeDefinition> {
-    iter_extensible_method!(directives, Node<Directive>);
-    iter_extensible_method!(fields, Node<FieldDefinition>);
-    iter_extensible_method!(implements_interfaces, NamedType);
-}
-
-impl TypeWithExtensions<InterfaceTypeDefinition> {
-    iter_extensible_method!(directives, Node<Directive>);
-    iter_extensible_method!(fields, Node<FieldDefinition>);
-    iter_extensible_method!(implements_interfaces, NamedType);
-}
-
-impl TypeWithExtensions<UnionTypeDefinition> {
-    iter_extensible_method!(directives, Node<Directive>);
-    iter_extensible_method!(members, NamedType);
-}
-
-impl TypeWithExtensions<EnumTypeDefinition> {
-    iter_extensible_method!(directives, Node<Directive>);
-    iter_extensible_method!(values, Node<EnumValueDefinition>);
-}
-
-impl TypeWithExtensions<InputObjectTypeDefinition> {
-    iter_extensible_method!(directives, Node<Directive>);
-    iter_extensible_method!(fields, Node<InputValueDefinition>);
-}
diff --git a/crates/apollo-compiler/src/ast/mod.rs b/crates/apollo-compiler/src/ast/mod.rs
index 61fc297..606995a 100644
--- a/crates/apollo-compiler/src/ast/mod.rs
+++ b/crates/apollo-compiler/src/ast/mod.rs
@@ -33,7 +33,6 @@
 
 use crate::Node;
 use crate::NodeStr;
-use std::collections::HashMap;
 
 pub(crate) mod from_cst;
 pub(crate) mod impls;
@@ -363,28 +362,3 @@ pub struct FloatValue(String);
 #[derive(Clone, Eq, PartialEq)]
 #[non_exhaustive]
 pub struct FloatOverflowError {}
-
-/// Trait implemented by extensible type definitions, to associate the extension type with the base
-/// definition type.
-pub(crate) trait Extensible {
-    type Extension;
-}
-
-#[derive(Debug, Clone, Hash, PartialEq, Eq)]
-pub(crate) struct TypeWithExtensions<T: Extensible> {
-    pub definition: Node<T>,
-    pub extensions: Vec<Node<T::Extension>>,
-}
-
-// TODO(@goto-bus-stop): may have to do Arc<TypeWithExtensions> as we need to clone
-// it for salsa reasons. OR pass (object|scalar|etc, Name) tuples to the salsa queries.
-#[derive(Debug, PartialEq, Eq)]
-pub(crate) struct TypeSystem {
-    pub schema: TypeWithExtensions<SchemaDefinition>,
-    pub objects: HashMap<Name, TypeWithExtensions<ObjectTypeDefinition>>,
-    pub scalars: HashMap<Name, TypeWithExtensions<ScalarTypeDefinition>>,
-    pub interfaces: HashMap<Name, TypeWithExtensions<InterfaceTypeDefinition>>,
-    pub unions: HashMap<Name, TypeWithExtensions<UnionTypeDefinition>>,
-    pub enums: HashMap<Name, TypeWithExtensions<EnumTypeDefinition>>,
-    pub input_objects: HashMap<Name, TypeWithExtensions<InputObjectTypeDefinition>>,
-}
diff --git a/crates/apollo-compiler/src/database/db.rs b/crates/apollo-compiler/src/database/db.rs
deleted file mode 100644
index 52df31c..0000000
--- a/crates/apollo-compiler/src/database/db.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-// All .expect() calls are used for parts of the GraphQL grammar that are
-// non-optional and will have an error produced in the parser if they are missing.
-
-use crate::database::InputStorage;
-use crate::database::ReprStorage;
-use crate::validation::ValidationStorage;
-
-#[salsa::database(InputStorage, ReprStorage, ValidationStorage)]
-#[derive(Default)]
-pub struct RootDatabase {
-    pub storage: salsa::Storage<RootDatabase>,
-}
-
-impl salsa::Database for RootDatabase {}
-
-// NOTE @lrlna: uncomment when we are fully thread-safe.
-
-impl salsa::ParallelDatabase for RootDatabase {
-    fn snapshot(&self) -> salsa::Snapshot<RootDatabase> {
-        salsa::Snapshot::new(RootDatabase {
-            storage: self.storage.snapshot(),
-        })
-    }
-}
diff --git a/crates/apollo-compiler/src/database/inputs.rs b/crates/apollo-compiler/src/database/inputs.rs
deleted file mode 100644
index d54ecb3..0000000
--- a/crates/apollo-compiler/src/database/inputs.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-use super::sources::{FileId, Source, SourceType};
-use std::sync::Arc;
-
-#[salsa::query_group(InputStorage)]
-pub(crate) trait InputDatabase {
-    /// Get input source of the corresponding file.
-    #[salsa::input]
-    fn input(&self, file_id: FileId) -> Source;
-
-    #[salsa::input]
-    fn schema_input(&self) -> Option<Arc<crate::Schema>>;
-
-    /// Get the GraphQL source text for a file.
-    #[salsa::invoke(source_code)]
-    fn source_code(&self, file_id: FileId) -> Arc<String>;
-
-    /// Get the source type (document/schema/executable) for a file.
-    #[salsa::invoke(source_type)]
-    fn source_type(&self, file_id: FileId) -> SourceType;
-
-    /// Get all file ids currently in the compiler.
-    #[salsa::input]
-    fn source_files(&self) -> Vec<FileId>;
-
-    /// Get all type system definition (GraphQL schema) files.
-    #[salsa::invoke(type_definition_files)]
-    fn type_definition_files(&self) -> Vec<FileId>;
-
-    /// Get all executable definition (GraphQL query) files.
-    #[salsa::invoke(executable_definition_files)]
-    fn executable_definition_files(&self) -> Vec<FileId>;
-}
-
-fn source_code(db: &dyn InputDatabase, file_id: FileId) -> Arc<String> {
-    db.input(file_id).text().clone()
-}
-
-fn source_type(db: &dyn InputDatabase, file_id: FileId) -> SourceType {
-    db.input(file_id).source_type()
-}
-
-fn type_definition_files(db: &dyn InputDatabase) -> Vec<FileId> {
-    db.source_files()
-        .into_iter()
-        .filter(|source| {
-            matches!(
-                db.source_type(*source),
-                SourceType::Schema | SourceType::Document
-            )
-        })
-        .collect()
-}
-
-fn executable_definition_files(db: &dyn InputDatabase) -> Vec<FileId> {
-    db.source_files()
-        .into_iter()
-        .filter(|source| {
-            matches!(
-                db.source_type(*source),
-                SourceType::Executable | SourceType::Document
-            )
-        })
-        .collect()
-}
diff --git a/crates/apollo-compiler/src/database/mod.rs b/crates/apollo-compiler/src/database/mod.rs
deleted file mode 100644
index 9595e51..0000000
--- a/crates/apollo-compiler/src/database/mod.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-pub mod db;
-
-mod inputs;
-mod repr;
-mod sources;
-
-pub(crate) use db::RootDatabase;
-pub(crate) use inputs::{InputDatabase, InputStorage};
-pub(crate) use repr::{ReprDatabase, ReprStorage};
-pub use sources::FileId;
-pub(crate) use sources::Source;
-pub(crate) use sources::SourceType;
diff --git a/crates/apollo-compiler/src/database/repr.rs b/crates/apollo-compiler/src/database/repr.rs
deleted file mode 100644
index ce797a1..0000000
--- a/crates/apollo-compiler/src/database/repr.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-use super::sources::SourceType;
-use super::InputDatabase;
-use crate::ast;
-use crate::schema::Implementers;
-use crate::schema::Name;
-use crate::validation::DiagnosticList;
-use crate::validation::FileId;
-use crate::validation::WithErrors;
-use std::collections::HashMap;
-use std::sync::Arc;
-
-/// Queries for parsing into the various in-memory representations of GraphQL documents
-#[salsa::query_group(ReprStorage)]
-pub(crate) trait ReprDatabase: InputDatabase {
-    #[salsa::invoke(ast)]
-    #[salsa::transparent]
-    fn ast(&self, file_id: FileId) -> Arc<ast::Document>;
-
-    #[salsa::invoke(schema)]
-    fn schema(&self) -> Arc<crate::Schema>;
-
-    #[salsa::invoke(executable_document)]
-    fn executable_document(&self, file_id: FileId) -> Arc<crate::ExecutableDocument>;
-
-    // TODO: another database trait? what to name it?
-
-    /// Returns a map of interface names to names of types that implement that interface
-    ///
-    /// `Schema` only stores the inverse relationship
-    /// (in [`ObjectType::implements_interfaces`] and [`InterfaceType::implements_interfaces`]),
-    /// so iterating the implementers of an interface requires a linear scan
-    /// of all types in the schema.
-    /// If that is repeated for multiple interfaces,
-    /// gathering them all at once amorticizes that cost.
-    #[salsa::invoke(implementers_map)]
-    fn implementers_map(&self) -> Arc<HashMap<Name, Implementers>>;
-}
-
-fn ast(db: &dyn ReprDatabase, file_id: FileId) -> Arc<ast::Document> {
-    db.input(file_id).ast.clone().unwrap()
-}
-
-fn schema(db: &dyn ReprDatabase) -> Arc<crate::Schema> {
-    if let Some(schema) = db.schema_input() {
-        return schema;
-    }
-    let mut builder = crate::Schema::builder();
-    for file_id in db.type_definition_files() {
-        let executable_definitions_are_errors = db.source_type(file_id) != SourceType::Document;
-        let ast = db.ast(file_id);
-        builder.add_ast_document(&ast, executable_definitions_are_errors);
-    }
-    match builder.build() {
-        Ok(schema)
-        | Err(WithErrors {
-            partial: schema, ..
-        }) => Arc::new(schema),
-    }
-}
-
-fn executable_document(db: &dyn ReprDatabase, file_id: FileId) -> Arc<crate::ExecutableDocument> {
-    let source_type = db.source_type(file_id);
-    let type_system_definitions_are_errors = source_type != SourceType::Document;
-    let mut errors = DiagnosticList::new(Default::default());
-    let executable = crate::executable::from_ast::document_from_ast(
-        Some(&db.schema()),
-        &db.ast(file_id),
-        &mut errors,
-        type_system_definitions_are_errors,
-    );
-    Arc::new(executable)
-}
-
-fn implementers_map(db: &dyn ReprDatabase) -> Arc<HashMap<Name, Implementers>> {
-    Arc::new(db.schema().implementers_map())
-}
diff --git a/crates/apollo-compiler/src/database/sources.rs b/crates/apollo-compiler/src/database/sources.rs
deleted file mode 100644
index 7647bb4..0000000
--- a/crates/apollo-compiler/src/database/sources.rs
+++ /dev/null
@@ -1,96 +0,0 @@
-use std::sync::Arc;
-use std::{fmt, num::NonZeroI64, path::PathBuf, sync::atomic};
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub enum SourceType {
-    Schema,
-    Executable,
-    Document,
-    TextOnly,
-}
-
-/// Represents a GraphQL source file.
-#[derive(Clone, Debug, Hash, PartialEq, Eq)]
-pub(crate) struct Source {
-    pub(crate) ty: SourceType,
-    pub(crate) filename: PathBuf,
-    pub(crate) text: Arc<String>,
-    pub(crate) ast: Option<Arc<crate::ast::Document>>,
-}
-
-impl Source {
-    pub fn source_type(&self) -> SourceType {
-        self.ty
-    }
-
-    pub fn text(&self) -> &Arc<String> {
-        &self.text
-    }
-}
-
-impl From<&'_ Arc<crate::SourceFile>> for Source {
-    fn from(file: &'_ Arc<crate::SourceFile>) -> Self {
-        Self {
-            ty: crate::database::SourceType::TextOnly,
-            filename: file.path.clone(),
-            text: Arc::new(file.source_text.clone()),
-            ast: None,
-        }
-    }
-}
-
-/// Integer identifier for a parsed source file.
-///
-/// Used internally to support validating for example a schema built from multiple source files,
-/// and having diagnostics point to relevant sources.
-#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
-pub struct FileId {
-    id: NonZeroI64,
-}
-
-impl fmt::Debug for FileId {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.id.fmt(f)
-    }
-}
-
-/// The next file ID to use. This is global so file IDs do not conflict between different compiler
-/// instances.
-static NEXT: atomic::AtomicI64 = atomic::AtomicI64::new(INITIAL);
-static INITIAL: i64 = 1;
-
-impl FileId {
-    /// The ID of the file implicitly added to type systems, for built-in scalars and introspection types
-    pub const BUILT_IN: Self = Self::const_new(-1);
-
-    /// Passed to Ariadne to create a report without a location
-    pub(crate) const NONE: Self = Self::const_new(-2);
-
-    pub(crate) const HACK_TMP: Self = Self::const_new(-3);
-
-    // Returning a different value every time does not sound like good `impl Default`
-    #[allow(clippy::new_without_default)]
-    pub fn new() -> Self {
-        let id = NEXT.fetch_add(1, atomic::Ordering::AcqRel);
-        Self {
-            id: NonZeroI64::new(id).unwrap(),
-        }
-    }
-
-    /// Reset file ID back to 1, used to get consistent results in tests.
-    ///
-    /// All tests in the process must use `#[serial_test::serial]`
-    #[doc(hidden)]
-    pub fn reset() {
-        NEXT.store(INITIAL, atomic::Ordering::Release)
-    }
-
-    const fn const_new(id: i64) -> Self {
-        // TODO: use unwrap() when const-stable https://github.com/rust-lang/rust/issues/67441
-        if let Some(id) = NonZeroI64::new(id) {
-            Self { id }
-        } else {
-            panic!()
-        }
-    }
-}
diff --git a/crates/apollo-compiler/src/diagnostic.rs b/crates/apollo-compiler/src/diagnostic.rs
index d65b8e1..fc0634c 100644
--- a/crates/apollo-compiler/src/diagnostic.rs
+++ b/crates/apollo-compiler/src/diagnostic.rs
@@ -4,11 +4,11 @@
 //! To use pretty-printing in custom errors, implement the [`ToCliReport`] trait.
 //!
 //! ```rust
+//! use apollo_compiler::NodeLocation;
 //! use apollo_compiler::Schema;
 //! use apollo_compiler::ast::Name;
 //! use apollo_compiler::diagnostic::CliReport;
 //! use apollo_compiler::diagnostic::Diagnostic;
-//! use apollo_compiler::diagnostic::NodeLocation;
 //! use apollo_compiler::diagnostic::ToCliReport;
 //!
 //! /// Error type for a small GraphQL schema linter.
@@ -52,7 +52,7 @@
 //! ready for formatting:
 //!
 //! ```rust
-//! # use apollo_compiler::{Schema, diagnostic::{ToCliReport, NodeLocation, CliReport}};
+//! # use apollo_compiler::{NodeLocation, Schema, diagnostic::{ToCliReport, CliReport}};
 //! # #[derive(Debug, thiserror::Error)]
 //! # #[error("")]
 //! # struct LintError {}
@@ -70,22 +70,20 @@
 use crate::execution::GraphQLError;
 use crate::execution::GraphQLLocation;
 use crate::validation::FileId;
+use crate::NodeLocation;
 use crate::SourceFile;
 use crate::SourceMap;
+#[cfg(doc)]
+use crate::{ExecutableDocument, Schema};
 use ariadne::ColorGenerator;
 use ariadne::ReportKind;
+use std::cell::Cell;
 use std::fmt;
 use std::io;
 use std::ops::Range;
 use std::sync::Arc;
 use std::sync::OnceLock;
 
-pub use crate::validation::NodeLocation;
-
-#[cfg(doc)]
-use crate::{ExecutableDocument, Schema};
-use std::cell::Cell;
-
 /// An error bundled together with a source map, for conversion either
 /// to a pretty-printable CLI report or to a JSON-serializable GraphQL error.
 ///
@@ -290,7 +288,7 @@ impl ariadne::Cache<FileId> for Cache<'_> {
         }
         if let Some(source_file) = self.0.get(file_id) {
             Ok(source_file.ariadne())
-        } else if *file_id == FileId::NONE || *file_id == FileId::HACK_TMP {
+        } else if *file_id == FileId::NONE {
             static EMPTY: OnceLock<ariadne::Source> = OnceLock::new();
             Ok(EMPTY.get_or_init(|| ariadne::Source::from(String::new())))
         } else {
diff --git a/crates/apollo-compiler/src/executable/mod.rs b/crates/apollo-compiler/src/executable/mod.rs
index cfd1cd8..2f0f9c8 100644
--- a/crates/apollo-compiler/src/executable/mod.rs
+++ b/crates/apollo-compiler/src/executable/mod.rs
@@ -22,9 +22,9 @@ pub use crate::ast::{
     VariableDefinition,
 };
 use crate::validation::DiagnosticList;
-use crate::validation::NodeLocation;
 use crate::validation::Valid;
 use crate::validation::WithErrors;
+use crate::NodeLocation;
 use std::fmt;
 use std::sync::Arc;
 
@@ -474,6 +474,10 @@ impl SelectionSet {
         }
     }
 
+    pub fn is_empty(&self) -> bool {
+        self.selections.is_empty()
+    }
+
     pub fn push(&mut self, selection: impl Into<Selection>) {
         self.selections.push(selection.into())
     }
diff --git a/crates/apollo-compiler/src/executable/validation.rs b/crates/apollo-compiler/src/executable/validation.rs
index 50d1920..409307a 100644
--- a/crates/apollo-compiler/src/executable/validation.rs
+++ b/crates/apollo-compiler/src/executable/validation.rs
@@ -1,33 +1,26 @@
 use super::FieldSet;
-use crate::ast;
+use crate::validation::fragment::validate_fragment_used;
+use crate::validation::operation::validate_operation_definitions;
 use crate::validation::selection::FieldsInSetCanMerge;
-use crate::validation::Details;
 use crate::validation::DiagnosticList;
-use crate::validation::FileId;
 use crate::validation::Valid;
 use crate::ExecutableDocument;
-use crate::InputDatabase;
 use crate::Schema;
-use crate::ValidationDatabase;
-use std::sync::Arc;
 
 pub(crate) fn validate_executable_document(
     errors: &mut DiagnosticList,
     schema: &Schema,
     document: &ExecutableDocument,
 ) {
-    validate_with_or_without_schema(errors, document);
+    validate_with_or_without_schema(errors, Some(schema), document);
     validate_with_schema(errors, schema, document);
-    compiler_validation(errors, Some(schema), document);
-    // TODO
 }
 
 pub(crate) fn validate_standalone_executable(
     errors: &mut DiagnosticList,
     document: &ExecutableDocument,
 ) {
-    validate_with_or_without_schema(errors, document);
-    compiler_validation(errors, None, document);
+    validate_with_or_without_schema(errors, None, document);
 }
 
 fn validate_with_schema(
@@ -43,107 +36,30 @@ fn validate_with_schema(
 }
 
 pub(crate) fn validate_with_or_without_schema(
-    _errors: &mut DiagnosticList,
-    _document: &ExecutableDocument,
-) {
-    // TODO
-}
-
-/// TODO: replace this with validation based on `ExecutableDocument` without a database
-fn compiler_validation(
     errors: &mut DiagnosticList,
     schema: Option<&Schema>,
     document: &ExecutableDocument,
 ) {
-    let mut compiler = crate::ApolloCompiler::new();
-    let mut ids = Vec::new();
-    if let Some(schema) = schema {
-        for (id, source) in schema.sources.iter() {
-            ids.push(*id);
-            compiler.db.set_input(*id, source.into());
-        }
-    }
-    for (id, source) in document.sources.iter() {
-        ids.push(*id);
-        compiler.db.set_input(*id, source.into());
-    }
-
-    if let Some(schema) = schema {
-        compiler.db.set_schema_input(Some(Arc::new(schema.clone())));
-    }
-
-    let ast_id = FileId::HACK_TMP;
-    ids.push(ast_id);
-    let ast = document.to_ast();
-    compiler.db.set_input(
-        ast_id,
-        crate::Source {
-            ty: crate::database::SourceType::Executable,
-            filename: Default::default(),
-            text: Default::default(),
-            ast: Some(Arc::new(ast)),
-        },
-    );
-    compiler.db.set_source_files(ids);
-    let diagnostics = if schema.is_some() {
-        compiler.db.validate_executable(ast_id)
-    } else {
-        compiler.db.validate_standalone_executable(ast_id)
-    };
-    for diagnostic in diagnostics {
-        errors.push(diagnostic.location, Details::CompilerDiagnostic(diagnostic))
+    validate_operation_definitions(errors, schema, document);
+    for def in document.fragments.values() {
+        validate_fragment_used(errors, document, def);
     }
 }
 
 pub(crate) fn validate_field_set(
-    errors: &mut DiagnosticList,
+    diagnostics: &mut DiagnosticList,
     schema: &Valid<Schema>,
     field_set: &FieldSet,
 ) {
-    let mut compiler = crate::ApolloCompiler::new();
-    let mut ids = Vec::new();
-    for (id, source) in &*schema.sources {
-        ids.push(*id);
-        compiler.db.set_input(*id, source.into());
-    }
-    for (id, source) in &*field_set.sources {
-        ids.push(*id);
-        compiler.db.set_input(*id, source.into());
-    }
-
-    compiler
-        .db
-        .set_schema_input(Some(Arc::new(schema.as_ref().clone())));
-
-    let ast_id = FileId::HACK_TMP;
-    ids.push(ast_id);
-    let ast = ast::Document::new();
-    compiler.db.set_input(
-        ast_id,
-        crate::Source {
-            ty: crate::database::SourceType::Executable,
-            filename: Default::default(),
-            text: Default::default(),
-            ast: Some(Arc::new(ast)),
-        },
-    );
-    compiler.db.set_source_files(ids);
-    let diagnostics = crate::validation::selection::validate_selection_set(
-        &compiler.db,
-        ast_id,
-        Some(&field_set.selection_set.ty),
-        &field_set.selection_set.to_ast(),
+    let document = &ExecutableDocument::new(); // No fragment definitions
+    crate::validation::selection::validate_selection_set(
+        diagnostics,
+        document,
+        Some((schema, &field_set.selection_set.ty)),
+        &field_set.selection_set,
         crate::validation::operation::OperationValidationConfig {
-            has_schema: true,
+            schema: Some(schema),
             variables: &[],
         },
-    );
-    //  if schema.is_some() {
-    //     compiler.db.validate_executable(ast_id)
-    // } else {
-    //     compiler.db.validate_standalone_executable(ast_id)
-    // };
-    for diagnostic in diagnostics {
-        errors.push(diagnostic.location, Details::CompilerDiagnostic(diagnostic))
-    }
+    )
 }
diff --git a/crates/apollo-compiler/src/lib.rs b/crates/apollo-compiler/src/lib.rs
index 7e53680..2db21ec 100644
--- a/crates/apollo-compiler/src/lib.rs
+++ b/crates/apollo-compiler/src/lib.rs
@@ -4,7 +4,6 @@
 mod macros;
 pub mod ast;
 pub mod coordinate;
-mod database;
 pub mod diagnostic;
 pub mod executable;
 pub mod execution;
@@ -14,30 +13,8 @@ mod parser;
 pub mod schema;
 pub mod validation;
 
-use crate::database::{InputDatabase, ReprDatabase, RootDatabase, Source};
-use crate::validation::ValidationDatabase;
-
 pub use self::executable::ExecutableDocument;
-pub use self::node::Node;
+pub use self::node::{FileId, Node, NodeLocation};
 pub use self::node_str::NodeStr;
 pub use self::parser::{parse_mixed_validate, Parser, SourceFile, SourceMap};
 pub use self::schema::Schema;
-
-pub(crate) struct ApolloCompiler {
-    pub db: RootDatabase,
-}
-
-/// Apollo compiler creates a context around your GraphQL. It creates references
-/// between various GraphQL types in scope.
-#[allow(clippy::new_without_default)]
-impl ApolloCompiler {
-    /// Create a new instance of Apollo Compiler.
-    pub fn new() -> Self {
-        let mut db = RootDatabase::default();
-        // TODO(@goto-bus-stop) can we make salsa fill in these defaults for us…?
-        db.set_source_files(vec![]);
-        db.set_schema_input(None);
-
-        Self { db }
-    }
-}
diff --git a/crates/apollo-compiler/src/node.rs b/crates/apollo-compiler/src/node.rs
index 3fe9e9a..8a31a03 100644
--- a/crates/apollo-compiler/src/node.rs
+++ b/crates/apollo-compiler/src/node.rs
@@ -1,7 +1,6 @@
 use crate::execution::GraphQLLocation;
 use crate::schema::Component;
 use crate::schema::ComponentOrigin;
-use crate::validation::FileId;
 use crate::SourceMap;
 use apollo_parser::SyntaxNode;
 use rowan::TextRange;
@@ -10,6 +9,8 @@ use std::fmt;
 use std::hash::BuildHasher;
 use std::hash::Hash;
 use std::hash::Hasher;
+use std::num::NonZeroI64;
+use std::sync::atomic;
 use std::sync::atomic::AtomicU64;
 use std::sync::atomic::Ordering;
 use std::sync::OnceLock;
@@ -48,6 +49,15 @@ pub struct NodeLocation {
     pub(crate) text_range: TextRange,
 }
 
+/// Integer identifier for a parsed source file.
+///
+/// Used internally to support validating for example a schema built from multiple source files,
+/// and having diagnostics point to relevant sources.
+#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
+pub struct FileId {
+    id: NonZeroI64,
+}
+
 impl<T> Node<T> {
     /// Create a new `Node` for something parsed from the given source location
     #[inline]
@@ -302,3 +312,48 @@ impl<T: serde::Serialize> serde::Serialize for Node<T> {
         T::serialize(self, serializer)
     }
 }
+
+impl fmt::Debug for FileId {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.id.fmt(f)
+    }
+}
+
+/// The next file ID to use. This is global so file IDs do not conflict between different compiler
+/// instances.
+static NEXT: atomic::AtomicI64 = atomic::AtomicI64::new(INITIAL);
+static INITIAL: i64 = 1;
+
+impl FileId {
+    /// The ID of the file implicitly added to type systems, for built-in scalars and introspection types
+    pub const BUILT_IN: Self = Self::const_new(-1);
+
+    /// Passed to Ariadne to create a report without a location
+    pub(crate) const NONE: Self = Self::const_new(-2);
+
+    // Returning a different value every time does not sound like good `impl Default`
+    #[allow(clippy::new_without_default)]
+    pub fn new() -> Self {
+        let id = NEXT.fetch_add(1, atomic::Ordering::AcqRel);
+        Self {
+            id: NonZeroI64::new(id).unwrap(),
+        }
+    }
+
+    /// Reset file ID back to 1, used to get consistent results in tests.
+    ///
+    /// All tests in the process must use `#[serial_test::serial]`
+    #[doc(hidden)]
+    pub fn reset() {
+        NEXT.store(INITIAL, atomic::Ordering::Release)
+    }
+
+    const fn const_new(id: i64) -> Self {
+        // TODO: use unwrap() when const-stable https://github.com/rust-lang/rust/issues/67441
+        if let Some(id) = NonZeroI64::new(id) {
+            Self { id }
+        } else {
+            panic!()
+        }
+    }
+}
diff --git a/crates/apollo-compiler/src/node_str.rs b/crates/apollo-compiler/src/node_str.rs
index a8f8b82..769a08b 100644
--- a/crates/apollo-compiler/src/node_str.rs
+++ b/crates/apollo-compiler/src/node_str.rs
@@ -1,7 +1,7 @@
 #![allow(unstable_name_collisions)] // for `sptr::Strict`
 
 use crate::execution::GraphQLLocation;
-use crate::validation::NodeLocation;
+use crate::NodeLocation;
 use crate::SourceMap;
 use sptr::Strict;
 use std::marker::PhantomData;
diff --git a/crates/apollo-compiler/src/parser.rs b/crates/apollo-compiler/src/parser.rs
index 82f22d0..7250afa 100644
--- a/crates/apollo-compiler/src/parser.rs
+++ b/crates/apollo-compiler/src/parser.rs
@@ -6,10 +6,10 @@ use crate::schema::SchemaBuilder;
 use crate::validation::Details;
 use crate::validation::DiagnosticList;
 use crate::validation::FileId;
-use crate::validation::NodeLocation;
 use crate::validation::Valid;
 use crate::validation::WithErrors;
 use crate::ExecutableDocument;
+use crate::NodeLocation;
 use crate::Schema;
 use indexmap::IndexMap;
 use std::path::Path;
diff --git a/crates/apollo-compiler/src/schema/component.rs b/crates/apollo-compiler/src/schema/component.rs
index 9054776..65a763a 100644
--- a/crates/apollo-compiler/src/schema/component.rs
+++ b/crates/apollo-compiler/src/schema/component.rs
@@ -1,6 +1,6 @@
 use crate::ast::Name;
-use crate::validation::NodeLocation;
 use crate::Node;
+use crate::NodeLocation;
 use std::fmt;
 use std::hash;
 use std::ops::Deref;
@@ -166,6 +166,11 @@ impl PartialEq<str> for ComponentName {
         self.as_str() == other
     }
 }
+impl PartialEq<Name> for ComponentName {
+    fn eq(&self, other: &Name) -> bool {
+        self.name == *other
+    }
+}
 
 impl Deref for ComponentName {
     type Target = Name;
diff --git a/crates/apollo-compiler/src/schema/mod.rs b/crates/apollo-compiler/src/schema/mod.rs
index fe32692..a41670d 100644
--- a/crates/apollo-compiler/src/schema/mod.rs
+++ b/crates/apollo-compiler/src/schema/mod.rs
@@ -2,8 +2,8 @@
 
 use crate::ast;
 use crate::validation::FileId;
-use crate::validation::NodeLocation;
 use crate::Node;
+use crate::NodeLocation;
 use crate::NodeStr;
 use crate::Parser;
 use indexmap::IndexMap;
@@ -844,6 +844,10 @@ impl DirectiveList {
         self.get(name).is_some()
     }
 
+    pub(crate) fn iter_ast(&self) -> impl Iterator<Item = &Node<ast::Directive>> {
+        self.0.iter().map(|component| &component.node)
+    }
+
     serialize_method!();
 }
 
diff --git a/crates/apollo-compiler/src/schema/validation.rs b/crates/apollo-compiler/src/schema/validation.rs
index 110ed89..56d0d8a 100644
--- a/crates/apollo-compiler/src/schema/validation.rs
+++ b/crates/apollo-compiler/src/schema/validation.rs
@@ -1,38 +1,21 @@
-use crate::validation::Details;
+use crate::validation::directive::validate_directive_definitions;
+use crate::validation::enum_::validate_enum_definitions;
+use crate::validation::input_object::validate_input_object_definitions;
+use crate::validation::interface::validate_interface_definitions;
+use crate::validation::object::validate_object_type_definitions;
+use crate::validation::scalar::validate_scalar_definitions;
+use crate::validation::schema::validate_schema_definition;
+use crate::validation::union_::validate_union_definitions;
 use crate::validation::DiagnosticList;
-use crate::validation::FileId;
-use crate::InputDatabase;
 use crate::Schema;
-use crate::ValidationDatabase;
-use std::sync::Arc;
 
 pub(crate) fn validate_schema(errors: &mut DiagnosticList, schema: &Schema) {
-    compiler_validation(errors, schema)
-}
-
-/// TODO: replace this with validation based on `Schema` without a database
-fn compiler_validation(errors: &mut DiagnosticList, schema: &Schema) {
-    let mut compiler = crate::ApolloCompiler::new();
-    let mut ids = Vec::new();
-    for (id, source) in schema.sources.iter() {
-        ids.push(*id);
-        compiler.db.set_input(*id, source.into());
-    }
-    let ast_id = FileId::HACK_TMP;
-    ids.push(ast_id);
-    let mut ast = crate::ast::Document::new();
-    ast.definitions.extend(schema.to_ast());
-    compiler.db.set_input(
-        ast_id,
-        crate::Source {
-            ty: crate::database::SourceType::Schema,
-            filename: Default::default(),
-            text: Default::default(),
-            ast: Some(Arc::new(ast)),
-        },
-    );
-    compiler.db.set_source_files(ids);
-    for diagnostic in compiler.db.validate_type_system() {
-        errors.push(diagnostic.location, Details::CompilerDiagnostic(diagnostic))
-    }
+    validate_schema_definition(errors, schema);
+    validate_scalar_definitions(errors, schema);
+    validate_enum_definitions(errors, schema);
+    validate_union_definitions(errors, schema);
+    validate_interface_definitions(errors, schema);
+    validate_directive_definitions(errors, schema);
+    validate_input_object_definitions(errors, schema);
+    validate_object_type_definitions(errors, schema);
 }
diff --git a/crates/apollo-compiler/src/validation/argument.rs b/crates/apollo-compiler/src/validation/argument.rs
index a615987..ab53e77 100644
--- a/crates/apollo-compiler/src/validation/argument.rs
+++ b/crates/apollo-compiler/src/validation/argument.rs
@@ -1,29 +1,28 @@
-use std::collections::HashMap;
-
-use crate::validation::diagnostics::{DiagnosticData, ValidationError};
-use crate::validation::NodeLocation;
+use crate::validation::diagnostics::DiagnosticData;
+use crate::validation::{DiagnosticList, NodeLocation};
 use crate::{ast, Node};
+use std::collections::HashMap;
 
-pub(crate) fn validate_arguments(arguments: &[Node<ast::Argument>]) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
+pub(crate) fn validate_arguments(
+    diagnostics: &mut DiagnosticList,
+    arguments: &[Node<ast::Argument>],
+) {
     let mut seen = HashMap::<_, Option<NodeLocation>>::new();
 
     for argument in arguments {
         let name = &argument.name;
         if let Some(&original_definition) = seen.get(name) {
             let redefined_definition = argument.location();
-            diagnostics.push(ValidationError::new(
+            diagnostics.push(
                 redefined_definition,
                 DiagnosticData::UniqueArgument {
                     name: name.clone(),
                     original_definition,
                     redefined_definition,
                 },
-            ));
+            );
         } else {
             seen.insert(name, argument.location());
         }
     }
-
-    diagnostics
 }
diff --git a/crates/apollo-compiler/src/validation/diagnostics.rs b/crates/apollo-compiler/src/validation/diagnostics.rs
index 30fb324..d2eba2b 100644
--- a/crates/apollo-compiler/src/validation/diagnostics.rs
+++ b/crates/apollo-compiler/src/validation/diagnostics.rs
@@ -5,29 +5,12 @@ use crate::ast::Type;
 use crate::coordinate::SchemaCoordinate;
 use crate::coordinate::TypeAttributeCoordinate;
 use crate::diagnostic::CliReport;
-use crate::diagnostic::NodeLocation;
+use crate::executable;
 use crate::Node;
+use crate::NodeLocation;
 use std::fmt;
 use thiserror::Error;
 
-#[derive(Debug, Error, Clone, PartialEq, Eq)]
-pub(crate) struct ValidationError {
-    pub location: Option<NodeLocation>,
-    pub data: DiagnosticData,
-}
-
-impl ValidationError {
-    pub fn new(location: Option<NodeLocation>, data: DiagnosticData) -> Self {
-        Self { location, data }
-    }
-}
-
-impl fmt::Display for ValidationError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        self.data.fmt(f)
-    }
-}
-
 /// Structured data about a diagnostic.
 #[derive(Debug, Error, Clone, Hash, PartialEq, Eq)]
 #[non_exhaustive]
@@ -269,7 +252,7 @@ pub(crate) enum DiagnosticData {
         /// Source location of just the "fragment FragName" part.
         head_location: Option<NodeLocation>,
         name: Name,
-        trace: Vec<Node<ast::FragmentSpread>>,
+        trace: Vec<Node<executable::FragmentSpread>>,
     },
     #[error("`{name}` contains too much nesting")]
     DeeplyNestedType {
@@ -280,9 +263,9 @@ pub(crate) enum DiagnosticData {
     RecursionError {},
 }
 
-impl ValidationError {
-    pub(crate) fn report(&self, report: &mut CliReport) {
-        match &self.data {
+impl DiagnosticData {
+    pub(crate) fn report(&self, main_location: Option<NodeLocation>, report: &mut CliReport) {
+        match self {
             DiagnosticData::UniqueVariable {
                 name,
                 original_definition,
@@ -336,7 +319,7 @@ impl ValidationError {
                 definition_location,
                 ..
             } => {
-                report.with_label_opt(self.location, "argument by this name not found");
+                report.with_label_opt(main_location, "argument by this name not found");
                 report.with_label_opt(
                     *definition_location,
                     format_args!("{coordinate} defined here"),
@@ -348,7 +331,7 @@ impl ValidationError {
                 definition_location,
             } => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("missing value for argument `{name}`"),
                 );
                 report.with_label_opt(*definition_location, "argument defined here");
@@ -359,23 +342,23 @@ impl ValidationError {
                 definition_location,
             } => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("missing value for field `{name}`"),
                 );
                 report.with_label_opt(*definition_location, "field defined here");
             }
             DiagnosticData::UndefinedDefinition { .. } => {
-                report.with_label_opt(self.location, "not found in this scope");
+                report.with_label_opt(main_location, "not found in this scope");
             }
             DiagnosticData::UndefinedDirective { .. } => {
-                report.with_label_opt(self.location, "directive not defined");
+                report.with_label_opt(main_location, "directive not defined");
             }
             DiagnosticData::UndefinedVariable { .. } => {
-                report.with_label_opt(self.location, "not found in this scope");
+                report.with_label_opt(main_location, "not found in this scope");
             }
             DiagnosticData::UndefinedFragment { name } => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("fragment `{name}` is not defined"),
                 );
             }
@@ -385,7 +368,7 @@ impl ValidationError {
                 definition_location,
             } => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("value does not exist on `{definition}` enum"),
                 );
                 report.with_label_opt(*definition_location, "enum defined here");
@@ -396,23 +379,23 @@ impl ValidationError {
                 definition_location,
             } => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("value does not exist on `{definition}` input object"),
                 );
                 report.with_label_opt(*definition_location, "input object defined here");
             }
             DiagnosticData::RecursiveDirectiveDefinition { name, trace } => {
-                report.with_label_opt(self.location, "recursive directive definition");
+                report.with_label_opt(main_location, "recursive directive definition");
                 label_recursive_trace(report, trace, name, |directive| &directive.name);
             }
             DiagnosticData::RecursiveInterfaceDefinition { name } => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("interface {name} cannot implement itself"),
                 );
             }
             DiagnosticData::RecursiveInputObjectDefinition { name, trace } => {
-                report.with_label_opt(self.location, "cyclical input object definition");
+                report.with_label_opt(main_location, "cyclical input object definition");
                 label_recursive_trace(report, trace, name, |reference| &reference.name);
             }
             DiagnosticData::RecursiveFragmentDefinition {
@@ -421,14 +404,14 @@ impl ValidationError {
                 trace,
             } => {
                 report.with_label_opt(
-                    head_location.or(self.location),
+                    head_location.or(main_location),
                     "recursive fragment definition",
                 );
                 label_recursive_trace(report, trace, name, |reference| &reference.fragment_name);
             }
             DiagnosticData::DeeplyNestedType { describe_type, .. } => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!(
                         "references a very long chain of {describe_type}s in its definition"
                     ),
@@ -442,7 +425,7 @@ impl ValidationError {
                 field_location,
             } => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("add `{field}` field to this type"),
                 );
                 report.with_label_opt(
@@ -470,15 +453,15 @@ impl ValidationError {
                     ),
                 );
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("{missing_interface} must also be implemented here"),
                 );
             }
             DiagnosticData::UnusedVariable { .. } => {
-                report.with_label_opt(self.location, "variable is never used");
+                report.with_label_opt(main_location, "variable is never used");
             }
             DiagnosticData::UnusedFragment { name } => {
-                report.with_label_opt(self.location, format_args!("`{name}` is defined here"));
+                report.with_label_opt(main_location, format_args!("`{name}` is defined here"));
                 report.with_help(format_args!(
                     "fragment `{name}` must be used in an operation"
                 ));
@@ -487,14 +470,14 @@ impl ValidationError {
                 name: _,
                 describe_type,
             } => {
-                report.with_label_opt(self.location, format_args!("this is {describe_type}"));
+                report.with_label_opt(main_location, format_args!("this is {describe_type}"));
                 report.with_help("Root operation type must be an object type.");
             }
             DiagnosticData::UnionMemberObjectType {
                 name: _,
                 describe_type,
             } => {
-                report.with_label_opt(self.location, format_args!("this is {describe_type}"));
+                report.with_label_opt(main_location, format_args!("this is {describe_type}"));
                 report.with_help("Union members must be object types.");
             }
             DiagnosticData::OutputType {
@@ -503,7 +486,7 @@ impl ValidationError {
                 type_location,
             } => {
                 report.with_label_opt(
-                    type_location.or(self.location),
+                    type_location.or(main_location),
                     format_args!("this is {describe_type}"),
                 );
                 report.with_help(format!("Scalars, Objects, Interfaces, Unions and Enums are output types. Change `{name}` field to return one of these output types."));
@@ -514,7 +497,7 @@ impl ValidationError {
                 type_location,
             } => {
                 report.with_label_opt(
-                    type_location.or(self.location),
+                    type_location.or(main_location),
                     format_args!("this is {describe_type}"),
                 );
                 report.with_help(format!("Scalars, Enums, and Input Objects are input types. Change `{name}` field to take one of these input types."));
@@ -525,14 +508,14 @@ impl ValidationError {
                 type_location,
             } => {
                 report.with_label_opt(
-                    type_location.or(self.location),
+                    type_location.or(main_location),
                     format_args!("this is {describe_type}"),
                 );
                 report.with_help("objects, unions, and interfaces cannot be used because variables can only be of input type");
             }
             DiagnosticData::QueryRootOperationType => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     "`query` root operation type must be defined here",
                 );
             }
@@ -543,7 +526,7 @@ impl ValidationError {
                 definition_location,
             } => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("directive cannot be used on {location}"),
                 );
                 report.with_label_opt(*definition_location, "directive defined here");
@@ -558,7 +541,7 @@ impl ValidationError {
                 definition_location,
             } => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("provided value is {describe_value_type}"),
                 );
                 report.with_label_opt(
@@ -567,10 +550,10 @@ impl ValidationError {
                 );
             }
             DiagnosticData::IntCoercionError { .. } => {
-                report.with_label_opt(self.location, "cannot be coerced to a 32-bit integer");
+                report.with_label_opt(main_location, "cannot be coerced to a 32-bit integer");
             }
             DiagnosticData::FloatCoercionError { .. } => {
-                report.with_label_opt(self.location, "cannot be coerced to a finite 64-bit float");
+                report.with_label_opt(main_location, "cannot be coerced to a finite 64-bit float");
             }
             DiagnosticData::UniqueDirective {
                 name,
@@ -581,7 +564,7 @@ impl ValidationError {
                     format_args!("directive `@{name}` first called here"),
                 );
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("directive `@{name}` called again here"),
                 );
             }
@@ -590,13 +573,13 @@ impl ValidationError {
                 describe_type,
             } => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("{coordinate} is {describe_type} and must select fields"),
                 );
             }
             DiagnosticData::InvalidFragmentTarget { name: _, ty } => {
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format!("fragment declares unsupported type condition `{ty}`"),
                 );
                 report.with_help("fragments cannot be defined on enums, scalars and input objects");
@@ -610,7 +593,7 @@ impl ValidationError {
             } => {
                 if let Some(name) = name {
                     report.with_label_opt(
-                        self.location,
+                        main_location,
                         format_args!("fragment `{name}` cannot be applied"),
                     );
                     // Only for named fragments: for inline fragments the type condition is right
@@ -622,7 +605,7 @@ impl ValidationError {
                         ),
                     );
                 } else {
-                    report.with_label_opt(self.location, "inline fragment cannot be applied");
+                    report.with_label_opt(main_location, "inline fragment cannot be applied");
                 }
                 report.with_label_opt(
                     *type_location,
@@ -642,7 +625,7 @@ impl ValidationError {
                     ),
                 );
                 report.with_label_opt(
-                    self.location,
+                    main_location,
                     format_args!("variable `${variable}` used here"),
                 );
             }
diff --git a/crates/apollo-compiler/src/validation/directive.rs b/crates/apollo-compiler/src/validation/directive.rs
index 1ab1ce5..10c7474 100644
--- a/crates/apollo-compiler/src/validation/directive.rs
+++ b/crates/apollo-compiler/src/validation/directive.rs
@@ -1,12 +1,12 @@
+use super::CycleError;
 use crate::coordinate::DirectiveArgumentCoordinate;
 use crate::coordinate::DirectiveCoordinate;
-use crate::validation::diagnostics::{DiagnosticData, ValidationError};
+use crate::validation::diagnostics::DiagnosticData;
+use crate::validation::DiagnosticList;
 use crate::validation::{NodeLocation, RecursionGuard, RecursionStack};
-use crate::{ast, schema, Node, ValidationDatabase};
+use crate::{ast, schema, Node};
 use std::collections::{HashMap, HashSet};
 
-use super::CycleError;
-
 /// This struct just groups functions that are used to find self-referential directives.
 /// The way to use it is to call `FindRecursiveDirective::check`.
 struct FindRecursiveDirective<'s> {
@@ -133,16 +133,16 @@ impl FindRecursiveDirective<'_> {
 }
 
 pub(crate) fn validate_directive_definition(
-    db: &dyn ValidationDatabase,
-    def: Node<ast::DirectiveDefinition>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = vec![];
-
-    diagnostics.extend(super::input_object::validate_argument_definitions(
-        db,
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+    def: &Node<ast::DirectiveDefinition>,
+) {
+    super::input_object::validate_argument_definitions(
+        diagnostics,
+        schema,
         &def.arguments,
         ast::DirectiveLocation::ArgumentDefinition,
-    ));
+    );
 
     let head_location = NodeLocation::recompose(def.location(), def.name.location());
 
@@ -150,88 +150,80 @@ pub(crate) fn validate_directive_definition(
     // references itself directly.
     //
     // Returns Recursive Definition error.
-    match FindRecursiveDirective::check(&db.schema(), &def) {
+    match FindRecursiveDirective::check(schema, def) {
         Ok(_) => {}
         Err(CycleError::Recursed(trace)) => {
-            diagnostics.push(ValidationError::new(
+            diagnostics.push(
                 head_location,
                 DiagnosticData::RecursiveDirectiveDefinition {
                     name: def.name.clone(),
                     trace,
                 },
-            ));
+            );
         }
-        Err(CycleError::Limit(_)) => diagnostics.push(ValidationError::new(
+        Err(CycleError::Limit(_)) => diagnostics.push(
             head_location,
             DiagnosticData::DeeplyNestedType {
                 name: def.name.clone(),
                 describe_type: "directive",
             },
-        )),
+        ),
     }
-
-    diagnostics
 }
 
-pub(crate) fn validate_directive_definitions(db: &dyn ValidationDatabase) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    for file_id in db.type_definition_files() {
-        for def in &db.ast(file_id).definitions {
-            if let ast::Definition::DirectiveDefinition(directive_definition) = def {
-                diagnostics.extend(db.validate_directive_definition(directive_definition.clone()));
-            }
-        }
+pub(crate) fn validate_directive_definitions(
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+) {
+    for directive_definition in schema.directive_definitions.values() {
+        validate_directive_definition(diagnostics, schema, directive_definition);
     }
-
-    diagnostics
 }
 
 // TODO(@goto-bus-stop) This is a big function: should probably not be generic over the iterator
 // type
 pub(crate) fn validate_directives<'dir>(
-    db: &dyn ValidationDatabase,
+    diagnostics: &mut DiagnosticList,
+    schema: Option<&crate::Schema>,
     dirs: impl Iterator<Item = &'dir Node<ast::Directive>>,
     dir_loc: ast::DirectiveLocation,
     var_defs: &[Node<ast::VariableDefinition>],
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
+) {
     let mut seen_directives = HashMap::<_, Option<NodeLocation>>::new();
 
-    let schema = db.schema();
     for dir in dirs {
-        diagnostics.extend(super::argument::validate_arguments(&dir.arguments));
+        super::argument::validate_arguments(diagnostics, &dir.arguments);
 
         let name = &dir.name;
         let loc = dir.location();
-        let directive_definition = schema.directive_definitions.get(name);
+        let directive_definition =
+            schema.and_then(|schema| Some((schema, schema.directive_definitions.get(name)?)));
 
         if let Some(&original_loc) = seen_directives.get(name) {
             let is_repeatable = directive_definition
-                .map(|def| def.repeatable)
+                .map(|(_, def)| def.repeatable)
                 // Assume unknown directives are repeatable to avoid producing confusing diagnostics
                 .unwrap_or(true);
 
             if !is_repeatable {
-                diagnostics.push(ValidationError::new(
+                diagnostics.push(
                     loc,
                     DiagnosticData::UniqueDirective {
                         name: name.clone(),
                         original_application: original_loc,
                     },
-                ));
+                );
             }
         } else {
             let loc = NodeLocation::recompose(dir.location(), dir.name.location());
             seen_directives.insert(&dir.name, loc);
         }
 
-        if let Some(directive_definition) = directive_definition {
+        if let Some((schema, directive_definition)) = directive_definition {
             let allowed_loc: HashSet<ast::DirectiveLocation> =
                 HashSet::from_iter(directive_definition.locations.iter().cloned());
             if !allowed_loc.contains(&dir_loc) {
-                diagnostics.push(ValidationError::new(
+                diagnostics.push(
                     loc,
                     DiagnosticData::UnsupportedLocation {
                         name: name.clone(),
@@ -239,36 +231,37 @@ pub(crate) fn validate_directives<'dir>(
                         valid_locations: directive_definition.locations.clone(),
                         definition_location: directive_definition.location(),
                     },
-                ));
+                );
             }
 
             for argument in &dir.arguments {
                 let input_value = directive_definition
                     .arguments
                     .iter()
-                    .find(|val| val.name == argument.name)
-                    .cloned();
+                    .find(|val| val.name == argument.name);
 
                 // @b(a: true)
                 if let Some(input_value) = input_value {
                     // TODO(@goto-bus-stop) do we really need value validation and variable
                     // validation separately?
-                    if let Some(diag) = super::variable::validate_variable_usage(
-                        input_value.clone(),
+                    if super::variable::validate_variable_usage(
+                        diagnostics,
+                        input_value,
                         var_defs,
                         argument,
                     )
-                    .err()
+                    .is_ok()
                     {
-                        diagnostics.push(diag)
-                    } else {
-                        let type_diags =
-                            super::value::validate_values(db, &input_value.ty, argument, var_defs);
-
-                        diagnostics.extend(type_diags);
+                        super::value::validate_values(
+                            diagnostics,
+                            schema,
+                            &input_value.ty,
+                            argument,
+                            var_defs,
+                        );
                     }
                 } else {
-                    diagnostics.push(ValidationError::new(
+                    diagnostics.push(
                         argument.location(),
                         DiagnosticData::UndefinedArgument {
                             name: argument.name.clone(),
@@ -278,7 +271,7 @@ pub(crate) fn validate_directives<'dir>(
                             .into(),
                             definition_location: loc,
                         },
-                    ));
+                    );
                 }
             }
             for arg_def in &directive_definition.arguments {
@@ -295,7 +288,7 @@ pub(crate) fn validate_directives<'dir>(
                 };
 
                 if arg_def.is_required() && is_null {
-                    diagnostics.push(ValidationError::new(
+                    diagnostics.push(
                         dir.location(),
                         DiagnosticData::RequiredArgument {
                             name: arg_def.name.clone(),
@@ -306,16 +299,14 @@ pub(crate) fn validate_directives<'dir>(
                             .into(),
                             definition_location: arg_def.location(),
                         },
-                    ));
+                    );
                 }
             }
         } else {
-            diagnostics.push(ValidationError::new(
+            diagnostics.push(
                 loc,
                 DiagnosticData::UndefinedDirective { name: name.clone() },
-            ))
+            )
         }
     }
-
-    diagnostics
 }
diff --git a/crates/apollo-compiler/src/validation/enum_.rs b/crates/apollo-compiler/src/validation/enum_.rs
index 60678f0..140f5a9 100644
--- a/crates/apollo-compiler/src/validation/enum_.rs
+++ b/crates/apollo-compiler/src/validation/enum_.rs
@@ -1,41 +1,42 @@
-use crate::validation::diagnostics::ValidationError;
-use crate::{ast, Node, ValidationDatabase};
+use crate::schema::{EnumType, ExtendedType};
+use crate::validation::DiagnosticList;
+use crate::{ast, Node};
 
-pub(crate) fn validate_enum_definitions(db: &dyn ValidationDatabase) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    for enum_ in db.ast_types().enums.values() {
-        diagnostics.extend(db.validate_enum_definition(enum_.clone()));
+pub(crate) fn validate_enum_definitions(diagnostics: &mut DiagnosticList, schema: &crate::Schema) {
+    for ty in schema.types.values() {
+        if let ExtendedType::Enum(enum_) = ty {
+            validate_enum_definition(diagnostics, schema, enum_);
+        }
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_enum_definition(
-    db: &dyn ValidationDatabase,
-    enum_def: ast::TypeWithExtensions<ast::EnumTypeDefinition>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = super::directive::validate_directives(
-        db,
-        enum_def.directives(),
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+    enum_def: &EnumType,
+) {
+    super::directive::validate_directives(
+        diagnostics,
+        Some(schema),
+        enum_def.directives.iter_ast(),
         ast::DirectiveLocation::Enum,
         // enums don't use variables
         Default::default(),
     );
 
-    for enum_val in enum_def.values() {
-        diagnostics.extend(validate_enum_value(db, enum_val));
+    for enum_val in enum_def.values.values() {
+        validate_enum_value(diagnostics, schema, enum_val);
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_enum_value(
-    db: &dyn ValidationDatabase,
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
     enum_val: &Node<ast::EnumValueDefinition>,
-) -> Vec<ValidationError> {
+) {
     super::directive::validate_directives(
-        db,
+        diagnostics,
+        Some(schema),
         enum_val.directives.iter(),
         ast::DirectiveLocation::EnumValue,
         // enum values don't use variables
diff --git a/crates/apollo-compiler/src/validation/field.rs b/crates/apollo-compiler/src/validation/field.rs
index 815ae68..e8b25d6 100644
--- a/crates/apollo-compiler/src/validation/field.rs
+++ b/crates/apollo-compiler/src/validation/field.rs
@@ -1,66 +1,67 @@
 use crate::coordinate::{FieldArgumentCoordinate, TypeAttributeCoordinate};
-use crate::validation::diagnostics::{DiagnosticData, ValidationError};
-use crate::validation::{FileId, ValidationDatabase};
-use crate::{ast, schema, Node};
+use crate::validation::diagnostics::DiagnosticData;
+use crate::{ast, executable, schema, ExecutableDocument, Node};
 
 use super::operation::OperationValidationConfig;
+use crate::ast::Name;
+use crate::schema::Component;
+use crate::validation::DiagnosticList;
+use indexmap::IndexMap;
 
 pub(crate) fn validate_field(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
+    diagnostics: &mut DiagnosticList,
+    document: &ExecutableDocument,
     // May be None if a parent selection was invalid
-    against_type: Option<&ast::NamedType>,
-    field: Node<ast::Field>,
+    against_type: Option<(&crate::Schema, &ast::NamedType)>,
+    field: &Node<executable::Field>,
     context: OperationValidationConfig<'_>,
-) -> Vec<ValidationError> {
+) {
     // First do all the validation that we can without knowing the type of the field.
 
-    let mut diagnostics = super::directive::validate_directives(
-        db,
+    super::directive::validate_directives(
+        diagnostics,
+        context.schema,
         field.directives.iter(),
         ast::DirectiveLocation::Field,
         context.variables,
     );
 
-    diagnostics.extend(super::argument::validate_arguments(&field.arguments));
+    super::argument::validate_arguments(diagnostics, &field.arguments);
 
     // Return early if we don't know the type--this can happen if we are nested deeply
     // inside a selection set that has a wrong field, or if we are validating a standalone
     // operation without a schema.
-    let Some(against_type) = against_type else {
-        return diagnostics;
+    let Some((schema, against_type)) = against_type else {
+        return;
     };
 
-    let schema = db.schema();
-
     if let Ok(field_definition) = schema.type_field(against_type, &field.name) {
         for argument in &field.arguments {
             let arg_definition = field_definition
                 .arguments
                 .iter()
-                .find(|val| val.name == argument.name)
-                .cloned();
+                .find(|val| val.name == argument.name);
             if let Some(arg_definition) = arg_definition {
-                if let Some(diag) = super::variable::validate_variable_usage(
-                    arg_definition.clone(),
+                if super::variable::validate_variable_usage(
+                    diagnostics,
+                    arg_definition,
                     context.variables,
                     argument,
                 )
-                .err()
+                .is_ok()
                 {
-                    diagnostics.push(diag)
-                } else {
-                    diagnostics.extend(super::value::validate_values(
-                        db,
+                    super::value::validate_values(
+                        diagnostics,
+                        schema,
                         &arg_definition.ty,
                         argument,
                         context.variables,
-                    ));
+                    );
                 }
             } else {
                 let loc = field_definition.location();
 
-                diagnostics.push(ValidationError::new(
+                diagnostics.push(
                     argument.location(),
                     DiagnosticData::UndefinedArgument {
                         name: argument.name.clone(),
@@ -71,7 +72,7 @@ pub(crate) fn validate_field(
                         .into(),
                         definition_location: loc,
                     },
-                ));
+                );
             }
         }
 
@@ -88,7 +89,7 @@ pub(crate) fn validate_field(
             };
 
             if arg_definition.is_required() && is_null {
-                diagnostics.push(ValidationError::new(
+                diagnostics.push(
                     field.location(),
                     DiagnosticData::RequiredArgument {
                         name: arg_definition.name.clone(),
@@ -100,56 +101,51 @@ pub(crate) fn validate_field(
                         .into(),
                         definition_location: arg_definition.location(),
                     },
-                ));
+                );
             }
         }
 
-        match validate_leaf_field_selection(db, field.clone(), &field_definition.ty) {
-            Err(diag) => diagnostics.push(diag),
-            Ok(_) => diagnostics.extend(super::selection::validate_selection_set(
-                db,
-                file_id,
-                Some(field_definition.ty.inner_named_type()),
+        if validate_leaf_field_selection(diagnostics, schema, field, &field_definition.ty).is_ok() {
+            super::selection::validate_selection_set(
+                diagnostics,
+                document,
+                Some((schema, field_definition.ty.inner_named_type())),
                 &field.selection_set,
                 context,
-            )),
+            )
         }
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_field_definition(
-    db: &dyn ValidationDatabase,
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
     field: &Node<ast::FieldDefinition>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = super::directive::validate_directives(
-        db,
+) {
+    super::directive::validate_directives(
+        diagnostics,
+        Some(schema),
         field.directives.iter(),
         ast::DirectiveLocation::FieldDefinition,
         // field definitions don't have variables
         Default::default(),
     );
 
-    diagnostics.extend(super::input_object::validate_argument_definitions(
-        db,
+    super::input_object::validate_argument_definitions(
+        diagnostics,
+        schema,
         &field.arguments,
         ast::DirectiveLocation::ArgumentDefinition,
-    ));
-
-    diagnostics
+    );
 }
 
 pub(crate) fn validate_field_definitions(
-    db: &dyn ValidationDatabase,
-    fields: Vec<Node<ast::FieldDefinition>>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    let schema = db.schema();
-
-    for field in &fields {
-        diagnostics.extend(validate_field_definition(db, field));
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+    fields: &IndexMap<Name, Component<ast::FieldDefinition>>,
+) {
+    for field in fields.values() {
+        validate_field_definition(diagnostics, schema, field);
 
         // Field types in Object Types must be of output type
         let loc = field.location();
@@ -157,35 +153,32 @@ pub(crate) fn validate_field_definitions(
         if let Some(field_ty) = schema.types.get(field.ty.inner_named_type()) {
             if !field_ty.is_output_type() {
                 // Output types are unreachable
-                diagnostics.push(ValidationError::new(
+                diagnostics.push(
                     loc,
                     DiagnosticData::OutputType {
                         name: field.name.clone(),
                         describe_type: field_ty.describe(),
                         type_location,
                     },
-                ));
+                );
             }
         } else {
-            diagnostics.push(ValidationError::new(
+            diagnostics.push(
                 type_location,
                 DiagnosticData::UndefinedDefinition {
                     name: field.ty.inner_named_type().clone(),
                 },
-            ));
+            );
         }
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_leaf_field_selection(
-    db: &dyn ValidationDatabase,
-    field: Node<ast::Field>,
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+    field: &Node<executable::Field>,
     field_type: &ast::Type,
-) -> Result<(), ValidationError> {
-    let schema = db.schema();
-
+) -> Result<(), ()> {
     let is_leaf = field.selection_set.is_empty();
     let tname = field_type.inner_named_type();
     let fname = &field.name;
@@ -204,7 +197,7 @@ pub(crate) fn validate_leaf_field_selection(
                 | schema::ExtendedType::Union(_)
         )
     {
-        Err(ValidationError::new(
+        diagnostics.push(
             field.location(),
             DiagnosticData::MissingSubselection {
                 coordinate: TypeAttributeCoordinate {
@@ -213,7 +206,8 @@ pub(crate) fn validate_leaf_field_selection(
                 },
                 describe_type: type_def.describe(),
             },
-        ))
+        );
+        Err(())
     } else {
         Ok(())
     }
diff --git a/crates/apollo-compiler/src/validation/fragment.rs b/crates/apollo-compiler/src/validation/fragment.rs
index a34a897..093566f 100644
--- a/crates/apollo-compiler/src/validation/fragment.rs
+++ b/crates/apollo-compiler/src/validation/fragment.rs
@@ -1,11 +1,15 @@
 use crate::ast;
+use crate::ast::Name;
+use crate::ast::NamedType;
+use crate::executable;
 use crate::schema;
 use crate::schema::Implementers;
-use crate::validation::diagnostics::{DiagnosticData, ValidationError};
+use crate::validation::diagnostics::DiagnosticData;
 use crate::validation::operation::OperationValidationConfig;
-use crate::validation::{CycleError, FileId, NodeLocation, RecursionGuard, RecursionStack};
+use crate::validation::DiagnosticList;
+use crate::validation::{CycleError, NodeLocation, RecursionGuard, RecursionStack};
+use crate::ExecutableDocument;
 use crate::Node;
-use crate::ValidationDatabase;
 use std::borrow::Cow;
 use std::collections::HashMap;
 use std::collections::HashSet;
@@ -15,8 +19,8 @@ use std::collections::HashSet;
 /// Spec: https://spec.graphql.org/October2021/#GetPossibleTypes()
 fn get_possible_types<'a>(
     type_definition: &schema::ExtendedType,
-    implementers_map: &'a HashMap<ast::Name, Implementers>,
-) -> Cow<'a, HashSet<ast::NamedType>> {
+    implementers_map: &'a HashMap<Name, Implementers>,
+) -> Cow<'a, HashSet<NamedType>> {
     match type_definition {
         // 1. If `type` is an object type, return a set containing `type`.
         schema::ExtendedType::Object(object) => {
@@ -42,41 +46,38 @@ fn get_possible_types<'a>(
 }
 
 fn validate_fragment_spread_type(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-    against_type: &ast::NamedType,
-    type_condition: &ast::NamedType,
-    selection: &ast::Selection,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-    let schema = db.schema();
-
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+    document: &ExecutableDocument,
+    against_type: &NamedType,
+    type_condition: &NamedType,
+    selection: &executable::Selection,
+) {
     // Another diagnostic will be raised if the type condition was wrong.
     // We reduce noise by silencing other issues with the fragment.
     let Some(type_condition_definition) = schema.types.get(type_condition) else {
-        return diagnostics;
+        return;
     };
 
     let Some(against_type_definition) = schema.types.get(against_type) else {
         // We cannot check anything if the parent type is unknown.
-        return diagnostics;
+        return;
     };
 
-    let implementers_map = db.implementers_map();
+    let implementers_map = schema.implementers_map();
     let concrete_parent_types = get_possible_types(against_type_definition, &implementers_map);
     let concrete_condition_types = get_possible_types(type_condition_definition, &implementers_map);
 
     let mut applicable_types = concrete_parent_types.intersection(&concrete_condition_types);
     if applicable_types.next().is_none() {
         // Report specific errors for the different kinds of fragments.
-        let diagnostic = match selection {
-            ast::Selection::Field(_) => unreachable!(),
-            ast::Selection::FragmentSpread(spread) => {
-                let named_fragments = db.ast_named_fragments(file_id);
+        match selection {
+            executable::Selection::Field(_) => unreachable!(),
+            executable::Selection::FragmentSpread(spread) => {
                 // TODO(@goto-bus-stop) Can we guarantee this unwrap()?
-                let fragment_definition = named_fragments.get(&spread.fragment_name).unwrap();
+                let fragment_definition = document.fragments.get(&spread.fragment_name).unwrap();
 
-                ValidationError::new(
+                diagnostics.push(
                     spread.location(),
                     DiagnosticData::InvalidFragmentSpread {
                         name: Some(spread.fragment_name.clone()),
@@ -87,7 +88,7 @@ fn validate_fragment_spread_type(
                     },
                 )
             }
-            ast::Selection::InlineFragment(inline) => ValidationError::new(
+            executable::Selection::InlineFragment(inline) => diagnostics.push(
                 inline.location(),
                 DiagnosticData::InvalidFragmentSpread {
                     name: None,
@@ -98,190 +99,167 @@ fn validate_fragment_spread_type(
                 },
             ),
         };
-
-        diagnostics.push(diagnostic);
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_inline_fragment(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-    against_type: Option<&ast::NamedType>,
-    inline: Node<ast::InlineFragment>,
+    diagnostics: &mut DiagnosticList,
+    document: &ExecutableDocument,
+    against_type: Option<(&crate::Schema, &ast::NamedType)>,
+    inline: &Node<executable::InlineFragment>,
     context: OperationValidationConfig<'_>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    diagnostics.extend(super::directive::validate_directives(
-        db,
+) {
+    super::directive::validate_directives(
+        diagnostics,
+        context.schema,
         inline.directives.iter(),
         ast::DirectiveLocation::InlineFragment,
         context.variables,
-    ));
+    );
 
-    let has_type_error = if context.has_schema {
-        let type_cond_diagnostics = if let Some(t) = &inline.type_condition {
-            validate_fragment_type_condition(db, None, t, inline.location())
-        } else {
-            Default::default()
-        };
-        let has_type_error = !type_cond_diagnostics.is_empty();
-        diagnostics.extend(type_cond_diagnostics);
-        has_type_error
-    } else {
-        false
-    };
+    let previous = diagnostics.len();
+    if let Some(schema) = context.schema {
+        if let Some(t) = &inline.type_condition {
+            validate_fragment_type_condition(diagnostics, schema, None, t, inline.location())
+        }
+    }
+    let has_type_error = diagnostics.len() > previous;
 
     // If there was an error with the type condition, it makes no sense to validate the selection,
     // as every field would be an error.
     if !has_type_error {
-        if let (Some(against_type), Some(type_condition)) = (against_type, &inline.type_condition) {
-            diagnostics.extend(validate_fragment_spread_type(
-                db,
-                file_id,
+        if let (Some((schema, against_type)), Some(type_condition)) =
+            (against_type, &inline.type_condition)
+        {
+            validate_fragment_spread_type(
+                diagnostics,
+                schema,
+                document,
                 against_type,
                 type_condition,
-                &ast::Selection::InlineFragment(inline.clone()),
-            ));
+                &executable::Selection::InlineFragment(inline.clone()),
+            );
         }
-        diagnostics.extend(super::selection::validate_selection_set(
-            db,
-            file_id,
-            inline.type_condition.as_ref().or(against_type),
+        super::selection::validate_selection_set(
+            diagnostics,
+            document,
+            if let (Some(schema), Some(ty)) = (&context.schema, &inline.type_condition) {
+                Some((schema, ty))
+            } else {
+                against_type
+            },
             &inline.selection_set,
             context,
-        ));
+        );
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_fragment_spread(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-    against_type: Option<&ast::NamedType>,
-    spread: Node<ast::FragmentSpread>,
+    diagnostics: &mut DiagnosticList,
+    document: &ExecutableDocument,
+    against_type: Option<(&crate::Schema, &NamedType)>,
+    spread: &Node<executable::FragmentSpread>,
     context: OperationValidationConfig<'_>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    diagnostics.extend(super::directive::validate_directives(
-        db,
+) {
+    super::directive::validate_directives(
+        diagnostics,
+        context.schema,
         spread.directives.iter(),
         ast::DirectiveLocation::FragmentSpread,
         context.variables,
-    ));
+    );
 
-    let named_fragments = db.ast_named_fragments(file_id);
-    match named_fragments.get(&spread.fragment_name) {
+    match document.fragments.get(&spread.fragment_name) {
         Some(def) => {
-            if let Some(against_type) = against_type {
-                diagnostics.extend(validate_fragment_spread_type(
-                    db,
-                    file_id,
+            if let Some((schema, against_type)) = against_type {
+                validate_fragment_spread_type(
+                    diagnostics,
+                    schema,
+                    document,
                     against_type,
-                    &def.type_condition,
-                    &ast::Selection::FragmentSpread(spread.clone()),
-                ));
+                    def.type_condition(),
+                    &executable::Selection::FragmentSpread(spread.clone()),
+                );
             }
-            diagnostics.extend(validate_fragment_definition(
-                db,
-                file_id,
-                def.clone(),
-                context,
-            ));
+            validate_fragment_definition(diagnostics, document, def, context);
         }
         None => {
-            diagnostics.push(ValidationError::new(
+            diagnostics.push(
                 spread.location(),
                 DiagnosticData::UndefinedFragment {
                     name: spread.fragment_name.clone(),
                 },
-            ));
+            );
         }
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_fragment_definition(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-    fragment: Node<ast::FragmentDefinition>,
+    diagnostics: &mut DiagnosticList,
+    document: &ExecutableDocument,
+    fragment: &Node<executable::Fragment>,
     context: OperationValidationConfig<'_>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-    let schema = db.schema();
-
-    diagnostics.extend(super::directive::validate_directives(
-        db,
+) {
+    super::directive::validate_directives(
+        diagnostics,
+        context.schema,
         fragment.directives.iter(),
         ast::DirectiveLocation::FragmentDefinition,
         context.variables,
-    ));
+    );
 
-    let has_type_error = if context.has_schema {
-        let type_cond_diagnostics = validate_fragment_type_condition(
-            db,
+    let previous = diagnostics.len();
+    if let Some(schema) = context.schema {
+        validate_fragment_type_condition(
+            diagnostics,
+            schema,
             Some(fragment.name.clone()),
-            &fragment.type_condition,
+            fragment.type_condition(),
             fragment.location(),
         );
-        let has_type_error = !type_cond_diagnostics.is_empty();
-        diagnostics.extend(type_cond_diagnostics);
-        has_type_error
-    } else {
-        false
-    };
+    }
+    let has_type_error = diagnostics.len() > previous;
 
-    let fragment_cycles_diagnostics = validate_fragment_cycles(db, file_id, &fragment);
-    let has_cycles = !fragment_cycles_diagnostics.is_empty();
-    diagnostics.extend(fragment_cycles_diagnostics);
+    let previous = diagnostics.len();
+    validate_fragment_cycles(diagnostics, document, fragment);
+    let has_cycles = diagnostics.len() > previous;
 
     if !has_type_error && !has_cycles {
         // If the type does not exist, do not attempt to validate the selections against it;
         // it has either already raised an error, or we are validating an executable without
         // a schema.
-        let type_condition = schema
-            .types
-            .contains_key(&fragment.type_condition)
-            .then_some(&fragment.type_condition);
+        let type_condition = context.schema.and_then(|schema| {
+            schema
+                .types
+                .contains_key(fragment.type_condition())
+                .then_some((schema, fragment.type_condition()))
+        });
 
-        diagnostics.extend(super::selection::validate_selection_set(
-            db,
-            file_id,
+        super::selection::validate_selection_set(
+            diagnostics,
+            document,
             type_condition,
             &fragment.selection_set,
             context,
-        ));
+        );
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_fragment_cycles(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-    def: &Node<ast::FragmentDefinition>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    // TODO pass in named fragments from outside this function, so it can be used on fully
-    // synthetic trees.
-    let named_fragments = db.ast_named_fragments(file_id);
-
+    diagnostics: &mut DiagnosticList,
+    document: &ExecutableDocument,
+    def: &Node<executable::Fragment>,
+) {
     /// If a fragment spread is recursive, returns a vec containing the spread that refers back to
     /// the original fragment, and a trace of each fragment spread back to the original fragment.
     fn detect_fragment_cycles(
-        named_fragments: &HashMap<ast::Name, Node<ast::FragmentDefinition>>,
-        selection_set: &[ast::Selection],
+        document: &ExecutableDocument,
+        selection_set: &executable::SelectionSet,
         visited: &mut RecursionGuard<'_>,
-    ) -> Result<(), CycleError<ast::FragmentSpread>> {
-        for selection in selection_set {
+    ) -> Result<(), CycleError<executable::FragmentSpread>> {
+        for selection in &selection_set.selections {
             match selection {
-                ast::Selection::FragmentSpread(spread) => {
+                executable::Selection::FragmentSpread(spread) => {
                     if visited.contains(&spread.fragment_name) {
                         if visited.first() == Some(&spread.fragment_name) {
                             return Err(CycleError::Recursed(vec![spread.clone()]));
@@ -289,20 +267,20 @@ pub(crate) fn validate_fragment_cycles(
                         continue;
                     }
 
-                    if let Some(fragment) = named_fragments.get(&spread.fragment_name) {
+                    if let Some(fragment) = document.fragments.get(&spread.fragment_name) {
                         detect_fragment_cycles(
-                            named_fragments,
+                            document,
                             &fragment.selection_set,
                             &mut visited.push(&fragment.name)?,
                         )
                         .map_err(|error| error.trace(spread))?;
                     }
                 }
-                ast::Selection::InlineFragment(inline) => {
-                    detect_fragment_cycles(named_fragments, &inline.selection_set, visited)?;
+                executable::Selection::InlineFragment(inline) => {
+                    detect_fragment_cycles(document, &inline.selection_set, visited)?;
                 }
-                ast::Selection::Field(field) => {
-                    detect_fragment_cycles(named_fragments, &field.selection_set, visited)?;
+                executable::Selection::Field(field) => {
+                    detect_fragment_cycles(document, &field.selection_set, visited)?;
                 }
             }
         }
@@ -312,45 +290,41 @@ pub(crate) fn validate_fragment_cycles(
 
     let mut visited = RecursionStack::with_root(def.name.clone()).with_limit(100);
 
-    match detect_fragment_cycles(&named_fragments, &def.selection_set, &mut visited.guard()) {
+    match detect_fragment_cycles(document, &def.selection_set, &mut visited.guard()) {
         Ok(_) => {}
         Err(CycleError::Recursed(trace)) => {
             let head_location = NodeLocation::recompose(def.location(), def.name.location());
 
-            diagnostics.push(ValidationError::new(
+            diagnostics.push(
                 def.location(),
                 DiagnosticData::RecursiveFragmentDefinition {
                     head_location,
                     name: def.name.clone(),
                     trace,
                 },
-            ));
+            );
         }
         Err(CycleError::Limit(_)) => {
             let head_location = NodeLocation::recompose(def.location(), def.name.location());
 
-            diagnostics.push(ValidationError::new(
+            diagnostics.push(
                 head_location,
                 DiagnosticData::DeeplyNestedType {
                     name: def.name.clone(),
                     describe_type: "fragment",
                 },
-            ));
+            );
         }
-    }
-
-    diagnostics
+    };
 }
 
 pub(crate) fn validate_fragment_type_condition(
-    db: &dyn ValidationDatabase,
-    fragment_name: Option<ast::Name>,
-    type_cond: &ast::NamedType,
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+    fragment_name: Option<Name>,
+    type_cond: &NamedType,
     fragment_location: Option<NodeLocation>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-    let schema = db.schema();
-
+) {
     let type_def = schema.types.get(type_cond);
     let is_composite = type_def
         .as_ref()
@@ -365,44 +339,33 @@ pub(crate) fn validate_fragment_type_condition(
         });
 
     if !is_composite {
-        diagnostics.push(ValidationError::new(
+        diagnostics.push(
             fragment_location,
             DiagnosticData::InvalidFragmentTarget {
                 name: fragment_name,
                 ty: type_cond.clone(),
             },
-        ));
+        );
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_fragment_used(
-    db: &dyn ValidationDatabase,
-    fragment: Node<ast::FragmentDefinition>,
-    file_id: FileId,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    let document = db.ast(file_id);
+    diagnostics: &mut DiagnosticList,
+    document: &ExecutableDocument,
+    fragment: &Node<executable::Fragment>,
+) {
     let fragment_name = &fragment.name;
 
-    let named_fragments = db.ast_named_fragments(file_id);
-    let operations = document
-        .definitions
-        .iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::OperationDefinition(operation) => Some(operation),
-            _ => None,
-        });
-
-    let mut all_selections = operations
-        .flat_map(|operation| &operation.selection_set)
+    let mut all_selections = document
+        .all_operations()
+        .map(|operation| &operation.selection_set)
         .chain(
-            named_fragments
+            document
+                .fragments
                 .values()
-                .flat_map(|fragment| &fragment.selection_set),
-        );
+                .map(|fragment| &fragment.selection_set),
+        )
+        .flat_map(|set| &set.selections);
 
     let is_used = all_selections.any(|sel| selection_uses_fragment(sel, fragment_name));
 
@@ -410,24 +373,24 @@ pub(crate) fn validate_fragment_used(
     //
     // Returns Unused Fragment error.
     if !is_used {
-        diagnostics.push(ValidationError::new(
+        diagnostics.push(
             fragment.location(),
             DiagnosticData::UnusedFragment {
                 name: fragment_name.clone(),
             },
-        ))
+        )
     }
-    diagnostics
 }
 
-fn selection_uses_fragment(sel: &ast::Selection, name: &str) -> bool {
+fn selection_uses_fragment(sel: &executable::Selection, name: &str) -> bool {
     let sub_selections = match sel {
-        ast::Selection::FragmentSpread(fragment) => return fragment.fragment_name == name,
-        ast::Selection::Field(field) => &field.selection_set,
-        ast::Selection::InlineFragment(inline) => &inline.selection_set,
+        executable::Selection::FragmentSpread(fragment) => return fragment.fragment_name == name,
+        executable::Selection::Field(field) => &field.selection_set,
+        executable::Selection::InlineFragment(inline) => &inline.selection_set,
     };
 
     sub_selections
+        .selections
         .iter()
         .any(|sel| selection_uses_fragment(sel, name))
 }
diff --git a/crates/apollo-compiler/src/validation/input_object.rs b/crates/apollo-compiler/src/validation/input_object.rs
index 501c26a..f6dcbdf 100644
--- a/crates/apollo-compiler/src/validation/input_object.rs
+++ b/crates/apollo-compiler/src/validation/input_object.rs
@@ -1,14 +1,14 @@
 use crate::ast;
-use crate::validation::diagnostics::{DiagnosticData, ValidationError};
-use crate::validation::{CycleError, RecursionGuard, RecursionStack};
+use crate::schema::{ExtendedType, InputObjectType};
+use crate::validation::diagnostics::DiagnosticData;
+use crate::validation::{CycleError, DiagnosticList, RecursionGuard, RecursionStack};
 use crate::Node;
-use crate::ValidationDatabase;
 use std::collections::HashMap;
 
 // Implements [Circular References](https://spec.graphql.org/October2021/#sec-Input-Objects.Circular-References)
 // part of the input object validation spec.
 struct FindRecursiveInputValue<'a> {
-    db: &'a dyn ValidationDatabase,
+    schema: &'a crate::Schema,
 }
 
 impl FindRecursiveInputValue<'_> {
@@ -17,14 +17,14 @@ impl FindRecursiveInputValue<'_> {
         seen: &mut RecursionGuard<'_>,
         def: &Node<ast::InputValueDefinition>,
     ) -> Result<(), CycleError<ast::InputValueDefinition>> {
-        return match &*def.ty {
+        match &*def.ty {
             // NonNull type followed by Named type is the one that's not allowed
             // to be cyclical, so this is only case we care about.
             //
             // Everything else may be a cyclical input value.
             ast::Type::NonNullNamed(name) => {
                 if !seen.contains(name) {
-                    if let Some(object_def) = self.db.ast_types().input_objects.get(name) {
+                    if let Some(object_def) = self.schema.get_input_object(name) {
                         self.input_object_definition(seen.push(name)?, object_def)
                             .map_err(|err| err.trace(def))?
                     }
@@ -35,15 +35,15 @@ impl FindRecursiveInputValue<'_> {
                 Ok(())
             }
             _ => Ok(()),
-        };
+        }
     }
 
     fn input_object_definition(
         &self,
         mut seen: RecursionGuard<'_>,
-        input_object: &ast::TypeWithExtensions<ast::InputObjectTypeDefinition>,
+        input_object: &InputObjectType,
     ) -> Result<(), CycleError<ast::InputValueDefinition>> {
-        for input_value in input_object.fields() {
+        for input_value in input_object.fields.values() {
             self.input_value_definition(&mut seen, input_value)?;
         }
 
@@ -51,78 +51,83 @@ impl FindRecursiveInputValue<'_> {
     }
 
     fn check(
-        db: &dyn ValidationDatabase,
-        input_object: &ast::TypeWithExtensions<ast::InputObjectTypeDefinition>,
+        schema: &crate::Schema,
+        input_object: &InputObjectType,
     ) -> Result<(), CycleError<ast::InputValueDefinition>> {
-        let mut recursion_stack = RecursionStack::with_root(input_object.definition.name.clone());
-        FindRecursiveInputValue { db }
+        let mut recursion_stack = RecursionStack::with_root(input_object.name.clone());
+        FindRecursiveInputValue { schema }
             .input_object_definition(recursion_stack.guard(), input_object)
     }
 }
 
 pub(crate) fn validate_input_object_definitions(
-    db: &dyn ValidationDatabase,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    for input_object in db.ast_types().input_objects.values() {
-        diagnostics.extend(db.validate_input_object_definition(input_object.clone()));
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+) {
+    for ty in schema.types.values() {
+        if let ExtendedType::InputObject(input_object) = ty {
+            validate_input_object_definition(diagnostics, schema, input_object);
+        }
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_input_object_definition(
-    db: &dyn ValidationDatabase,
-    input_object: ast::TypeWithExtensions<ast::InputObjectTypeDefinition>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = super::directive::validate_directives(
-        db,
-        input_object.directives(),
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+    input_object: &Node<InputObjectType>,
+) {
+    super::directive::validate_directives(
+        diagnostics,
+        Some(schema),
+        input_object.directives.iter_ast(),
         ast::DirectiveLocation::InputObject,
         // input objects don't use variables
         Default::default(),
     );
 
-    match FindRecursiveInputValue::check(db, &input_object) {
+    match FindRecursiveInputValue::check(schema, input_object) {
         Ok(_) => {}
-        Err(CycleError::Recursed(trace)) => diagnostics.push(ValidationError::new(
-            input_object.definition.location(),
+        Err(CycleError::Recursed(trace)) => diagnostics.push(
+            input_object.location(),
             DiagnosticData::RecursiveInputObjectDefinition {
-                name: input_object.definition.name.clone(),
+                name: input_object.name.clone(),
                 trace,
             },
-        )),
+        ),
         Err(CycleError::Limit(_)) => {
-            diagnostics.push(ValidationError::new(
-                input_object.definition.location(),
+            diagnostics.push(
+                input_object.location(),
                 DiagnosticData::DeeplyNestedType {
-                    name: input_object.definition.name.clone(),
+                    name: input_object.name.clone(),
                     describe_type: "input object",
                 },
-            ));
+            );
         }
     }
 
     // Fields in an Input Object Definition must be unique
     //
     // Returns Unique Definition error.
-    let fields: Vec<_> = input_object.fields().cloned().collect();
-    diagnostics.extend(validate_input_value_definitions(
-        db,
+    let fields: Vec<_> = input_object
+        .fields
+        .values()
+        .map(|c| c.node.clone())
+        .collect();
+    validate_input_value_definitions(
+        diagnostics,
+        schema,
         &fields,
         ast::DirectiveLocation::InputFieldDefinition,
-    ));
-
-    diagnostics
+    );
 }
 
 pub(crate) fn validate_argument_definitions(
-    db: &dyn ValidationDatabase,
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
     input_values: &[Node<ast::InputValueDefinition>],
     directive_location: ast::DirectiveLocation,
-) -> Vec<ValidationError> {
-    let mut diagnostics = validate_input_value_definitions(db, input_values, directive_location);
+) {
+    validate_input_value_definitions(diagnostics, schema, input_values, directive_location);
 
     let mut seen: HashMap<ast::Name, &Node<ast::InputValueDefinition>> = HashMap::new();
     for input_value in input_values {
@@ -131,62 +136,56 @@ pub(crate) fn validate_argument_definitions(
             let (original_definition, redefined_definition) =
                 (prev_value.location(), input_value.location());
 
-            diagnostics.push(ValidationError::new(
+            diagnostics.push(
                 original_definition,
                 DiagnosticData::UniqueInputValue {
                     name: name.clone(),
                     original_definition,
                     redefined_definition,
                 },
-            ));
+            );
         } else {
             seen.insert(name.clone(), input_value);
         }
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_input_value_definitions(
-    db: &dyn ValidationDatabase,
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
     input_values: &[Node<ast::InputValueDefinition>],
     directive_location: ast::DirectiveLocation,
-) -> Vec<ValidationError> {
-    let schema = db.schema();
-
-    let mut diagnostics = Vec::new();
-
+) {
     for input_value in input_values {
-        diagnostics.extend(super::directive::validate_directives(
-            db,
+        super::directive::validate_directives(
+            diagnostics,
+            Some(schema),
             input_value.directives.iter(),
             directive_location,
             Default::default(), // No variables in an input value definition
-        ));
+        );
         // Input values must only contain input types.
         let loc = input_value.location();
         if let Some(field_ty) = schema.types.get(input_value.ty.inner_named_type()) {
             if !field_ty.is_input_type() {
-                diagnostics.push(ValidationError::new(
+                diagnostics.push(
                     loc,
                     DiagnosticData::InputType {
                         name: input_value.name.clone(),
                         describe_type: field_ty.describe(),
                         type_location: input_value.ty.location(),
                     },
-                ));
+                );
             }
         } else {
             let named_type = input_value.ty.inner_named_type();
             let loc = named_type.location();
-            diagnostics.push(ValidationError::new(
+            diagnostics.push(
                 loc,
                 DiagnosticData::UndefinedDefinition {
                     name: named_type.clone(),
                 },
-            ));
+            );
         }
     }
-
-    diagnostics
 }
diff --git a/crates/apollo-compiler/src/validation/interface.rs b/crates/apollo-compiler/src/validation/interface.rs
index 36e118f..843c522 100644
--- a/crates/apollo-compiler/src/validation/interface.rs
+++ b/crates/apollo-compiler/src/validation/interface.rs
@@ -1,35 +1,34 @@
-use crate::{
-    ast, schema,
-    validation::diagnostics::{DiagnosticData, ValidationError},
-    ValidationDatabase,
-};
-use std::collections::HashSet;
-
-pub(crate) fn validate_interface_definitions(db: &dyn ValidationDatabase) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    for interface in db.ast_types().interfaces.values() {
-        diagnostics.extend(db.validate_interface_definition(interface.clone()));
+use crate::schema::{ComponentName, ExtendedType, InterfaceType, Name};
+use crate::validation::diagnostics::DiagnosticData;
+use crate::validation::field::validate_field_definitions;
+use crate::validation::DiagnosticList;
+use crate::{ast, Node, NodeLocation};
+use indexmap::IndexSet;
+
+pub(crate) fn validate_interface_definitions(
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+) {
+    for ty in schema.types.values() {
+        if let ExtendedType::Interface(interface) = ty {
+            validate_interface_definition(diagnostics, schema, interface);
+        }
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_interface_definition(
-    db: &dyn ValidationDatabase,
-    interface: ast::TypeWithExtensions<ast::InterfaceTypeDefinition>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    let schema = db.schema();
-
-    diagnostics.extend(super::directive::validate_directives(
-        db,
-        interface.directives(),
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+    interface: &Node<InterfaceType>,
+) {
+    super::directive::validate_directives(
+        diagnostics,
+        Some(schema),
+        interface.directives.iter_ast(),
         ast::DirectiveLocation::Interface,
         // interfaces don't use variables
         Default::default(),
-    ));
+    );
 
     // Interface must not implement itself.
     //
@@ -46,70 +45,62 @@ pub(crate) fn validate_interface_definition(
     //   id: ID!
     //   name: String
     // }
-    for implements_interface in interface.implements_interfaces() {
-        if *implements_interface == interface.definition.name {
-            diagnostics.push(ValidationError::new(
+    for implements_interface in &interface.implements_interfaces {
+        if *implements_interface == interface.name {
+            diagnostics.push(
                 implements_interface.location(),
                 DiagnosticData::RecursiveInterfaceDefinition {
-                    name: implements_interface.clone(),
+                    name: implements_interface.name.clone(),
                 },
-            ));
+            );
         }
     }
 
     // Interface Type field validation.
-    let field_definitions = interface.fields().cloned().collect();
-    diagnostics.extend(db.validate_field_definitions(field_definitions));
+    validate_field_definitions(diagnostics, schema, &interface.fields);
 
     // Implements Interfaceds validation.
-    let implements_interfaces: Vec<_> = interface.implements_interfaces().cloned().collect();
-    diagnostics.extend(validate_implements_interfaces(
-        db,
-        &interface.definition.clone().into(),
-        &implements_interfaces,
-    ));
+    validate_implements_interfaces(
+        diagnostics,
+        schema,
+        &interface.name,
+        interface.location(),
+        &interface.implements_interfaces,
+    );
 
     // When defining an interface that implements another interface, the
     // implementing interface must define each field that is specified by
     // the implemented interface.
     //
     // Returns a Missing Field error.
-    let field_names: HashSet<ast::Name> =
-        interface.fields().map(|field| field.name.clone()).collect();
-    for implements_interface in interface.implements_interfaces() {
-        if let Some(schema::ExtendedType::Interface(super_interface)) =
-            schema.types.get(implements_interface)
-        {
+    for implements_interface in &interface.implements_interfaces {
+        if let Some(super_interface) = schema.get_interface(implements_interface) {
             for super_field in super_interface.fields.values() {
-                if field_names.contains(&super_field.name) {
+                if interface.fields.contains_key(&super_field.name) {
                     continue;
                 }
-                diagnostics.push(ValidationError::new(
-                    interface.definition.location(),
+                diagnostics.push(
+                    interface.location(),
                     DiagnosticData::MissingInterfaceField {
-                        name: interface.definition.name.clone(),
+                        name: interface.name.clone(),
                         implements_location: implements_interface.location(),
-                        interface: implements_interface.clone(),
+                        interface: implements_interface.name.clone(),
                         field: super_field.name.clone(),
                         field_location: super_field.location(),
                     },
-                ));
+                );
             }
         }
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_implements_interfaces(
-    db: &dyn ValidationDatabase,
-    implementor: &ast::Definition,
-    implements_interfaces: &[ast::Name],
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    let schema = db.schema();
-
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+    implementor_name: &Name,
+    implementor_location: Option<NodeLocation>,
+    implements_interfaces: &IndexSet<ComponentName>,
+) {
     let interface_definitions = implements_interfaces
         .iter()
         .filter_map(|name| {
@@ -129,12 +120,12 @@ pub(crate) fn validate_implements_interfaces(
 
         // interface_name.loc should always be Some
         let loc = interface_name.location();
-        diagnostics.push(ValidationError::new(
+        diagnostics.push(
             loc,
             DiagnosticData::UndefinedDefinition {
-                name: interface_name.clone(),
+                name: interface_name.name.clone(),
             },
-        ));
+        );
     }
 
     // Transitively implemented interfaces must be defined on an implementing
@@ -153,20 +144,15 @@ pub(crate) fn validate_implements_interfaces(
             continue;
         }
 
-        let definition_loc = implementor.location();
-        // let via_loc = via_interface
-        //     .location();
         let transitive_loc = transitive_interface.location();
-        diagnostics.push(ValidationError::new(
-            definition_loc,
+        diagnostics.push(
+            implementor_location,
             DiagnosticData::TransitiveImplementedInterfaces {
-                interface: implementor.name().unwrap().clone(),
-                via_interface: via_interface.clone(),
+                interface: implementor_name.clone(),
+                via_interface: via_interface.name.clone(),
                 missing_interface: transitive_interface.clone(),
                 transitive_interface_location: transitive_loc,
             },
-        ));
+        );
     }
-
-    diagnostics
 }
diff --git a/crates/apollo-compiler/src/validation/mod.rs b/crates/apollo-compiler/src/validation/mod.rs
index e907685..52e8938 100644
--- a/crates/apollo-compiler/src/validation/mod.rs
+++ b/crates/apollo-compiler/src/validation/mod.rs
@@ -4,39 +4,35 @@
 #[cfg(doc)]
 use crate::{ExecutableDocument, Schema};
 
-mod argument;
-mod directive;
-mod enum_;
-mod field;
-mod fragment;
-mod input_object;
-mod interface;
-mod object;
+pub(crate) mod argument;
+pub(crate) mod diagnostics;
+pub(crate) mod directive;
+pub(crate) mod enum_;
+pub(crate) mod field;
+pub(crate) mod fragment;
+pub(crate) mod input_object;
+pub(crate) mod interface;
+pub(crate) mod object;
 pub(crate) mod operation;
-mod scalar;
-mod schema;
+pub(crate) mod scalar;
+pub(crate) mod schema;
 pub(crate) mod selection;
-mod union_;
-mod validation_db;
-mod value;
-mod variable;
-
-pub(crate) mod diagnostics;
+pub(crate) mod union_;
+pub(crate) mod value;
+pub(crate) mod variable;
 
 use crate::ast::Name;
 use crate::diagnostic::{CliReport, Diagnostic, ToCliReport};
 use crate::executable::BuildError as ExecutableBuildError;
 use crate::execution::{GraphQLError, Response};
 use crate::schema::BuildError as SchemaBuildError;
-use crate::Node;
 use crate::SourceMap;
+use crate::{Node, NodeLocation};
 use indexmap::IndexSet;
 use std::fmt;
 use std::sync::Arc;
-pub(crate) use validation_db::{ValidationDatabase, ValidationStorage};
 
-pub use crate::database::FileId;
-pub use crate::node::NodeLocation;
+pub(crate) use crate::node::FileId;
 
 /// Wraps a [`Schema`] or [`ExecutableDocument`] to mark it
 /// as [valid](https://spec.graphql.org/October2021/#sec-Validation).
@@ -205,7 +201,7 @@ pub(crate) enum Details {
     ExecutableBuildError(ExecutableBuildError),
     // TODO: Merge ValidationError into this enum
     #[error(transparent)]
-    CompilerDiagnostic(diagnostics::ValidationError),
+    CompilerDiagnostic(diagnostics::DiagnosticData),
     #[error("too much recursion")]
     RecursionLimitError,
 }
@@ -217,7 +213,7 @@ impl ToCliReport for DiagnosticData {
 
     fn report(&self, report: &mut CliReport) {
         if let Details::CompilerDiagnostic(diagnostic) = &self.details {
-            diagnostic.report(report);
+            diagnostic.report(self.location, report);
             return;
         }
 
@@ -630,6 +626,12 @@ impl From<ExecutableBuildError> for Details {
     }
 }
 
+impl From<diagnostics::DiagnosticData> for Details {
+    fn from(value: diagnostics::DiagnosticData) -> Self {
+        Details::CompilerDiagnostic(value)
+    }
+}
+
 const DEFAULT_RECURSION_LIMIT: usize = 32;
 
 #[derive(Debug, Clone, thiserror::Error)]
diff --git a/crates/apollo-compiler/src/validation/object.rs b/crates/apollo-compiler/src/validation/object.rs
index a17fa0c..58e62af 100644
--- a/crates/apollo-compiler/src/validation/object.rs
+++ b/crates/apollo-compiler/src/validation/object.rs
@@ -1,82 +1,71 @@
-use crate::{
-    ast,
-    validation::diagnostics::{DiagnosticData, ValidationError},
-    ValidationDatabase,
-};
-use std::collections::HashSet;
+use crate::ast;
+use crate::schema::ExtendedType;
+use crate::schema::ObjectType;
+use crate::validation::diagnostics::DiagnosticData;
+use crate::validation::field::validate_field_definitions;
+use crate::validation::DiagnosticList;
+use crate::Node;
 
 pub(crate) fn validate_object_type_definitions(
-    db: &dyn ValidationDatabase,
-) -> Vec<ValidationError> {
-    let mut diagnostics = vec![];
-
-    let defs = &db.ast_types().objects;
-    for def in defs.values() {
-        diagnostics.extend(db.validate_object_type_definition(def.clone()))
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+) {
+    for ty in schema.types.values() {
+        if let ExtendedType::Object(object) = ty {
+            validate_object_type_definition(diagnostics, schema, object)
+        }
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_object_type_definition(
-    db: &dyn ValidationDatabase,
-    object: ast::TypeWithExtensions<ast::ObjectTypeDefinition>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    let schema = db.schema();
-
-    diagnostics.extend(super::directive::validate_directives(
-        db,
-        object.directives(),
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+    object: &Node<ObjectType>,
+) {
+    super::directive::validate_directives(
+        diagnostics,
+        Some(schema),
+        object.directives.iter_ast(),
         ast::DirectiveLocation::Object,
         // objects don't use variables
         Default::default(),
-    ));
-
-    // Collect all fields, including duplicates
-    let field_definitions: Vec<_> = object.fields().cloned().collect();
-    let field_names: HashSet<_> = field_definitions
-        .iter()
-        .map(|field| field.name.clone())
-        .collect();
+    );
 
     // Object Type field validations.
-    diagnostics.extend(db.validate_field_definitions(field_definitions));
+    validate_field_definitions(diagnostics, schema, &object.fields);
 
     // Implements Interfaces validation.
-    let implements_interfaces: Vec<_> = object.implements_interfaces().cloned().collect();
-    diagnostics.extend(super::interface::validate_implements_interfaces(
-        db,
-        &object.definition.clone().into(),
-        &implements_interfaces,
-    ));
+    super::interface::validate_implements_interfaces(
+        diagnostics,
+        schema,
+        &object.name,
+        object.location(),
+        &object.implements_interfaces,
+    );
 
     // When defining an interface that implements another interface, the
     // implementing interface must define each field that is specified by
     // the implemented interface.
     //
     // Returns a Missing Field error.
-    for implements_interface in object.implements_interfaces() {
+    for implements_interface in &object.implements_interfaces {
         if let Some(interface) = schema.get_interface(implements_interface) {
             for interface_field in interface.fields.values() {
-                if field_names.contains(&interface_field.name) {
+                if object.fields.contains_key(&interface_field.name) {
                     continue;
                 }
 
-                diagnostics.push(ValidationError::new(
-                    object.definition.location(),
+                diagnostics.push(
+                    object.location(),
                     DiagnosticData::MissingInterfaceField {
-                        name: object.definition.name.clone(),
+                        name: object.name.clone(),
                         implements_location: implements_interface.location(),
-                        interface: implements_interface.clone(),
+                        interface: implements_interface.name.clone(),
                         field: interface_field.name.clone(),
                         field_location: interface_field.location(),
                     },
-                ));
+                );
             }
         }
     }
-
-    diagnostics
 }
diff --git a/crates/apollo-compiler/src/validation/operation.rs b/crates/apollo-compiler/src/validation/operation.rs
index 5527942..a5f9c59 100644
--- a/crates/apollo-compiler/src/validation/operation.rs
+++ b/crates/apollo-compiler/src/validation/operation.rs
@@ -1,14 +1,12 @@
-use crate::validation::diagnostics::ValidationError;
 use crate::validation::DiagnosticList;
-use crate::validation::FileId;
-use crate::{ast, executable, Node, ValidationDatabase};
+use crate::{ast, executable, ExecutableDocument, Node, Schema};
 
-#[derive(Debug, Clone, Hash, PartialEq, Eq)]
-pub(crate) struct OperationValidationConfig<'vars> {
-    /// When false, rules that require a schema to validate are disabled.
-    pub has_schema: bool,
+#[derive(Debug, Clone)]
+pub(crate) struct OperationValidationConfig<'a> {
+    /// When None, rules that require a schema to validate are disabled.
+    pub schema: Option<&'a crate::Schema>,
     /// The variables defined for this operation.
-    pub variables: &'vars [Node<ast::VariableDefinition>],
+    pub variables: &'a [Node<ast::VariableDefinition>],
 }
 
 pub(crate) fn validate_subscription(
@@ -57,74 +55,53 @@ pub(crate) fn validate_subscription(
 }
 
 pub(crate) fn validate_operation(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-    operation: Node<ast::OperationDefinition>,
-    has_schema: bool,
-) -> Vec<ValidationError> {
-    let mut diagnostics = vec![];
-
+    diagnostics: &mut DiagnosticList,
+    schema: Option<&Schema>,
+    document: &ExecutableDocument,
+    operation: &executable::Operation,
+) {
     let config = OperationValidationConfig {
-        has_schema,
+        schema,
         variables: &operation.variables,
     };
 
-    let schema = db.schema();
-    let against_type = schema.root_operation(operation.operation_type);
+    let against_type = if let Some(schema) = schema {
+        schema
+            .root_operation(operation.operation_type)
+            .map(|ty| (schema, ty))
+    } else {
+        None
+    };
 
-    diagnostics.extend(super::directive::validate_directives(
-        db,
+    super::directive::validate_directives(
+        diagnostics,
+        schema,
         operation.directives.iter(),
         operation.operation_type.into(),
         &operation.variables,
-    ));
-    diagnostics.extend(super::variable::validate_variable_definitions(
-        db,
+    );
+    super::variable::validate_variable_definitions(
+        diagnostics,
+        config.schema,
         &operation.variables,
-        config.has_schema,
-    ));
+    );
 
-    diagnostics.extend(super::variable::validate_unused_variables(
-        db,
-        file_id,
-        operation.clone(),
-    ));
-    diagnostics.extend(super::selection::validate_selection_set(
-        db,
-        file_id,
+    super::variable::validate_unused_variables(diagnostics, document, operation);
+    super::selection::validate_selection_set(
+        diagnostics,
+        document,
         against_type,
         &operation.selection_set,
         config,
-    ));
-
-    diagnostics
-}
-
-pub(crate) fn validate_operation_definitions_inner(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-    has_schema: bool,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-    let document = db.ast(file_id);
-
-    for definition in &document.definitions {
-        if let ast::Definition::OperationDefinition(operation) = definition {
-            diagnostics.extend(validate_operation(
-                db,
-                file_id,
-                operation.clone(),
-                has_schema,
-            ));
-        }
-    }
-
-    diagnostics
+    );
 }
 
 pub(crate) fn validate_operation_definitions(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-) -> Vec<ValidationError> {
-    validate_operation_definitions_inner(db, file_id, false)
+    diagnostics: &mut DiagnosticList,
+    schema: Option<&Schema>,
+    document: &ExecutableDocument,
+) {
+    for operation in document.all_operations() {
+        validate_operation(diagnostics, schema, document, operation);
+    }
 }
diff --git a/crates/apollo-compiler/src/validation/scalar.rs b/crates/apollo-compiler/src/validation/scalar.rs
index 1589cd5..353193e 100644
--- a/crates/apollo-compiler/src/validation/scalar.rs
+++ b/crates/apollo-compiler/src/validation/scalar.rs
@@ -1,28 +1,27 @@
-use crate::{ast, schema, validation::diagnostics::ValidationError, Node, ValidationDatabase};
+use crate::validation::DiagnosticList;
+use crate::{ast, schema, Node};
 
-pub(crate) fn validate_scalar_definitions(db: &dyn ValidationDatabase) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    let schema = db.schema();
+pub(crate) fn validate_scalar_definitions(
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+) {
     for def in schema.types.values() {
         if let schema::ExtendedType::Scalar(scalar) = def {
-            diagnostics.extend(db.validate_scalar_definition(scalar.clone()));
+            validate_scalar_definition(diagnostics, schema, scalar);
         }
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_scalar_definition(
-    db: &dyn ValidationDatabase,
-    scalar_def: Node<schema::ScalarType>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+    scalar_def: &Node<schema::ScalarType>,
+) {
     // All built-in scalars must be omitted for brevity.
     if !scalar_def.is_built_in() {
-        diagnostics.extend(super::directive::validate_directives(
-            db,
+        super::directive::validate_directives(
+            diagnostics,
+            Some(schema),
             scalar_def
                 .directives
                 .iter()
@@ -30,8 +29,6 @@ pub(crate) fn validate_scalar_definition(
             ast::DirectiveLocation::Scalar,
             // scalars don't use variables
             Default::default(),
-        ));
+        );
     }
-
-    diagnostics
 }
diff --git a/crates/apollo-compiler/src/validation/schema.rs b/crates/apollo-compiler/src/validation/schema.rs
index 510b635..e24aa50 100644
--- a/crates/apollo-compiler/src/validation/schema.rs
+++ b/crates/apollo-compiler/src/validation/schema.rs
@@ -1,78 +1,61 @@
 use crate::ast;
 use crate::schema;
 use crate::validation::diagnostics::DiagnosticData;
-use crate::validation::diagnostics::ValidationError;
-use crate::Node;
-use crate::ValidationDatabase;
+use crate::validation::DiagnosticList;
 
-pub(crate) fn validate_schema_definition(
-    db: &dyn ValidationDatabase,
-    schema_definition: ast::TypeWithExtensions<ast::SchemaDefinition>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    let root_operations: Vec<_> = schema_definition.root_operations().cloned().collect();
+pub(crate) fn validate_schema_definition(diagnostics: &mut DiagnosticList, schema: &crate::Schema) {
     // A GraphQL schema must have a Query root operation.
-    let has_query = root_operations
-        .iter()
-        .any(|op| op.0 == ast::OperationType::Query);
-    if !has_query {
-        let location = schema_definition.definition.location();
-        diagnostics.push(ValidationError::new(
-            location,
-            DiagnosticData::QueryRootOperationType,
-        ));
+    if schema.schema_definition.query.is_none() {
+        let location = schema.schema_definition.location();
+        diagnostics.push(location, DiagnosticData::QueryRootOperationType);
     }
-    diagnostics.extend(validate_root_operation_definitions(db, &root_operations));
+    validate_root_operation_definitions(diagnostics, schema);
 
-    diagnostics.extend(super::directive::validate_directives(
-        db,
-        schema_definition.directives(),
+    super::directive::validate_directives(
+        diagnostics,
+        Some(schema),
+        schema.schema_definition.directives.iter_ast(),
         ast::DirectiveLocation::Schema,
         // schemas don't use variables
         Default::default(),
-    ));
-
-    diagnostics
+    );
 }
 
 // All root operations in a schema definition must be unique.
 //
 // Return a Unique Operation Definition error in case of a duplicate name.
 pub(crate) fn validate_root_operation_definitions(
-    db: &dyn ValidationDatabase,
-    root_op_defs: &[Node<(ast::OperationType, ast::NamedType)>],
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    let schema = db.schema();
-
-    for op in root_op_defs {
-        let (_op_type, name) = &**op;
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+) {
+    for op in [
+        &schema.schema_definition.query,
+        &schema.schema_definition.mutation,
+        &schema.schema_definition.subscription,
+    ] {
+        let Some(name) = op else { continue };
 
         // Root Operation Named Type must be of Object Type.
         //
         // Return a Object Type error if it's any other type definition.
-        let type_def = schema.types.get(name);
+        let type_def = schema.types.get(name.as_ref());
         if let Some(type_def) = type_def {
             if !matches!(type_def, schema::ExtendedType::Object(_)) {
-                let op_loc = name.location();
-                diagnostics.push(ValidationError::new(
-                    op_loc,
+                diagnostics.push(
+                    name.location(),
                     DiagnosticData::RootOperationObjectType {
-                        name: name.clone(),
+                        name: name.name.clone(),
                         describe_type: type_def.describe(),
                     },
-                ));
+                );
             }
         } else {
-            let op_loc = name.location();
-            diagnostics.push(ValidationError::new(
-                op_loc,
-                DiagnosticData::UndefinedDefinition { name: name.clone() },
-            ));
+            diagnostics.push(
+                name.location(),
+                DiagnosticData::UndefinedDefinition {
+                    name: name.name.clone(),
+                },
+            );
         }
     }
-
-    diagnostics
 }
diff --git a/crates/apollo-compiler/src/validation/selection.rs b/crates/apollo-compiler/src/validation/selection.rs
index 672199f..514e9c6 100644
--- a/crates/apollo-compiler/src/validation/selection.rs
+++ b/crates/apollo-compiler/src/validation/selection.rs
@@ -1,9 +1,11 @@
+use crate::ast::Name;
+use crate::ast::NamedType;
 use crate::coordinate::TypeAttributeCoordinate;
 use crate::executable::BuildError;
-use crate::validation::diagnostics::ValidationError;
+use crate::executable::SelectionSet;
 use crate::validation::operation::OperationValidationConfig;
 use crate::validation::DiagnosticList;
-use crate::validation::{FileId, ValidationDatabase};
+use crate::ExecutableDocument;
 use crate::{ast, executable, schema, Node};
 use apollo_parser::LimitTracker;
 use indexmap::IndexMap;
@@ -15,7 +17,7 @@ use std::rc::Rc;
 #[derive(Debug, Clone, Copy, Hash)]
 pub(crate) struct FieldSelection<'a> {
     /// The type of the selection set this field selection is part of.
-    pub parent_type: &'a ast::NamedType,
+    pub parent_type: &'a NamedType,
     pub field: &'a Node<executable::Field>,
 }
 
@@ -30,7 +32,7 @@ impl FieldSelection<'_> {
 
 /// Expand one or more selection sets to a list of all fields selected.
 pub(crate) fn expand_selections<'doc>(
-    fragments: &'doc IndexMap<ast::Name, Node<executable::Fragment>>,
+    fragments: &'doc IndexMap<Name, Node<executable::Fragment>>,
     selection_sets: impl Iterator<Item = &'doc executable::SelectionSet>,
 ) -> Vec<FieldSelection<'doc>> {
     let mut selections = vec![];
@@ -73,7 +75,7 @@ fn is_composite(ty: &schema::ExtendedType) -> bool {
 /// A temporary index for frequent argument lookups by name, using a hash map if there are many
 /// arguments.
 enum ArgumentLookup<'a> {
-    Map(HashMap<&'a ast::Name, &'a Node<ast::Argument>>),
+    Map(HashMap<&'a Name, &'a Node<ast::Argument>>),
     List(&'a [Node<ast::Argument>]),
 }
 impl<'a> ArgumentLookup<'a> {
@@ -85,7 +87,7 @@ impl<'a> ArgumentLookup<'a> {
         }
     }
 
-    fn by_name(&self, name: &ast::Name) -> Option<&'a Node<ast::Argument>> {
+    fn by_name(&self, name: &Name) -> Option<&'a Node<ast::Argument>> {
         match self {
             Self::Map(map) => map.get(name).copied(),
             Self::List(list) => list.iter().find(|arg| arg.name == *name),
@@ -277,7 +279,7 @@ impl OnceBool {
 /// Represents a merged field set that may or may not be valid.
 struct MergedFieldSet<'doc> {
     selections: Vec<FieldSelection<'doc>>,
-    grouped_by_output_names: OnceCell<IndexMap<ast::Name, Vec<FieldSelection<'doc>>>>,
+    grouped_by_output_names: OnceCell<IndexMap<Name, Vec<FieldSelection<'doc>>>>,
     grouped_by_common_parents: OnceCell<Vec<Vec<FieldSelection<'doc>>>>,
     same_response_shape_guard: OnceBool,
     same_for_common_parents_guard: OnceBool,
@@ -539,63 +541,39 @@ impl<'s, 'doc> FieldsInSetCanMerge<'s, 'doc> {
 }
 
 pub(crate) fn validate_selection_set(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-    against_type: Option<&ast::NamedType>,
-    selection_set: &[ast::Selection],
+    diagnostics: &mut DiagnosticList,
+    document: &ExecutableDocument,
+    against_type: Option<(&crate::Schema, &NamedType)>,
+    selection_set: &SelectionSet,
     context: OperationValidationConfig<'_>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = vec![];
-
-    diagnostics.extend(validate_selections(
-        db,
-        file_id,
-        against_type,
-        selection_set,
-        context,
-    ));
-
-    diagnostics
-}
-
-pub(crate) fn validate_selections(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-    against_type: Option<&ast::NamedType>,
-    selection_set: &[ast::Selection],
-    context: OperationValidationConfig<'_>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = vec![];
-
-    for selection in selection_set {
+) {
+    for selection in &selection_set.selections {
         match selection {
-            ast::Selection::Field(field) => diagnostics.extend(super::field::validate_field(
-                db,
-                file_id,
+            executable::Selection::Field(field) => super::field::validate_field(
+                diagnostics,
+                document,
                 against_type,
-                field.clone(),
+                field,
                 context.clone(),
-            )),
-            ast::Selection::FragmentSpread(fragment) => {
-                diagnostics.extend(super::fragment::validate_fragment_spread(
-                    db,
-                    file_id,
+            ),
+            executable::Selection::FragmentSpread(fragment) => {
+                super::fragment::validate_fragment_spread(
+                    diagnostics,
+                    document,
                     against_type,
-                    fragment.clone(),
+                    fragment,
                     context.clone(),
-                ))
+                )
             }
-            ast::Selection::InlineFragment(inline) => {
-                diagnostics.extend(super::fragment::validate_inline_fragment(
-                    db,
-                    file_id,
+            executable::Selection::InlineFragment(inline) => {
+                super::fragment::validate_inline_fragment(
+                    diagnostics,
+                    document,
                     against_type,
-                    inline.clone(),
+                    inline,
                     context.clone(),
-                ))
+                )
             }
         }
     }
-
-    diagnostics
 }
diff --git a/crates/apollo-compiler/src/validation/union_.rs b/crates/apollo-compiler/src/validation/union_.rs
index a248c5f..9b784c1 100644
--- a/crates/apollo-compiler/src/validation/union_.rs
+++ b/crates/apollo-compiler/src/validation/union_.rs
@@ -1,60 +1,55 @@
-use crate::{
-    ast, schema,
-    validation::diagnostics::{DiagnosticData, ValidationError},
-    ValidationDatabase,
-};
-
-pub(crate) fn validate_union_definitions(db: &dyn ValidationDatabase) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    for def in db.ast_types().unions.values() {
-        diagnostics.extend(db.validate_union_definition(def.clone()));
+use crate::schema::{self, ExtendedType, UnionType};
+use crate::validation::diagnostics::DiagnosticData;
+use crate::validation::DiagnosticList;
+use crate::{ast, Node};
+
+pub(crate) fn validate_union_definitions(diagnostics: &mut DiagnosticList, schema: &crate::Schema) {
+    for ty in schema.types.values() {
+        if let ExtendedType::Union(def) = ty {
+            validate_union_definition(diagnostics, schema, def);
+        }
     }
-
-    diagnostics
 }
 
 pub(crate) fn validate_union_definition(
-    db: &dyn ValidationDatabase,
-    union_def: ast::TypeWithExtensions<ast::UnionTypeDefinition>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = super::directive::validate_directives(
-        db,
-        union_def.directives(),
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
+    union_def: &Node<UnionType>,
+) {
+    super::directive::validate_directives(
+        diagnostics,
+        Some(schema),
+        union_def.directives.iter_ast(),
         ast::DirectiveLocation::Union,
         // unions don't use variables
         Default::default(),
     );
 
-    let schema = db.schema();
-
-    for union_member in union_def.members() {
+    for union_member in &union_def.members {
         let member_location = union_member.location();
         // TODO: (?) A Union type must include one or more unique member types.
 
-        match schema.types.get(union_member) {
+        match schema.types.get(&union_member.name) {
             None => {
                 // Union member must be defined.
-                diagnostics.push(ValidationError::new(
+                diagnostics.push(
                     member_location,
                     DiagnosticData::UndefinedDefinition {
-                        name: union_member.clone(),
+                        name: union_member.name.clone(),
                     },
-                ));
+                );
             }
             Some(schema::ExtendedType::Object(_)) => {} // good
             Some(ty) => {
                 // Union member must be of object type.
-                diagnostics.push(ValidationError::new(
+                diagnostics.push(
                     member_location,
                     DiagnosticData::UnionMemberObjectType {
-                        name: union_member.clone(),
+                        name: union_member.name.clone(),
                         describe_type: ty.describe(),
                     },
-                ));
+                );
             }
         }
     }
-
-    diagnostics
 }
diff --git a/crates/apollo-compiler/src/validation/validation_db.rs b/crates/apollo-compiler/src/validation/validation_db.rs
deleted file mode 100644
index 296ca02..0000000
--- a/crates/apollo-compiler/src/validation/validation_db.rs
+++ /dev/null
@@ -1,353 +0,0 @@
-use crate::validation::diagnostics::ValidationError;
-use crate::validation::{
-    self, directive, enum_, input_object, interface, object, operation, scalar, union_, FileId,
-};
-use crate::{ast, name, schema, InputDatabase, Node, ReprDatabase};
-use std::collections::HashMap;
-use std::sync::Arc;
-
-use super::field;
-
-#[salsa::query_group(ValidationStorage)]
-pub(crate) trait ValidationDatabase: InputDatabase + ReprDatabase {
-    fn ast_types(&self) -> Arc<ast::TypeSystem>;
-    fn ast_named_fragments(
-        &self,
-        file_id: FileId,
-    ) -> Arc<HashMap<ast::Name, Node<ast::FragmentDefinition>>>;
-
-    /// Validate all documents.
-    fn validate(&self) -> Vec<ValidationError>;
-
-    /// Validate the type system, combined of all type system documents known to
-    /// the compiler.
-    #[salsa::invoke(validate_type_system)]
-    fn validate_type_system(&self) -> Vec<ValidationError>;
-
-    /// Validate an executable document.
-    #[salsa::invoke(validate_executable)]
-    fn validate_executable(&self, file_id: FileId) -> Vec<ValidationError>;
-
-    /// Validate a standalone executable document, without knowledge of the type system it executes
-    /// against.
-    ///
-    /// This runs a subset of the validations from `validate_executable`.
-    #[salsa::invoke(validate_standalone_executable)]
-    fn validate_standalone_executable(&self, file_id: FileId) -> Vec<ValidationError>;
-
-    #[salsa::invoke(validation::schema::validate_schema_definition)]
-    fn validate_schema_definition(
-        &self,
-        def: ast::TypeWithExtensions<ast::SchemaDefinition>,
-    ) -> Vec<ValidationError>;
-
-    #[salsa::invoke(scalar::validate_scalar_definitions)]
-    fn validate_scalar_definitions(&self) -> Vec<ValidationError>;
-
-    #[salsa::invoke(scalar::validate_scalar_definition)]
-    fn validate_scalar_definition(
-        &self,
-        scalar_def: Node<schema::ScalarType>,
-    ) -> Vec<ValidationError>;
-
-    #[salsa::invoke(enum_::validate_enum_definitions)]
-    fn validate_enum_definitions(&self) -> Vec<ValidationError>;
-
-    #[salsa::invoke(enum_::validate_enum_definition)]
-    fn validate_enum_definition(
-        &self,
-        enum_: ast::TypeWithExtensions<ast::EnumTypeDefinition>,
-    ) -> Vec<ValidationError>;
-
-    #[salsa::invoke(union_::validate_union_definitions)]
-    fn validate_union_definitions(&self) -> Vec<ValidationError>;
-
-    #[salsa::invoke(union_::validate_union_definition)]
-    fn validate_union_definition(
-        &self,
-        union_: ast::TypeWithExtensions<ast::UnionTypeDefinition>,
-    ) -> Vec<ValidationError>;
-
-    #[salsa::invoke(interface::validate_interface_definitions)]
-    fn validate_interface_definitions(&self) -> Vec<ValidationError>;
-
-    #[salsa::invoke(interface::validate_interface_definition)]
-    fn validate_interface_definition(
-        &self,
-        interface: ast::TypeWithExtensions<ast::InterfaceTypeDefinition>,
-    ) -> Vec<ValidationError>;
-
-    #[salsa::invoke(directive::validate_directive_definition)]
-    fn validate_directive_definition(
-        &self,
-        directive_definition: Node<ast::DirectiveDefinition>,
-    ) -> Vec<ValidationError>;
-
-    #[salsa::invoke(directive::validate_directive_definitions)]
-    fn validate_directive_definitions(&self) -> Vec<ValidationError>;
-
-    #[salsa::invoke(input_object::validate_input_object_definitions)]
-    fn validate_input_object_definitions(&self) -> Vec<ValidationError>;
-
-    #[salsa::invoke(input_object::validate_input_object_definition)]
-    fn validate_input_object_definition(
-        &self,
-        input_object: ast::TypeWithExtensions<ast::InputObjectTypeDefinition>,
-    ) -> Vec<ValidationError>;
-
-    #[salsa::invoke(object::validate_object_type_definitions)]
-    fn validate_object_type_definitions(&self) -> Vec<ValidationError>;
-
-    #[salsa::invoke(object::validate_object_type_definition)]
-    fn validate_object_type_definition(
-        &self,
-        def: ast::TypeWithExtensions<ast::ObjectTypeDefinition>,
-    ) -> Vec<ValidationError>;
-
-    #[salsa::invoke(field::validate_field_definitions)]
-    fn validate_field_definitions(
-        &self,
-        fields: Vec<Node<ast::FieldDefinition>>,
-    ) -> Vec<ValidationError>;
-
-    #[salsa::invoke(operation::validate_operation_definitions)]
-    fn validate_operation_definitions(&self, file_id: FileId) -> Vec<ValidationError>;
-}
-
-fn ast_types(db: &dyn ValidationDatabase) -> Arc<ast::TypeSystem> {
-    let mut objects = HashMap::new();
-    let mut scalars = HashMap::new();
-    let mut interfaces = HashMap::new();
-    let mut unions = HashMap::new();
-    let mut enums = HashMap::new();
-    let mut input_objects = HashMap::new();
-
-    let mut schema_definition = None;
-    let mut schema_extensions = vec![];
-
-    for file_id in db.type_definition_files() {
-        if file_id == FileId::BUILT_IN {
-            continue;
-        }
-
-        let document = db.ast(file_id);
-        for definition in document.definitions.iter() {
-            match definition {
-                ast::Definition::SchemaDefinition(schema) => {
-                    schema_definition = Some(schema.clone());
-                }
-                ast::Definition::ObjectTypeDefinition(object) => {
-                    objects.insert(
-                        object.name.clone(),
-                        ast::TypeWithExtensions {
-                            definition: object.clone(),
-                            extensions: vec![],
-                        },
-                    );
-                }
-                ast::Definition::ScalarTypeDefinition(scalar) => {
-                    scalars.insert(
-                        scalar.name.clone(),
-                        ast::TypeWithExtensions {
-                            definition: scalar.clone(),
-                            extensions: vec![],
-                        },
-                    );
-                }
-                ast::Definition::InterfaceTypeDefinition(interface) => {
-                    interfaces.insert(
-                        interface.name.clone(),
-                        ast::TypeWithExtensions {
-                            definition: interface.clone(),
-                            extensions: vec![],
-                        },
-                    );
-                }
-                ast::Definition::UnionTypeDefinition(union_) => {
-                    unions.insert(
-                        union_.name.clone(),
-                        ast::TypeWithExtensions {
-                            definition: union_.clone(),
-                            extensions: vec![],
-                        },
-                    );
-                }
-                ast::Definition::EnumTypeDefinition(enum_) => {
-                    enums.insert(
-                        enum_.name.clone(),
-                        ast::TypeWithExtensions {
-                            definition: enum_.clone(),
-                            extensions: vec![],
-                        },
-                    );
-                }
-                ast::Definition::InputObjectTypeDefinition(input_object) => {
-                    input_objects.insert(
-                        input_object.name.clone(),
-                        ast::TypeWithExtensions {
-                            definition: input_object.clone(),
-                            extensions: vec![],
-                        },
-                    );
-                }
-                _ => (),
-            }
-        }
-        for definition in document.definitions.iter() {
-            match definition {
-                ast::Definition::SchemaExtension(schema) => {
-                    schema_extensions.push(schema.clone());
-                }
-                ast::Definition::ObjectTypeExtension(extension) => {
-                    if let Some(ty) = objects.get_mut(&extension.name) {
-                        ty.extensions.push(extension.clone());
-                    }
-                }
-                ast::Definition::ScalarTypeExtension(extension) => {
-                    if let Some(ty) = scalars.get_mut(&extension.name) {
-                        ty.extensions.push(extension.clone());
-                    }
-                }
-                ast::Definition::InterfaceTypeExtension(extension) => {
-                    if let Some(ty) = interfaces.get_mut(&extension.name) {
-                        ty.extensions.push(extension.clone());
-                    }
-                }
-                ast::Definition::UnionTypeExtension(extension) => {
-                    if let Some(ty) = unions.get_mut(&extension.name) {
-                        ty.extensions.push(extension.clone());
-                    }
-                }
-                ast::Definition::EnumTypeExtension(extension) => {
-                    if let Some(ty) = enums.get_mut(&extension.name) {
-                        ty.extensions.push(extension.clone());
-                    }
-                }
-                ast::Definition::InputObjectTypeExtension(extension) => {
-                    if let Some(ty) = input_objects.get_mut(&extension.name) {
-                        ty.extensions.push(extension.clone());
-                    }
-                }
-                _ => (),
-            }
-        }
-    }
-
-    let schema = ast::TypeWithExtensions {
-        definition: schema_definition.unwrap_or_else(|| {
-            Node::new(ast::SchemaDefinition {
-                description: None,
-                directives: ast::DirectiveList::new(),
-                root_operations: {
-                    let mut operations = Vec::with_capacity(3);
-                    let query_name = name!("Query");
-                    if objects.contains_key(&query_name) {
-                        operations.push((ast::OperationType::Query, query_name).into());
-                    }
-                    let mutation_name = name!("Mutation");
-                    if objects.contains_key(&mutation_name) {
-                        operations.push((ast::OperationType::Mutation, mutation_name).into());
-                    }
-                    let subscription_name = name!("Subscription");
-                    if objects.contains_key(&subscription_name) {
-                        operations
-                            .push((ast::OperationType::Subscription, subscription_name).into());
-                    }
-                    operations
-                },
-            })
-        }),
-        extensions: schema_extensions,
-    };
-
-    Arc::new(ast::TypeSystem {
-        schema,
-        objects,
-        scalars,
-        interfaces,
-        unions,
-        enums,
-        input_objects,
-    })
-}
-
-pub(crate) fn ast_named_fragments(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-) -> Arc<HashMap<ast::Name, Node<ast::FragmentDefinition>>> {
-    let document = db.ast(file_id);
-    let mut named_fragments = HashMap::new();
-    for definition in &document.definitions {
-        if let ast::Definition::FragmentDefinition(fragment) = definition {
-            named_fragments
-                .entry(fragment.name.clone())
-                .or_insert(fragment.clone());
-        }
-    }
-    Arc::new(named_fragments)
-}
-
-pub(crate) fn validate(db: &dyn ValidationDatabase) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    diagnostics.extend(db.validate_type_system());
-
-    for file_id in db.executable_definition_files() {
-        diagnostics.extend(db.validate_executable(file_id));
-    }
-
-    diagnostics
-}
-
-pub(crate) fn validate_type_system(db: &dyn ValidationDatabase) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    let schema = db.ast_types().schema.clone();
-    diagnostics.extend(db.validate_schema_definition(schema));
-
-    diagnostics.extend(db.validate_scalar_definitions());
-    diagnostics.extend(db.validate_enum_definitions());
-    diagnostics.extend(db.validate_union_definitions());
-
-    diagnostics.extend(db.validate_interface_definitions());
-    diagnostics.extend(db.validate_directive_definitions());
-    diagnostics.extend(db.validate_input_object_definitions());
-    diagnostics.extend(db.validate_object_type_definitions());
-
-    diagnostics
-}
-
-fn validate_executable_inner(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-    has_schema: bool,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    diagnostics.extend(super::operation::validate_operation_definitions_inner(
-        db, file_id, has_schema,
-    ));
-    for def in db.ast_named_fragments(file_id).values() {
-        diagnostics.extend(super::fragment::validate_fragment_used(
-            db,
-            def.clone(),
-            file_id,
-        ));
-    }
-
-    diagnostics
-}
-
-pub(crate) fn validate_standalone_executable(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-) -> Vec<ValidationError> {
-    validate_executable_inner(db, file_id, false)
-}
-
-pub(crate) fn validate_executable(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-) -> Vec<ValidationError> {
-    validate_executable_inner(db, file_id, true)
-}
diff --git a/crates/apollo-compiler/src/validation/value.rs b/crates/apollo-compiler/src/validation/value.rs
index 3fdfb90..b874f6d 100644
--- a/crates/apollo-compiler/src/validation/value.rs
+++ b/crates/apollo-compiler/src/validation/value.rs
@@ -1,12 +1,17 @@
 use crate::ast;
 use crate::coordinate::TypeAttributeCoordinate;
 use crate::schema;
-use crate::validation::diagnostics::{DiagnosticData, ValidationError};
-use crate::validation::ValidationDatabase;
+use crate::validation::diagnostics::DiagnosticData;
+
+use crate::validation::DiagnosticList;
 use crate::Node;
 
-fn unsupported_type(value: &Node<ast::Value>, declared_type: &Node<ast::Type>) -> ValidationError {
-    ValidationError::new(
+fn unsupported_type(
+    diagnostics: &mut DiagnosticList,
+    value: &Node<ast::Value>,
+    declared_type: &Node<ast::Type>,
+) {
+    diagnostics.push(
         value.location(),
         DiagnosticData::UnsupportedValueType {
             describe_value_type: value.describe(),
@@ -17,24 +22,22 @@ fn unsupported_type(value: &Node<ast::Value>, declared_type: &Node<ast::Type>) -
 }
 
 pub(crate) fn validate_values(
-    db: &dyn ValidationDatabase,
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
     ty: &Node<ast::Type>,
     argument: &Node<ast::Argument>,
     var_defs: &[Node<ast::VariableDefinition>],
-) -> Vec<ValidationError> {
-    let mut diagnostics = vec![];
-    value_of_correct_type(db, ty, &argument.value, var_defs, &mut diagnostics);
-    diagnostics
+) {
+    value_of_correct_type(diagnostics, schema, ty, &argument.value, var_defs);
 }
 
 pub(crate) fn value_of_correct_type(
-    db: &dyn ValidationDatabase,
+    diagnostics: &mut DiagnosticList,
+    schema: &crate::Schema,
     ty: &Node<ast::Type>,
     arg_value: &Node<ast::Value>,
     var_defs: &[Node<ast::VariableDefinition>],
-    diagnostics: &mut Vec<ValidationError>,
 ) {
-    let schema = db.schema();
     let Some(type_definition) = schema.types.get(ty.inner_named_type()) else {
         return;
     };
@@ -56,27 +59,27 @@ pub(crate) fn value_of_correct_type(
                 "ID" => {}
                 "Int" => {
                     if int.try_to_i32().is_err() {
-                        diagnostics.push(ValidationError::new(
+                        diagnostics.push(
                             arg_value.location(),
                             DiagnosticData::IntCoercionError {
                                 value: int.as_str().to_owned(),
                             },
-                        ))
+                        )
                     }
                 }
                 "Float" => {
                     if int.try_to_f64().is_err() {
-                        diagnostics.push(ValidationError::new(
+                        diagnostics.push(
                             arg_value.location(),
                             DiagnosticData::FloatCoercionError {
                                 value: int.as_str().to_owned(),
                             },
-                        ))
+                        )
                     }
                 }
-                _ => diagnostics.push(unsupported_type(arg_value, ty)),
+                _ => unsupported_type(diagnostics, arg_value, ty),
             },
-            _ => diagnostics.push(unsupported_type(arg_value, ty)),
+            _ => unsupported_type(diagnostics, arg_value, ty),
         },
         // When expected as an input type, both integer and float input
         // values are accepted. All other input values, including strings
@@ -87,15 +90,15 @@ pub(crate) fn value_of_correct_type(
             schema::ExtendedType::Scalar(scalar) if !scalar.is_built_in() => {}
             schema::ExtendedType::Scalar(scalar) if scalar.name == "Float" => {
                 if float.try_to_f64().is_err() {
-                    diagnostics.push(ValidationError::new(
+                    diagnostics.push(
                         arg_value.location(),
                         DiagnosticData::FloatCoercionError {
                             value: float.as_str().to_owned(),
                         },
-                    ))
+                    )
                 }
             }
-            _ => diagnostics.push(unsupported_type(arg_value, ty)),
+            _ => unsupported_type(diagnostics, arg_value, ty),
         },
         // When expected as an input type, only valid Unicode string input
         // values are accepted. All other input values must raise a request
@@ -109,10 +112,10 @@ pub(crate) fn value_of_correct_type(
                 // string, ids and custom scalars are ok, and
                 // don't need a diagnostic.
                 if scalar.is_built_in() && !matches!(scalar.name.as_str(), "String" | "ID") {
-                    diagnostics.push(unsupported_type(arg_value, ty));
+                    unsupported_type(diagnostics, arg_value, ty);
                 }
             }
-            _ => diagnostics.push(unsupported_type(arg_value, ty)),
+            _ => unsupported_type(diagnostics, arg_value, ty),
         },
         // When expected as an input type, only boolean input values are
         // accepted. All other input values must raise a request error
@@ -120,14 +123,14 @@ pub(crate) fn value_of_correct_type(
         ast::Value::Boolean(_) => match &type_definition {
             schema::ExtendedType::Scalar(scalar) => {
                 if scalar.is_built_in() && scalar.name.as_str() != "Boolean" {
-                    diagnostics.push(unsupported_type(arg_value, ty));
+                    unsupported_type(diagnostics, arg_value, ty);
                 }
             }
-            _ => diagnostics.push(unsupported_type(arg_value, ty)),
+            _ => unsupported_type(diagnostics, arg_value, ty),
         },
         ast::Value::Null => {
             if ty.is_non_null() {
-                diagnostics.push(unsupported_type(arg_value, ty));
+                unsupported_type(diagnostics, arg_value, ty);
             }
         }
         ast::Value::Variable(var_name) => match &type_definition {
@@ -140,23 +143,23 @@ pub(crate) fn value_of_correct_type(
                     // compare if two Types are the same
                     // TODO(@goto-bus-stop) This should use the is_assignable_to check
                     if var_def.ty.inner_named_type() != ty.inner_named_type() {
-                        diagnostics.push(unsupported_type(arg_value, ty));
+                        unsupported_type(diagnostics, arg_value, ty);
                     } else if let Some(default_value) = &var_def.default_value {
                         if var_def.ty.is_non_null() && default_value.is_null() {
-                            diagnostics.push(unsupported_type(default_value, &var_def.ty))
+                            unsupported_type(diagnostics, default_value, &var_def.ty)
                         } else {
                             value_of_correct_type(
-                                db,
+                                diagnostics,
+                                schema,
                                 &var_def.ty,
                                 default_value,
                                 var_defs,
-                                diagnostics,
                             )
                         }
                     }
                 }
             }
-            _ => diagnostics.push(unsupported_type(arg_value, ty)),
+            _ => unsupported_type(diagnostics, arg_value, ty),
         },
         ast::Value::Enum(value) => match &type_definition {
             schema::ExtendedType::Scalar(scalar) if !scalar.is_built_in() => {
@@ -164,17 +167,17 @@ pub(crate) fn value_of_correct_type(
             }
             schema::ExtendedType::Enum(enum_) => {
                 if !enum_.values.contains_key(value) {
-                    diagnostics.push(ValidationError::new(
+                    diagnostics.push(
                         value.location(),
                         DiagnosticData::UndefinedEnumValue {
                             value: value.clone(),
                             definition: enum_.name.clone(),
                             definition_location: enum_.location(),
                         },
-                    ));
+                    );
                 }
             }
-            _ => diagnostics.push(unsupported_type(arg_value, ty)),
+            _ => unsupported_type(diagnostics, arg_value, ty),
         },
         // When expected as an input, list values are accepted only when
         // each item in the list can be accepted by the list’s item type.
@@ -189,15 +192,15 @@ pub(crate) fn value_of_correct_type(
                 // A named type can still accept a list if it is a custom scalar.
                 || matches!(type_definition, schema::ExtendedType::Scalar(scalar) if !scalar.is_built_in());
             if !accepts_list {
-                diagnostics.push(unsupported_type(arg_value, ty))
+                unsupported_type(diagnostics, arg_value, ty)
             } else {
                 let item_type = ty.same_location(ty.item_type().clone());
                 if type_definition.is_input_type() {
                     for v in li {
-                        value_of_correct_type(db, &item_type, v, var_defs, diagnostics);
+                        value_of_correct_type(diagnostics, schema, &item_type, v, var_defs);
                     }
                 } else {
-                    diagnostics.push(unsupported_type(arg_value, &item_type));
+                    unsupported_type(diagnostics, arg_value, &item_type);
                 }
             }
         }
@@ -211,14 +214,14 @@ pub(crate) fn value_of_correct_type(
                 // Add a diagnostic if a value does not exist on the input
                 // object type
                 if let Some((name, value)) = undefined_field {
-                    diagnostics.push(ValidationError::new(
+                    diagnostics.push(
                         value.location(),
                         DiagnosticData::UndefinedInputValue {
                             value: name.clone(),
                             definition: input_obj.name.clone(),
                             definition_location: input_obj.location(),
                         },
-                    ));
+                    );
                 }
 
                 input_obj.fields.iter().for_each(|(input_name, f)| {
@@ -233,7 +236,7 @@ pub(crate) fn value_of_correct_type(
                     // is null or missing entirely, an error should be
                     // raised.
                     if (ty.is_non_null() && f.default_value.is_none()) && (is_missing || is_null) {
-                        diagnostics.push(ValidationError::new(
+                        diagnostics.push(
                             arg_value.location(),
                             DiagnosticData::RequiredField {
                                 name: input_name.clone(),
@@ -243,17 +246,17 @@ pub(crate) fn value_of_correct_type(
                                 },
                                 definition_location: f.location(),
                             },
-                        ));
+                        );
                     }
 
                     let used_val = obj.iter().find(|(obj_name, ..)| obj_name == input_name);
 
                     if let Some((_, v)) = used_val {
-                        value_of_correct_type(db, ty, v, var_defs, diagnostics);
+                        value_of_correct_type(diagnostics, schema, ty, v, var_defs);
                     }
                 })
             }
-            _ => diagnostics.push(unsupported_type(arg_value, ty)),
+            _ => unsupported_type(diagnostics, arg_value, ty),
         },
     }
 }
diff --git a/crates/apollo-compiler/src/validation/variable.rs b/crates/apollo-compiler/src/validation/variable.rs
index 2a7d686..3335044 100644
--- a/crates/apollo-compiler/src/validation/variable.rs
+++ b/crates/apollo-compiler/src/validation/variable.rs
@@ -1,31 +1,29 @@
-use crate::validation::diagnostics::{DiagnosticData, ValidationError};
+use crate::validation::diagnostics::DiagnosticData;
 use crate::validation::{
-    FileId, NodeLocation, RecursionGuard, RecursionLimitError, RecursionStack,
+    DiagnosticList, NodeLocation, RecursionGuard, RecursionLimitError, RecursionStack,
 };
-use crate::{ast, Node, ValidationDatabase};
+use crate::{ast, executable, ExecutableDocument, Node};
 use std::collections::hash_map::Entry;
 use std::collections::{HashMap, HashSet};
 
 pub(crate) fn validate_variable_definitions(
-    db: &dyn ValidationDatabase,
+    diagnostics: &mut DiagnosticList,
+    schema: Option<&crate::Schema>,
     variables: &[Node<ast::VariableDefinition>],
-    has_schema: bool,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-    let schema = db.schema();
-
+) {
     let mut seen: HashMap<ast::Name, &Node<ast::VariableDefinition>> = HashMap::new();
     for variable in variables.iter() {
-        diagnostics.extend(super::directive::validate_directives(
-            db,
+        super::directive::validate_directives(
+            diagnostics,
+            schema,
             variable.directives.iter(),
             ast::DirectiveLocation::VariableDefinition,
             // let's assume that variable definitions cannot reference other
             // variables and provide them as arguments to directives
             Default::default(),
-        ));
+        );
 
-        if has_schema {
+        if let Some(schema) = &schema {
             let ty = &variable.ty;
             let type_definition = schema.types.get(ty.inner_named_type());
 
@@ -34,21 +32,21 @@ pub(crate) fn validate_variable_definitions(
                     // OK!
                 }
                 Some(type_definition) => {
-                    diagnostics.push(ValidationError::new(
+                    diagnostics.push(
                         variable.location(),
                         DiagnosticData::VariableInputType {
                             name: variable.name.clone(),
                             describe_type: type_definition.describe(),
                             type_location: ty.location(),
                         },
-                    ));
+                    );
                 }
-                None => diagnostics.push(ValidationError::new(
+                None => diagnostics.push(
                     variable.location(),
                     DiagnosticData::UndefinedDefinition {
                         name: ty.inner_named_type().clone(),
                     },
-                )),
+                ),
             }
         }
 
@@ -56,55 +54,40 @@ pub(crate) fn validate_variable_definitions(
             Entry::Occupied(original) => {
                 let original_definition = original.get().location();
                 let redefined_definition = variable.location();
-                diagnostics.push(ValidationError::new(
+                diagnostics.push(
                     redefined_definition,
                     DiagnosticData::UniqueVariable {
                         name: variable.name.clone(),
                         original_definition,
                         redefined_definition,
                     },
-                ));
+                );
             }
             Entry::Vacant(entry) => {
                 entry.insert(variable);
             }
         }
     }
-
-    diagnostics
 }
 
-type NamedFragments = HashMap<ast::Name, Node<ast::FragmentDefinition>>;
-
-fn walk_selections(
-    document: &ast::Document,
-    selections: &[ast::Selection],
-    mut f: impl FnMut(&NamedFragments, &ast::Selection),
+fn walk_selections<'doc>(
+    document: &'doc ExecutableDocument,
+    selections: &'doc executable::SelectionSet,
+    mut f: impl FnMut(&'doc executable::Selection),
 ) -> Result<(), RecursionLimitError> {
-    let named_fragments: NamedFragments = document
-        .definitions
-        .iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::FragmentDefinition(fragment) => {
-                Some((fragment.name.clone(), fragment.clone()))
-            }
-            _ => None,
-        })
-        .collect();
-
-    fn walk_selections_inner<'ast, 'guard>(
-        named_fragments: &'ast NamedFragments,
-        selections: &'ast [ast::Selection],
+    fn walk_selections_inner<'doc, 'guard>(
+        document: &'doc ExecutableDocument,
+        selection_set: &'doc executable::SelectionSet,
         guard: &mut RecursionGuard<'guard>,
-        f: &mut dyn FnMut(&NamedFragments, &ast::Selection),
+        f: &mut dyn FnMut(&'doc executable::Selection),
     ) -> Result<(), RecursionLimitError> {
-        for selection in selections {
-            f(named_fragments, selection);
+        for selection in &selection_set.selections {
+            f(selection);
             match selection {
-                ast::Selection::Field(field) => {
-                    walk_selections_inner(named_fragments, &field.selection_set, guard, f)?;
+                executable::Selection::Field(field) => {
+                    walk_selections_inner(document, &field.selection_set, guard, f)?;
                 }
-                ast::Selection::FragmentSpread(fragment) => {
+                executable::Selection::FragmentSpread(fragment) => {
                     // Prevent chasing a cyclical reference.
                     // Note we do not report `CycleError::Recursed` here, as that is already caught
                     // by the cyclical fragment validation--we just need to ensure that we don't
@@ -113,18 +96,19 @@ fn walk_selections(
                         continue;
                     }
 
-                    if let Some(fragment_definition) = named_fragments.get(&fragment.fragment_name)
+                    if let Some(fragment_definition) =
+                        document.fragments.get(&fragment.fragment_name)
                     {
                         walk_selections_inner(
-                            named_fragments,
+                            document,
                             &fragment_definition.selection_set,
                             &mut guard.push(&fragment.fragment_name)?,
                             f,
                         )?;
                     }
                 }
-                ast::Selection::InlineFragment(fragment) => {
-                    walk_selections_inner(named_fragments, &fragment.selection_set, guard, f)?;
+                executable::Selection::InlineFragment(fragment) => {
+                    walk_selections_inner(document, &fragment.selection_set, guard, f)?;
                 }
             }
         }
@@ -132,16 +116,16 @@ fn walk_selections(
     }
 
     let mut stack = RecursionStack::new().with_limit(100);
-    let result = walk_selections_inner(&named_fragments, selections, &mut stack.guard(), &mut f);
+    let result = walk_selections_inner(document, selections, &mut stack.guard(), &mut f);
     result
 }
 
-fn variables_in_value(value: &ast::Value) -> impl Iterator<Item = ast::Name> + '_ {
+fn variables_in_value(value: &ast::Value) -> impl Iterator<Item = &ast::Name> + '_ {
     let mut value_stack = vec![value];
     std::iter::from_fn(move || {
         while let Some(value) = value_stack.pop() {
             match value {
-                ast::Value::Variable(variable) => return Some(variable.clone()),
+                ast::Value::Variable(variable) => return Some(variable),
                 ast::Value::List(list) => value_stack.extend(list.iter().map(|value| &**value)),
                 ast::Value::Object(fields) => {
                     value_stack.extend(fields.iter().map(|(_, value)| &**value))
@@ -153,13 +137,13 @@ fn variables_in_value(value: &ast::Value) -> impl Iterator<Item = ast::Name> + '
     })
 }
 
-fn variables_in_arguments(args: &[Node<ast::Argument>]) -> impl Iterator<Item = ast::Name> + '_ {
+fn variables_in_arguments(args: &[Node<ast::Argument>]) -> impl Iterator<Item = &ast::Name> + '_ {
     args.iter().flat_map(|arg| variables_in_value(&arg.value))
 }
 
 fn variables_in_directives(
     directives: &[Node<ast::Directive>],
-) -> impl Iterator<Item = ast::Name> + '_ {
+) -> impl Iterator<Item = &ast::Name> + '_ {
     directives
         .iter()
         .flat_map(|directive| variables_in_arguments(&directive.arguments))
@@ -172,17 +156,11 @@ fn variables_in_directives(
 //   a: field (arg: $var2)
 // }
 pub(crate) fn validate_unused_variables(
-    db: &dyn ValidationDatabase,
-    file_id: FileId,
-    operation: Node<ast::OperationDefinition>,
-) -> Vec<ValidationError> {
-    let mut diagnostics = Vec::new();
-
-    let defined_vars: HashSet<_> = operation
-        .variables
-        .iter()
-        .map(|var| var.name.clone())
-        .collect();
+    diagnostics: &mut DiagnosticList,
+    document: &ExecutableDocument,
+    operation: &executable::Operation,
+) {
+    let defined_vars: HashSet<_> = operation.variables.iter().map(|var| &var.name).collect();
     let locations: HashMap<_, _> = operation
         .variables
         .iter()
@@ -193,62 +171,55 @@ pub(crate) fn validate_unused_variables(
             )
         })
         .collect();
-    let mut used_vars = HashSet::<ast::Name>::new();
+    let mut used_vars = HashSet::new();
     let walked = walk_selections(
-        &db.ast(file_id),
+        document,
         &operation.selection_set,
-        |named_fragments, selection| match selection {
-            ast::Selection::Field(field) => {
+        |selection| match selection {
+            executable::Selection::Field(field) => {
                 used_vars.extend(variables_in_directives(&field.directives));
                 used_vars.extend(variables_in_arguments(&field.arguments));
             }
-            ast::Selection::FragmentSpread(fragment) => {
-                if let Some(fragment_def) = named_fragments.get(&fragment.fragment_name) {
+            executable::Selection::FragmentSpread(fragment) => {
+                if let Some(fragment_def) = document.fragments.get(&fragment.fragment_name) {
                     used_vars.extend(variables_in_directives(&fragment_def.directives));
                 }
                 used_vars.extend(variables_in_directives(&fragment.directives));
             }
-            ast::Selection::InlineFragment(fragment) => {
+            executable::Selection::InlineFragment(fragment) => {
                 used_vars.extend(variables_in_directives(&fragment.directives));
             }
         },
     );
     if walked.is_err() {
-        diagnostics.push(ValidationError::new(
-            None,
-            DiagnosticData::RecursionError {},
-        ));
-        return diagnostics;
+        diagnostics.push(None, DiagnosticData::RecursionError {});
+        return;
     }
 
-    let unused_vars = defined_vars.difference(&used_vars);
-
-    diagnostics.extend(unused_vars.map(|unused_var| {
-        let loc = locations[unused_var];
-        ValidationError::new(
-            loc,
+    for &unused_var in defined_vars.difference(&used_vars) {
+        diagnostics.push(
+            locations[unused_var],
             DiagnosticData::UnusedVariable {
                 name: unused_var.clone(),
             },
         )
-    }));
-
-    diagnostics
+    }
 }
 
 pub(crate) fn validate_variable_usage(
-    var_usage: Node<ast::InputValueDefinition>,
+    diagnostics: &mut DiagnosticList,
+    var_usage: &Node<ast::InputValueDefinition>,
     var_defs: &[Node<ast::VariableDefinition>],
     argument: &Node<ast::Argument>,
-) -> Result<(), ValidationError> {
+) -> Result<(), ()> {
     if let ast::Value::Variable(var_name) = &*argument.value {
         // Let var_def be the VariableDefinition named
         // variable_name defined within operation.
         let var_def = var_defs.iter().find(|v| v.name == *var_name);
         if let Some(var_def) = var_def {
-            let is_allowed = is_variable_usage_allowed(var_def, &var_usage);
+            let is_allowed = is_variable_usage_allowed(var_def, var_usage);
             if !is_allowed {
-                return Err(ValidationError::new(
+                diagnostics.push(
                     argument.location(),
                     DiagnosticData::DisallowedVariableUsage {
                         variable: var_def.name.clone(),
@@ -258,15 +229,17 @@ pub(crate) fn validate_variable_usage(
                         argument_type: (*var_usage.ty).clone(),
                         argument_location: argument.location(),
                     },
-                ));
+                );
+                return Err(());
             }
         } else {
-            return Err(ValidationError::new(
+            diagnostics.push(
                 argument.value.location(),
                 DiagnosticData::UndefinedVariable {
                     name: var_name.clone(),
                 },
-            ));
+            );
+            return Err(());
         }
     }
     // It's super confusing to produce a diagnostic here if either the


Test patch:
diff --git a/crates/apollo-compiler/tests/snapshot_tests.rs b/crates/apollo-compiler/tests/snapshot_tests.rs
index e6a45cb..0cf68b9 100644
--- a/crates/apollo-compiler/tests/snapshot_tests.rs
+++ b/crates/apollo-compiler/tests/snapshot_tests.rs
@@ -12,7 +12,7 @@ use apollo_compiler::name;
 use apollo_compiler::schema;
 use apollo_compiler::ty;
 use apollo_compiler::validation::DiagnosticList;
-use apollo_compiler::validation::FileId;
+use apollo_compiler::FileId;
 use apollo_compiler::Schema;
 use expect_test::expect_file;
 use indexmap::IndexMap;