Repository: apollographql/apollo-rs
Pull request #671 (2023-10-05 11:05:16)

Make ApolloCompiler private + grab bag of API tweaks


Patch:
diff --git a/crates/apollo-compiler/README.md b/crates/apollo-compiler/README.md
index 851c040..c700382 100644
--- a/crates/apollo-compiler/README.md
+++ b/crates/apollo-compiler/README.md
@@ -18,9 +18,15 @@
 </div>
 
 ## Features
-* Ergonomic API on top the CST created by `apollo-parser`
-* GraphQL validation and diagnostics reporting
-    * Validation is a work in progress, stay tuned for further validation rules implementation.
+
+* A (comparatively) low-level AST for GraphQL grammar, 
+  and high-level representation of `Schema` and `ExecutableDocument`.
+* All three can be parsed (using `apollo-parser` internally), 
+  created or modified programatically,
+  and serialized.
+* Validation of schemas and executable documents, as defined [in the GraphQL specification][val].
+
+[val]: https://spec.graphql.org/October2021/#sec-Validation
 
 ## Getting started
 Add the dependency to start using `apollo-compiler`:
@@ -42,13 +48,10 @@ apollo-compiler = "0.11.0"
 Older version may or may not be compatible.
 
 ## Usage
-`apollo-compiler` is built using [`salsa`] to provide a
-memoised query system on top of the CST produced by `apollo-parser`.
-The idea is that all relationships between GraphQL types are pre-established and pre-computed, so you are able to always find the reference to say a field Type, or a Directive.
 
 You can get started with `apollo-compiler`:
 ```rust
-use apollo_compiler::ApolloCompiler;
+use apollo_compiler::Schema;
 
 let input = r#"
   interface Pet {
@@ -79,25 +82,20 @@ let input = r#"
   }
 "#;
 
-let mut compiler = ApolloCompiler::new();
-compiler.add_document(input, "document.graphql");
+let schema = Schema::parse(input, "document.graphql");
 
-let diagnostics = compiler.validate();
-for diagnostic in &diagnostics {
-    // this will pretty-print diagnostics using the miette crate.
-    println!("{}", diagnostic);
-}
-assert!(diagnostics.is_empty());
+/// In case of validation errors, the panic message will be nicely formatted
+/// to point at relevant parts of the source file(s)
+schema.validate().unwrap();
 ```
 
 ### Examples
 #### Accessing fragment definition field types
 
 ```rust
-use apollo_compiler::{ApolloCompiler, ReprDatabase, Node, executable};
-use miette::Result;
+use apollo_compiler::{Schema, ExecutableDocument, Node, executable};
 
-fn main() -> Result<()> {
+fn main() {
     let schema_input = r#"
     type User {
       id: ID
@@ -122,43 +120,36 @@ fn main() -> Result<()> {
     }
     "#;
 
-    let mut compiler = ApolloCompiler::new();
-    let _schema_id = compiler.add_type_system(schema_input, "schema.graphql");
-    let query_id = compiler.add_executable(query_input, "query.graphql");
+    let schema = Schema::parse(schema_input, "schema.graphql");
+    let document = ExecutableDocument::parse(&schema, query_input, "query.graphql");
 
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{}", diagnostic);
-    }
-    assert!(diagnostics.is_empty());
+    schema.validate().unwrap();
+    document.validate(&schema).unwrap();
 
-    let document = compiler.db.executable_document(query_id);
     let op = document.get_operation(Some("getUser")).expect("getUser query does not exist");
-    let fragment_in_op: Vec<&executable::Fragment> = op.selection_set.selections.iter().filter_map(|sel| match sel {
+    let fragment_in_op = op.selection_set.selections.iter().filter_map(|sel| match sel {
         executable::Selection::FragmentSpread(spread) => {
             Some(document.fragments.get(&spread.fragment_name)?.as_ref())
         }
         _ => None
-    }).collect();
+    }).collect::<Vec<&executable::Fragment>>();
 
-    let fragment_fields: Vec<&Node<executable::Field>> = fragment_in_op.iter().flat_map(|frag| {
+    let fragment_fields = fragment_in_op.iter().flat_map(|frag| {
         frag.selection_set.fields()
-    }).collect();
-    let field_ty: Vec<&str> = fragment_fields
+    }).collect::<Vec<&Node<executable::Field>>>();
+    let field_ty = fragment_fields
         .iter()
         .map(|f| f.ty().inner_named_type().as_str())
-        .collect();
+        .collect::<Vec<&str>>();
     assert_eq!(field_ty, ["ID", "String", "URL"]);
-    Ok(())
 }
 ```
 
 #### Get a directive defined on a field used in a query operation definition.
 ```rust
-use apollo_compiler::{ApolloCompiler, ReprDatabase, Node, executable};
-use anyhow::{anyhow, Result};
+use apollo_compiler::{Schema, ExecutableDocument, Node, executable};
 
-fn main() -> Result<()> {
+fn main() {
     let schema_input = r#"
     type Query {
       topProducts: Product
@@ -192,22 +183,12 @@ fn main() -> Result<()> {
     }
     "#;
 
+    let schema = Schema::parse(schema_input, "schema.graphql");
+    let document = ExecutableDocument::parse(&schema, query_input, "query.graphql");
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(schema_input, "schema.graphql");
-    let query_id = compiler.add_executable(query_input, "query.graphql");
+    schema.validate().unwrap();
+    document.validate(&schema).unwrap();
 
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{}", diagnostic);
-    }
-    let error_diagnostics = diagnostics
-        .iter()
-        .filter(|diag| diag.data.is_error())
-        .collect::<Vec<_>>();
-    assert!(error_diagnostics.is_empty());
-
-    let document = compiler.db.executable_document(query_id);
     let get_product_op = document
         .get_operation(Some("getProduct"))
         .expect("getProduct query does not exist");
@@ -228,15 +209,11 @@ fn main() -> Result<()> {
         .map(|dir| &dir.name)
         .collect();
     assert_eq!(in_stock_directive, ["join__field"]);
-    Ok(())
 }
 ```
 
 #### Printing diagnostics for a faulty GraphQL document
 ```rust
-use apollo_compiler::ApolloCompiler;
-use apollo_compiler::ReprDatabase;
-
 let input = r#"
 query {
   cat {
@@ -293,11 +270,8 @@ type Cat implements Pet {
 union CatOrDog = Cat | Dog
 "#;
 
-let mut compiler = ApolloCompiler::new();
-let id = compiler.add_document(input, "document.graphql");
+let (schema, executable) = apollo_compiler::parse_mixed(input, "document.graphql");
 
-let schema = compiler.db.schema();
-let executable = compiler.db.executable_document(id);
 if let Err(diagnostics) = schema.validate() {
     println!("{diagnostics}")
 }
@@ -309,8 +283,8 @@ if let Err(diagnostics) = executable.validate(&schema) {
 ## License
 Licensed under either of
 
-- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
-- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
+- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
 
 at your option.
 
diff --git a/crates/apollo-compiler/benches/multi_source.rs b/crates/apollo-compiler/benches/multi_source.rs
index ab96fca..e57d6e7 100644
--- a/crates/apollo-compiler/benches/multi_source.rs
+++ b/crates/apollo-compiler/benches/multi_source.rs
@@ -1,23 +1,18 @@
-use apollo_compiler::{ApolloCompiler, ReprDatabase};
+use apollo_compiler::ExecutableDocument;
+use apollo_compiler::Schema;
 use criterion::*;
 
-fn compile(schema: &str, query: &str) -> ApolloCompiler {
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(schema, "schema.graphql");
-    let executable_id = compiler.add_executable(query, "query.graphql");
-
-    black_box(black_box(compiler.db.executable_document(executable_id)));
-    black_box(black_box(compiler.db.schema()));
-
-    compiler
+fn compile(schema: &str, query: &str) -> (Schema, ExecutableDocument) {
+    let schema = Schema::parse(schema, "schema.graphql");
+    let doc = ExecutableDocument::parse(&schema, query, "query.graphql");
+    black_box((schema, doc))
 }
 
 fn compile_and_validate(schema: &str, query: &str) {
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(schema, "schema.graphql");
-    compiler.add_executable(query, "query.graphql");
-
-    black_box(compiler.validate());
+    let schema = Schema::parse(schema, "schema.graphql");
+    let doc = ExecutableDocument::parse(&schema, query, "query.graphql");
+    let _ = black_box(schema.validate());
+    let _ = black_box(doc.validate(&schema));
 }
 
 fn bench_simple_query_compiler(c: &mut Criterion) {
diff --git a/crates/apollo-compiler/examples/extend_db.rs b/crates/apollo-compiler/examples/extend_db.rs
deleted file mode 100644
index 22ef1e2..0000000
--- a/crates/apollo-compiler/examples/extend_db.rs
+++ /dev/null
@@ -1,184 +0,0 @@
-use std::{fmt, path::Path, sync::Arc};
-
-use apollo_compiler::{
-    database::{InputStorage, ReprStorage},
-    schema::Name,
-    FileId, InputDatabase, ReprDatabase, Source,
-};
-use miette::{Diagnostic, Report, SourceSpan};
-use thiserror::Error;
-
-/// A small example public API for this linter example.
-#[derive(Default)]
-pub struct Linter {
-    pub db: LinterDatabase,
-}
-
-impl Linter {
-    /// Create a new instance of Linter.
-    pub fn new() -> Self {
-        Default::default()
-    }
-
-    pub fn document(&mut self, input: &str, path: impl AsRef<Path>) -> FileId {
-        let id = FileId::new();
-        self.db.set_input(
-            id,
-            Source::document(path.as_ref().to_owned(), input.to_string()),
-        );
-
-        // Inform the queries about this new file.
-        let mut source_files = self.db.source_files();
-        source_files.push(id);
-        self.db.set_source_files(source_files);
-
-        id
-    }
-
-    /// Runs lints.
-    pub fn lint(&self) -> Vec<LintDiagnostic> {
-        self.db.lint()
-    }
-}
-
-// Includes all the necessary database's storage units that will now be
-// accessible from LinterDatabase.
-#[salsa::database(InputStorage, ReprStorage, LintValidationStorage)]
-#[derive(Default)]
-pub struct LinterDatabase {
-    pub storage: salsa::Storage<LinterDatabase>,
-}
-
-impl salsa::Database for LinterDatabase {}
-
-// This is important if your LinterDatabase storage needs to be accessed from in
-// a multi-threaded environment. You can drop this otherwise.
-impl salsa::ParallelDatabase for LinterDatabase {
-    fn snapshot(&self) -> salsa::Snapshot<LinterDatabase> {
-        salsa::Snapshot::new(LinterDatabase {
-            storage: self.storage.snapshot(),
-        })
-    }
-}
-
-// LintValidation database. It's based on four other Apollo Compiler databases.
-#[salsa::query_group(LintValidationStorage)]
-pub trait LintValidation: InputDatabase + ReprDatabase {
-    // Define any queries that should be part of this database.
-    fn lint(&self) -> Vec<LintDiagnostic>;
-    fn capitalised_definitions(&self) -> Vec<LintDiagnostic>;
-}
-
-// Implemenatation of the queries defined above. The lint query calls on
-// capitalised_definitions query. You ideally want queries to be based on other
-// queries.
-fn lint(db: &dyn LintValidation) -> Vec<LintDiagnostic> {
-    let mut lints = Vec::new();
-    lints.extend(db.capitalised_definitions());
-
-    lints
-}
-
-fn capitalised_definitions(db: &dyn LintValidation) -> Vec<LintDiagnostic> {
-    let mut lints = Vec::new();
-    for &id in db.executable_definition_files().iter() {
-        let doc = db.executable_document(id);
-        capitalised_names(db, doc.named_operations.keys(), &mut lints);
-        capitalised_names(db, doc.fragments.keys(), &mut lints);
-    }
-    let schema = db.schema();
-    capitalised_names(db, schema.types.keys(), &mut lints);
-    capitalised_names(db, schema.directive_definitions.keys(), &mut lints);
-    lints
-}
-
-fn capitalised_names<'a>(
-    db: &dyn LintValidation,
-    names: impl IntoIterator<Item = &'a Name>,
-    lints: &mut Vec<LintDiagnostic>,
-) {
-    for name in names {
-        if let Some(first_char) = name.chars().next() {
-            if !first_char.is_uppercase() {
-                if let Some(loc) = name.location() {
-                    let offset = loc.offset();
-                    let len = loc.node_len();
-
-                    lints.push(LintDiagnostic::CapitalisedDefinitions(
-                        CapitalisedDefinitions {
-                            src: Arc::new(db.source_code(loc.file_id()).to_string()),
-                            definition: (offset, len).into(),
-                        },
-                    ))
-                }
-            }
-        }
-    }
-}
-
-// Lint Diagnostics.
-#[derive(Clone, Debug, Hash, PartialEq, Eq)]
-pub enum LintDiagnostic {
-    CapitalisedDefinitions(CapitalisedDefinitions),
-}
-
-// This is specific to ensure lints are pretty printed. We are using `miette`'s
-// Report feature here.
-impl LintDiagnostic {
-    pub fn report(&self) -> Report {
-        match self {
-            LintDiagnostic::CapitalisedDefinitions(lint) => Report::new(lint.clone()),
-        }
-    }
-}
-
-// The pretty printed reports are only available with `Display`, otherwise lints
-// will be just structs, which is nice if you wish your tools to be further
-// processed.
-impl fmt::Display for LintDiagnostic {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        writeln!(f, "{:?}", self.report())
-    }
-}
-
-#[derive(Diagnostic, Debug, Error, Clone, Hash, PartialEq, Eq)]
-#[error("definitions should be capitalised")]
-#[diagnostic(code("graphql linter diagnostic"))]
-pub struct CapitalisedDefinitions {
-    #[source_code]
-    pub src: Arc<String>,
-
-    #[label = "capitalise this definition"]
-    pub definition: SourceSpan,
-}
-
-fn main() {
-    let input = r#"
-type query {
-  topProducts: Product
-  customer: User
-}
-
-type product {
-  type: String
-  price(setPrice: Int): Int
-}
-
-type user {
-  id: ID
-  name: String
-  profilePic(size: Int): URL
-}
-
-scalar url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
-    "#;
-
-    let mut linter = Linter::new();
-    linter.document(input, "document.graphql");
-    let lints = linter.lint();
-
-    // Display lints.
-    for lint in &lints {
-        println!("{lint}")
-    }
-}
diff --git a/crates/apollo-compiler/examples/extract_directives_used_by_query.rs b/crates/apollo-compiler/examples/extract_directives_used_by_query.rs
index d399fbd..6a93d14 100644
--- a/crates/apollo-compiler/examples/extract_directives_used_by_query.rs
+++ b/crates/apollo-compiler/examples/extract_directives_used_by_query.rs
@@ -1,17 +1,12 @@
 //! This example collects all directives declared on the fields that are queried by an operation.
 
 use apollo_compiler::executable::Directive;
-use apollo_compiler::ApolloCompiler;
-use apollo_compiler::FileId;
+use apollo_compiler::ExecutableDocument;
 use apollo_compiler::Node;
-use apollo_compiler::ReprDatabase;
+use apollo_compiler::Schema;
 
-fn get_directives_used_in_query(
-    compiler: &ApolloCompiler,
-    query_id: &FileId,
-) -> Vec<Node<Directive>> {
+fn get_directives_used_in_query(doc: &ExecutableDocument) -> Vec<&Node<Directive>> {
     // seed the stack with top-level fields
-    let doc = &compiler.db.executable_document(*query_id);
     let mut stack: Vec<_> = doc
         .all_operations()
         .flat_map(|op| op.definition().selection_set.fields())
@@ -21,7 +16,7 @@ fn get_directives_used_in_query(
 
     // depth first search for nested fields with directives
     while let Some(field) = stack.pop() {
-        directives.extend(field.definition.directives.iter().cloned());
+        directives.extend(field.definition.directives.iter());
         stack.extend(field.selection_set.fields());
     }
 
@@ -57,8 +52,7 @@ fn main() {
           }
         "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(schema_src, "not-used-here.graphql");
+    let schema = Schema::parse(schema_src, "not-used-here.graphql");
 
     let query_src0 = r#"query {
           directivesQuery {
@@ -70,9 +64,9 @@ fn main() {
           }
         }
         "#;
-    let query_id0 = compiler.add_executable(query_src0, "not-used-here.graphql");
+    let query0 = ExecutableDocument::parse(&schema, query_src0, "not-used-here.graphql");
 
-    let directives = get_directives_used_in_query(&compiler, &query_id0);
+    let directives = get_directives_used_in_query(&query0);
     assert_eq!(directives.len(), 4);
 
     let query_src1 = r#"query {
@@ -81,9 +75,9 @@ fn main() {
           }
         }
         "#;
-    let query_id1 = compiler.add_executable(query_src1, "not-used-here.graphql");
+    let query1 = ExecutableDocument::parse(&schema, query_src1, "not-used-here.graphql");
 
-    let directives = get_directives_used_in_query(&compiler, &query_id1);
+    let directives = get_directives_used_in_query(&query1);
     assert_eq!(directives.len(), 2);
 
     let query_src2 = r#"query {
@@ -92,8 +86,8 @@ fn main() {
           }
         }
         "#;
-    let query_id2 = compiler.add_executable(query_src2, "not-used-here.graphql");
+    let query2 = ExecutableDocument::parse(&schema, query_src2, "not-used-here.graphql");
 
-    let directives = get_directives_used_in_query(&compiler, &query_id2);
+    let directives = get_directives_used_in_query(&query2);
     assert_eq!(directives.len(), 0);
 }
diff --git a/crates/apollo-compiler/examples/file_watcher.rs b/crates/apollo-compiler/examples/file_watcher.rs
index 50065d4..50df604 100644
--- a/crates/apollo-compiler/examples/file_watcher.rs
+++ b/crates/apollo-compiler/examples/file_watcher.rs
@@ -7,7 +7,8 @@ use std::{
 };
 
 use anyhow::{anyhow, Result};
-use apollo_compiler::{ApolloCompiler, FileId};
+use apollo_compiler::ExecutableDocument;
+use apollo_compiler::Schema;
 use notify::{Config, EventKind, PollWatcher, RecursiveMode, Watcher};
 
 fn main() -> Result<()> {
@@ -17,32 +18,30 @@ fn main() -> Result<()> {
 }
 
 pub struct FileWatcher {
-    compiler: ApolloCompiler,
-    manifest: HashMap<PathBuf, FileId>,
+    manifest: HashMap<PathBuf, (Schema, ExecutableDocument)>,
 }
 
 #[allow(clippy::new_without_default)]
 impl FileWatcher {
     pub fn new() -> Self {
         Self {
-            compiler: ApolloCompiler::new(),
             manifest: HashMap::new(),
         }
     }
 
-    // The `watch` fn first goes over every document in a given directory and
-    // creates it as a new document with compiler's
-    // `compiler.add_document()`.
+    // The `watch` fn first goes over every document in a given directory, and
+    // parse them all, and validate them all.
     //
-    // We then watch for file changes, and update
-    // each changed document with `compiler.update_document()`.
+    // We then watch for file changes, and reparse and revalidates relevant files.
     pub fn watch(&mut self, dir: impl AsRef<Path>) -> Result<()> {
         for entry in fs::read_dir(&dir)? {
             let (proposed_document, src_path) = get_schema_and_maybe_path(entry?)?;
             self.add_document(proposed_document, src_path)?;
         }
 
-        self.validate();
+        for entry in self.manifest.values() {
+            self.validate(entry);
+        }
 
         self.watch_broadcast(dir.as_ref())
     }
@@ -73,13 +72,8 @@ impl FileWatcher {
                                 match fs::read_to_string(&path) {
                                     Ok(contents) => {
                                         println!("changes detected in {}", path.display());
-                                        let file_id = self.manifest.get(&fs::canonicalize(&path)?);
-                                        if let Some(file_id) = file_id {
-                                            self.compiler.update_document(*file_id, &contents);
-                                        } else {
-                                            self.add_document(contents, path)?;
-                                        }
-                                        self.validate();
+                                        let path = self.add_document(contents, path)?;
+                                        self.validate(&self.manifest[&path]);
                                     }
                                     Err(e) => {
                                         println!(
@@ -116,17 +110,19 @@ impl FileWatcher {
         &mut self,
         proposed_document: String,
         src_path: PathBuf,
-    ) -> Result<(), anyhow::Error> {
-        let file_id = self.compiler.add_document(&proposed_document, &src_path);
+    ) -> Result<PathBuf, anyhow::Error> {
         let full_path = fs::canonicalize(&src_path)?;
-        self.manifest.insert(full_path, file_id);
-        Ok(())
+        let doc = apollo_compiler::parse_mixed(proposed_document, src_path);
+        self.manifest.insert(full_path.clone(), doc);
+        Ok(full_path)
     }
 
-    fn validate(&self) {
-        let diagnostics = self.compiler.validate();
-        for diag in diagnostics {
-            println!("{diag}")
+    fn validate(&self, (schema, executable): &(Schema, ExecutableDocument)) {
+        if let Err(err) = schema.validate() {
+            println!("{err}")
+        }
+        if let Err(err) = executable.validate(schema) {
+            println!("{err}")
         }
     }
 }
diff --git a/crates/apollo-compiler/examples/hello_world.rs b/crates/apollo-compiler/examples/hello_world.rs
index b939c7d..d9c3730 100644
--- a/crates/apollo-compiler/examples/hello_world.rs
+++ b/crates/apollo-compiler/examples/hello_world.rs
@@ -1,7 +1,5 @@
 use apollo_compiler::executable;
-use apollo_compiler::ApolloCompiler;
 use apollo_compiler::Node;
-use apollo_compiler::ReprDatabase;
 use std::fs;
 use std::path::Path;
 
@@ -9,11 +7,7 @@ fn compile_query() -> Option<Node<executable::Fragment>> {
     let file = Path::new("crates/apollo-compiler/examples/query_with_errors.graphql");
     let src = fs::read_to_string(file).expect("Could not read schema file.");
 
-    let mut compiler = ApolloCompiler::new();
-    let document_id = compiler.add_document(&src, file);
-    // let errors = ctx.validate();
-
-    let document = compiler.db.executable_document(document_id);
+    let (_, document) = apollo_compiler::parse_mixed(src, file);
     let operation_names: Vec<_> = document
         .named_operations
         .keys()
diff --git a/crates/apollo-compiler/examples/multi_source_validation.rs b/crates/apollo-compiler/examples/multi_source_validation.rs
index 5f465d0..434181a 100644
--- a/crates/apollo-compiler/examples/multi_source_validation.rs
+++ b/crates/apollo-compiler/examples/multi_source_validation.rs
@@ -1,6 +1,9 @@
-use std::{fs, io, path::Path};
-
-use apollo_compiler::ApolloCompiler;
+use apollo_compiler::parse_mixed;
+use apollo_compiler::ExecutableDocument;
+use apollo_compiler::Schema;
+use std::fs;
+use std::io;
+use std::path::Path;
 
 fn main() -> io::Result<()> {
     compile_from_dir()?;
@@ -11,50 +14,42 @@ fn main() -> io::Result<()> {
 
 // Read all files regardless of what they are and validate them.
 fn compile_from_dir() -> io::Result<()> {
-    let mut compiler = ApolloCompiler::new();
-
     let dir = Path::new("crates/apollo-compiler/examples/documents");
     if dir.is_dir() {
         for entry in fs::read_dir(dir)? {
             let entry = entry?;
             let src = fs::read_to_string(entry.path()).expect("Could not read document file.");
-            compiler.add_document(&src, entry.path());
+            let (schema, executable) = parse_mixed(&src, entry.path());
+            schema.validate().unwrap();
+            executable.validate(&schema).unwrap();
         }
     }
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert_eq!(diagnostics.len(), 1);
-
     Ok(())
 }
+
 // Read and set schemas and queries explicitly.
 fn compile_schema_and_query_files() -> io::Result<()> {
-    let mut compiler = ApolloCompiler::new();
-
-    // add a schema file
+    // Build a schema from two files
     let schema = Path::new("crates/apollo-compiler/examples/documents/schema.graphql");
     let src = fs::read_to_string(schema).expect("Could not read schema file.");
-    compiler.add_type_system(&src, schema);
 
     // schema_extension is still a file containing a type system, and it also gets added under .add_type_system API
     let schema_ext =
         Path::new("crates/apollo-compiler/examples/documents/schema_extension.graphql");
-    let src = fs::read_to_string(schema_ext).expect("Could not read schema ext file.");
-    compiler.add_type_system(&src, schema_ext);
+    let src_ext = fs::read_to_string(schema_ext).expect("Could not read schema ext file.");
+
+    let schema = Schema::builder()
+        .parse(src, schema)
+        .parse(src_ext, schema_ext)
+        .build();
+    schema.validate().unwrap();
 
-    // get_dog_name is a query-only file and gets added with a .add_executable API
+    // get_dog_name is a query-only file
     let query = Path::new("crates/apollo-compiler/examples/documents/get_dog_name.graphql");
     let src = fs::read_to_string(query).expect("Could not read query file.");
-    compiler.add_executable(&src, query);
+    let doc = ExecutableDocument::parse(&schema, src, query);
 
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert_eq!(diagnostics.len(), 1);
+    doc.validate(&schema).unwrap();
 
     Ok(())
 }
@@ -131,15 +126,10 @@ query getDogName {
   }
 }
     "#;
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(schema, "schema.graphl");
-    compiler.add_executable(query, "query.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert_eq!(diagnostics.len(), 1);
+    let schema = Schema::parse(schema, "schema.graphl");
+    schema.validate().unwrap();
+    let doc = ExecutableDocument::parse(&schema, query, "query.graphql");
+    doc.validate(&schema).unwrap();
 
     Ok(())
 }
diff --git a/crates/apollo-compiler/examples/validate.rs b/crates/apollo-compiler/examples/validate.rs
index 8d7d366..add0cea 100644
--- a/crates/apollo-compiler/examples/validate.rs
+++ b/crates/apollo-compiler/examples/validate.rs
@@ -1,4 +1,3 @@
-use apollo_compiler::ApolloCompiler;
 use std::io::Read;
 
 /// A simple program to run the validations implemented by apollo-compiler
@@ -11,7 +10,7 @@ fn main() {
         Some("-") | None => {
             let mut source = String::new();
             std::io::stdin().read_to_string(&mut source).unwrap();
-            (source, "input.graphql".to_string())
+            (source, "stdin.graphql".to_string())
         }
         Some(filename) => (
             std::fs::read_to_string(filename).unwrap(),
@@ -19,14 +18,18 @@ fn main() {
         ),
     };
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(&source, &filename);
-
-    let diagnostics = compiler.validate();
-
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
+    let (schema, executable) = apollo_compiler::parse_mixed(source, filename);
+    let schema_result = schema.validate();
+    let executable_result = executable.validate(&schema);
+    let has_errors = schema_result.is_err() || executable_result.is_err();
+    match schema_result {
+        Ok(warnings) => println!("{warnings}"),
+        Err(errors) => println!("{errors}"),
+    }
+    match executable_result {
+        Ok(()) => {}
+        Err(errors) => println!("{errors}"),
     }
 
-    std::process::exit(if diagnostics.is_empty() { 0 } else { 1 });
+    std::process::exit(if has_errors { 1 } else { 0 });
 }
diff --git a/crates/apollo-compiler/src/arc.rs b/crates/apollo-compiler/src/arc.rs
deleted file mode 100644
index 4d7061c..0000000
--- a/crates/apollo-compiler/src/arc.rs
+++ /dev/null
@@ -1,174 +0,0 @@
-use std::hash::Hash;
-use std::hash::Hasher;
-use std::sync::atomic::AtomicU64;
-use std::sync::atomic::Ordering;
-
-use std::collections::hash_map::RandomState;
-use std::fmt;
-use std::hash::BuildHasher;
-use std::sync::OnceLock;
-
-/// A thread-safe reference-counted smart pointer that caches `Hash`.
-///
-/// Weak references are not supported.
-///
-/// For the cache to be correct, **`T` is expected to have a stable hash**
-/// a long as no `&mut T` exclusive reference to it is given out.
-/// Generally this excludes interior mutability.
-///
-/// This `Arc` cannot implement [`Borrow<T>`][std::borrow::Borrow] because `Arc<T> as Hash`
-/// produces a result (the hash of the cached hash) different from `T as Hash`.
-pub struct Arc<T>(triomphe::Arc<ArcInner<T>>);
-
-#[derive(Clone)]
-struct ArcInner<T> {
-    hash_cache: HashCache,
-    value: T,
-}
-
-pub(crate) struct HashCache(AtomicU64);
-
-impl<T> Arc<T> {
-    pub fn new(value: T) -> Self {
-        Self(triomphe::Arc::new(ArcInner {
-            hash_cache: HashCache::new(),
-            value,
-        }))
-    }
-
-    /// Returns whether two `Arc`s point to the same memory allocation.
-    pub fn ptr_eq(&self, other: &Self) -> bool {
-        triomphe::Arc::ptr_eq(&self.0, &other.0)
-    }
-
-    /// Returns a mutable reference to `T`, cloning it if necessary
-    ///
-    /// This is functionally equivalent to [`Arc::make_mut`][mm] from the standard library.
-    ///
-    /// If this `Arc` is uniquely owned, `make_mut()` will provide a mutable
-    /// reference to the contents. If not, `make_mut()` will create a _new_ `Arc`
-    /// with a copy of the contents, update `self` to point to it, and provide
-    /// a mutable reference to its contents.
-    ///
-    /// This is useful for implementing copy-on-write schemes where you wish to
-    /// avoid copying things if your `Harc` is not shared.
-    ///
-    /// [mm]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html#method.make_mut
-    pub fn make_mut(&mut self) -> &mut T
-    where
-        T: Clone,
-    {
-        let inner = triomphe::Arc::make_mut(&mut self.0);
-        // Clear the cache as mutation through the returned `&mut T` may invalidate it
-        inner.hash_cache.clear();
-        &mut inner.value
-    }
-
-    /// Returns a mutable reference to `T` if this `Arc` is uniquely owned
-    pub fn get_mut(&mut self) -> Option<&mut T> {
-        triomphe::Arc::get_mut(&mut self.0).map(|inner| &mut inner.value)
-    }
-}
-
-impl<T> std::ops::Deref for Arc<T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0.value
-    }
-}
-
-impl<T> Clone for Arc<T> {
-    fn clone(&self) -> Self {
-        Self(self.0.clone())
-    }
-}
-
-impl<T: Eq> Eq for Arc<T> {}
-
-impl<T: PartialEq> PartialEq for Arc<T> {
-    fn eq(&self, other: &Self) -> bool {
-        self.ptr_eq(other) // fast path
-        || self.0.value == other.0.value // hash cache not included
-    }
-}
-
-impl<T: Hash> Hash for Arc<T> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.0.hash_cache.get(&self.0.value).hash(state)
-    }
-}
-
-impl HashCache {
-    const NOT_COMPUTED_YET: u64 = 0;
-
-    pub(crate) fn new() -> Self {
-        Self(AtomicU64::new(Self::NOT_COMPUTED_YET))
-    }
-
-    pub(crate) fn clear(&mut self) {
-        *self.0.get_mut() = Self::NOT_COMPUTED_YET
-    }
-
-    pub(crate) fn get<T: ?Sized + Hash>(&self, value: &T) -> u64 {
-        let hash = self.0.load(Ordering::Relaxed);
-        if hash != Self::NOT_COMPUTED_YET {
-            // cache hit
-            hash
-        } else {
-            self.get_slow_path(value)
-        }
-    }
-
-    // It is possible for multiple threads to race and take this path for the same `HarcInner`.
-    // This is ok as they should compute the same result.
-    // We save on the extra space that `OnceLock<u64>` would occupy,
-    // at the cost of extra computation in the unlikely case of this race.
-    #[cold]
-    #[inline(never)]
-    fn get_slow_path<T: ?Sized + Hash>(&self, value: &T) -> u64 {
-        /// We share a single `BuildHasher` process-wide,
-        /// not only for the race described above but also
-        /// so that multiple `HarcInner`’s with the same contents have the same hash.
-        static SHARED_RANDOM: OnceLock<RandomState> = OnceLock::new();
-        let mut hasher = SHARED_RANDOM.get_or_init(RandomState::new).build_hasher();
-        value.hash(&mut hasher);
-        let mut hash = hasher.finish();
-        // Don’t use the marker value for an actual hash
-        if hash == Self::NOT_COMPUTED_YET {
-            hash += 1
-        }
-        self.0.store(hash, Ordering::Relaxed);
-        hash
-    }
-}
-
-impl Clone for HashCache {
-    fn clone(&self) -> Self {
-        Self(AtomicU64::new(self.0.load(Ordering::Relaxed)))
-    }
-}
-
-impl<T: fmt::Debug> fmt::Debug for Arc<T> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.0.value.fmt(f)
-    }
-}
-
-impl<T: Default> Default for Arc<T> {
-    fn default() -> Self {
-        Self::new(Default::default())
-    }
-}
-
-impl<T> AsRef<T> for Arc<T> {
-    fn as_ref(&self) -> &T {
-        self
-    }
-}
-
-impl AsRef<str> for Arc<String> {
-    fn as_ref(&self) -> &str {
-        self
-    }
-}
diff --git a/crates/apollo-compiler/src/ast/from_cst.rs b/crates/apollo-compiler/src/ast/from_cst.rs
index 3313dbd..ec2a2dc 100644
--- a/crates/apollo-compiler/src/ast/from_cst.rs
+++ b/crates/apollo-compiler/src/ast/from_cst.rs
@@ -1,6 +1,5 @@
 use crate::ast;
 use crate::ast::Document;
-use crate::Arc;
 use crate::FileId;
 use crate::Node;
 use crate::NodeLocation;
@@ -9,6 +8,7 @@ use apollo_parser::cst;
 use apollo_parser::cst::CstNode;
 use apollo_parser::SyntaxNode;
 use apollo_parser::S;
+use std::sync::Arc;
 
 impl Document {
     pub(crate) fn from_cst(
diff --git a/crates/apollo-compiler/src/ast/impls.rs b/crates/apollo-compiler/src/ast/impls.rs
index 96004b7..778b39c 100644
--- a/crates/apollo-compiler/src/ast/impls.rs
+++ b/crates/apollo-compiler/src/ast/impls.rs
@@ -91,11 +91,16 @@ impl Document {
         let mut builder = Schema::builder();
         builder.add_ast_document(self, executable_definitions_are_errors);
         let schema = builder.build();
-        let executable = crate::executable::from_ast::document_from_ast(
+
+        let mut executable = crate::executable::from_ast::document_from_ast(
             Some(&schema),
             self,
             type_system_definitions_are_errors,
         );
+        if let Some((_id, file)) = &mut executable.source {
+            // The same parse errors are in `schema.sources`, so they would be redundant here.
+            Arc::make_mut(file).parse_errors = Vec::new()
+        }
         (schema, executable)
     }
 
@@ -373,8 +378,8 @@ impl Directives {
 
     /// Returns an iterator of directives with the given name.
     ///
-    /// This method is best for repeatable directives. For non-repeatable directives,
-    /// see [`directive_by_name`][Self::directive_by_name] (singular)
+    /// This method is best for repeatable directives.
+    /// See also [`get`][Self::get] for non-repeatable directives.
     pub fn get_all<'def: 'name, 'name>(
         &'def self,
         name: &'name str,
@@ -384,8 +389,8 @@ impl Directives {
 
     /// Returns the first directive with the given name, if any.
     ///
-    /// This method is best for non-repeatable directives. For repeatable directives,
-    /// see [`directives_by_name`][Self::directives_by_name] (plural)
+    /// This method is best for non-repeatable directives.
+    /// See also [`get_all`][Self::get_all] for repeatable directives.
     pub fn get(&self, name: &str) -> Option<&Node<Directive>> {
         self.get_all(name).next()
     }
diff --git a/crates/apollo-compiler/src/ast/mod.rs b/crates/apollo-compiler/src/ast/mod.rs
index 6fb7846..e455621 100644
--- a/crates/apollo-compiler/src/ast/mod.rs
+++ b/crates/apollo-compiler/src/ast/mod.rs
@@ -1,4 +1,6 @@
-//! *Abstract Syntax Tree* for GraphQL documents
+//! *Abstract Syntax Tree* for GraphQL documents.
+//! Lower-level than [`Schema`][crate::Schema]
+//! or [`ExecutableDocument`][crate::ExecutableDocument].
 //!
 //! This AST aims to faithfully represent documents that conform to the GraphQL
 //! [syntactic grammar], except for their [ignored tokens].
@@ -13,8 +15,7 @@
 //!
 //! ## Parsing
 //!
-//! When parsing an input string, use [`Parser`] to obtain a [`ParseResult`]
-//! which contains a [`Document`].
+//! Start with [`Document::parse`], or [`Parser`][crate::Parser] to change the parser configuration.
 //!
 //! ## Structural sharing and mutation
 //!
@@ -30,12 +31,12 @@
 //! that has chaining methods for setting serialization configuration,
 //! and also implements `Display` and `ToString`.
 
-use crate::Arc;
 use crate::FileId;
 use crate::Node;
 use crate::NodeStr;
 use crate::SourceFile;
 use std::collections::HashMap;
+use std::sync::Arc;
 
 mod from_cst;
 pub(crate) mod impls;
@@ -363,12 +364,12 @@ pub struct FloatOverflowError {}
 
 /// Trait implemented by extensible type definitions, to associate the extension type with the base
 /// definition type.
-pub trait Extensible {
+pub(crate) trait Extensible {
     type Extension;
 }
 
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
-pub struct TypeWithExtensions<T: Extensible> {
+pub(crate) struct TypeWithExtensions<T: Extensible> {
     pub definition: Node<T>,
     pub extensions: Vec<Node<T::Extension>>,
 }
@@ -376,7 +377,7 @@ pub struct TypeWithExtensions<T: Extensible> {
 // 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 struct TypeSystem {
+pub(crate) struct TypeSystem {
     pub schema: TypeWithExtensions<SchemaDefinition>,
     pub objects: HashMap<Name, TypeWithExtensions<ObjectTypeDefinition>>,
     pub scalars: HashMap<Name, TypeWithExtensions<ScalarTypeDefinition>>,
diff --git a/crates/apollo-compiler/src/ast/serialize.rs b/crates/apollo-compiler/src/ast/serialize.rs
index e11e0ee..fab07b7 100644
--- a/crates/apollo-compiler/src/ast/serialize.rs
+++ b/crates/apollo-compiler/src/ast/serialize.rs
@@ -870,13 +870,6 @@ macro_rules! impl_display {
                 }
             }
 
-            /// Serialize to GraphQL syntax with the default configuration
-            impl Display for crate::Arc<$ty> {
-                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-                    self.serialize().fmt(f)
-                }
-            }
-
             /// Serialize to GraphQL syntax
             impl Display for Serialize<'_, $ty> {
                 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
diff --git a/crates/apollo-compiler/src/database/inputs.rs b/crates/apollo-compiler/src/database/inputs.rs
index 3d82f74..7232919 100644
--- a/crates/apollo-compiler/src/database/inputs.rs
+++ b/crates/apollo-compiler/src/database/inputs.rs
@@ -1,9 +1,8 @@
 use super::sources::{FileId, Source, SourceType};
-use crate::Arc;
-use crate::Schema;
 use ariadne::{Cache as AriadneCache, Source as AriadneSource};
 use std::collections::HashMap;
 use std::path::PathBuf;
+use std::sync::Arc;
 
 #[derive(Debug, thiserror::Error)]
 #[error("Unknown file ID")]
@@ -56,17 +55,6 @@ impl std::fmt::Debug for SourceCache {
 
 #[salsa::query_group(InputStorage)]
 pub trait InputDatabase {
-    /// Get the currently set recursion limit.
-    #[salsa::input]
-    fn recursion_limit(&self) -> Option<usize>;
-
-    /// Get the currently set token limit.
-    #[salsa::input]
-    fn token_limit(&self) -> Option<usize>;
-
-    #[salsa::input]
-    fn schema_input(&self) -> Option<Arc<Schema>>;
-
     /// Get input source of the corresponding file.
     #[salsa::input]
     fn input(&self, file_id: FileId) -> Source;
@@ -83,19 +71,6 @@ pub trait InputDatabase {
     #[salsa::input]
     fn source_files(&self) -> Vec<FileId>;
 
-    /// Find source file by file name.
-    fn source_file(&self, path: PathBuf) -> Option<FileId>;
-
-    /// Get the GraphQL source text for a file, split up into lines for
-    /// printing diagnostics.
-    #[salsa::invoke(source_with_lines)]
-    fn source_with_lines(&self, file_id: FileId) -> Arc<AriadneSource>;
-
-    /// Get all GraphQL sources known to the compiler, split up into lines
-    /// for printing diagnostics.
-    #[salsa::invoke(source_cache)]
-    fn source_cache(&self) -> Arc<SourceCache>;
-
     /// Get all type system definition (GraphQL schema) files.
     #[salsa::invoke(type_definition_files)]
     fn type_definition_files(&self) -> Vec<FileId>;
@@ -109,42 +84,17 @@ fn source_code(db: &dyn InputDatabase, file_id: FileId) -> Arc<String> {
     db.input(file_id).text().clone()
 }
 
-fn source_file(db: &dyn InputDatabase, path: PathBuf) -> Option<FileId> {
-    db.source_files()
-        .iter()
-        .find(|id| db.input(**id).filename() == path)
-        .copied()
-}
-
 fn source_type(db: &dyn InputDatabase, file_id: FileId) -> SourceType {
     db.input(file_id).source_type()
 }
 
-fn source_with_lines(db: &dyn InputDatabase, file_id: FileId) -> Arc<AriadneSource> {
-    let code = db.source_code(file_id);
-    Arc::new(AriadneSource::from(code))
-}
-
-fn source_cache(db: &dyn InputDatabase) -> Arc<SourceCache> {
-    let file_ids = db.source_files();
-    let sources = file_ids
-        .iter()
-        .map(|&id| (id, db.source_with_lines(id)))
-        .collect();
-    let paths = file_ids
-        .iter()
-        .map(|&id| (id, db.input(id).filename().to_owned()))
-        .collect();
-    Arc::new(SourceCache { sources, paths })
-}
-
 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 | SourceType::BuiltIn
+                SourceType::Schema | SourceType::Document
             )
         })
         .collect()
diff --git a/crates/apollo-compiler/src/database/repr.rs b/crates/apollo-compiler/src/database/repr.rs
index fc126fa..a977565 100644
--- a/crates/apollo-compiler/src/database/repr.rs
+++ b/crates/apollo-compiler/src/database/repr.rs
@@ -2,31 +2,18 @@ use super::sources::SourceType;
 use super::InputDatabase;
 use crate::ast;
 use crate::schema::Name;
-use crate::Arc;
 use crate::FileId;
-use crate::SourceFile;
 use std::collections::HashMap;
 use std::collections::HashSet;
+use std::sync::Arc;
 
 /// Queries for parsing into the various in-memory representations of GraphQL documents
 #[salsa::query_group(ReprStorage)]
 pub trait ReprDatabase: InputDatabase {
-    #[salsa::invoke(ast_parse_result)]
-    #[doc(hidden)]
-    fn _ast_parse_result(&self, file_id: FileId) -> Arc<ParseResult>;
-
     #[salsa::invoke(ast)]
     #[salsa::transparent]
     fn ast(&self, file_id: FileId) -> Arc<ast::Document>;
 
-    #[salsa::invoke(recursion_reached)]
-    #[salsa::transparent]
-    fn recursion_reached(&self, file_id: FileId) -> usize;
-
-    #[salsa::invoke(tokens_reached)]
-    #[salsa::transparent]
-    fn tokens_reached(&self, file_id: FileId) -> usize;
-
     #[salsa::invoke(schema)]
     fn schema(&self) -> Arc<crate::Schema>;
 
@@ -47,62 +34,11 @@ pub trait ReprDatabase: InputDatabase {
     fn implementers_map(&self) -> Arc<HashMap<Name, HashSet<Name>>>;
 }
 
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct ParseResult {
-    document: Arc<ast::Document>,
-    recursion_reached: usize,
-    tokens_reached: usize,
-}
-
-fn ast_parse_result(db: &dyn ReprDatabase, file_id: FileId) -> Arc<ParseResult> {
-    let input = db.source_code(file_id);
-    let mut parser = apollo_parser::Parser::new(&input);
-    if let Some(limit) = db.recursion_limit() {
-        parser = parser.recursion_limit(limit);
-    }
-    if let Some(limit) = db.token_limit() {
-        parser = parser.token_limit(limit);
-    }
-    let tree = parser.parse();
-    let recursion_reached = tree.recursion_limit().high;
-    let tokens_reached = tree.token_limit().high;
-    let source_file = Arc::new(SourceFile {
-        path: db.input(file_id).filename,
-        source_text: String::clone(&db.source_code(file_id)),
-        parse_errors: tree.errors().cloned().collect(),
-    });
-    let document = Arc::new(ast::Document::from_cst(
-        tree.document(),
-        file_id,
-        source_file,
-    ));
-    Arc::new(ParseResult {
-        document,
-        recursion_reached,
-        tokens_reached,
-    })
-}
-
 fn ast(db: &dyn ReprDatabase, file_id: FileId) -> Arc<ast::Document> {
-    db.input(file_id)
-        .ast
-        .clone()
-        .unwrap_or_else(|| db._ast_parse_result(file_id).document.clone())
-}
-
-fn recursion_reached(db: &dyn ReprDatabase, file_id: FileId) -> usize {
-    db._ast_parse_result(file_id).recursion_reached
-}
-
-fn tokens_reached(db: &dyn ReprDatabase, file_id: FileId) -> usize {
-    db._ast_parse_result(file_id).tokens_reached
+    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;
@@ -124,7 +60,7 @@ fn executable_document(db: &dyn ReprDatabase, file_id: FileId) -> Arc<crate::Exe
         if let Some((_, source_file)) = &mut executable.source {
             // The same parse errors will be in db.schema().sources,
             // so they would be redundant here.
-            source_file.make_mut().parse_errors = Vec::new()
+            Arc::make_mut(source_file).parse_errors = Vec::new()
         }
     }
     Arc::new(executable)
diff --git a/crates/apollo-compiler/src/database/sources.rs b/crates/apollo-compiler/src/database/sources.rs
index d08b254..4b8459c 100644
--- a/crates/apollo-compiler/src/database/sources.rs
+++ b/crates/apollo-compiler/src/database/sources.rs
@@ -1,54 +1,14 @@
-use crate::Arc;
-use std::{
-    fmt,
-    num::NonZeroI64,
-    path::{Path, PathBuf},
-    sync::atomic,
-};
+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,
-    BuiltIn,
     TextOnly,
 }
 
-impl SourceType {
-    /// Returns `true` if the source type is [`BuiltIn`].
-    ///
-    /// [`BuiltIn`]: SourceType::BuiltIn
-    #[must_use]
-    pub fn is_built_in(&self) -> bool {
-        matches!(self, Self::BuiltIn)
-    }
-
-    /// Returns `true` if the source type is [`Document`].
-    ///
-    /// [`Document`]: SourceType::Document
-    #[must_use]
-    pub fn is_document(&self) -> bool {
-        matches!(self, Self::Document)
-    }
-
-    /// Returns `true` if the source type is [`Executable`].
-    ///
-    /// [`Executable`]: SourceType::Executable
-    #[must_use]
-    pub fn is_executable(&self) -> bool {
-        matches!(self, Self::Executable)
-    }
-
-    /// Returns `true` if the source type is [`Schema`].
-    ///
-    /// [`Schema`]: SourceType::Schema
-    #[must_use]
-    pub fn is_schema(&self) -> bool {
-        matches!(self, Self::Schema)
-    }
-}
-
 /// Represents a GraphQL source file.
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub struct Source {
@@ -59,56 +19,6 @@ pub struct Source {
 }
 
 impl Source {
-    /// Create a GraphQL schema source file.
-    pub fn schema(filename: PathBuf, text: impl Into<String>) -> Self {
-        Self {
-            ty: SourceType::Schema,
-            filename,
-            text: Arc::new(text.into()),
-            ast: None,
-        }
-    }
-
-    /// Create a GraphQL executable source file.
-    pub fn executable(filename: PathBuf, text: impl Into<String>) -> Self {
-        Self {
-            ty: SourceType::Executable,
-            filename,
-            text: Arc::new(text.into()),
-            ast: None,
-        }
-    }
-
-    /// Create a GraphQL document source file.
-    ///
-    /// A Document can contain type definitions *and* executable definitions. You can also use it
-    /// when you don't know the actual source type.
-    pub fn document(filename: PathBuf, text: impl Into<String>) -> Self {
-        Self {
-            ty: SourceType::Document,
-            filename,
-            text: Arc::new(text.into()),
-            ast: None,
-        }
-    }
-
-    /// Create a GraphQL type system file with built in types.
-    // TODO(goto-bus-stop,SimonSapin): remove
-    #[allow(unused)]
-    #[deprecated = "New AST does not have this"]
-    pub(crate) fn built_in(filename: PathBuf, text: impl Into<String>) -> Self {
-        Self {
-            ty: SourceType::BuiltIn,
-            filename,
-            text: Arc::new(text.into()),
-            ast: None,
-        }
-    }
-
-    pub fn filename(&self) -> &Path {
-        &self.filename
-    }
-
     pub fn source_type(&self) -> SourceType {
         self.ty
     }
@@ -118,8 +28,8 @@ impl Source {
     }
 }
 
-impl From<&'_ crate::Arc<crate::SourceFile>> for Source {
-    fn from(file: &'_ crate::Arc<crate::SourceFile>) -> Self {
+impl From<&'_ Arc<crate::SourceFile>> for Source {
+    fn from(file: &'_ Arc<crate::SourceFile>) -> Self {
         Self {
             ty: crate::database::SourceType::TextOnly,
             filename: file.path.clone(),
@@ -129,6 +39,10 @@ impl From<&'_ crate::Arc<crate::SourceFile>> for Source {
     }
 }
 
+/// 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,
diff --git a/crates/apollo-compiler/src/diagnostics.rs b/crates/apollo-compiler/src/diagnostics.rs
index ae02b1c..5808de4 100644
--- a/crates/apollo-compiler/src/diagnostics.rs
+++ b/crates/apollo-compiler/src/diagnostics.rs
@@ -1,6 +1,4 @@
 use crate::ast::DirectiveLocation;
-use crate::database::{InputDatabase, SourceCache};
-use crate::Arc;
 use crate::NodeLocation;
 use std::fmt;
 use thiserror::Error;
@@ -21,7 +19,6 @@ impl Label {
 
 #[derive(Debug, Error, Clone, PartialEq, Eq)]
 pub struct ApolloDiagnostic {
-    cache: Arc<SourceCache>,
     pub location: NodeLocation,
     pub labels: Vec<Label>,
     pub help: Option<String>,
@@ -29,13 +26,8 @@ pub struct ApolloDiagnostic {
 }
 
 impl ApolloDiagnostic {
-    pub fn new<DB: InputDatabase + ?Sized>(
-        db: &DB,
-        location: NodeLocation,
-        data: DiagnosticData,
-    ) -> Self {
+    pub fn new<DB: ?Sized>(_db: &DB, location: NodeLocation, data: DiagnosticData) -> Self {
         Self {
-            cache: db.source_cache(),
             location,
             labels: vec![],
             help: None,
@@ -73,14 +65,6 @@ impl fmt::Display for ApolloDiagnostic {
 #[derive(Debug, Error, Clone, Hash, PartialEq, Eq)]
 #[non_exhaustive]
 pub enum DiagnosticData {
-    #[error("syntax error: {message}")]
-    SyntaxError { message: String },
-    #[error("limit exceeded: {message}")]
-    LimitExceeded { message: String },
-    #[error("expected identifier")]
-    MissingIdent,
-    #[error("executable documents must not contain {kind}")]
-    ExecutableDefinition { kind: &'static str },
     #[error("the {ty} `{name}` is defined multiple times in the document")]
     UniqueDefinition {
         ty: &'static str,
@@ -107,18 +91,6 @@ pub enum DiagnosticData {
         fields: usize,
         subscription: NodeLocation,
     },
-    #[error("{ty} root operation type is not defined")]
-    UnsupportedOperation {
-        // current operation type: subscription, mutation, query
-        ty: &'static str,
-    },
-    #[error("cannot query field `{field}` on type `{ty}`")]
-    UndefinedField {
-        /// Field name
-        field: String,
-        /// Type name being queried
-        ty: String,
-    },
     #[error("the argument `{name}` is not supported")]
     UndefinedArgument { name: String },
     #[error("cannot find type `{name}` in this document")]
@@ -148,15 +120,6 @@ pub enum DiagnosticData {
         /// type definition
         definition: String,
     },
-    #[error("type extension for `{name}` is the wrong kind")]
-    WrongTypeExtension {
-        /// Name of the type being extended
-        name: String,
-        /// Location of the original definition. This may be None when extending a builtin GraphQL type.
-        definition: NodeLocation,
-        /// Location of the extension
-        extension: NodeLocation,
-    },
     #[error("`{name}` directive definition cannot reference itself")]
     RecursiveDirectiveDefinition { name: String },
     #[error("interface {name} cannot implement itself")]
@@ -167,24 +130,10 @@ pub enum DiagnosticData {
     RecursiveFragmentDefinition { name: String },
     #[error("values in an Enum Definition should be capitalized")]
     CapitalizedValue { value: String },
-    #[error("fields must be unique in a definition")]
-    UniqueField {
-        /// Name of the non-unique field.
-        field: String,
-        original_definition: NodeLocation,
-        redefined_definition: NodeLocation,
-    },
     #[error("type does not satisfy interface `{interface}`: missing field `{field}`")]
     MissingInterfaceField { interface: String, field: String },
-    #[error("missing `{field}` field")]
-    MissingField {
-        // current field that should be defined
-        field: String,
-    },
     #[error("the required argument `{name}` is not provided")]
     RequiredArgument { name: String },
-    #[error("type `{ty}` can only implement interface `{interface}` once")]
-    DuplicateImplementsInterface { ty: String, interface: String },
     #[error(
         "Transitively implemented interfaces must also be defined on an implementing interface or object"
     )]
@@ -212,15 +161,6 @@ pub enum DiagnosticData {
     ScalarSpecificationURL,
     #[error("missing query root operation type in schema definition")]
     QueryRootOperationType,
-    #[error("built-in scalars must be omitted for brevity")]
-    BuiltInScalarDefinition,
-    #[error("`${name}` variable must be of an input type")]
-    VariableInputType {
-        /// Varialbe name.
-        name: String,
-        /// The kind of type that the variable is declared with.
-        ty: &'static str,
-    },
     #[error("unused variable: `{name}`")]
     UnusedVariable { name: String },
     #[error("`{name}` field must return an object type")]
@@ -268,8 +208,6 @@ pub enum DiagnosticData {
         /// Name of the field
         field: String,
     },
-    #[error("subselection set for scalar and enum types must be empty")]
-    DisallowedSubselection,
     #[error("interface, union and object types must have a subselection set")]
     MissingSubselection,
     #[error("operation must not select different types using the same field name `{field}`")]
@@ -279,11 +217,6 @@ pub enum DiagnosticData {
         original_selection: NodeLocation,
         redefined_selection: NodeLocation,
     },
-    #[error("fragments must be specified on types that exist in the schema")]
-    InvalidFragment {
-        /// Name of the type on which the fragment is declared
-        ty: Option<String>,
-    },
     #[error("fragments can not be declared on primitive types")]
     InvalidFragmentTarget {
         /// Name of the type on which the fragment is declared
@@ -331,7 +264,10 @@ impl From<Label> for ariadne::Label<NodeLocation> {
 }
 
 impl ApolloDiagnostic {
-    pub fn to_report(&self, config: ariadne::Config) -> ariadne::Report<'static, NodeLocation> {
+    pub(crate) fn to_report(
+        &self,
+        config: ariadne::Config,
+    ) -> ariadne::Report<'static, NodeLocation> {
         use ariadne::{ColorGenerator, Report, ReportKind};
 
         let severity = if self.data.is_advice() {
@@ -355,12 +291,4 @@ impl ApolloDiagnostic {
         }
         builder.finish()
     }
-
-    pub fn format_no_color(&self) -> String {
-        let mut buf = std::io::Cursor::new(Vec::<u8>::new());
-        self.to_report(ariadne::Config::default().with_color(false))
-            .write(self.cache.as_ref(), &mut buf)
-            .unwrap();
-        String::from_utf8(buf.into_inner()).unwrap()
-    }
 }
diff --git a/crates/apollo-compiler/src/executable/from_ast.rs b/crates/apollo-compiler/src/executable/from_ast.rs
index 865b1f1..8b41076 100644
--- a/crates/apollo-compiler/src/executable/from_ast.rs
+++ b/crates/apollo-compiler/src/executable/from_ast.rs
@@ -103,7 +103,7 @@ impl Operation {
         ast: &ast::OperationDefinition,
     ) -> Option<Self> {
         let ty = if let Some(s) = schema {
-            s.root_operation(ast.operation_type)?.node.clone()
+            s.root_operation(ast.operation_type)?.clone()
         } else {
             // Hack for validate_standalone_excutable
             ast.operation_type.default_type_name().into()
@@ -208,10 +208,10 @@ impl SelectionSet {
                                 ),
                             }
                         }
-                        Err(schema::FieldLookupError::NoSuchField(type_def)) => {
+                        Err(schema::FieldLookupError::NoSuchField(type_name, _)) => {
                             errors.errors.push(BuildError::UndefinedField {
                                 location: ast.name.location(),
-                                type_name: type_def.name().clone(),
+                                type_name: type_name.clone(),
                                 field_name: ast.name.clone(),
                                 path: errors.path.clone(),
                             })
diff --git a/crates/apollo-compiler/src/executable/mod.rs b/crates/apollo-compiler/src/executable/mod.rs
index b74bd3f..e733dba 100644
--- a/crates/apollo-compiler/src/executable/mod.rs
+++ b/crates/apollo-compiler/src/executable/mod.rs
@@ -1,6 +1,8 @@
+//! High-level representation of an executable document,
+//! which can contain operations and fragments.
+
 use crate::ast;
 use crate::schema;
-use crate::Arc;
 use crate::FileId;
 use crate::Node;
 use crate::Parser;
@@ -10,6 +12,7 @@ use indexmap::map::Entry;
 use indexmap::IndexMap;
 use std::collections::HashSet;
 use std::path::Path;
+use std::sync::Arc;
 
 pub(crate) mod from_ast;
 mod serialize;
diff --git a/crates/apollo-compiler/src/executable/validation.rs b/crates/apollo-compiler/src/executable/validation.rs
index ecc1daa..27facc7 100644
--- a/crates/apollo-compiler/src/executable/validation.rs
+++ b/crates/apollo-compiler/src/executable/validation.rs
@@ -1,12 +1,12 @@
 use super::BuildError;
 use crate::validation::Details;
 use crate::validation::Diagnostics;
-use crate::Arc;
 use crate::ExecutableDocument;
 use crate::FileId;
 use crate::InputDatabase;
 use crate::Schema;
 use crate::ValidationDatabase;
+use std::sync::Arc;
 
 pub(crate) fn validate_executable_document(
     errors: &mut Diagnostics,
diff --git a/crates/apollo-compiler/src/lib.rs b/crates/apollo-compiler/src/lib.rs
index a8e5300..d7282c6 100644
--- a/crates/apollo-compiler/src/lib.rs
+++ b/crates/apollo-compiler/src/lib.rs
@@ -2,249 +2,42 @@
 
 #[macro_use]
 mod macros;
-mod arc;
 pub mod ast;
-pub mod database;
-pub mod diagnostics;
+mod database;
+mod diagnostics;
 pub mod executable;
 mod node;
 mod node_str;
 mod parser;
 pub mod schema;
-pub mod validation;
+mod validation;
 
-use salsa::ParallelDatabase;
-use std::path::Path;
+use crate::database::{InputDatabase, ReprDatabase, RootDatabase, Source};
+use crate::diagnostics::ApolloDiagnostic;
+use crate::validation::ValidationDatabase;
 
-pub use self::arc::Arc;
-pub use self::database::{FileId, InputDatabase, ReprDatabase, RootDatabase, Source};
-pub use self::diagnostics::ApolloDiagnostic;
+pub use self::database::FileId;
 pub use self::executable::ExecutableDocument;
 pub use self::node::{Node, NodeLocation};
 pub use self::node_str::NodeStr;
 pub use self::parser::{parse_mixed, Parser, SourceFile};
-use self::validation::ValidationDatabase;
-pub use schema::Schema;
+pub use self::schema::Schema;
+pub use self::validation::Diagnostics;
 
-pub struct ApolloCompiler {
+pub(crate) struct ApolloCompiler {
     pub db: RootDatabase,
 }
 
-/// A read-only, `Sync` snapshot of the database.
-pub type Snapshot = salsa::Snapshot<RootDatabase>;
-
 /// Apollo compiler creates a context around your GraphQL. It creates references
 /// between various GraphQL types in scope.
-///
-/// ## Example
-///
-/// ```rust
-/// use apollo_compiler::ApolloCompiler;
-///
-/// let input = r#"
-///   interface Pet {
-///     name: String
-///   }
-///
-///   type Dog implements Pet {
-///     name: String
-///     nickname: String
-///     barkVolume: Int
-///   }
-///
-///   type Cat implements Pet {
-///     name: String
-///     nickname: String
-///     meowVolume: Int
-///   }
-///
-///   union CatOrDog = Cat | Dog
-///
-///   type Human {
-///     name: String
-///     pets: [Pet]
-///   }
-///
-///   type Query {
-///     human: Human
-///   }
-/// "#;
-///
-/// let mut compiler = ApolloCompiler::new();
-/// compiler.add_type_system(input, "schema.graphql");
-///
-/// let diagnostics = compiler.validate();
-/// for diagnostic in &diagnostics {
-///     // this will pretty-print diagnostics using the miette crate.
-///     println!("{}", diagnostic);
-/// }
-/// assert!(diagnostics.is_empty());
-/// ```
 #[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_recursion_limit(None);
-        db.set_token_limit(None);
-        db.set_schema_input(None);
         db.set_source_files(vec![]);
 
         Self { db }
     }
-
-    /// Create a new compiler with a pre-loaded GraphQL schema.
-    ///
-    /// This compiler can then only be used for executable documents that execute against that
-    /// schema.
-    pub fn from_schema(schema: Arc<Schema>) -> Self {
-        let mut compiler = Self::new();
-        compiler.db.set_schema_input(Some(schema));
-
-        compiler
-    }
-
-    /// Configure the recursion limit to use during parsing.
-    /// Recursion limit must be set prior to adding sources to the compiler.
-    pub fn recursion_limit(mut self, limit: usize) -> Self {
-        if !self.db.source_files().is_empty() {
-            panic!(
-                "There are already parsed files in the compiler. \
-                 Setting recursion limit after files are parsed is not supported."
-            );
-        }
-        self.db.set_recursion_limit(Some(limit));
-        self
-    }
-
-    /// Configure the token limit to use during parsing.
-    /// Token limit must be set prior to adding sources to the compiler.
-    pub fn token_limit(mut self, limit: usize) -> Self {
-        if !self.db.source_files().is_empty() {
-            panic!(
-                "There are already parsed files in the compiler. \
-                 Setting token limit after files are parsed is not supported."
-            );
-        }
-        self.db.set_token_limit(Some(limit));
-        self
-    }
-
-    fn add_input(&mut self, source: Source) -> FileId {
-        let file_id = FileId::new();
-        let mut sources = self.db.source_files();
-        sources.push(file_id);
-        self.db.set_input(file_id, source);
-        self.db.set_source_files(sources);
-
-        file_id
-    }
-
-    /// Add a document with executable _and_ type system definitions and
-    /// extensions to the compiler.
-    ///
-    /// The `path` argument is used to display diagnostics. If your GraphQL document
-    /// doesn't come from a file, you can make up a name or provide the empty string.
-    /// It does not need to be unique.
-    ///
-    /// Returns a `FileId` that you can use to update the source text of this document.
-    pub fn add_document(&mut self, input: &str, path: impl AsRef<Path>) -> FileId {
-        if self.db.schema_input().is_some() {
-            panic!(
-                "Having both string inputs and pre-computed inputs \
-                 for type system definitions is not supported"
-            )
-        }
-        let filename = path.as_ref().to_owned();
-        self.add_input(Source::document(filename, input))
-    }
-
-    /// Add a document with type system definitions and extensions only to the compiler.
-    ///
-    /// The `path` argument is used to display diagnostics. If your GraphQL document
-    /// doesn't come from a file, you can make up a name or provide the empty string.
-    /// It does not need to be unique.
-    ///
-    /// Returns a `FileId` that you can use to update the source text of this document.
-    pub fn add_type_system(&mut self, input: &str, path: impl AsRef<Path>) -> FileId {
-        if self.db.schema_input().is_some() {
-            panic!(
-                "Having both string inputs and pre-computed inputs \
-                 for type system definitions is not supported"
-            )
-        }
-        let filename = path.as_ref().to_owned();
-        self.add_input(Source::schema(filename, input))
-    }
-
-    /// Add a an executable document to the compiler.
-    ///
-    /// The `path` argument is used to display diagnostics. If your GraphQL document
-    /// doesn't come from a file, you can make up a name or provide the empty string.
-    /// It does not need to be unique.
-    ///
-    /// Returns a `FileId` that you can use to update the source text of this document.
-    pub fn add_executable(&mut self, input: &str, path: impl AsRef<Path>) -> FileId {
-        let filename = path.as_ref().to_owned();
-        self.add_input(Source::executable(filename, input))
-    }
-
-    /// Update an existing GraphQL document with new source text. Queries that depend
-    /// on this document will be recomputed.
-    pub fn update_document(&mut self, file_id: FileId, input: &str) {
-        let document = self.db.input(file_id);
-        self.db.set_input(
-            file_id,
-            Source::document(document.filename().to_owned(), input),
-        )
-    }
-
-    /// Update an existing GraphQL document with new source text. Queries that depend
-    /// on this document will be recomputed.
-    pub fn update_type_system(&mut self, file_id: FileId, input: &str) {
-        let schema = self.db.input(file_id);
-        self.db
-            .set_input(file_id, Source::schema(schema.filename().to_owned(), input))
-    }
-
-    /// Update an existing GraphQL document with new source text. Queries that depend
-    /// on this document will be recomputed.
-    pub fn update_executable(&mut self, file_id: FileId, input: &str) {
-        let executable = self.db.input(file_id);
-        self.db.set_input(
-            file_id,
-            Source::executable(executable.filename().to_owned(), input),
-        )
-    }
-
-    /// Get a snapshot of the current database.
-    pub fn snapshot(&self) -> Snapshot {
-        self.db.snapshot()
-    }
-
-    /// Validate your GraphQL input. Returns Diagnostics that you can pretty-print.
-    ///
-    /// ## Example
-    /// ```rust
-    /// use apollo_compiler::ApolloCompiler;
-    /// let input = r#"
-    /// type Query {
-    ///   website: URL,
-    ///   amount: Int
-    /// }
-    /// "#;
-    ///
-    /// let mut compiler = ApolloCompiler::new();
-    /// compiler.add_document(input, "document.graphql");
-    ///
-    /// let diagnostics = compiler.validate();
-    /// for diagnostic in &diagnostics {
-    ///     println!("{}", diagnostic);
-    /// }
-    /// assert_eq!(diagnostics.len(), 1);
-    /// ```
-    pub fn validate(&self) -> Vec<ApolloDiagnostic> {
-        self.db.validate()
-    }
 }
diff --git a/crates/apollo-compiler/src/node.rs b/crates/apollo-compiler/src/node.rs
index 3d8627e..4232233 100644
--- a/crates/apollo-compiler/src/node.rs
+++ b/crates/apollo-compiler/src/node.rs
@@ -1,30 +1,42 @@
 use crate::schema::Component;
 use crate::schema::ComponentOrigin;
-use crate::Arc;
 use crate::FileId;
 use apollo_parser::SyntaxNode;
 use rowan::TextRange;
+use std::collections::hash_map::RandomState;
 use std::fmt;
-use std::hash;
+use std::hash::BuildHasher;
+use std::hash::Hash;
+use std::hash::Hasher;
+use std::sync::atomic::AtomicU64;
+use std::sync::atomic::Ordering;
+use std::sync::OnceLock;
 
-/// Smart pointer to some GraphQL node.
+/// A thread-safe reference-counted smart pointer for GraphQL nodes.
 ///
-/// Similar to [`apollo_compiler::Arc`][Arc] (thread-safe, reference-counted, cached `Hash`)
-/// but additionally carries an optional [`NodeLocation`].
-/// This location notably allows diagnostics to point to relevant parts of parsed input files.
+/// Similar to [`std::sync::Arc<T>`] but:
 ///
-/// Like `Arc`, `Node<T>` cannot implement [`Borrow<T>`][std::borrow::Borrow]
-/// because `Node<T> as Hash` produces a result (the hash of the cached hash)
-/// different from `T as Hash`.
-#[derive(Hash, Eq, PartialEq)]
-pub struct Node<T>(Arc<NodeInner<T>>);
+/// * In addition to `T`, contains an optional [`NodeLocation`].
+///   This location notably allows diagnostics to point to relevant parts of parsed input files.
+/// * [`std::hash::Hash`] is implemented by caching the result of hashing `T`.
+/// * Weak references are not supported.
+///
+/// For the cache to be correct, **`T` is expected to have a stable hash**
+/// a long as no `&mut T` exclusive reference to it is given out.
+/// Generally this excludes interior mutability.
+///
+/// `Node<T>` cannot implement [`Borrow<T>`][std::borrow::Borrow] because `Node<T> as Hash`
+/// produces a result (the hash of the cached hash) different from `T as Hash`.
+pub struct Node<T>(triomphe::Arc<NodeInner<T>>);
 
-#[derive(Clone)]
 struct NodeInner<T> {
     location: Option<NodeLocation>,
+    hash_cache: AtomicU64,
     node: T,
 }
 
+const HASH_NOT_COMPUTED_YET: u64 = 0;
+
 /// The source location of a parsed node: file ID and range within that file.
 #[derive(Clone, Copy, Hash, PartialEq, Eq)]
 pub struct NodeLocation {
@@ -34,23 +46,23 @@ pub struct NodeLocation {
 
 impl<T> Node<T> {
     /// Create a new `Node` for something parsed from the given source location
+    #[inline]
     pub fn new_parsed(node: T, location: NodeLocation) -> Self {
-        Self(Arc::new(NodeInner {
-            location: Some(location),
-            node,
-        }))
+        Self::new_opt_location(node, Some(location))
     }
 
     /// Create a new `Node` for something created programatically, not parsed from a source file
+    #[inline]
     pub fn new(node: T) -> Self {
-        Self(Arc::new(NodeInner {
-            location: None,
-            node,
-        }))
+        Self::new_opt_location(node, None)
     }
 
     pub(crate) fn new_opt_location(node: T, location: Option<NodeLocation>) -> Self {
-        Self(Arc::new(NodeInner { location, node }))
+        Self(triomphe::Arc::new(NodeInner {
+            location,
+            node,
+            hash_cache: AtomicU64::new(HASH_NOT_COMPUTED_YET),
+        }))
     }
 
     pub fn location(&self) -> Option<NodeLocation> {
@@ -65,10 +77,7 @@ impl<T> Node<T> {
 
     /// Returns the given `node` at the same location as `self` (e.g. for a type conversion).
     pub fn same_location<U>(&self, node: U) -> Node<U> {
-        Node(Arc::new(NodeInner {
-            location: self.0.location,
-            node,
-        }))
+        Node::new_opt_location(node, self.0.location)
     }
 
     pub fn to_component(&self, origin: ComponentOrigin) -> Component<T> {
@@ -82,17 +91,29 @@ impl<T> Node<T> {
 
     /// Returns whether two `Node`s point to the same memory allocation
     pub fn ptr_eq(&self, other: &Self) -> bool {
-        self.0.ptr_eq(&other.0)
+        triomphe::Arc::ptr_eq(&self.0, &other.0)
     }
 
     /// Returns a mutable reference to `T`, cloning it if necessary
     ///
-    /// See [`Arc::make_mut`].
+    /// This is functionally equivalent to [`Arc::make_mut`][mm] from the standard library.
+    ///
+    /// If this `Node` is uniquely owned, `make_mut()` will provide a mutable
+    /// reference to the contents. If not, `make_mut()` will create a _new_ `Node`
+    /// with a clone of the contents, update `self` to point to it, and provide
+    /// a mutable reference to its contents.
+    ///
+    /// This is useful for implementing copy-on-write schemes where you wish to
+    /// avoid copying things if your `Node` is not shared.
+    ///
+    /// [mm]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html#method.make_mut
     pub fn make_mut(&mut self) -> &mut T
     where
         T: Clone,
     {
-        let inner = self.0.make_mut();
+        let inner = triomphe::Arc::make_mut(&mut self.0);
+        // Clear the cache as mutation through the returned `&mut T` may invalidate it
+        *inner.hash_cache.get_mut() = HASH_NOT_COMPUTED_YET;
         // TODO: should the `inner.location` be set to `None` here?
         // After a node is mutated it is kind of not from that source location anymore
         &mut inner.node
@@ -100,7 +121,7 @@ impl<T> Node<T> {
 
     /// Returns a mutable reference to `T` if this `Node` is uniquely owned
     pub fn get_mut(&mut self) -> Option<&mut T> {
-        self.0.get_mut().map(|inner| &mut inner.node)
+        triomphe::Arc::get_mut(&mut self.0).map(|inner| &mut inner.node)
     }
 }
 
@@ -118,6 +139,12 @@ impl<T> Clone for Node<T> {
     }
 }
 
+impl<T: Default> Default for Node<T> {
+    fn default() -> Self {
+        Self::new(T::default())
+    }
+}
+
 impl<T: fmt::Debug> fmt::Debug for Node<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         if let Some(location) = self.location() {
@@ -133,18 +160,48 @@ impl<T: fmt::Display> fmt::Display for Node<T> {
     }
 }
 
-impl<T: Eq> Eq for NodeInner<T> {}
+impl<T: Eq> Eq for Node<T> {}
 
-impl<T: PartialEq> PartialEq for NodeInner<T> {
+impl<T: PartialEq> PartialEq for Node<T> {
     fn eq(&self, other: &Self) -> bool {
-        self.node == other.node // location not included
+        self.ptr_eq(other) // fast path
+        || self.0.node == other.0.node // location and hash_cache not included
+    }
+}
+
+impl<T: Hash> Hash for Node<T> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let hash = self.0.hash_cache.load(Ordering::Relaxed);
+        if hash != HASH_NOT_COMPUTED_YET {
+            // cache hit
+            hash
+        } else {
+            hash_slow_path(&self.0)
+        }
+        .hash(state)
     }
 }
 
-impl<T: hash::Hash> hash::Hash for NodeInner<T> {
-    fn hash<H: hash::Hasher>(&self, state: &mut H) {
-        self.node.hash(state) // location not included
+// It is possible for multiple threads to race and take this path for the same `NodeInner`.
+// This is ok as they should compute the same result.
+// We save on the extra space that `OnceLock<u64>` would occupy,
+// at the cost of extra computation in the unlikely case of this race.
+#[cold]
+#[inline(never)]
+fn hash_slow_path<T: Hash>(inner: &NodeInner<T>) -> u64 {
+    /// We share a single `BuildHasher` process-wide,
+    /// not only for the race described above but also
+    /// so that multiple `HarcInner`’s with the same contents have the same hash.
+    static SHARED_RANDOM: OnceLock<RandomState> = OnceLock::new();
+    let mut hasher = SHARED_RANDOM.get_or_init(RandomState::new).build_hasher();
+    inner.node.hash(&mut hasher);
+    let mut hash = hasher.finish();
+    // Don’t use the marker value for an actual hash
+    if hash == HASH_NOT_COMPUTED_YET {
+        hash += 1
     }
+    inner.hash_cache.store(hash, Ordering::Relaxed);
+    hash
 }
 
 impl<T> AsRef<T> for Node<T> {
@@ -159,6 +216,16 @@ impl<T> From<T> for Node<T> {
     }
 }
 
+impl<T: Clone> Clone for NodeInner<T> {
+    fn clone(&self) -> Self {
+        Self {
+            location: self.location,
+            hash_cache: AtomicU64::new(self.hash_cache.load(Ordering::Relaxed)),
+            node: self.node.clone(),
+        }
+    }
+}
+
 impl NodeLocation {
     pub(crate) fn new(file_id: FileId, node: &'_ SyntaxNode) -> Self {
         Self {
diff --git a/crates/apollo-compiler/src/parser.rs b/crates/apollo-compiler/src/parser.rs
index fba3973..f1db79a 100644
--- a/crates/apollo-compiler/src/parser.rs
+++ b/crates/apollo-compiler/src/parser.rs
@@ -2,13 +2,13 @@ use crate::ast::Document;
 use crate::schema::SchemaBuilder;
 use crate::validation::Details;
 use crate::validation::Diagnostics;
-use crate::Arc;
 use crate::ExecutableDocument;
 use crate::FileId;
 use crate::NodeLocation;
 use crate::Schema;
 use std::path::Path;
 use std::path::PathBuf;
+use std::sync::Arc;
 
 /// Configuration for parsing an input string as GraphQL syntax
 #[derive(Default, Debug, Clone)]
@@ -19,6 +19,7 @@ pub struct Parser {
     tokens_reached: usize,
 }
 
+/// Records for validation information about a file that was parsed
 #[derive(Debug, Clone)]
 pub struct SourceFile {
     pub(crate) path: PathBuf,
@@ -48,7 +49,7 @@ impl Parser {
     }
 
     /// Configure the recursion to use while parsing.
-    pub fn recursion_limit(&mut self, value: usize) -> &mut Self {
+    pub fn recursion_limit(mut self, value: usize) -> Self {
         self.recursion_limit = Some(value);
         self
     }
@@ -56,7 +57,7 @@ impl Parser {
     /// Configure the limit on the number of tokens to parse.
     /// If an input document is too big, parsing will be aborted.
     /// By default, there is no limit.
-    pub fn token_limit(&mut self, value: usize) -> &mut Self {
+    pub fn token_limit(mut self, value: usize) -> Self {
         self.token_limit = Some(value);
         self
     }
@@ -67,7 +68,7 @@ impl Parser {
     /// to identify this source file to users.
     ///
     /// Parsing is fault-tolerant, so a document is always returned.
-    /// In case of a parse error, [`Document::parse_errors`] will return relevant information
+    /// In case of a parse error, [`Document::check_parse_errors`] will return relevant information
     /// and some nodes may be missing in the built document.
     pub fn parse_ast(
         &mut self,
diff --git a/crates/apollo-compiler/src/schema/from_ast.rs b/crates/apollo-compiler/src/schema/from_ast.rs
index 33871aa..1e0ea08 100644
--- a/crates/apollo-compiler/src/schema/from_ast.rs
+++ b/crates/apollo-compiler/src/schema/from_ast.rs
@@ -21,7 +21,7 @@ impl SchemaBuilder {
             schema: Schema {
                 sources: IndexMap::new(),
                 build_errors: Vec::new(),
-                root_operations: None,
+                schema_definition: None,
                 directive_definitions: IndexMap::new(),
                 types: IndexMap::new(),
             },
@@ -45,7 +45,7 @@ impl SchemaBuilder {
             builder.schema.build_errors.is_empty()
                 && builder.orphan_type_extensions.is_empty()
                 && builder.orphan_schema_extensions.is_empty()
-                && builder.schema.root_operations.is_none(),
+                && builder.schema.schema_definition.is_none(),
         );
         builder
     }
@@ -53,8 +53,9 @@ impl SchemaBuilder {
     /// Parse an input file with the default configuration as an additional input for this schema.
     ///
     /// Create a [`Parser`] to use different parser configuration.
-    pub fn parse(&mut self, source_text: impl Into<String>, path: impl AsRef<Path>) {
-        Parser::new().parse_into_schema_builder(source_text, path, self)
+    pub fn parse(mut self, source_text: impl Into<String>, path: impl AsRef<Path>) -> Self {
+        Parser::new().parse_into_schema_builder(source_text, path, &mut self);
+        self
     }
 
     /// Add an AST document to the schema being built
@@ -70,9 +71,9 @@ impl SchemaBuilder {
         }
         for definition in &document.definitions {
             match definition {
-                ast::Definition::SchemaDefinition(def) => match &self.schema.root_operations {
+                ast::Definition::SchemaDefinition(def) => match &self.schema.schema_definition {
                     None => {
-                        self.schema.root_operations = Some(RootOperations::from_ast(
+                        self.schema.schema_definition = Some(SchemaDefinition::from_ast(
                             &mut self.schema.build_errors,
                             def,
                             &self.orphan_schema_extensions,
@@ -89,7 +90,7 @@ impl SchemaBuilder {
                     }
                 },
                 ast::Definition::DirectiveDefinition(def) => {
-                    if let Err(previous) =
+                    if let Err((_prev_name, previous)) =
                         insert_sticky(&mut self.schema.directive_definitions, &def.name, || {
                             def.clone()
                         })
@@ -104,14 +105,16 @@ impl SchemaBuilder {
                     }
                 }
                 ast::Definition::ScalarTypeDefinition(def) => {
-                    if let Err(previous) = insert_sticky(&mut self.schema.types, &def.name, || {
-                        ExtendedType::Scalar(ScalarType::from_ast(
-                            def,
-                            self.orphan_type_extensions
-                                .remove(&def.name)
-                                .unwrap_or_default(),
-                        ))
-                    }) {
+                    if let Err((prev_name, previous)) =
+                        insert_sticky(&mut self.schema.types, &def.name, || {
+                            ExtendedType::Scalar(ScalarType::from_ast(
+                                def,
+                                self.orphan_type_extensions
+                                    .remove(&def.name)
+                                    .unwrap_or_default(),
+                            ))
+                        })
+                    {
                         self.schema.build_errors.push(if previous.is_built_in() {
                             BuildError::BuiltInScalarTypeRedefinition {
                                 location: def.location(),
@@ -119,109 +122,119 @@ impl SchemaBuilder {
                         } else {
                             BuildError::TypeDefinitionCollision {
                                 location: def.name.location(),
-                                previous_location: previous.name().location(),
+                                previous_location: prev_name.location(),
                                 name: def.name.clone(),
                             }
                         })
                     }
                 }
                 ast::Definition::ObjectTypeDefinition(def) => {
-                    if let Err(previous) = insert_sticky(&mut self.schema.types, &def.name, || {
-                        ExtendedType::Object(ObjectType::from_ast(
-                            &mut self.schema.build_errors,
-                            def,
-                            self.orphan_type_extensions
-                                .remove(&def.name)
-                                .unwrap_or_default(),
-                        ))
-                    }) {
+                    if let Err((prev_name, _previous)) =
+                        insert_sticky(&mut self.schema.types, &def.name, || {
+                            ExtendedType::Object(ObjectType::from_ast(
+                                &mut self.schema.build_errors,
+                                def,
+                                self.orphan_type_extensions
+                                    .remove(&def.name)
+                                    .unwrap_or_default(),
+                            ))
+                        })
+                    {
                         self.schema
                             .build_errors
                             .push(BuildError::TypeDefinitionCollision {
                                 location: def.name.location(),
-                                previous_location: previous.name().location(),
+                                previous_location: prev_name.location(),
                                 name: def.name.clone(),
                             })
                     }
                 }
                 ast::Definition::InterfaceTypeDefinition(def) => {
-                    if let Err(previous) = insert_sticky(&mut self.schema.types, &def.name, || {
-                        ExtendedType::Interface(InterfaceType::from_ast(
-                            &mut self.schema.build_errors,
-                            def,
-                            self.orphan_type_extensions
-                                .remove(&def.name)
-                                .unwrap_or_default(),
-                        ))
-                    }) {
+                    if let Err((prev_name, _previous)) =
+                        insert_sticky(&mut self.schema.types, &def.name, || {
+                            ExtendedType::Interface(InterfaceType::from_ast(
+                                &mut self.schema.build_errors,
+                                def,
+                                self.orphan_type_extensions
+                                    .remove(&def.name)
+                                    .unwrap_or_default(),
+                            ))
+                        })
+                    {
                         self.schema
                             .build_errors
                             .push(BuildError::TypeDefinitionCollision {
                                 location: def.name.location(),
-                                previous_location: previous.name().location(),
+                                previous_location: prev_name.location(),
                                 name: def.name.clone(),
                             })
                     }
                 }
                 ast::Definition::UnionTypeDefinition(def) => {
-                    if let Err(previous) = insert_sticky(&mut self.schema.types, &def.name, || {
-                        ExtendedType::Union(UnionType::from_ast(
-                            &mut self.schema.build_errors,
-                            def,
-                            self.orphan_type_extensions
-                                .remove(&def.name)
-                                .unwrap_or_default(),
-                        ))
-                    }) {
+                    if let Err((prev_name, _)) =
+                        insert_sticky(&mut self.schema.types, &def.name, || {
+                            ExtendedType::Union(UnionType::from_ast(
+                                &mut self.schema.build_errors,
+                                def,
+                                self.orphan_type_extensions
+                                    .remove(&def.name)
+                                    .unwrap_or_default(),
+                            ))
+                        })
+                    {
                         self.schema
                             .build_errors
                             .push(BuildError::TypeDefinitionCollision {
                                 location: def.name.location(),
-                                previous_location: previous.name().location(),
+                                previous_location: prev_name.location(),
                                 name: def.name.clone(),
                             })
                     }
                 }
                 ast::Definition::EnumTypeDefinition(def) => {
-                    if let Err(previous) = insert_sticky(&mut self.schema.types, &def.name, || {
-                        ExtendedType::Enum(EnumType::from_ast(
-                            &mut self.schema.build_errors,
-                            def,
-                            self.orphan_type_extensions
-                                .remove(&def.name)
-                                .unwrap_or_default(),
-                        ))
-                    }) {
+                    if let Err((prev_name, _previous)) =
+                        insert_sticky(&mut self.schema.types, &def.name, || {
+                            ExtendedType::Enum(EnumType::from_ast(
+                                &mut self.schema.build_errors,
+                                def,
+                                self.orphan_type_extensions
+                                    .remove(&def.name)
+                                    .unwrap_or_default(),
+                            ))
+                        })
+                    {
                         self.schema
                             .build_errors
                             .push(BuildError::TypeDefinitionCollision {
                                 location: def.name.location(),
-                                previous_location: previous.name().location(),
+                                previous_location: prev_name.location(),
                                 name: def.name.clone(),
                             })
                     }
                 }
                 ast::Definition::InputObjectTypeDefinition(def) => {
-                    if let Err(previous) = insert_sticky(&mut self.schema.types, &def.name, || {
-                        ExtendedType::InputObject(InputObjectType::from_ast(
-                            &mut self.schema.build_errors,
-                            def,
-                            self.orphan_type_extensions
-                                .remove(&def.name)
-                                .unwrap_or_default(),
-                        ))
-                    }) {
+                    if let Err((prev_name, _previous)) =
+                        insert_sticky(&mut self.schema.types, &def.name, || {
+                            ExtendedType::InputObject(InputObjectType::from_ast(
+                                &mut self.schema.build_errors,
+                                def,
+                                self.orphan_type_extensions
+                                    .remove(&def.name)
+                                    .unwrap_or_default(),
+                            ))
+                        })
+                    {
                         self.schema
                             .build_errors
                             .push(BuildError::TypeDefinitionCollision {
                                 location: def.name.location(),
-                                previous_location: previous.name().location(),
+                                previous_location: prev_name.location(),
                                 name: def.name.clone(),
                             })
                     }
                 }
                 ast::Definition::SchemaExtension(ext) => {
-                    if let Some(root) = &mut self.schema.root_operations {
+                    if let Some(root) = &mut self.schema.schema_definition {
                         root.make_mut()
                             .extend_ast(&mut self.schema.build_errors, ext)
                     } else {
@@ -229,7 +242,7 @@ impl SchemaBuilder {
                     }
                 }
                 ast::Definition::ScalarTypeExtension(ext) => {
-                    if let Some(ty) = self.schema.types.get_mut(&ext.name) {
+                    if let Some((_, ty_name, ty)) = self.schema.types.get_full_mut(&ext.name) {
                         if let ExtendedType::Scalar(ty) = ty {
                             ty.make_mut().extend_ast(ext)
                         } else {
@@ -239,7 +252,7 @@ impl SchemaBuilder {
                                     location: ext.name.location(),
                                     name: ext.name.clone(),
                                     describe_ext: definition.describe(),
-                                    def_location: ty.name().location(),
+                                    def_location: ty_name.location(),
                                     describe_def: ty.describe(),
                                 })
                         }
@@ -251,7 +264,7 @@ impl SchemaBuilder {
                     }
                 }
                 ast::Definition::ObjectTypeExtension(ext) => {
-                    if let Some(ty) = self.schema.types.get_mut(&ext.name) {
+                    if let Some((_, ty_name, ty)) = self.schema.types.get_full_mut(&ext.name) {
                         if let ExtendedType::Object(ty) = ty {
                             ty.make_mut().extend_ast(&mut self.schema.build_errors, ext)
                         } else {
@@ -261,7 +274,7 @@ impl SchemaBuilder {
                                     location: ext.name.location(),
                                     name: ext.name.clone(),
                                     describe_ext: definition.describe(),
-                                    def_location: ty.name().location(),
+                                    def_location: ty_name.location(),
                                     describe_def: ty.describe(),
                                 })
                         }
@@ -273,7 +286,7 @@ impl SchemaBuilder {
                     }
                 }
                 ast::Definition::InterfaceTypeExtension(ext) => {
-                    if let Some(ty) = self.schema.types.get_mut(&ext.name) {
+                    if let Some((_, ty_name, ty)) = self.schema.types.get_full_mut(&ext.name) {
                         if let ExtendedType::Interface(ty) = ty {
                             ty.make_mut().extend_ast(&mut self.schema.build_errors, ext)
                         } else {
@@ -283,7 +296,7 @@ impl SchemaBuilder {
                                     location: ext.name.location(),
                                     name: ext.name.clone(),
                                     describe_ext: definition.describe(),
-                                    def_location: ty.name().location(),
+                                    def_location: ty_name.location(),
                                     describe_def: ty.describe(),
                                 })
                         }
@@ -295,7 +308,7 @@ impl SchemaBuilder {
                     }
                 }
                 ast::Definition::UnionTypeExtension(ext) => {
-                    if let Some(ty) = self.schema.types.get_mut(&ext.name) {
+                    if let Some((_, ty_name, ty)) = self.schema.types.get_full_mut(&ext.name) {
                         if let ExtendedType::Union(ty) = ty {
                             ty.make_mut().extend_ast(&mut self.schema.build_errors, ext)
                         } else {
@@ -305,7 +318,7 @@ impl SchemaBuilder {
                                     location: ext.name.location(),
                                     name: ext.name.clone(),
                                     describe_ext: definition.describe(),
-                                    def_location: ty.name().location(),
+                                    def_location: ty_name.location(),
                                     describe_def: ty.describe(),
                                 })
                         }
@@ -317,7 +330,7 @@ impl SchemaBuilder {
                     }
                 }
                 ast::Definition::EnumTypeExtension(ext) => {
-                    if let Some(ty) = self.schema.types.get_mut(&ext.name) {
+                    if let Some((_, ty_name, ty)) = self.schema.types.get_full_mut(&ext.name) {
                         if let ExtendedType::Enum(ty) = ty {
                             ty.make_mut().extend_ast(&mut self.schema.build_errors, ext)
                         } else {
@@ -327,7 +340,7 @@ impl SchemaBuilder {
                                     location: ext.name.location(),
                                     name: ext.name.clone(),
                                     describe_ext: definition.describe(),
-                                    def_location: ty.name().location(),
+                                    def_location: ty_name.location(),
                                     describe_def: ty.describe(),
                                 })
                         }
@@ -339,7 +352,7 @@ impl SchemaBuilder {
                     }
                 }
                 ast::Definition::InputObjectTypeExtension(ext) => {
-                    if let Some(ty) = self.schema.types.get_mut(&ext.name) {
+                    if let Some((_, ty_name, ty)) = self.schema.types.get_full_mut(&ext.name) {
                         if let ExtendedType::InputObject(ty) = ty {
                             ty.make_mut().extend_ast(&mut self.schema.build_errors, ext)
                         } else {
@@ -349,7 +362,7 @@ impl SchemaBuilder {
                                     location: ext.name.location(),
                                     name: ext.name.clone(),
                                     describe_ext: definition.describe(),
-                                    def_location: ty.name().location(),
+                                    def_location: ty_name.location(),
                                     describe_def: ty.describe(),
                                 })
                         }
@@ -406,7 +419,7 @@ impl SchemaBuilder {
     }
 }
 
-impl RootOperations {
+impl SchemaDefinition {
     fn from_ast(
         errors: &mut Vec<BuildError>,
         definition: &Node<ast::SchemaDefinition>,
@@ -476,7 +489,6 @@ impl ScalarType {
         extensions: Vec<ast::Definition>,
     ) -> Node<Self> {
         let mut ty = Self {
-            name: definition.name.clone(),
             description: definition.description.clone(),
             directives: definition
                 .directives
@@ -510,7 +522,6 @@ impl ObjectType {
         extensions: Vec<ast::Definition>,
     ) -> Node<Self> {
         let mut ty = Self {
-            name: definition.name.clone(),
             description: definition.description.clone(),
             implements_interfaces: collect_sticky_set(
                 definition
@@ -602,7 +613,6 @@ impl InterfaceType {
         extensions: Vec<ast::Definition>,
     ) -> Node<Self> {
         let mut ty = Self {
-            name: definition.name.clone(),
             description: definition.description.clone(),
             implements_interfaces: collect_sticky_set(
                 definition
@@ -694,7 +704,6 @@ impl UnionType {
         extensions: Vec<ast::Definition>,
     ) -> Node<Self> {
         let mut ty = Self {
-            name: definition.name.clone(),
             description: definition.description.clone(),
             directives: definition
                 .directives
@@ -759,7 +768,6 @@ impl EnumType {
         extensions: Vec<ast::Definition>,
     ) -> Node<Self> {
         let mut ty = Self {
-            name: definition.name.clone(),
             description: definition.description.clone(),
             directives: definition
                 .directives
@@ -826,7 +834,6 @@ impl InputObjectType {
         extensions: Vec<ast::Definition>,
     ) -> Node<Self> {
         let mut ty = Self {
-            name: definition.name.clone(),
             description: definition.description.clone(),
             directives: definition
                 .directives
@@ -884,23 +891,21 @@ impl InputObjectType {
     }
 }
 
-/// Like `IndexMap::insert`, but does not replace the value if an equivalent key is already in the map.
+/// Like `IndexMap::insert`, but does not replace the value
+/// if an equivalent key is already in the map.
 ///
-/// Returns wether the value was inserted
-fn insert_sticky<K, V>(
-    map: &mut IndexMap<K, V>,
-    key: impl Into<K>,
+/// In that (error) case, returns the existing key and value
+fn insert_sticky<'map, V>(
+    map: &'map mut IndexMap<Name, V>,
+    key: &Name,
     make_value: impl FnOnce() -> V,
-) -> Result<(), &V>
-where
-    K: std::hash::Hash + Eq,
-{
-    match map.entry(key.into()) {
+) -> Result<(), (&'map Name, &'map V)> {
+    match map.entry(key.clone()) {
         Entry::Vacant(entry) => {
             entry.insert(make_value());
             Ok(())
         }
-        Entry::Occupied(entry) => Err(entry.into_mut()),
+        Entry::Occupied(_) => Err(map.get_key_value(key).unwrap()),
     }
 }
 
diff --git a/crates/apollo-compiler/src/schema/mod.rs b/crates/apollo-compiler/src/schema/mod.rs
index f872a7b..3f53105 100644
--- a/crates/apollo-compiler/src/schema/mod.rs
+++ b/crates/apollo-compiler/src/schema/mod.rs
@@ -1,7 +1,6 @@
 //! High-level representation of a GraphQL schema
 
 use crate::ast;
-use crate::Arc;
 use crate::FileId;
 use crate::Node;
 use crate::NodeLocation;
@@ -13,6 +12,7 @@ use indexmap::IndexSet;
 use std::collections::HashMap;
 use std::collections::HashSet;
 use std::path::Path;
+use std::sync::Arc;
 use std::sync::OnceLock;
 
 mod component;
@@ -41,7 +41,10 @@ pub struct Schema {
     build_errors: Vec<BuildError>,
 
     /// The `schema` definition and its extensions, defining root operations
-    pub root_operations: Option<Node<RootOperations>>,
+    ///
+    /// For more convenient access to its directives regardless of `Option`,
+    /// see [`schema_definition_directives`][Self::schema_definition_directives]
+    pub schema_definition: Option<Node<SchemaDefinition>>,
 
     /// Built-in and explicit directive definitions
     pub directive_definitions: IndexMap<Name, Node<DirectiveDefinition>>,
@@ -52,8 +55,8 @@ pub struct Schema {
 }
 
 /// The `schema` definition and its extensions, defining root operations
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct RootOperations {
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct SchemaDefinition {
     pub description: Option<NodeStr>,
     pub directives: Directives,
 
@@ -85,14 +88,12 @@ pub enum ExtendedType {
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ScalarType {
-    pub name: Name,
     pub description: Option<NodeStr>,
     pub directives: Directives,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct ObjectType {
-    pub name: Name,
     pub description: Option<NodeStr>,
     pub implements_interfaces: IndexSet<ComponentStr>,
     pub directives: Directives,
@@ -106,7 +107,6 @@ pub struct ObjectType {
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct InterfaceType {
-    pub name: Name,
     pub description: Option<NodeStr>,
 
     /// * Key: name of an implemented interface
@@ -125,7 +125,6 @@ pub struct InterfaceType {
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct UnionType {
-    pub name: Name,
     pub description: Option<NodeStr>,
     pub directives: Directives,
 
@@ -137,7 +136,6 @@ pub struct UnionType {
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct EnumType {
-    pub name: Name,
     pub description: Option<NodeStr>,
     pub directives: Directives,
     pub values: IndexMap<Name, Component<EnumValueDefinition>>,
@@ -145,7 +143,6 @@ pub struct EnumType {
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct InputObjectType {
-    pub name: Name,
     pub description: Option<NodeStr>,
     pub directives: Directives,
     pub fields: IndexMap<Name, Component<InputValueDefinition>>,
@@ -283,7 +280,7 @@ pub(crate) enum BuildError {
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum FieldLookupError<'schema> {
     NoSuchType,
-    NoSuchField(&'schema ExtendedType),
+    NoSuchField(&'schema NamedType, &'schema ExtendedType),
 }
 
 impl Schema {
@@ -335,6 +332,15 @@ impl Schema {
         }
     }
 
+    /// Directives of the `schema` definition and its extensions
+    pub fn schema_definition_directives(&self) -> &Directives {
+        if let Some(def) = &self.schema_definition {
+            &def.directives
+        } else {
+            Directives::EMPTY
+        }
+    }
+
     /// Returns the type with the given name, if it is a scalar type
     pub fn get_scalar(&self, name: &str) -> Option<&Node<ScalarType>> {
         if let Some(ExtendedType::Scalar(ty)) = self.types.get(name) {
@@ -389,32 +395,72 @@ impl Schema {
         }
     }
 
+    /// Returns the name of the object type for the `query` root operation
+    pub fn query_root_operation(&self) -> Option<&NamedType> {
+        if let Some(root_operations) = &self.schema_definition {
+            root_operations
+                .query
+                .as_ref()
+                .map(|component| &component.node)
+        } else {
+            self.default_root_operation(ast::OperationType::Query)
+        }
+    }
+
+    /// Returns the name of the object type for the `mutation` root operation
+    pub fn mutation_root_operation(&self) -> Option<&NamedType> {
+        if let Some(root_operations) = &self.schema_definition {
+            root_operations
+                .mutation
+                .as_ref()
+                .map(|component| &component.node)
+        } else {
+            self.default_root_operation(ast::OperationType::Mutation)
+        }
+    }
+
+    /// Returns the name of the object type for the `subscription` root operation
+    pub fn subscription_root_operation(&self) -> Option<&NamedType> {
+        if let Some(root_operations) = &self.schema_definition {
+            root_operations
+                .subscription
+                .as_ref()
+                .map(|component| &component.node)
+        } else {
+            self.default_root_operation(ast::OperationType::Subscription)
+        }
+    }
+
     /// Returns the name of the object type for the root operation with the given operation kind
-    pub fn root_operation(&self, operation_type: ast::OperationType) -> Option<&ComponentStr> {
-        if let Some(root_operations) = &self.root_operations {
+    pub fn root_operation(&self, operation_type: ast::OperationType) -> Option<&NamedType> {
+        if let Some(root_operations) = &self.schema_definition {
             match operation_type {
                 ast::OperationType::Query => &root_operations.query,
                 ast::OperationType::Mutation => &root_operations.mutation,
                 ast::OperationType::Subscription => &root_operations.subscription,
             }
             .as_ref()
+            .map(|component| &component.node)
         } else {
-            let name = operation_type.default_type_name();
-            macro_rules! as_static {
-                () => {{
-                    static OBJECT_TYPE_NAME: OnceLock<ComponentStr> = OnceLock::new();
-                    OBJECT_TYPE_NAME
-                        .get_or_init(|| Name::new(name).to_component(ComponentOrigin::Definition))
-                }};
-            }
-            self.get_object(name)
-                .is_some()
-                .then(|| match operation_type {
-                    ast::OperationType::Query => as_static!(),
-                    ast::OperationType::Mutation => as_static!(),
-                    ast::OperationType::Subscription => as_static!(),
-                })
+            self.default_root_operation(operation_type)
+        }
+    }
+
+    fn default_root_operation(&self, operation_type: ast::OperationType) -> Option<&NamedType> {
+        let name = operation_type.default_type_name();
+        macro_rules! as_static {
+            () => {{
+                static OBJECT_TYPE_NAME: OnceLock<Name> = OnceLock::new();
+                OBJECT_TYPE_NAME.get_or_init(|| Name::new(name))
+            }};
         }
+        self.get_object(name)
+            .is_some()
+            .then(|| match operation_type {
+                ast::OperationType::Query => as_static!(),
+                ast::OperationType::Mutation => as_static!(),
+                ast::OperationType::Subscription => as_static!(),
+            })
     }
 
     /// Returns the definition of a type’s explicit field or meta-field.
@@ -423,9 +469,9 @@ impl Schema {
         type_name: &str,
         field_name: &str,
     ) -> Result<&Component<FieldDefinition>, FieldLookupError<'_>> {
-        let ty_def = self
+        let (ty_def_name, ty_def) = self
             .types
-            .get(type_name)
+            .get_key_value(type_name)
             .ok_or(FieldLookupError::NoSuchType)?;
         self.meta_fields_definitions(type_name)
             .iter()
@@ -438,7 +484,7 @@ impl Schema {
                 | ExtendedType::Enum(_)
                 | ExtendedType::InputObject(_) => None,
             })
-            .ok_or(FieldLookupError::NoSuchField(ty_def))
+            .ok_or(FieldLookupError::NoSuchField(ty_def_name, ty_def))
     }
 
     /// Returns a map of interface names to names of types that implement that interface
@@ -535,10 +581,7 @@ impl Schema {
                 }),
             ]
         });
-        if self
-            .root_operation(ast::OperationType::Query)
-            .is_some_and(|n| n.node == type_name)
-        {
+        if self.query_root_operation().is_some_and(|n| n == type_name) {
             // __typename: String!
             // __schema: __Schema!
             // __type(name: String!): __Type
@@ -581,7 +624,7 @@ impl Schema {
     serialize_method!();
 }
 
-impl RootOperations {
+impl SchemaDefinition {
     /// Collect `schema` extensions that contribute any component
     ///
     /// The order of the returned set is unspecified but deterministic
@@ -610,18 +653,6 @@ impl RootOperations {
 }
 
 impl ExtendedType {
-    /// Return the name of the type.
-    pub fn name(&self) -> &ast::Name {
-        match self {
-            Self::Scalar(ty) => &ty.name,
-            Self::Object(ty) => &ty.name,
-            Self::Interface(ty) => &ty.name,
-            Self::Union(ty) => &ty.name,
-            Self::Enum(ty) => &ty.name,
-            Self::InputObject(ty) => &ty.name,
-        }
-    }
-
     /// Return the source location of the type's base definition.
     ///
     /// If the type has extensions, those are not covered by this location.
@@ -674,7 +705,8 @@ impl ExtendedType {
     /// Returns true if a value of this type can be used as an input value.
     ///
     /// # Spec
-    /// This implements spec function `IsInputType(type)`: https://spec.graphql.org/draft/#IsInputType()
+    /// This implements spec function
+    /// [`IsInputType(type)`](https://spec.graphql.org/draft/#IsInputType())
     pub fn is_input_type(&self) -> bool {
         matches!(self, Self::Scalar(_) | Self::Enum(_) | Self::InputObject(_))
     }
@@ -682,7 +714,8 @@ impl ExtendedType {
     /// Returns true if a value of this type can be used as an output value.
     ///
     /// # Spec
-    /// This implements spec function `IsOutputType(type)`: https://spec.graphql.org/draft/#IsOutputType()
+    /// This implements spec function
+    /// [`IsOutputType(type)`](https://spec.graphql.org/draft/#IsOutputType())
     pub fn is_output_type(&self) -> bool {
         matches!(
             self,
@@ -853,14 +886,16 @@ impl InputObjectType {
 }
 
 impl Directives {
-    pub fn new() -> Self {
+    const EMPTY: &Self = &Self::new();
+
+    pub const fn new() -> Self {
         Self(Vec::new())
     }
 
     /// Returns an iterator of directives with the given name.
     ///
-    /// This method is best for repeatable directives. For non-repeatable directives,
-    /// see [`directive_by_name`][Self::directive_by_name] (singular)
+    /// This method is best for repeatable directives.
+    /// See also [`get`][Self::get] for non-repeatable directives.
     pub fn get_all<'def: 'name, 'name>(
         &'def self,
         name: &'name str,
@@ -870,8 +905,8 @@ impl Directives {
 
     /// Returns the first directive with the given name, if any.
     ///
-    /// This method is best for non-repeatable directives. For repeatable directives,
-    /// see [`directives_by_name`][Self::directives_by_name] (plural)
+    /// This method is best for non-repeatable directives.
+    /// See also [`get_all`][Self::get_all] for repeatable directives.
     pub fn get(&self, name: &str) -> Option<&Component<Directive>> {
         self.get_all(name).next()
     }
@@ -940,11 +975,11 @@ impl PartialEq for Schema {
         let Self {
             sources: _,      // ignored
             build_errors: _, // ignored
-            root_operations,
+            schema_definition: root_operations,
             directive_definitions,
             types,
         } = self;
-        *root_operations == other.root_operations
+        *root_operations == other.schema_definition
             && *directive_definitions == other.directive_definitions
             && *types == other.types
     }
@@ -986,6 +1021,42 @@ impl From<Node<InputObjectType>> for ExtendedType {
     }
 }
 
+impl From<ScalarType> for ExtendedType {
+    fn from(ty: ScalarType) -> Self {
+        Self::Scalar(ty.into())
+    }
+}
+
+impl From<ObjectType> for ExtendedType {
+    fn from(ty: ObjectType) -> Self {
+        Self::Object(ty.into())
+    }
+}
+
+impl From<InterfaceType> for ExtendedType {
+    fn from(ty: InterfaceType) -> Self {
+        Self::Interface(ty.into())
+    }
+}
+
+impl From<UnionType> for ExtendedType {
+    fn from(ty: UnionType) -> Self {
+        Self::Union(ty.into())
+    }
+}
+
+impl From<EnumType> for ExtendedType {
+    fn from(ty: EnumType) -> Self {
+        Self::Enum(ty.into())
+    }
+}
+
+impl From<InputObjectType> for ExtendedType {
+    fn from(ty: InputObjectType) -> Self {
+        Self::InputObject(ty.into())
+    }
+}
+
 // TODO: use `std::sync::LazyLock` when available https://github.com/rust-lang/rust/issues/109736
 struct LazyLock<T> {
     value: OnceLock<T>,
diff --git a/crates/apollo-compiler/src/schema/serialize.rs b/crates/apollo-compiler/src/schema/serialize.rs
index 71bc015..65c86b6 100644
--- a/crates/apollo-compiler/src/schema/serialize.rs
+++ b/crates/apollo-compiler/src/schema/serialize.rs
@@ -14,7 +14,7 @@ impl Schema {
 
 impl Schema {
     pub(crate) fn to_ast(&self) -> impl Iterator<Item = ast::Definition> + '_ {
-        self.root_operations
+        self.schema_definition
             .as_ref()
             .into_iter()
             .flat_map(|root| root.to_ast())
@@ -35,7 +35,7 @@ impl Schema {
     }
 }
 
-impl Node<RootOperations> {
+impl Node<SchemaDefinition> {
     fn to_ast(&self) -> impl Iterator<Item = ast::Definition> + '_ {
         let root_ops = |ext: Option<&ExtensionId>| -> Vec<Node<(OperationType, Name)>> {
             let root_op = |op: &Option<ComponentStr>, ty| {
diff --git a/crates/apollo-compiler/src/schema/validation.rs b/crates/apollo-compiler/src/schema/validation.rs
index 4703f74..0bc6d85 100644
--- a/crates/apollo-compiler/src/schema/validation.rs
+++ b/crates/apollo-compiler/src/schema/validation.rs
@@ -1,11 +1,11 @@
 use super::BuildError;
 use crate::validation::Details;
 use crate::validation::Diagnostics;
-use crate::Arc;
 use crate::FileId;
 use crate::InputDatabase;
 use crate::Schema;
 use crate::ValidationDatabase;
+use std::sync::Arc;
 
 pub(crate) fn validate_schema(
     errors: &mut Diagnostics,
diff --git a/crates/apollo-compiler/src/validation/argument.rs b/crates/apollo-compiler/src/validation/argument.rs
index 4b505b1..bad649e 100644
--- a/crates/apollo-compiler/src/validation/argument.rs
+++ b/crates/apollo-compiler/src/validation/argument.rs
@@ -7,7 +7,7 @@ use crate::{
     Node, NodeLocation,
 };
 
-pub fn validate_arguments(
+pub(crate) fn validate_arguments(
     db: &dyn ValidationDatabase,
     arguments: &[Node<ast::Argument>],
 ) -> Vec<ApolloDiagnostic> {
diff --git a/crates/apollo-compiler/src/validation/directive.rs b/crates/apollo-compiler/src/validation/directive.rs
index ae7958d..1bb51f3 100644
--- a/crates/apollo-compiler/src/validation/directive.rs
+++ b/crates/apollo-compiler/src/validation/directive.rs
@@ -131,7 +131,7 @@ impl FindRecursiveDirective<'_> {
     }
 }
 
-pub fn validate_directive_definition(
+pub(crate) fn validate_directive_definition(
     db: &dyn ValidationDatabase,
     def: Node<ast::DirectiveDefinition>,
 ) -> Vec<ApolloDiagnostic> {
@@ -175,7 +175,7 @@ pub fn validate_directive_definition(
     diagnostics
 }
 
-pub fn validate_directive_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub(crate) fn validate_directive_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     let mut diagnostics = Vec::new();
 
     for file_id in db.type_definition_files() {
@@ -191,7 +191,7 @@ pub fn validate_directive_definitions(db: &dyn ValidationDatabase) -> Vec<Apollo
 
 // TODO(@goto-bus-stop) This is a big function: should probably not be generic over the iterator
 // type
-pub fn validate_directives<'dir>(
+pub(crate) fn validate_directives<'dir>(
     db: &dyn ValidationDatabase,
     dirs: impl Iterator<Item = &'dir Node<ast::Directive>>,
     dir_loc: ast::DirectiveLocation,
diff --git a/crates/apollo-compiler/src/validation/enum_.rs b/crates/apollo-compiler/src/validation/enum_.rs
index 909a054..88951fc 100644
--- a/crates/apollo-compiler/src/validation/enum_.rs
+++ b/crates/apollo-compiler/src/validation/enum_.rs
@@ -4,7 +4,7 @@ use crate::{
     Node, ValidationDatabase,
 };
 
-pub fn validate_enum_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub(crate) fn validate_enum_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     let mut diagnostics = Vec::new();
 
     for enum_ in db.ast_types().enums.values() {
@@ -14,7 +14,7 @@ pub fn validate_enum_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagn
     diagnostics
 }
 
-pub fn validate_enum_definition(
+pub(crate) fn validate_enum_definition(
     db: &dyn ValidationDatabase,
     enum_def: ast::TypeWithExtensions<ast::EnumTypeDefinition>,
 ) -> Vec<ApolloDiagnostic> {
diff --git a/crates/apollo-compiler/src/validation/field.rs b/crates/apollo-compiler/src/validation/field.rs
index d31e296..ca3502d 100644
--- a/crates/apollo-compiler/src/validation/field.rs
+++ b/crates/apollo-compiler/src/validation/field.rs
@@ -3,13 +3,14 @@ use crate::{
     diagnostics::{ApolloDiagnostic, DiagnosticData, Label},
     schema,
     validation::ValidationDatabase,
-    Node,
+    FileId, Node,
 };
 
 use super::operation::OperationValidationConfig;
 
-pub fn validate_field(
+pub(crate) fn validate_field(
     db: &dyn ValidationDatabase,
+    file_id: FileId,
     // May be None if a parent selection was invalid
     against_type: Option<&ast::NamedType>,
     field: Node<ast::Field>,
@@ -117,6 +118,7 @@ pub fn validate_field(
             Err(diag) => diagnostics.push(diag),
             Ok(_) => diagnostics.extend(super::selection::validate_selection_set2(
                 db,
+                file_id,
                 Some(field_definition.ty.inner_named_type()),
                 &field.selection_set,
                 context,
@@ -127,7 +129,7 @@ pub fn validate_field(
     diagnostics
 }
 
-pub fn validate_field_definition(
+pub(crate) fn validate_field_definition(
     db: &dyn ValidationDatabase,
     field: &Node<ast::FieldDefinition>,
 ) -> Vec<ApolloDiagnostic> {
@@ -148,7 +150,7 @@ pub fn validate_field_definition(
     diagnostics
 }
 
-pub fn validate_field_definitions(
+pub(crate) fn validate_field_definitions(
     db: &dyn ValidationDatabase,
     fields: Vec<Node<ast::FieldDefinition>>,
 ) -> Vec<ApolloDiagnostic> {
@@ -211,7 +213,7 @@ pub fn validate_field_definitions(
     diagnostics
 }
 
-pub fn validate_leaf_field_selection(
+pub(crate) fn validate_leaf_field_selection(
     db: &dyn ValidationDatabase,
     field: Node<ast::Field>,
     field_type: &ast::Type,
diff --git a/crates/apollo-compiler/src/validation/fragment.rs b/crates/apollo-compiler/src/validation/fragment.rs
index 92b4b24..0ee14ec 100644
--- a/crates/apollo-compiler/src/validation/fragment.rs
+++ b/crates/apollo-compiler/src/validation/fragment.rs
@@ -12,7 +12,7 @@ use super::operation::OperationValidationConfig;
 /// Given a type definition, find all the type names that can be used for fragment spreading.
 ///
 /// Spec: https://spec.graphql.org/October2021/#GetPossibleTypes()
-pub fn get_possible_types<'a>(
+pub(crate) fn get_possible_types<'a>(
     schema: &'a schema::Schema,
     type_name: &'a ast::Name,
 ) -> HashSet<&'a ast::NamedType> {
@@ -20,7 +20,7 @@ pub fn get_possible_types<'a>(
         // 1. If `type` is an object type, return a set containing `type`.
         Some(schema::ExtendedType::Object(_)) => std::iter::once(type_name).collect(),
         // 2. If `type` is an interface type, return the set of types implementing `type`.
-        Some(schema::ExtendedType::Interface(intf)) => {
+        Some(schema::ExtendedType::Interface(_)) => {
             // TODO(@goto-bus-stop): use db.implementers_map()
             schema
                 .types
@@ -31,7 +31,7 @@ pub fn get_possible_types<'a>(
                         _ => return None,
                     };
 
-                    if implements.contains(&intf.name) {
+                    if implements.contains(type_name) {
                         Some(name)
                     } else {
                         None
@@ -51,6 +51,7 @@ pub 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,
@@ -78,8 +79,7 @@ fn validate_fragment_spread_type(
         let diagnostic = match selection {
             ast::Selection::Field(_) => unreachable!(),
             ast::Selection::FragmentSpread(spread) => {
-                let named_fragments =
-                    db.ast_named_fragments(selection.location().unwrap().file_id());
+                let named_fragments = db.ast_named_fragments(file_id);
                 // TODO(@goto-bus-stop) Can we guarantee this unwrap()?
                 let fragment_definition = named_fragments.get(&spread.fragment_name).unwrap();
 
@@ -128,8 +128,9 @@ fn validate_fragment_spread_type(
     diagnostics
 }
 
-pub fn validate_inline_fragment(
+pub(crate) fn validate_inline_fragment(
     db: &dyn ValidationDatabase,
+    file_id: FileId,
     against_type: Option<&ast::NamedType>,
     inline: Node<ast::InlineFragment>,
     context: OperationValidationConfig<'_>,
@@ -162,6 +163,7 @@ pub fn validate_inline_fragment(
         if let (Some(against_type), Some(type_condition)) = (against_type, &inline.type_condition) {
             diagnostics.extend(validate_fragment_spread_type(
                 db,
+                file_id,
                 against_type,
                 type_condition,
                 &ast::Selection::InlineFragment(inline.clone()),
@@ -169,6 +171,7 @@ pub fn validate_inline_fragment(
         }
         diagnostics.extend(super::selection::validate_selection_set2(
             db,
+            file_id,
             inline.type_condition.as_ref().or(against_type),
             &inline.selection_set,
             context,
@@ -178,8 +181,9 @@ pub fn validate_inline_fragment(
     diagnostics
 }
 
-pub fn validate_fragment_spread(
+pub(crate) fn validate_fragment_spread(
     db: &dyn ValidationDatabase,
+    file_id: FileId,
     against_type: Option<&ast::NamedType>,
     spread: Node<ast::FragmentSpread>,
     context: OperationValidationConfig<'_>,
@@ -193,18 +197,24 @@ pub fn validate_fragment_spread(
         context.variables.clone(),
     ));
 
-    let named_fragments = db.ast_named_fragments(spread.location().unwrap().file_id());
+    let named_fragments = db.ast_named_fragments(file_id);
     match named_fragments.get(&spread.fragment_name) {
         Some(def) => {
             if let Some(against_type) = against_type {
                 diagnostics.extend(validate_fragment_spread_type(
                     db,
+                    file_id,
                     against_type,
                     &def.type_condition,
                     &ast::Selection::FragmentSpread(spread.clone()),
                 ));
             }
-            diagnostics.extend(validate_fragment_definition(db, def.clone(), context));
+            diagnostics.extend(validate_fragment_definition(
+                db,
+                file_id,
+                def.clone(),
+                context,
+            ));
         }
         None => {
             diagnostics.push(
@@ -226,8 +236,9 @@ pub fn validate_fragment_spread(
     diagnostics
 }
 
-pub fn validate_fragment_definition(
+pub(crate) fn validate_fragment_definition(
     db: &dyn ValidationDatabase,
+    file_id: FileId,
     fragment: Node<ast::FragmentDefinition>,
     context: OperationValidationConfig<'_>,
 ) -> Vec<ApolloDiagnostic> {
@@ -254,7 +265,7 @@ pub fn validate_fragment_definition(
         false
     };
 
-    let fragment_cycles_diagnostics = validate_fragment_cycles(db, &fragment);
+    let fragment_cycles_diagnostics = validate_fragment_cycles(db, file_id, &fragment);
     let has_cycles = !fragment_cycles_diagnostics.is_empty();
     diagnostics.extend(fragment_cycles_diagnostics);
 
@@ -269,6 +280,7 @@ pub fn validate_fragment_definition(
 
         diagnostics.extend(super::selection::validate_selection_set2(
             db,
+            file_id,
             type_condition,
             &fragment.selection_set,
             context,
@@ -278,15 +290,16 @@ pub fn validate_fragment_definition(
     diagnostics
 }
 
-pub fn validate_fragment_cycles(
+pub(crate) fn validate_fragment_cycles(
     db: &dyn ValidationDatabase,
+    file_id: FileId,
     def: &Node<ast::FragmentDefinition>,
 ) -> Vec<ApolloDiagnostic> {
     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(def.location().unwrap().file_id());
+    let named_fragments = db.ast_named_fragments(file_id);
 
     fn detect_fragment_cycles(
         named_fragments: &HashMap<ast::Name, Node<ast::FragmentDefinition>>,
@@ -350,7 +363,7 @@ pub fn validate_fragment_cycles(
     diagnostics
 }
 
-pub fn validate_fragment_type_condition(
+pub(crate) fn validate_fragment_type_condition(
     db: &dyn ValidationDatabase,
     type_cond: &ast::NamedType,
     fragment_location: NodeLocation,
@@ -396,7 +409,7 @@ pub fn validate_fragment_type_condition(
     diagnostics
 }
 
-pub fn validate_fragment_used(
+pub(crate) fn validate_fragment_used(
     db: &dyn ValidationDatabase,
     fragment: Node<ast::FragmentDefinition>,
     file_id: FileId,
diff --git a/crates/apollo-compiler/src/validation/input_object.rs b/crates/apollo-compiler/src/validation/input_object.rs
index 8fd817f..28581f1 100644
--- a/crates/apollo-compiler/src/validation/input_object.rs
+++ b/crates/apollo-compiler/src/validation/input_object.rs
@@ -61,7 +61,9 @@ impl FindRecursiveInputValue<'_> {
     }
 }
 
-pub fn validate_input_object_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub(crate) fn validate_input_object_definitions(
+    db: &dyn ValidationDatabase,
+) -> Vec<ApolloDiagnostic> {
     let mut diagnostics = Vec::new();
 
     for input_object in db.ast_types().input_objects.values() {
@@ -71,7 +73,7 @@ pub fn validate_input_object_definitions(db: &dyn ValidationDatabase) -> Vec<Apo
     diagnostics
 }
 
-pub fn validate_input_object_definition(
+pub(crate) fn validate_input_object_definition(
     db: &dyn ValidationDatabase,
     input_object: ast::TypeWithExtensions<ast::InputObjectTypeDefinition>,
 ) -> Vec<ApolloDiagnostic> {
@@ -116,7 +118,7 @@ pub fn validate_input_object_definition(
     diagnostics
 }
 
-pub fn validate_argument_definitions(
+pub(crate) fn validate_argument_definitions(
     db: &dyn ValidationDatabase,
     input_values: &[Node<ast::InputValueDefinition>],
     directive_location: ast::DirectiveLocation,
@@ -160,7 +162,7 @@ pub fn validate_argument_definitions(
     diagnostics
 }
 
-pub fn validate_input_value_definitions(
+pub(crate) fn validate_input_value_definitions(
     db: &dyn ValidationDatabase,
     input_values: &[Node<ast::InputValueDefinition>],
     directive_location: ast::DirectiveLocation,
diff --git a/crates/apollo-compiler/src/validation/interface.rs b/crates/apollo-compiler/src/validation/interface.rs
index 912f952..844067a 100644
--- a/crates/apollo-compiler/src/validation/interface.rs
+++ b/crates/apollo-compiler/src/validation/interface.rs
@@ -5,7 +5,7 @@ use crate::{
 };
 use std::collections::HashSet;
 
-pub fn validate_interface_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub(crate) fn validate_interface_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     let mut diagnostics = Vec::new();
 
     for interface in db.ast_types().interfaces.values() {
@@ -15,7 +15,7 @@ pub fn validate_interface_definitions(db: &dyn ValidationDatabase) -> Vec<Apollo
     diagnostics
 }
 
-pub fn validate_interface_definition(
+pub(crate) fn validate_interface_definition(
     db: &dyn ValidationDatabase,
     interface: ast::TypeWithExtensions<ast::InterfaceTypeDefinition>,
 ) -> Vec<ApolloDiagnostic> {
@@ -128,7 +128,7 @@ pub fn validate_interface_definition(
     diagnostics
 }
 
-pub fn validate_implements_interfaces(
+pub(crate) fn validate_implements_interfaces(
     db: &dyn ValidationDatabase,
     implementor: &ast::Definition,
     implements_interfaces: &[ast::Name],
diff --git a/crates/apollo-compiler/src/validation/mod.rs b/crates/apollo-compiler/src/validation/mod.rs
index 529c168..60e4c08 100644
--- a/crates/apollo-compiler/src/validation/mod.rs
+++ b/crates/apollo-compiler/src/validation/mod.rs
@@ -18,7 +18,6 @@ mod variable;
 
 use crate::executable::BuildError as ExecutableBuildError;
 use crate::schema::BuildError as SchemaBuildError;
-use crate::Arc;
 use crate::FileId;
 use crate::NodeLocation;
 use crate::NodeStr;
@@ -30,7 +29,8 @@ use std::collections::hash_map::Entry;
 use std::collections::HashMap;
 use std::fmt;
 use std::io;
-pub use validation_db::{ValidationDatabase, ValidationStorage};
+use std::sync::Arc;
+pub(crate) use validation_db::{ValidationDatabase, ValidationStorage};
 
 pub struct Diagnostics(Box<DiagnosticsBoxed>);
 
@@ -263,6 +263,14 @@ impl Error {
 }
 
 impl Diagnostics {
+    /// Returns a human-readable string formatting, without color codes regardless of stderr.
+    ///
+    /// `Display` and `.to_string()` are meant for printing to stderr,
+    /// and will include ANSI color codes if stderr is detected to be a terminal.
+    pub fn to_string_no_color(&self) -> String {
+        format!("{self:#}")
+    }
+
     pub(crate) fn new(sources: IndexMap<FileId, Arc<SourceFile>>) -> Self {
         Self(Box::new(DiagnosticsBoxed {
             errors: Vec::new(),
@@ -409,7 +417,7 @@ impl RecursionStack {
     }
 
     /// Return the actual API for tracking recursive uses.
-    pub fn guard(&mut self) -> RecursionGuard<'_> {
+    pub(crate) fn guard(&mut self) -> RecursionGuard<'_> {
         RecursionGuard(&mut self.seen)
     }
 }
diff --git a/crates/apollo-compiler/src/validation/object.rs b/crates/apollo-compiler/src/validation/object.rs
index a237c23..8dd0114 100644
--- a/crates/apollo-compiler/src/validation/object.rs
+++ b/crates/apollo-compiler/src/validation/object.rs
@@ -5,7 +5,9 @@ use crate::{
 };
 use std::collections::HashSet;
 
-pub fn validate_object_type_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub(crate) fn validate_object_type_definitions(
+    db: &dyn ValidationDatabase,
+) -> Vec<ApolloDiagnostic> {
     let mut diagnostics = vec![];
 
     let defs = &db.ast_types().objects;
@@ -16,7 +18,7 @@ pub fn validate_object_type_definitions(db: &dyn ValidationDatabase) -> Vec<Apol
     diagnostics
 }
 
-pub fn validate_object_type_definition(
+pub(crate) fn validate_object_type_definition(
     db: &dyn ValidationDatabase,
     object: ast::TypeWithExtensions<ast::ObjectTypeDefinition>,
 ) -> Vec<ApolloDiagnostic> {
diff --git a/crates/apollo-compiler/src/validation/operation.rs b/crates/apollo-compiler/src/validation/operation.rs
index 1978345..262442b 100644
--- a/crates/apollo-compiler/src/validation/operation.rs
+++ b/crates/apollo-compiler/src/validation/operation.rs
@@ -14,6 +14,7 @@ pub struct OperationValidationConfig<'vars> {
 
 pub(crate) fn validate_operation(
     db: &dyn ValidationDatabase,
+    file_id: FileId,
     operation: Node<ast::OperationDefinition>,
     has_schema: bool,
 ) -> Vec<ApolloDiagnostic> {
@@ -27,15 +28,13 @@ pub(crate) fn validate_operation(
     let schema = db.schema();
     let against_type = schema.root_operation(operation.operation_type);
 
-    let named_fragments = db.ast_named_fragments(operation.location().unwrap().file_id());
+    let named_fragments = db.ast_named_fragments(file_id);
     let q = ast::NamedType::new("Query");
 
     if operation.operation_type == ast::OperationType::Subscription {
         let fields = super::selection::operation_fields(
             &named_fragments,
-            against_type
-                .map(|component| &component.node)
-                .unwrap_or_else(|| &q),
+            against_type.unwrap_or(&q),
             &operation.selection_set,
         );
 
@@ -105,11 +104,13 @@ pub(crate) fn validate_operation(
 
     diagnostics.extend(super::variable::validate_unused_variables(
         db,
+        file_id,
         operation.clone(),
     ));
     diagnostics.extend(super::selection::validate_selection_set2(
         db,
-        against_type.map(|component| &component.node),
+        file_id,
+        against_type,
         &operation.selection_set,
         config,
     ));
@@ -127,14 +128,19 @@ pub(crate) fn validate_operation_definitions_inner(
 
     for definition in &document.definitions {
         if let ast::Definition::OperationDefinition(operation) = definition {
-            diagnostics.extend(validate_operation(db, operation.clone(), has_schema));
+            diagnostics.extend(validate_operation(
+                db,
+                file_id,
+                operation.clone(),
+                has_schema,
+            ));
         }
     }
 
     diagnostics
 }
 
-pub fn validate_operation_definitions(
+pub(crate) fn validate_operation_definitions(
     db: &dyn ValidationDatabase,
     file_id: FileId,
 ) -> Vec<ApolloDiagnostic> {
diff --git a/crates/apollo-compiler/src/validation/scalar.rs b/crates/apollo-compiler/src/validation/scalar.rs
index 05efe89..dd59b58 100644
--- a/crates/apollo-compiler/src/validation/scalar.rs
+++ b/crates/apollo-compiler/src/validation/scalar.rs
@@ -4,7 +4,7 @@ use crate::{
     schema, Node, ValidationDatabase,
 };
 
-pub fn validate_scalar_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub(crate) fn validate_scalar_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     let mut diagnostics = Vec::new();
 
     let schema = db.schema();
@@ -17,7 +17,7 @@ pub fn validate_scalar_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDia
     diagnostics
 }
 
-pub fn validate_scalar_definition(
+pub(crate) fn validate_scalar_definition(
     db: &dyn ValidationDatabase,
     scalar_def: Node<schema::ScalarType>,
 ) -> Vec<ApolloDiagnostic> {
diff --git a/crates/apollo-compiler/src/validation/schema.rs b/crates/apollo-compiler/src/validation/schema.rs
index 0503caf..545ec90 100644
--- a/crates/apollo-compiler/src/validation/schema.rs
+++ b/crates/apollo-compiler/src/validation/schema.rs
@@ -4,7 +4,7 @@ use crate::{
     schema, ApolloDiagnostic, Node, ValidationDatabase,
 };
 
-pub fn validate_schema_definition(
+pub(crate) fn validate_schema_definition(
     db: &dyn ValidationDatabase,
     schema_definition: ast::TypeWithExtensions<ast::SchemaDefinition>,
 ) -> Vec<ApolloDiagnostic> {
@@ -40,7 +40,7 @@ pub fn validate_schema_definition(
 // All root operations in a schema definition must be unique.
 //
 // Return a Unique Operation Definition error in case of a duplicate name.
-pub fn validate_root_operation_definitions(
+pub(crate) fn validate_root_operation_definitions(
     db: &dyn ValidationDatabase,
     root_op_defs: &[Node<(ast::OperationType, ast::NamedType)>],
 ) -> Vec<ApolloDiagnostic> {
diff --git a/crates/apollo-compiler/src/validation/selection.rs b/crates/apollo-compiler/src/validation/selection.rs
index 718ae33..c155fc9 100644
--- a/crates/apollo-compiler/src/validation/selection.rs
+++ b/crates/apollo-compiler/src/validation/selection.rs
@@ -5,7 +5,7 @@ use crate::{
     diagnostics::{ApolloDiagnostic, DiagnosticData, Label},
     schema,
     validation::ValidationDatabase,
-    Node,
+    FileId, Node,
 };
 
 use super::operation::OperationValidationConfig;
@@ -84,6 +84,7 @@ pub(crate) fn operation_fields<'a>(
 /// Spec: https://spec.graphql.org/October2021/#SameResponseShape()
 pub(crate) fn same_response_shape(
     db: &dyn ValidationDatabase,
+    file_id: FileId,
     field_a: FieldAgainstType<'_>,
     field_b: FieldAgainstType<'_>,
 ) -> Result<(), ApolloDiagnostic> {
@@ -179,8 +180,7 @@ pub(crate) fn same_response_shape(
         }
         // 6. Assert: typeA and typeB are both composite types.
         (def_a, def_b) if is_composite(def_a) && is_composite(def_b) => {
-            let named_fragments =
-                db.ast_named_fragments(field_a.field.location().unwrap().file_id());
+            let named_fragments = db.ast_named_fragments(file_id);
             let mut merged_set = operation_fields(
                 &named_fragments,
                 field_a.against_type,
@@ -200,7 +200,7 @@ pub(crate) fn same_response_shape(
                 };
                 for subfield_b in rest {
                     // 9a. If SameResponseShape(subfieldA, subfieldB) is false, return false.
-                    same_response_shape(db, *subfield_a, *subfield_b)?;
+                    same_response_shape(db, file_id, *subfield_a, *subfield_b)?;
                 }
             }
 
@@ -306,6 +306,7 @@ fn identical_arguments(
 /// Spec: https://spec.graphql.org/October2021/#FieldsInSetCanMerge()
 pub(crate) fn fields_in_set_can_merge(
     db: &dyn ValidationDatabase,
+    file_id: FileId,
     named_fragments: &HashMap<ast::Name, Node<ast::FragmentDefinition>>,
     against_type: &ast::NamedType,
     selection_set: &[ast::Selection],
@@ -329,7 +330,7 @@ pub(crate) fn fields_in_set_can_merge(
         // 2. Given each pair of members fieldA and fieldB in fieldsForName:
         for field_b in rest {
             // 2a. SameResponseShape(fieldA, fieldB) must be true.
-            if let Err(diagnostic) = same_response_shape(db, *field_a, *field_b) {
+            if let Err(diagnostic) = same_response_shape(db, file_id, *field_a, *field_b) {
                 diagnostics.push(diagnostic);
                 continue;
             }
@@ -378,6 +379,7 @@ pub(crate) fn fields_in_set_can_merge(
                 // 2biv. FieldsInSetCanMerge(mergedSet) must be true.
                 if let Err(sub_diagnostics) = fields_in_set_can_merge(
                     db,
+                    file_id,
                     named_fragments,
                     parent_a.ty.inner_named_type(),
                     &merged_set,
@@ -396,25 +398,23 @@ pub(crate) fn fields_in_set_can_merge(
     }
 }
 
-pub fn validate_selection_set2(
+pub(crate) fn validate_selection_set2(
     db: &dyn ValidationDatabase,
+    file_id: FileId,
     against_type: Option<&ast::NamedType>,
     selection_set: &[ast::Selection],
     context: OperationValidationConfig<'_>,
 ) -> Vec<ApolloDiagnostic> {
     let mut diagnostics = vec![];
 
-    // TODO(@goto-bus-stop) named fragments for synthetic ASTs
-    let named_fragments = selection_set
-        .get(0)
-        .and_then(|field| Some(db.ast_named_fragments(field.location()?.file_id())));
+    let named_fragments = Some(db.ast_named_fragments(file_id));
     // `named_fragments` will be None if we have 0 fields, where this validation is irrelevant
     // anyways.
     // If `against_type` is None, we don't know the actual type of anything, so we cannot run this
     // validation.
     if let (Some(named_fragments), Some(against_type)) = (named_fragments, against_type) {
         if let Err(diagnostic) =
-            fields_in_set_can_merge(db, &named_fragments, against_type, selection_set)
+            fields_in_set_can_merge(db, file_id, &named_fragments, against_type, selection_set)
         {
             diagnostics.extend(diagnostic);
         }
@@ -422,6 +422,7 @@ pub fn validate_selection_set2(
 
     diagnostics.extend(validate_selections(
         db,
+        file_id,
         against_type,
         selection_set,
         context,
@@ -430,8 +431,9 @@ pub fn validate_selection_set2(
     diagnostics
 }
 
-pub fn validate_selections(
+pub(crate) fn validate_selections(
     db: &dyn ValidationDatabase,
+    file_id: FileId,
     against_type: Option<&ast::NamedType>,
     selection_set: &[ast::Selection],
     context: OperationValidationConfig<'_>,
@@ -442,6 +444,7 @@ pub fn validate_selections(
         match selection {
             ast::Selection::Field(field) => diagnostics.extend(super::field::validate_field(
                 db,
+                file_id,
                 against_type,
                 field.clone(),
                 context.clone(),
@@ -449,6 +452,7 @@ pub fn validate_selections(
             ast::Selection::FragmentSpread(fragment) => {
                 diagnostics.extend(super::fragment::validate_fragment_spread(
                     db,
+                    file_id,
                     against_type,
                     fragment.clone(),
                     context.clone(),
@@ -457,6 +461,7 @@ pub fn validate_selections(
             ast::Selection::InlineFragment(inline) => {
                 diagnostics.extend(super::fragment::validate_inline_fragment(
                     db,
+                    file_id,
                     against_type,
                     inline.clone(),
                     context.clone(),
diff --git a/crates/apollo-compiler/src/validation/union_.rs b/crates/apollo-compiler/src/validation/union_.rs
index 191636e..df488c3 100644
--- a/crates/apollo-compiler/src/validation/union_.rs
+++ b/crates/apollo-compiler/src/validation/union_.rs
@@ -4,7 +4,7 @@ use crate::{
     schema, ValidationDatabase,
 };
 
-pub fn validate_union_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub(crate) fn validate_union_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     let mut diagnostics = Vec::new();
 
     for def in db.ast_types().unions.values() {
@@ -14,7 +14,7 @@ pub fn validate_union_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiag
     diagnostics
 }
 
-pub fn validate_union_definition(
+pub(crate) fn validate_union_definition(
     db: &dyn ValidationDatabase,
     union_def: ast::TypeWithExtensions<ast::UnionTypeDefinition>,
 ) -> Vec<ApolloDiagnostic> {
diff --git a/crates/apollo-compiler/src/validation/validation_db.rs b/crates/apollo-compiler/src/validation/validation_db.rs
index 07c6c75..d4220cf 100644
--- a/crates/apollo-compiler/src/validation/validation_db.rs
+++ b/crates/apollo-compiler/src/validation/validation_db.rs
@@ -5,14 +5,15 @@ use crate::{
     validation::{
         self, directive, enum_, input_object, interface, object, operation, scalar, union_,
     },
-    Arc, FileId, InputDatabase, Node, ReprDatabase,
+    FileId, InputDatabase, Node, ReprDatabase,
 };
 use std::collections::HashMap;
+use std::sync::Arc;
 
 use super::field;
 
 #[salsa::query_group(ValidationStorage)]
-pub trait ValidationDatabase: InputDatabase + ReprDatabase {
+pub(crate) trait ValidationDatabase: InputDatabase + ReprDatabase {
     fn ast_types(&self) -> Arc<ast::TypeSystem>;
     fn ast_named_fragments(
         &self,
@@ -274,7 +275,7 @@ fn ast_types(db: &dyn ValidationDatabase) -> Arc<ast::TypeSystem> {
     })
 }
 
-pub fn ast_named_fragments(
+pub(crate) fn ast_named_fragments(
     db: &dyn ValidationDatabase,
     file_id: FileId,
 ) -> Arc<HashMap<ast::Name, Node<ast::FragmentDefinition>>> {
@@ -290,7 +291,7 @@ pub fn ast_named_fragments(
     Arc::new(named_fragments)
 }
 
-pub fn validate(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub(crate) fn validate(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     let mut diagnostics = Vec::new();
 
     diagnostics.extend(db.validate_type_system());
@@ -306,7 +307,7 @@ fn location_sort_key(diagnostic: &ApolloDiagnostic) -> (FileId, usize) {
     (diagnostic.location.file_id(), diagnostic.location.offset())
 }
 
-pub fn validate_type_system(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub(crate) fn validate_type_system(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     let mut diagnostics = Vec::new();
 
     let schema = db.ast_types().schema.clone();
@@ -347,13 +348,16 @@ fn validate_executable_inner(
     diagnostics
 }
 
-pub fn validate_standalone_executable(
+pub(crate) fn validate_standalone_executable(
     db: &dyn ValidationDatabase,
     file_id: FileId,
 ) -> Vec<ApolloDiagnostic> {
     validate_executable_inner(db, file_id, false)
 }
 
-pub fn validate_executable(db: &dyn ValidationDatabase, file_id: FileId) -> Vec<ApolloDiagnostic> {
+pub(crate) fn validate_executable(
+    db: &dyn ValidationDatabase,
+    file_id: FileId,
+) -> Vec<ApolloDiagnostic> {
     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 1998420..64d1590 100644
--- a/crates/apollo-compiler/src/validation/value.rs
+++ b/crates/apollo-compiler/src/validation/value.rs
@@ -41,7 +41,7 @@ fn unsupported_type(
 //for bigint
 /*
 */
-pub fn validate_values2(
+pub(crate) fn validate_values2(
     db: &dyn ValidationDatabase,
     ty: &Node<ast::Type>,
     argument: &Node<ast::Argument>,
@@ -52,7 +52,7 @@ pub fn validate_values2(
     diagnostics
 }
 
-pub fn value_of_correct_type2(
+pub(crate) fn value_of_correct_type2(
     db: &dyn ValidationDatabase,
     ty: &Node<ast::Type>,
     arg_value: &Node<ast::Value>,
diff --git a/crates/apollo-compiler/src/validation/variable.rs b/crates/apollo-compiler/src/validation/variable.rs
index ae34041..e38a395 100644
--- a/crates/apollo-compiler/src/validation/variable.rs
+++ b/crates/apollo-compiler/src/validation/variable.rs
@@ -1,12 +1,12 @@
 use crate::{
     ast,
     diagnostics::{ApolloDiagnostic, DiagnosticData, Label},
-    schema, Node, NodeLocation, ValidationDatabase,
+    schema, FileId, Node, NodeLocation, ValidationDatabase,
 };
 use std::collections::hash_map::Entry;
 use std::collections::{HashMap, HashSet};
 
-pub fn validate_variable_definitions2(
+pub(crate) fn validate_variable_definitions2(
     db: &dyn ValidationDatabase,
     variables: &[Node<ast::VariableDefinition>],
     has_schema: bool,
@@ -215,8 +215,9 @@ fn variables_in_directives(
 //   a: field (arg: $var1)
 //   a: field (arg: $var2)
 // }
-pub fn validate_unused_variables(
+pub(crate) fn validate_unused_variables(
     db: &dyn ValidationDatabase,
+    file_id: FileId,
     operation: Node<ast::OperationDefinition>,
 ) -> Vec<ApolloDiagnostic> {
     let defined_vars: HashSet<_> = operation
@@ -236,7 +237,7 @@ pub fn validate_unused_variables(
         .collect();
     let mut used_vars = HashSet::<ast::Name>::new();
     walk_selections(
-        &db.ast(operation.location().unwrap().file_id()),
+        &db.ast(file_id),
         &operation.selection_set,
         |selection| match selection {
             ast::Selection::Field(field) => {
@@ -271,7 +272,7 @@ pub fn validate_unused_variables(
     diagnostics
 }
 
-pub fn validate_variable_usage2(
+pub(crate) fn validate_variable_usage2(
     db: &dyn ValidationDatabase,
     var_usage: Node<ast::InputValueDefinition>,
     var_defs: &[Node<ast::VariableDefinition>],
diff --git a/crates/apollo-encoder/README.md b/crates/apollo-encoder/README.md
index 1131aec..c84d1ef 100644
--- a/crates/apollo-encoder/README.md
+++ b/crates/apollo-encoder/README.md
@@ -163,8 +163,8 @@ assert_eq!(
 
 Licensed under either of
 
-- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
-- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
+- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
 
 at your option.
 
diff --git a/crates/apollo-parser/README.md b/crates/apollo-parser/README.md
index 2f004b0..70940a7 100644
--- a/crates/apollo-parser/README.md
+++ b/crates/apollo-parser/README.md
@@ -156,8 +156,8 @@ let input = "
 ## License
 Licensed under either of
 
-- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
-- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
+- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
 
 at your option.
 
diff --git a/crates/apollo-smith/README.md b/crates/apollo-smith/README.md
index 8d80396..ecabb12 100644
--- a/crates/apollo-smith/README.md
+++ b/crates/apollo-smith/README.md
@@ -140,8 +140,8 @@ apollo-smith = { version = "0.3.1", features = ["parser-impl"] }
 ## License
 Licensed under either of
 
-- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
-- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
+- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
 
 at your option.
 


Test patch:
diff --git a/crates/apollo-compiler/test_data/diagnostics/0089_fragment_type_condition_on_non_existent_types.txt b/crates/apollo-compiler/test_data/diagnostics/0089_fragment_type_condition_on_non_existent_types.txt
index 040ba69..1e67f6d 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0089_fragment_type_condition_on_non_existent_types.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0089_fragment_type_condition_on_non_existent_types.txt
@@ -1,3 +1,10 @@
+Error: cannot find fragment `invalidFragmentCondition` in this document
+   ╭─[0089_fragment_type_condition_on_non_existent_types.graphql:3:5]
+   │
+ 3 │     ...invalidFragmentCondition
+   │     ─────────────┬─────────────  
+   │                  ╰─────────────── fragment `invalidFragmentCondition` is not defined
+───╯
 Error: type condition `MissingSecondType` of inline fragment is not a type defined in the schema
    ╭─[0089_fragment_type_condition_on_non_existent_types.graphql:5:12]
    │
diff --git a/crates/apollo-compiler/tests/executable.rs b/crates/apollo-compiler/tests/executable.rs
index 2a8caef..ee70e43 100644
--- a/crates/apollo-compiler/tests/executable.rs
+++ b/crates/apollo-compiler/tests/executable.rs
@@ -1,5 +1,6 @@
-use apollo_compiler::ApolloCompiler;
-use apollo_compiler::ReprDatabase;
+use apollo_compiler::parse_mixed;
+use apollo_compiler::ExecutableDocument;
+use apollo_compiler::Schema;
 
 #[test]
 fn get_operations() {
@@ -13,24 +14,19 @@ fn get_operations() {
     let several_named_op = r#"query getName { name } query getAnotherName { name }"#;
     let noop = r#""#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(type_system, "ts.graphql");
-    let id = compiler.add_executable(op, "op.graphql");
-    let doc = compiler.db.executable_document(id);
+    let schema = Schema::parse(type_system, "ts.graphql");
+    let doc = ExecutableDocument::parse(&schema, op, "op.graphql");
     assert!(doc.get_operation(None).is_ok());
 
-    compiler.update_executable(id, named_op);
-    let doc = compiler.db.executable_document(id);
+    let doc = ExecutableDocument::parse(&schema, named_op, "op.graphql");
     assert!(doc.get_operation(Some("getName")).is_ok());
     assert!(doc.get_operation(None).is_ok());
 
-    compiler.update_executable(id, several_named_op);
-    let doc = compiler.db.executable_document(id);
+    let doc = ExecutableDocument::parse(&schema, several_named_op, "op.graphql");
     assert!(doc.get_operation(Some("getName")).is_ok());
     assert!(doc.get_operation(None).is_err());
 
-    compiler.update_executable(id, noop);
-    let doc = compiler.db.executable_document(id);
+    let doc = ExecutableDocument::parse(&schema, noop, "op.graphql");
     assert!(doc.get_operation(Some("getName")).is_err());
     assert!(doc.get_operation(None).is_err());
 }
@@ -58,10 +54,7 @@ fn is_introspection_operation() {
           }
         }
     "#;
-
-    let mut compiler = ApolloCompiler::new();
-    let id = compiler.add_document(query_input, "query.graphql");
-    let doc = compiler.db.executable_document(id);
+    let (_, doc) = parse_mixed(query_input, "query.graphql");
     assert!(doc.named_operations["TypeIntrospect"].is_introspection(&doc));
 }
 
@@ -94,12 +87,8 @@ fn is_not_introspection_operation() {
         }
     "#;
 
-    let mut compiler = ApolloCompiler::new();
-    let query_id = compiler.add_document(query_input, "query.graphql");
-    let mutation_id = compiler.add_document(mutation_input, "mutation.graphql");
-
-    let query_doc = compiler.db.executable_document(query_id);
-    let mutation_doc = compiler.db.executable_document(mutation_id);
+    let (_, query_doc) = parse_mixed(query_input, "query.graphql");
+    let (_, mutation_doc) = parse_mixed(mutation_input, "mutation.graphql");
 
     assert!(!query_doc.named_operations["CheckStock"].is_introspection(&query_doc));
     assert!(!mutation_doc.named_operations["PurchaseBasket"].is_introspection(&mutation_doc));
@@ -176,13 +165,8 @@ fn is_introspection_deep() {
 
     let query_input_not_introspect = query_input.replace("...onRooten2", "...onRooten2_not_intro");
 
-    let mut compiler = ApolloCompiler::new();
-    let query_id = compiler.add_document(query_input, "query.graphql");
-    let query_not_introspect_id =
-        compiler.add_document(&query_input_not_introspect, "query2.graphql");
-
-    let query_doc = compiler.db.executable_document(query_id);
-    let query_not_introspect_doc = compiler.db.executable_document(query_not_introspect_id);
+    let (_, query_doc) = parse_mixed(query_input, "query.graphql");
+    let (_, query_not_introspect_doc) = parse_mixed(query_input_not_introspect, "query2.graphql");
 
     assert!(query_doc.named_operations["IntrospectDeepFragments"].is_introspection(&query_doc));
     assert!(
@@ -230,12 +214,8 @@ fn is_introspection_repeated_fragment() {
       }
     "#;
 
-    let mut compiler = ApolloCompiler::new();
-    let query_id_indirect = compiler.add_document(query_input_indirect, "indirect.graphql");
-    let query_id_direct = compiler.add_document(query_input_direct, "direct.graphql");
-
-    let query_doc_indirect = compiler.db.executable_document(query_id_indirect);
-    let query_doc_direct = compiler.db.executable_document(query_id_direct);
+    let (_, query_doc_indirect) = parse_mixed(query_input_indirect, "indirect.graphql");
+    let (_, query_doc_direct) = parse_mixed(query_input_direct, "direct.graphql");
 
     assert!(
         query_doc_indirect.named_operations["IntrospectRepeatedIndirectFragment"]
diff --git a/crates/apollo-compiler/tests/merge_schemas.rs b/crates/apollo-compiler/tests/merge_schemas.rs
index 8f36867..c19bc2b 100644
--- a/crates/apollo-compiler/tests/merge_schemas.rs
+++ b/crates/apollo-compiler/tests/merge_schemas.rs
@@ -3,8 +3,6 @@ use apollo_compiler::schema;
 use apollo_compiler::schema::Component;
 use apollo_compiler::schema::ComponentStr;
 use apollo_compiler::schema::ExtendedType;
-use apollo_compiler::ApolloCompiler;
-use apollo_compiler::ReprDatabase;
 use apollo_compiler::Schema;
 use indexmap::IndexMap;
 use indexmap::IndexSet;
@@ -13,20 +11,12 @@ type MergeError = &'static str;
 
 /// Naively merge multiple schemas into one
 fn merge_schemas(inputs: &[&str]) -> Result<String, MergeError> {
-    let mut compiler = ApolloCompiler::new();
-    let id = compiler.add_document("", "");
-    let schemas: Vec<_> = inputs
-        .iter()
-        .map(|input| {
-            compiler.update_document(id, input);
-            compiler.db.schema()
-        })
-        .collect();
     let mut merged = Schema::new();
-    for schema in schemas {
+    for &input in inputs {
+        let schema = Schema::parse(input, "schema.graphql");
         merge_options_or(
-            &mut merged.root_operations,
-            &schema.root_operations,
+            &mut merged.schema_definition,
+            &schema.schema_definition,
             |merged, new| {
                 let merged = merged.make_mut();
                 merge_options(&mut merged.description, &new.description)?;
diff --git a/crates/apollo-compiler/tests/misc.rs b/crates/apollo-compiler/tests/misc.rs
index 3869c18..77f70b2 100644
--- a/crates/apollo-compiler/tests/misc.rs
+++ b/crates/apollo-compiler/tests/misc.rs
@@ -1,8 +1,8 @@
 use apollo_compiler::executable::Selection;
+use apollo_compiler::parse_mixed;
 use apollo_compiler::schema::ExtendedType;
-use apollo_compiler::ApolloCompiler;
-use apollo_compiler::Arc;
-use apollo_compiler::ReprDatabase;
+use apollo_compiler::ExecutableDocument;
+use apollo_compiler::Schema;
 use std::collections::HashMap;
 
 #[test]
@@ -25,9 +25,8 @@ query ExampleQuery {
 }
       "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(schema, "schema.graphql");
-    compiler.add_executable(query, "query.graphql");
+    let schema = Schema::parse(schema, "schema.graphql");
+    let _doc = ExecutableDocument::parse(&schema, query, "query.graphql");
 }
 
 #[test]
@@ -65,16 +64,10 @@ type User {
     scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
     "#;
 
-    let mut compiler = ApolloCompiler::new();
-    let document_id = compiler.add_document(input, "document.graphql");
+    let (schema, doc) = parse_mixed(input, "document.graphql");
+    schema.validate().unwrap();
+    doc.validate(&schema).unwrap();
 
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
-
-    let doc = compiler.db.executable_document(document_id);
     let operation_names: Vec<_> = doc.named_operations.keys().map(|n| n.as_str()).collect();
     assert_eq!(["ExampleQuery"], operation_names.as_slice());
 
@@ -112,16 +105,10 @@ type Query {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    let document_id = compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
+    let (schema, doc) = parse_mixed(input, "document.graphql");
+    schema.validate().unwrap();
+    doc.validate(&schema).unwrap();
 
-    let doc = compiler.db.executable_document(document_id);
     let op = doc.get_operation(Some("ExampleQuery")).unwrap();
     let field_names: Vec<&str> = op.selection_set.fields().map(|f| f.name.as_str()).collect();
     assert_eq!(
@@ -167,13 +154,10 @@ type Concrete implements Interface {
 union Union = Concrete
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    let document_id = compiler.add_document(input, "document.graphql");
+    let (schema, doc) = parse_mixed(input, "document.graphql");
+    schema.validate().unwrap();
+    doc.validate(&schema).unwrap();
 
-    let diagnostics = compiler.validate();
-    assert!(diagnostics.is_empty());
-
-    let doc = compiler.db.executable_document(document_id);
     let op = doc.get_operation(Some("ExampleQuery")).unwrap();
     let fields: Vec<_> = op.selection_set.fields().collect();
 
@@ -264,16 +248,10 @@ enum join__Graph {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    let document_id = compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
+    let (schema, doc) = parse_mixed(input, "document.graphql");
+    schema.validate().unwrap();
+    doc.validate(&schema).unwrap();
 
-    let doc = compiler.db.executable_document(document_id);
     // Get the types of the two top level fields - topProducts and size
     let get_product_op = doc.get_operation(Some("getProduct")).unwrap();
     let op_fields: Vec<_> = get_product_op.selection_set.fields().collect();
@@ -339,13 +317,17 @@ scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
     let customer_query = r#"{ customer { id } }"#;
     let colliding_query = r#"query getProduct { topProducts { type, price } }"#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(schema, "schema.graphql");
-    compiler.add_executable(product_query, "query.graphql");
-    compiler.add_executable(customer_query, "query.graphql");
-    compiler.add_executable(colliding_query, "query.graphql");
-
-    assert_eq!(compiler.validate(), &[]);
+    let schema = Schema::parse(schema, "schema.graphql");
+    schema.validate().unwrap();
+    ExecutableDocument::parse(&schema, product_query, "query.graphql")
+        .validate(&schema)
+        .unwrap();
+    ExecutableDocument::parse(&schema, customer_query, "query.graphql")
+        .validate(&schema)
+        .unwrap();
+    ExecutableDocument::parse(&schema, colliding_query, "query.graphql")
+        .validate(&schema)
+        .unwrap();
 }
 
 #[test]
@@ -386,17 +368,12 @@ fragment vipCustomer on User {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(schema, "schema.graphql");
-    let query_id = compiler.add_executable(query, "query.graphql");
+    let schema = Schema::parse(schema, "schema.graphql");
+    let doc = ExecutableDocument::parse(&schema, query, "query.graphql");
 
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
+    schema.validate().unwrap();
+    doc.validate(&schema).unwrap();
 
-    let doc = compiler.db.executable_document(query_id);
     let op = doc.get_operation(Some("getProduct")).unwrap();
     let fragment_in_op: Vec<_> = op
         .selection_set
@@ -446,14 +423,9 @@ type Result {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
+    let (schema, doc) = parse_mixed(input, "document.graphql");
+    schema.validate().unwrap();
+    doc.validate(&schema).unwrap();
 }
 
 #[test]
@@ -467,16 +439,8 @@ type Query {
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
-
-    let schema = compiler.db.schema();
+    let schema = Schema::parse(input, "document.graphql");
+    schema.validate().unwrap();
 
     let directives: Vec<_> = schema
         .get_scalar("URL")
@@ -502,16 +466,9 @@ enum Pet {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
+    let schema = Schema::parse(input, "document.graphql");
+    schema.validate().unwrap();
 
-    let schema = compiler.db.schema();
     let enum_values: Vec<_> = schema.get_enum("Pet").unwrap().values.keys().collect();
     assert_eq!(enum_values, ["CAT", "DOG", "FOX"]);
 }
@@ -540,16 +497,9 @@ type SearchQuery {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
+    let schema = Schema::parse(input, "document.graphql");
+    schema.validate().unwrap();
 
-    let schema = compiler.db.schema();
     let union_type = schema.get_union("SearchResult").unwrap();
     let union_members: Vec<_> = union_type.members.iter().collect();
     assert_eq!(union_members, ["Photo", "Person"]);
@@ -574,16 +524,9 @@ type Book @delegateField(name: "pageCount") @delegateField(name: "author") {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
+    let schema = Schema::parse(input, "document.graphql");
+    schema.validate().unwrap();
 
-    let schema = compiler.db.schema();
     let locations: Vec<_> = schema.directive_definitions["delegateField"]
         .locations
         .iter()
@@ -609,16 +552,9 @@ input Point2D {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
+    let schema = Schema::parse(input, "document.graphql");
+    schema.validate().unwrap();
 
-    let schema = compiler.db.schema();
     let fields: Vec<_> = schema
         .get_input_object("Point2D")
         .unwrap()
@@ -641,16 +577,9 @@ directive @directiveA(name: String) on OBJECT | INTERFACE
 directive @directiveB(name: String) on OBJECT | INTERFACE
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
+    let schema = Schema::parse(input, "document.graphql");
+    schema.validate().unwrap();
 
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
-
-    let schema = compiler.db.schema();
     let book_obj = schema.get_object("Book").unwrap();
 
     let directive_names: Vec<_> = book_obj.directives.iter().map(|d| &d.name).collect();
@@ -673,16 +602,9 @@ enum Number {
 scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
+    let schema = Schema::parse(input, "document.graphql");
+    schema.validate().unwrap();
 
-    let schema = compiler.db.schema();
     let person = schema.get_object("Person").unwrap();
 
     let field_ty_directive: Vec<_> = person
@@ -726,16 +648,9 @@ input Person {
 scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
+    let schema = Schema::parse(input, "document.graphql");
+    schema.validate().unwrap();
 
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    assert!(diagnostics.is_empty());
-
-    let schema = compiler.db.schema();
     let person = schema.get_input_object("Person").unwrap();
 
     let field_ty_directive: Vec<_> = person
@@ -834,17 +749,16 @@ type User
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-    // the scalar warning diagnostic
-    assert_eq!(diagnostics.len(), 1);
+    let schema = Schema::parse(input, "document.graphql");
+    let warnings = schema.validate().unwrap().to_string_no_color();
+    assert!(
+        warnings.contains(
+            "Advice: custom scalars should provide a scalar specification URL \
+             via the @specifiedBy directive"
+        ),
+        "{warnings}"
+    );
 
-    let schema = compiler.db.schema();
     let object_names: Vec<_> = schema
         .types
         .iter()
@@ -868,66 +782,29 @@ type Query {
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}");
-    }
-
-    assert!(diagnostics.is_empty());
-
-    let snapshot = compiler.snapshot();
-    let snapshot2 = compiler.snapshot();
-
-    let thread1 = std::thread::spawn(move || {
-        let schema = snapshot.schema();
-        assert!(schema.get_object("Query").is_some());
-    });
-    let thread2 = std::thread::spawn(move || {
-        let schema = snapshot2.schema();
-        assert_eq!(
-            schema
-                .types
-                .values()
-                .filter(|ty| ty.is_scalar() && !ty.is_built_in())
-                .count(),
-            1
-        );
+    let schema = Schema::parse(input, "document.graphql");
+    schema.validate().unwrap();
+
+    let (result1, result2) = std::thread::scope(|scope| {
+        let thread1 = scope.spawn(|| {
+            assert!(schema.get_object("Query").is_some());
+        });
+        let thread2 = scope.spawn(|| {
+            assert_eq!(
+                schema
+                    .types
+                    .values()
+                    .filter(|ty| ty.is_scalar() && !ty.is_built_in())
+                    .count(),
+                1
+            );
+        });
+        (thread1.join(), thread2.join())
     });
 
-    thread1.join().expect("get_object return None");
-    thread2.join().expect("scalars failed");
-}
-
-#[test]
-fn inputs_can_be_updated() {
-    let input = r#"
-type Query {
-  website: URL,
-  amount: Int
-}
-"#;
-
-    let mut compiler = ApolloCompiler::new();
-    let input_id = compiler.add_document(input, "document.graphql");
-
-    let schema = compiler.db.schema();
-    let object_type = schema.get_object("Query").unwrap();
-    assert!(object_type.directives.is_empty());
-
-    let input = r#"
-type Query @withDirective {
-  website: URL,
-  amount: Int
-}
-"#;
-    compiler.update_document(input_id, input);
-
-    let schema = compiler.db.schema();
-    let object_type = schema.get_object("Query").unwrap();
-    assert_eq!(object_type.directives.len(), 1);
+    // Neither thread panicked
+    result1.expect("get_object return None");
+    result2.expect("scalars failed");
 }
 
 #[test]
@@ -937,37 +814,38 @@ type Query {
     website: URL,
     amount: Int
 }
+scalar URL
 "#;
     let query = "{ website }";
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(sdl, "schema.graphql");
-    let schema = compiler.db.schema();
-
-    let handles: Vec<_> = (0..2)
-        .map(|_| {
-            let cloned = Arc::clone(&schema); // cheap refcount increment
-            std::thread::spawn(move || {
-                let mut compiler = ApolloCompiler::from_schema(cloned);
-                let query_id = compiler.add_executable(query, "query.graphql");
-                let document = compiler.db.executable_document(query_id);
-                let selections = &document
-                    .anonymous_operation
-                    .as_ref()?
-                    .selection_set
-                    .selections;
-
-                match selections.get(0)? {
-                    Selection::Field(field) => {
-                        Some(field.definition.ty.inner_named_type().to_string())
+    let schema = Schema::parse(sdl, "schema.graphql");
+    schema.validate().unwrap();
+
+    let results = std::thread::scope(|scope| {
+        let handles: Vec<_> = (0..2)
+            .map(|_| {
+                scope.spawn(|| {
+                    let document = ExecutableDocument::parse(&schema, query, "query.graphql");
+                    let selections = &document
+                        .anonymous_operation
+                        .as_ref()?
+                        .selection_set
+                        .selections;
+
+                    match selections.get(0)? {
+                        Selection::Field(field) => {
+                            Some(field.definition.ty.inner_named_type().to_string())
+                        }
+                        _ => None,
                     }
-                    _ => None,
-                }
+                })
             })
-        })
-        .collect();
-    assert_eq!(handles.len(), 2);
-    for handle in handles {
-        assert_eq!(handle.join().unwrap().as_deref(), Some("URL"));
+            .collect();
+        handles.into_iter().map(|h| h.join()).collect::<Vec<_>>()
+    });
+
+    assert_eq!(results.len(), 2);
+    for result in results {
+        assert_eq!(result.unwrap().as_deref(), Some("URL"));
     }
 }
diff --git a/crates/apollo-compiler/tests/parser.rs b/crates/apollo-compiler/tests/parser.rs
index 2594c79..7c960a7 100644
--- a/crates/apollo-compiler/tests/parser.rs
+++ b/crates/apollo-compiler/tests/parser.rs
@@ -1,9 +1,8 @@
-use apollo_compiler::ApolloCompiler;
-use apollo_compiler::ReprDatabase;
+use apollo_compiler::Parser;
 
 #[test]
 fn it_errors_when_selection_set_recursion_limit_exceeded() {
-    let schema = r#"
+    let input = r#"
     query {
       Q1 {
         url {
@@ -12,17 +11,20 @@ fn it_errors_when_selection_set_recursion_limit_exceeded() {
       }
     }
     "#;
-    let mut compiler = ApolloCompiler::new().recursion_limit(1);
-    let id = compiler.add_document(schema, "schema.graphql");
-    let ast = compiler.db.ast(id);
-    assert_eq!(compiler.db.recursion_reached(id), 2);
-    assert!(ast.check_parse_errors().is_err());
+    let mut parser = Parser::new().recursion_limit(1);
+    let ast = parser.parse_ast(input, "doc.graphql");
+    assert_eq!(parser.recursion_reached(), 2);
+    let errors = ast.check_parse_errors().unwrap_err().to_string_no_color();
+    assert!(
+        errors.contains("parser recursion limit reached"),
+        "{errors}"
+    );
     assert_eq!(ast.definitions.len(), 1);
 }
 
 #[test]
 fn it_passes_when_selection_set_recursion_limit_is_not_exceeded() {
-    let schema = r#"
+    let input = r#"
     query {
       Q1 {
         Q2 {
@@ -33,10 +35,9 @@ fn it_passes_when_selection_set_recursion_limit_is_not_exceeded() {
       }
     }
     "#;
-    let mut compiler = ApolloCompiler::new().recursion_limit(7);
-    let id = compiler.add_document(schema, "schema.graphql");
-    let ast = compiler.db.ast(id);
-    assert_eq!(compiler.db.recursion_reached(id), 4);
+    let mut parser = Parser::new().recursion_limit(7);
+    let ast = parser.parse_ast(input, "doc.graphql");
+    assert_eq!(parser.recursion_reached(), 4);
     ast.check_parse_errors().unwrap();
     assert_eq!(ast.definitions.len(), 1);
 }
@@ -48,22 +49,21 @@ fn it_errors_when_selection_set_token_limit_is_exceeded() {
       field(arg1: Int, arg2: Int, arg3: Int, arg4: Int, arg5: Int, arg6: Int): Int
     }
     "#;
-    let mut compiler = ApolloCompiler::new().token_limit(18);
-    let id = compiler.add_document(schema, "schema.graphql");
-    let ast = compiler.db.ast(id);
-    let errors = ast.check_parse_errors().unwrap_err().to_string();
+    let ast = Parser::new()
+        .token_limit(18)
+        .parse_ast(schema, "doc.graphql");
+    let errors = ast.check_parse_errors().unwrap_err().to_string_no_color();
     assert!(
         errors.contains("token limit reached, aborting lexing"),
-        "{}",
-        errors
+        "{errors}"
     );
-    assert!(errors.contains("schema.graphql:3:30"), "{}", errors);
+    assert!(errors.contains("doc.graphql:3:30"), "{errors}");
     assert_eq!(ast.definitions.len(), 1);
 }
 
 #[test]
 fn it_errors_with_multiple_limits() {
-    let schema = r#"
+    let input = r#"
         query {
             a {
                 a {
@@ -74,47 +74,25 @@ fn it_errors_with_multiple_limits() {
             }
         }
     "#;
-    let mut compiler = ApolloCompiler::new().token_limit(22).recursion_limit(10);
-    let id = compiler.add_document(schema, "schema.graphql");
-    let ast = compiler.db.ast(id);
-    let errors = ast.check_parse_errors().unwrap_err().to_string();
+    let ast = Parser::new()
+        .token_limit(22)
+        .recursion_limit(10)
+        .parse_ast(input, "doc.graphql");
+    let errors = ast.check_parse_errors().unwrap_err().to_string_no_color();
     assert!(
         errors.contains("token limit reached, aborting lexing"),
-        "{}",
-        errors
+        "{errors}"
     );
-    assert!(errors.contains("schema.graphql:8:18"), "{}", errors);
+    assert!(errors.contains("doc.graphql:8:18"), "{errors}");
 
-    let mut compiler = ApolloCompiler::new().recursion_limit(3).token_limit(200);
-    let id = compiler.add_document(schema, "schema.graphql");
-    let ast = compiler.db.ast(id);
-    let errors = ast.check_parse_errors().unwrap_err().to_string();
+    let ast = Parser::new()
+        .token_limit(200)
+        .recursion_limit(3)
+        .parse_ast(input, "doc.graphql");
+    let errors = ast.check_parse_errors().unwrap_err().to_string_no_color();
     assert!(
         errors.contains("parser recursion limit reached"),
-        "{}",
-        errors
+        "{errors}"
     );
-    assert!(errors.contains("schema.graphql:6:25"), "{}", errors);
-}
-
-#[test]
-fn token_limit_with_multiple_sources() {
-    let schema = r#"
-    type Query {
-        website: URL,
-        amount: Int
-    }
-
-    scalar URL @specifiedBy(url: "a.com");
-    "#;
-    let query = "{ website }";
-
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(schema, "schema.graphql");
-    let schema = compiler.db.schema();
-
-    let mut compiler2 = ApolloCompiler::from_schema(schema).token_limit(2);
-    let id = compiler2.add_executable(query, "query.graphql");
-    let ast = compiler2.db.ast(id);
-    assert!(ast.check_parse_errors().is_err());
+    assert!(errors.contains("doc.graphql:6:25"), "{errors}");
 }
diff --git a/crates/apollo-compiler/tests/schema.rs b/crates/apollo-compiler/tests/schema.rs
index 536ea33..294bced 100644
--- a/crates/apollo-compiler/tests/schema.rs
+++ b/crates/apollo-compiler/tests/schema.rs
@@ -1,5 +1,3 @@
-use apollo_compiler::ApolloCompiler;
-use apollo_compiler::ReprDatabase;
 use apollo_compiler::Schema;
 
 #[test]
@@ -20,10 +18,8 @@ fn find_definitions_with_directive() {
         }
     "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(schema, "schema.graphql");
+    let schema = Schema::parse(schema, "schema.graphql");
 
-    let schema = compiler.db.schema();
     let mut key_definition_names: Vec<&str> = schema
         .types
         .iter()
@@ -86,9 +82,8 @@ interface Inter {
 
 #[test]
 fn is_subtype() {
-    fn gen_schema_types(schema: &str) -> ApolloCompiler {
-        let base_schema = with_supergraph_boilerplate(
-            r#"
+    fn gen_schema_types(schema: &str) -> Schema {
+        let base_schema = r#"
             type Query {
                 me: String
             }
@@ -103,17 +98,16 @@ fn is_subtype() {
             }
 
             union UnionType2 = Foo | Bar
-            "#,
-        );
-        let schema = format!("{base_schema}\n{schema}");
-        let mut compiler = ApolloCompiler::new();
-        compiler.add_document(&schema, "schema.graphql");
-        compiler
+            "#;
+        Schema::builder()
+            .parse(SUPERGRAPH_BOILERPLATE, "boilerplate")
+            .parse(base_schema, "base")
+            .parse(schema, "schema")
+            .build()
     }
 
-    fn gen_schema_interfaces(schema: &str) -> ApolloCompiler {
-        let base_schema = with_supergraph_boilerplate(
-            r#"
+    fn gen_schema_interfaces(schema: &str) -> Schema {
+        let base_schema = r#"
             type Query {
                 me: String
             }
@@ -129,16 +123,15 @@ fn is_subtype() {
 
             type ObjectType2 implements Foo & Bar { me: String }
             interface InterfaceType2 implements Foo & Bar { me: String }
-            "#,
-        );
-        let schema = format!("{base_schema}\n{schema}");
-        let mut compiler = ApolloCompiler::new();
-        compiler.add_document(&schema, "schema.graphql");
-        compiler
+            "#;
+        Schema::builder()
+            .parse(SUPERGRAPH_BOILERPLATE, "boilerplate")
+            .parse(base_schema, "base")
+            .parse(schema, "schema")
+            .build()
     }
 
-    let ctx = gen_schema_types("union UnionType = Foo | Bar | Baz");
-    let schema = ctx.db.schema();
+    let schema = gen_schema_types("union UnionType = Foo | Bar | Baz");
     assert!(schema.is_subtype("UnionType", "Foo"));
     assert!(schema.is_subtype("UnionType", "Bar"));
     assert!(schema.is_subtype("UnionType", "Baz"));
@@ -148,8 +141,7 @@ fn is_subtype() {
     assert!(!schema.is_subtype("NotAType", "Foo"));
     assert!(!schema.is_subtype("Foo", "UnionType"));
 
-    let ctx = gen_schema_interfaces("type ObjectType implements Foo & Bar & Baz { me: String }");
-    let schema = ctx.db.schema();
+    let schema = gen_schema_interfaces("type ObjectType implements Foo & Bar & Baz { me: String }");
     assert!(schema.is_subtype("Foo", "ObjectType"));
     assert!(schema.is_subtype("Bar", "ObjectType"));
     assert!(schema.is_subtype("Baz", "ObjectType"));
@@ -159,9 +151,8 @@ fn is_subtype() {
     assert!(!schema.is_subtype("Foo", "NotAType"));
     assert!(!schema.is_subtype("ObjectType", "Foo"));
 
-    let ctx =
+    let schema =
         gen_schema_interfaces("interface InterfaceType implements Foo & Bar & Baz { me: String }");
-    let schema = ctx.db.schema();
     assert!(schema.is_subtype("Foo", "InterfaceType"));
     assert!(schema.is_subtype("Bar", "InterfaceType"));
     assert!(schema.is_subtype("Baz", "InterfaceType"));
@@ -171,30 +162,24 @@ fn is_subtype() {
     assert!(!schema.is_subtype("Foo", "NotAType"));
     assert!(!schema.is_subtype("InterfaceType", "Foo"));
 
-    let ctx = gen_schema_types("extend union UnionType2 = Baz");
-    let schema = ctx.db.schema();
+    let schema = gen_schema_types("extend union UnionType2 = Baz");
     assert!(schema.is_subtype("UnionType2", "Foo"));
     assert!(schema.is_subtype("UnionType2", "Bar"));
     assert!(schema.is_subtype("UnionType2", "Baz"));
 
-    let ctx = gen_schema_interfaces("extend type ObjectType2 implements Baz { me2: String }");
-    let schema = ctx.db.schema();
+    let schema = gen_schema_interfaces("extend type ObjectType2 implements Baz { me2: String }");
     assert!(schema.is_subtype("Foo", "ObjectType2"));
     assert!(schema.is_subtype("Bar", "ObjectType2"));
     assert!(schema.is_subtype("Baz", "ObjectType2"));
 
-    let ctx =
+    let schema =
         gen_schema_interfaces("extend interface InterfaceType2 implements Baz { me2: String }");
-    let schema = ctx.db.schema();
     assert!(schema.is_subtype("Foo", "InterfaceType2"));
     assert!(schema.is_subtype("Bar", "InterfaceType2"));
     assert!(schema.is_subtype("Baz", "InterfaceType2"));
 }
 
-fn with_supergraph_boilerplate(content: &str) -> String {
-    format!(
-        "{}\n{}",
-        r#"
+const SUPERGRAPH_BOILERPLATE: &str = r#"
         schema
             @core(feature: "https://specs.apollo.dev/core/v0.1")
             @core(feature: "https://specs.apollo.dev/join/v0.1") {
@@ -206,7 +191,4 @@ fn with_supergraph_boilerplate(content: &str) -> String {
             TEST @join__graph(name: "test", url: "http://localhost:4001/graphql")
         }
 
-        "#,
-        content
-    )
-}
+        "#;
diff --git a/crates/apollo-compiler/tests/snapshot_tests.rs b/crates/apollo-compiler/tests/snapshot_tests.rs
index 43e7889..9732bcc 100644
--- a/crates/apollo-compiler/tests/snapshot_tests.rs
+++ b/crates/apollo-compiler/tests/snapshot_tests.rs
@@ -8,10 +8,9 @@
 // Note: ALL #[test] functions must also have #[serial], to make FileId::reset
 
 use apollo_compiler::ast;
-use apollo_compiler::validation::Diagnostics;
-use apollo_compiler::ApolloCompiler;
+use apollo_compiler::Diagnostics;
 use apollo_compiler::FileId;
-use apollo_compiler::ReprDatabase;
+use apollo_compiler::Schema;
 use expect_test::expect_file;
 use indexmap::IndexMap;
 use serial_test::serial;
@@ -33,10 +32,8 @@ use std::path::PathBuf;
 #[serial]
 fn validation() {
     dir_tests(&test_data_dir(), &["ok"], "txt", |text, path| {
-        let mut compiler = ApolloCompiler::new();
-        let file_id = compiler.add_document(text, path.file_name().unwrap());
-        let schema = compiler.db.schema();
-        let executable = compiler.db.executable_document(file_id);
+        let ast = ast::Document::parse(text, path.file_name().unwrap());
+        let (schema, executable) = ast.to_mixed();
 
         let schema_validation_errors = schema.validate().err();
         let executable_validation_errors = executable.validate(&schema).err();
@@ -46,32 +43,25 @@ fn validation() {
             path,
         );
 
-        let ast = compiler.db.ast(file_id);
         format!("{ast:#?}")
     });
 
     dir_tests(&test_data_dir(), &["diagnostics"], "txt", |text, path| {
-        let mut compiler = ApolloCompiler::new();
         let filename = path.file_name().unwrap().to_str().unwrap();
         let is_type_system = filename.contains("type_system_document");
         let is_executable = filename.contains("executable_document");
         let schema_validation_errors;
         let executable_validation_errors;
         if is_type_system {
-            compiler.add_type_system(text, filename);
-            schema_validation_errors = compiler.db.schema().validate().err();
+            schema_validation_errors = Schema::parse(text, filename).validate().err();
             executable_validation_errors = None;
         } else if is_executable {
-            let file_id = compiler.add_executable(text, filename);
-            executable_validation_errors = compiler
-                .db
-                .ast(file_id)
+            executable_validation_errors = ast::Document::parse(text, filename)
                 .validate_standalone_executable()
                 .err();
             schema_validation_errors = None;
         } else {
-            let file_id = compiler.add_document(text, filename);
-            let schema = compiler.db.schema();
+            let (schema, executable) = apollo_compiler::parse_mixed(text, filename);
             schema_validation_errors = match schema.validate() {
                 Ok(warnings) => {
                     if warnings.to_string().is_empty() {
@@ -82,11 +72,7 @@ fn validation() {
                 }
                 Err(e) => Some(e),
             };
-            executable_validation_errors = compiler
-                .db
-                .executable_document(file_id)
-                .validate(&schema)
-                .err();
+            executable_validation_errors = executable.validate(&schema).err();
         };
         let mut formatted = String::new();
         if let Some(errors) = &schema_validation_errors {
diff --git a/crates/apollo-compiler/tests/validation/interface.rs b/crates/apollo-compiler/tests/validation/interface.rs
index 63d2a2e..a09779c 100644
--- a/crates/apollo-compiler/tests/validation/interface.rs
+++ b/crates/apollo-compiler/tests/validation/interface.rs
@@ -1,5 +1,5 @@
-use apollo_compiler::ApolloCompiler;
-use apollo_compiler::ReprDatabase;
+use apollo_compiler::parse_mixed;
+use apollo_compiler::Schema;
 
 #[test]
 fn it_fails_validation_with_duplicate_operation_fields() {
@@ -20,14 +20,12 @@ interface NamedEntity {
 
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "schema.graphql");
-    let diagnostics = compiler.db.schema().validate().unwrap_err();
-    let diagnostics = format!("{diagnostics:#}");
+    let schema = Schema::parse(input, "schema.graphql");
+    let errors = schema.validate().unwrap_err().to_string_no_color();
     assert!(
-        diagnostics
+        errors
             .contains("duplicate definitions for the `name` field of interface type `NamedEntity`"),
-        "{diagnostics}"
+        "{errors}"
     );
 }
 
@@ -55,14 +53,12 @@ interface NamedEntity {
 
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "schema.graphql");
+    let schema = Schema::parse(input, "schema.graphql");
 
-    let diagnostics = compiler.db.schema().validate().unwrap_err();
-    let diagnostics = format!("{diagnostics:#}");
+    let errors = schema.validate().unwrap_err().to_string_no_color();
     assert!(
-        diagnostics.contains("the type `NamedEntity` is defined multiple times"),
-        "{diagnostics}"
+        errors.contains("the type `NamedEntity` is defined multiple times"),
+        "{errors}"
     );
 }
 
@@ -84,14 +80,13 @@ interface NamedEntity implements NamedEntity {
 
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "schema.graphql");
+    let schema = Schema::parse(input, "schema.graphql");
 
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}")
-    }
-    assert_eq!(diagnostics.len(), 1);
+    let errors = schema.validate().unwrap_err().to_string_no_color();
+    assert!(
+        errors.contains("interface NamedEntity cannot implement itself"),
+        "{errors}"
+    );
 }
 
 #[test]
@@ -105,14 +100,13 @@ interface NamedEntity implements NewEntity {
 
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "schema.graphql");
+    let schema = Schema::parse(input, "schema.graphql");
 
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}")
-    }
-    assert_eq!(diagnostics.len(), 1);
+    let errors = schema.validate().unwrap_err().to_string_no_color();
+    assert!(
+        errors.contains("cannot find type `NewEntity` in this document"),
+        "{errors}"
+    );
 }
 
 #[test]
@@ -136,14 +130,13 @@ interface Image implements Resource & Node {
   thumbnail: String
 }
 "#;
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "schema.graphql");
+    let schema = Schema::parse(input, "schema.graphql");
 
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}")
-    }
-    assert_eq!(diagnostics.len(), 1);
+    let errors = schema.validate().unwrap_err().to_string_no_color();
+    assert!(
+        errors.contains("type does not satisfy interface `Resource`: missing field `width`"),
+        "{errors}"
+    );
 }
 
 #[test]
@@ -201,12 +194,11 @@ input Point2D {
 
 scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "schema.graphql");
-
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}")
-    }
-    assert_eq!(diagnostics.len(), 3);
+    let (schema, _executable) = parse_mixed(input, "doc.graphql");
+
+    let errors = schema.validate().unwrap_err().to_string_no_color();
+    assert!(
+        errors.contains("`coordinates` field must return an output type"),
+        "{errors}"
+    );
 }
diff --git a/crates/apollo-compiler/tests/validation/mod.rs b/crates/apollo-compiler/tests/validation/mod.rs
index 9f1f11f..e2cad2d 100644
--- a/crates/apollo-compiler/tests/validation/mod.rs
+++ b/crates/apollo-compiler/tests/validation/mod.rs
@@ -3,9 +3,9 @@ mod object;
 mod operation;
 mod variable;
 
-use apollo_compiler::validation::ValidationDatabase;
-use apollo_compiler::ApolloCompiler;
-use apollo_compiler::ReprDatabase;
+use apollo_compiler::ast;
+use apollo_compiler::ExecutableDocument;
+use apollo_compiler::Schema;
 
 #[test]
 fn executable_and_type_system_definitions() {
@@ -21,12 +21,11 @@ query {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(input_type_system, "schema.graphql");
-    compiler.add_executable(input_executable, "query.graphql");
+    let schema = Schema::parse(input_type_system, "schema.graphql");
+    let executable = ExecutableDocument::parse(&schema, input_executable, "query.graphql");
 
-    let diagnostics = compiler.validate();
-    assert!(diagnostics.is_empty());
+    schema.validate().unwrap();
+    executable.validate(&schema).unwrap();
 }
 
 #[test]
@@ -46,19 +45,17 @@ query {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(input_type_system, "schema.graphql");
-    let id = compiler.add_executable(input_executable, "query.graphql");
+    let schema = Schema::parse(input_type_system, "schema.graphql");
+    let executable = ExecutableDocument::parse(&schema, input_executable, "query.graphql");
 
-    let schema = compiler.db.schema();
-    let diagnostics = compiler
-        .db
-        .executable_document(id)
+    schema.validate().unwrap();
+    let errors = executable
         .validate(&schema)
         .unwrap_err()
-        .to_string();
+        .to_string_no_color();
     assert!(
-        diagnostics.contains("an executable document must not contain an object type definition")
+        errors.contains("an executable document must not contain an object type definition"),
+        "{errors}"
     );
 }
 
@@ -79,16 +76,17 @@ fragment q on Query {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(input_type_system, "schema.graphql");
-    compiler.add_executable(input_executable, "query.graphql");
+    let schema = Schema::parse(input_type_system, "schema.graphql");
+    let executable = ExecutableDocument::parse(&schema, input_executable, "query.graphql");
 
-    let diagnostics = compiler.validate();
-
-    assert_eq!(diagnostics.len(), 1);
-    assert_eq!(
-        diagnostics[0].data.to_string(),
-        "`q` fragment cannot reference itself"
+    schema.validate().unwrap();
+    let errors = executable
+        .validate(&schema)
+        .unwrap_err()
+        .to_string_no_color();
+    assert!(
+        errors.contains("`q` fragment cannot reference itself"),
+        "{errors}"
     );
 }
 
@@ -116,43 +114,43 @@ fragment q on TestObject {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_type_system(input_type_system, "schema.graphql");
-    compiler.add_executable(input_executable, "query.graphql");
+    let schema = Schema::parse(input_type_system, "schema.graphql");
+    let executable = ExecutableDocument::parse(&schema, input_executable, "query.graphql");
 
-    let diagnostics = compiler.validate();
-
-    assert_eq!(diagnostics.len(), 1);
-    assert_eq!(
-        diagnostics[0].data.to_string(),
-        "`q` fragment cannot reference itself"
+    schema.validate().unwrap();
+    let errors = executable
+        .validate(&schema)
+        .unwrap_err()
+        .to_string_no_color();
+    assert!(
+        errors.contains("`q` fragment cannot reference itself"),
+        "{errors}"
     );
 }
 
 #[test]
 fn validation_without_type_system() {
-    let mut compiler = ApolloCompiler::new();
-
-    let valid_id = compiler.add_executable(r#"{ obj { name nickname } }"#, "valid.graphql");
-    let diagnostics = compiler.db.validate_standalone_executable(valid_id);
+    let doc = ast::Document::parse(r#"{ obj { name nickname } }"#, "valid.graphql");
     // We don't know what `obj` refers to, so assume it is valid.
-    assert!(diagnostics.is_empty());
+    doc.validate_standalone_executable().unwrap();
 
-    let unused_frag_id = compiler.add_executable(
+    let doc = ast::Document::parse(
         r#"
             fragment A on Type { a }
             query { b }
         "#,
         "dupe_frag.graphql",
     );
-    let diagnostics = compiler.db.validate_standalone_executable(unused_frag_id);
-    assert_eq!(diagnostics.len(), 1);
-    assert_eq!(
-        diagnostics[0].data.to_string(),
-        "fragment `A` must be used in an operation"
+    let errors = doc
+        .validate_standalone_executable()
+        .unwrap_err()
+        .to_string_no_color();
+    assert!(
+        errors.contains("fragment `A` must be used in an operation"),
+        "{errors}"
     );
 
-    let dupe_frag_id = compiler.add_executable(
+    let doc = ast::Document::parse(
         r#"
             fragment A on Type { a }
             fragment A on Type { b }
@@ -160,42 +158,37 @@ fn validation_without_type_system() {
         "#,
         "dupe_frag.graphql",
     );
-    let diagnostics = compiler
-        .db
-        .ast(dupe_frag_id)
+    let errors = doc
         .validate_standalone_executable()
-        .unwrap_err();
-    let diagnostics = format!("{diagnostics:#}");
+        .unwrap_err()
+        .to_string_no_color();
     assert!(
-        diagnostics.contains("the fragment `A` is defined multiple times in the document"),
-        "{diagnostics}"
+        errors.contains("the fragment `A` is defined multiple times in the document"),
+        "{errors}"
     );
 
-    let unknown_frag_id = compiler.add_executable(r#"{ ...A }"#, "unknown_frag.graphql");
-    let diagnostics = compiler.db.validate_standalone_executable(unknown_frag_id);
-    assert_eq!(diagnostics.len(), 1);
-    assert_eq!(
-        diagnostics[0].data.to_string(),
-        "cannot find fragment `A` in this document"
+    let doc = ast::Document::parse(r#"{ ...A }"#, "unknown_frag.graphql");
+    let errors = doc
+        .validate_standalone_executable()
+        .unwrap_err()
+        .to_string_no_color();
+    assert!(
+        errors.contains("cannot find fragment `A` in this document"),
+        "{errors}"
     );
 }
 
 #[test]
 fn validate_variable_usage_without_type_system() {
-    let mut compiler = ApolloCompiler::new();
-    let id = compiler.add_executable(r#"
-query nullableStringArg($nonNullableVar: String!, $nonNullableList: [String!]!, $nonNullableListList: [[Int!]!]) {
-  arguments {
-    nullableString(nullableString: $nonNullableVar)
-    nullableList(nullableList: $nonNullableList)
-    nullableListList(nullableListList: $nonNullableListList)
-  }
-}
-"#, "query.graphql");
-
-    let diagnostics = compiler.db.validate_standalone_executable(id);
-    for diag in &diagnostics {
-        println!("{diag}")
+    let input = r#"
+    query nullableStringArg($nonNullableVar: String!, $nonNullableList: [String!]!, $nonNullableListList: [[Int!]!]) {
+      arguments {
+        nullableString(nullableString: $nonNullableVar)
+        nullableList(nullableList: $nonNullableList)
+        nullableListList(nullableListList: $nonNullableListList)
+      }
     }
-    assert_eq!(diagnostics.len(), 0);
+    "#;
+    let doc = ast::Document::parse(input, "query.graphql");
+    doc.validate_standalone_executable().unwrap()
 }
diff --git a/crates/apollo-compiler/tests/validation/object.rs b/crates/apollo-compiler/tests/validation/object.rs
index acd12cf..93f462f 100644
--- a/crates/apollo-compiler/tests/validation/object.rs
+++ b/crates/apollo-compiler/tests/validation/object.rs
@@ -1,4 +1,4 @@
-use apollo_compiler::ApolloCompiler;
+use apollo_compiler::parse_mixed;
 
 #[test]
 fn it_generates_diagnostics_for_non_output_field_types() {
@@ -57,12 +57,11 @@ input Point2D {
 
 scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "schema.graphql");
+    let (schema, _executable) = parse_mixed(input, "doc.graphql");
 
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}")
-    }
-    assert_eq!(diagnostics.len(), 3);
+    let errors = schema.validate().unwrap_err().to_string_no_color();
+    assert!(
+        errors.contains("`coordinates` field must return an output type"),
+        "{errors}"
+    );
 }
diff --git a/crates/apollo-compiler/tests/validation/operation.rs b/crates/apollo-compiler/tests/validation/operation.rs
index 9b2d955..0ff3893 100644
--- a/crates/apollo-compiler/tests/validation/operation.rs
+++ b/crates/apollo-compiler/tests/validation/operation.rs
@@ -1,5 +1,4 @@
-use apollo_compiler::ApolloCompiler;
-use apollo_compiler::ReprDatabase;
+use apollo_compiler::parse_mixed;
 
 #[test]
 fn it_fails_validation_with_missing_ident() {
@@ -32,21 +31,19 @@ type Cat {
   meowVolume: Int
 }
 "#;
-    let mut compiler = ApolloCompiler::new();
-    let id = compiler.add_document(input, "doc.graphql");
 
-    let schema = compiler.db.schema();
-    let diagnostics = compiler
-        .db
-        .executable_document(id)
+    let (schema, executable) = parse_mixed(input, "schema.graphql");
+
+    schema.validate().unwrap();
+    let errors = executable
         .validate(&schema)
-        .unwrap_err();
-    let diagnostics = format!("{diagnostics:#}");
+        .unwrap_err()
+        .to_string_no_color();
     assert!(
-        diagnostics.contains(
+        errors.contains(
             "anonymous operation cannot be selected when the document contains other operations"
         ),
-        "{diagnostics}"
+        "{errors}"
     );
 }
 
@@ -89,19 +86,17 @@ type Cat implements Pet {
   meowVolume: Int
 }
 "#;
-    let mut compiler = ApolloCompiler::new();
-    let id = compiler.add_document(input, "doc.graphql");
 
-    let schema = compiler.db.schema();
-    let diagnostics = compiler
-        .db
-        .executable_document(id)
+    let (schema, executable) = parse_mixed(input, "schema.graphql");
+
+    schema.validate().unwrap();
+    let errors = executable
         .validate(&schema)
-        .unwrap_err();
-    let diagnostics = format!("{diagnostics:#}");
+        .unwrap_err()
+        .to_string_no_color();
     assert!(
-        diagnostics.contains("the operation `getName` is defined multiple times in the document"),
-        "{diagnostics}"
+        errors.contains("the operation `getName` is defined multiple times in the document"),
+        "{errors}"
     );
 }
 
@@ -143,14 +138,11 @@ type Cat implements Pet {
   meowVolume: Int
 }
 "#;
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "schema.graphql");
 
-    let diagnostics = compiler.validate();
-    for diagnostic in &diagnostics {
-        println!("{diagnostic}")
-    }
-    assert!(diagnostics.is_empty());
+    let (schema, executable) = parse_mixed(input, "schema.graphql");
+
+    schema.validate().unwrap();
+    executable.validate(&schema).unwrap();
 }
 
 #[test]
@@ -185,20 +177,18 @@ type Cat implements Pet {
   meowVolume: Int
 }
 "#;
-    let mut compiler = ApolloCompiler::new();
-    let id = compiler.add_document(input, "doc.graphql");
 
-    let schema = compiler.db.schema();
-    let diagnostics = compiler
-        .db
-        .executable_document(id)
+    let (schema, executable) = parse_mixed(input, "schema.graphql");
+
+    schema.validate().unwrap();
+    let errors = executable
         .validate(&schema)
-        .unwrap_err();
-    let diagnostics = format!("{diagnostics:#}");
+        .unwrap_err()
+        .to_string_no_color();
     assert!(
-        diagnostics
+        errors
             .contains("`subscription` is not defined in the schema and is therefore not supported"),
-        "{diagnostics}"
+        "{errors}"
     );
 }
 
@@ -225,22 +215,19 @@ type Product {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    let id = compiler.add_document(input, "doc.graphql");
+    let (schema, executable) = parse_mixed(input, "schema.graphql");
 
-    let schema = compiler.db.schema();
-    let diagnostics = compiler
-        .db
-        .executable_document(id)
+    schema.validate().unwrap();
+    let errors = executable
         .validate(&schema)
-        .unwrap_err();
-    let diagnostics = format!("{diagnostics:#}");
+        .unwrap_err()
+        .to_string_no_color();
     assert!(
-        diagnostics.contains("`Query` does not have a field `noName`"),
-        "{diagnostics}"
+        errors.contains("`Query` does not have a field `noName`"),
+        "{errors}"
     );
     assert!(
-        diagnostics.contains("type `Product` does not have a field `price`"),
-        "{diagnostics}"
+        errors.contains("type `Product` does not have a field `price`"),
+        "{errors}"
     );
 }
diff --git a/crates/apollo-compiler/tests/validation/variable.rs b/crates/apollo-compiler/tests/validation/variable.rs
index 878df6a..69a6295 100644
--- a/crates/apollo-compiler/tests/validation/variable.rs
+++ b/crates/apollo-compiler/tests/validation/variable.rs
@@ -1,4 +1,4 @@
-use apollo_compiler::ApolloCompiler;
+use apollo_compiler::parse_mixed;
 
 #[test]
 fn it_raises_undefined_variable_in_query_error() {
@@ -37,16 +37,21 @@ type Products {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-
-    for error in &diagnostics {
-        println!("{error}")
-    }
-
-    assert_eq!(diagnostics.len(), 2);
+    let (schema, executable) = parse_mixed(input, "schema.graphql");
+
+    schema.validate().unwrap();
+    let errors = executable
+        .validate(&schema)
+        .unwrap_err()
+        .to_string_no_color();
+    assert!(
+        errors.contains("variable `undefinedVariable` is not defined"),
+        "{errors}"
+    );
+    assert!(
+        errors.contains("variable `dimensions` is not defined"),
+        "{errors}"
+    );
 }
 
 #[test]
@@ -69,14 +74,17 @@ type Product {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-
-    for error in diagnostics {
-        println!("{error}")
-    }
+    let (schema, executable) = parse_mixed(input, "schema.graphql");
+
+    schema.validate().unwrap();
+    let errors = executable
+        .validate(&schema)
+        .unwrap_err()
+        .to_string_no_color();
+    assert!(
+        errors.contains("unused variable: `unusedVariable`"),
+        "{errors}"
+    );
 }
 
 #[test]
@@ -120,14 +128,19 @@ type Product {
 }
 "#;
 
-    let mut compiler = ApolloCompiler::new();
-    compiler.add_document(input, "document.graphql");
-
-    let diagnostics = compiler.validate();
-
-    for error in &diagnostics {
-        println!("{error}")
-    }
-
-    assert_eq!(diagnostics.len(), 2);
+    let (schema, executable) = parse_mixed(input, "schema.graphql");
+
+    schema.validate().unwrap();
+    let errors = executable
+        .validate(&schema)
+        .unwrap_err()
+        .to_string_no_color();
+    assert!(
+        errors.contains("variable `goldStatus` is not defined"),
+        "{errors}"
+    );
+    assert!(
+        errors.contains("variable `dimensions` is not defined"),
+        "{errors}"
+    );
 }