Repository: apollographql/apollo-rs
Pull request #368 (2022-11-17 07:27:17)

feat(compiler): multi file support

goto-bus-stop: With different input types to salsa, we were discussing how to distinguish them, and when to use which input query. We talked about trying the different input types in turn, so if we were eg. looking up source code for a file, we could do (pseudocode):
```rust
db.input_document(id)
  .or_else(|| db.input_schema(id))
```
but salsa panics if an input cannot be resolved, so that wont actually work without further changes.

It's possible to do that if we store the source type (schema/query/document) in the source manifest. that could still be a good idea but I think this way is simpler: the compiler now uses 1 input type, which is a structure contaniing the source type AND the source strirng together, and everything can just use that directly and only check the source type if it's relevant. the API for typical uses is still the same.
goto-bus-stop: @SimonSapin the PR still needs some work, for example the FileIds are still UUIDs here. I wanted to see if we can use salsa's interning for that, or else do the hashing strategy. But I think it's ready for an initial review and I could really use some feedback 😇 
goto-bus-stop: OK, now the `.schema()`/`.executable()`/`.document()` methods use salsa's interning to turn the file name into a file ID, and if you use a path that was already known to the database, it updates the file isntead of adding a new one. I think it's readyish to go.

the SourceManifest is basically replaced by having a `Vec` of all known file IDs stored in the database, and a `.input(file_id) -> source` query that includes the source code + filename in the output. you can use that to do `db.source_code(file_id)` for each file or something if necessary.

salsa interning would be really useful for the HIR too if we were to fully rely on it … but that would come in the way of the initialize-from-precomputed-HIR idea.

at the moment this doesn't come with a way to remove a file.
goto-bus-stop: > I haven’t read through src/validation yet. Are there "meaningful" changes there or is it all plumbing file IDs and updating calls for signature changes?

It's basically all plumbing. `validation_db` has one meaningful change which is making a global `validate()` call run through _all_ the registered executable documents, but that's it.
SimonSapin: > OK, now the `.schema()`/`.executable()`/`.document()` methods use salsa's interning to turn the file name into a file ID, and if you use a path that was already known to the database, it updates the file isntead of adding a new one. I think it's readyish to go.

Sorry I missed this earlier. A mandatory `PathBuf` is slightly awkward for input that might not come from a file at all, but even more so if it has to be unique. As long as the Router only deals with one executable document at a time per compiler instance it can hard-code a dummy string, but if that changes it’d need to generate random strings which feels like busywork

goto-bus-stop: Hmm yes. I wasn't sure if this would be ideal long-term, but didn't want to change it if there was no reason to.

The alternative I had in mind was having something like `(&mut db).create_file_id()` which returns a unique file ID that you have to hold onto yourself. Then there would be no filename, or it could have an associated non-unique filename for diagnostics.
(This could also happen a bit more behind the scenes if we have `db.create_query("source code")` create a new unique file ID, and require use of a separate method like `db.update_query(id, "new code")` to change an existing input.)
Would that be better for a router-like case?
SimonSapin: It looks like nothing uses the filename? It only serves to identify a previous source to change its text? In that case yes I’d prefer to remove filenames and have separate methods for updating an existing source (by `FileId`) v.s. creating a new one (returning a new `FileId`, which could be assigned outside of Salsa with a private `next_file_id: usize` field)
goto-bus-stop: Kept the filename as is, as it's marginally more work to remove it and we will need it eventually…

Patch:
diff --git a/crates/apollo-compiler/Cargo.toml b/crates/apollo-compiler/Cargo.toml
index 51407b9..68a45fe 100644
--- a/crates/apollo-compiler/Cargo.toml
+++ b/crates/apollo-compiler/Cargo.toml
@@ -22,7 +22,7 @@ rowan = "0.15.5"
 salsa = "0.16.1"
 uuid = { version = "1.1", features = ["serde", "v4"] }
 ordered-float = { version = "2.10.0", features = ["std"] }
-miette = { version = "4.7.1", features = ["fancy"] }
+miette = { version = "5.5.0", features = ["fancy"] }
 thiserror = "1.0.31"
 
 [target.'cfg(target_arch = "wasm32")'.dependencies]
diff --git a/crates/apollo-compiler/README.md b/crates/apollo-compiler/README.md
index cce2497..4901e41 100644
--- a/crates/apollo-compiler/README.md
+++ b/crates/apollo-compiler/README.md
@@ -73,8 +73,10 @@ let input = r#"
   }
 "#;
 
-let ctx = ApolloCompiler::new(input);
-let diagnostics = ctx.validate();
+let mut compiler = ApolloCompiler::new();
+compiler.create_document(input, "document.graphql");
+
+let diagnostics = compiler.validate();
 for diagnostic in &diagnostics {
     // this will pretty-print diagnostics using the miette crate.
     println!("{}", diagnostic);
@@ -90,21 +92,7 @@ use apollo_compiler::{ApolloCompiler, hir, DocumentDatabase};
 use miette::Result;
 
 fn main() -> Result<()> {
-    let input = r#"
-    query getProduct {
-      topProducts {
-          type
-      }
-      ... vipCustomer
-    }
-
-    #fragment definition where we want to know the field types.
-    fragment vipCustomer on User {
-      id
-      name
-      profilePic(size: 50)
-    }
-
+    let schema_input = r#"
     type Query {
       topProducts: Product
       customer: User
@@ -123,18 +111,37 @@ fn main() -> Result<()> {
 
     scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
     "#;
+    let query_input = r#"
+    query getProduct {
+      topProducts {
+          type
+      }
+      ... vipCustomer
+    }
+
+    #fragment definition where we want to know the field types.
+    fragment vipCustomer on User {
+      id
+      name
+      profilePic(size: 50)
+    }
+    "#;
+
+    let mut compiler = ApolloCompiler::new();
+    let _schema_id = compiler.create_schema(schema_input, "schema.graphql");
+    let query_id = compiler.create_executable(query_input, "query.graphql");
 
-    let ctx = ApolloCompiler::new(input);
-    let diagnostics = ctx.validate();
+    let diagnostics = compiler.validate();
     for diagnostic in &diagnostics {
         println!("{}", diagnostic);
     }
     assert!(diagnostics.is_empty());
 
-    let op = ctx.db.find_operation_by_name(String::from("getProduct")).expect("getProduct query does not exist");
+    let op = compiler.db.find_operation_by_name(query_id, String::from("getProduct"))
+        .expect("getProduct query does not exist");
     let fragment_in_op: Vec<hir::FragmentDefinition> = op.selection_set().selection().iter().filter_map(|sel| match sel {
         hir::Selection::FragmentSpread(frag) => {
-            Some(frag.fragment(&ctx.db)?.as_ref().clone())
+            Some(frag.fragment(&compiler.db)?.as_ref().clone())
         }
         _ => None
     }).collect();
@@ -142,7 +149,7 @@ fn main() -> Result<()> {
     let fragment_fields: Vec<hir::Field> = fragment_in_op.iter().flat_map(|frag| frag.selection_set().fields()).collect();
     let field_ty: Vec<String> = fragment_fields
         .iter()
-        .filter_map(|f| Some(f.ty(&ctx.db)?.name()))
+        .filter_map(|f| Some(f.ty(&compiler.db)?.name()))
         .collect();
     assert_eq!(field_ty, ["ID", "String", "URL"]);
     Ok(())
@@ -155,15 +162,7 @@ use apollo_compiler::{ApolloCompiler, hir, HirDatabase};
 use anyhow::{anyhow, Result};
 
 fn main() -> Result<()> {
-    let input = r#"
-    query getProduct {
-      size
-      topProducts {
-        name
-        inStock
-      }
-    }
-
+    let schema_input = r#"
     type Query {
       topProducts: Product
       name: String
@@ -179,26 +178,33 @@ fn main() -> Result<()> {
       weight: Int
     }
     "#;
-
-    let ctx = ApolloCompiler::new(input);
-    let diagnostics = ctx.validate();
-    for diagnostic in &diagnostics {
-        println!("{}", diagnostic);
+    let query_input = r#"
+    query getProduct {
+      size
+      topProducts {
+        name
+        inStock
+      }
     }
-    assert!(diagnostics.is_empty());
+    "#;
 
-    let ctx = ApolloCompiler::new(input);
-    let diagnostics = ctx.validate();
+
+    let mut compiler = ApolloCompiler::new();
+    compiler.create_schema(schema_input, "schema.graphql");
+    let query_id = compiler.create_executable(query_input, "query.graphql");
+
+    let diagnostics = compiler.validate();
     for diagnostic in &diagnostics {
         println!("{}", diagnostic);
     }
     assert!(diagnostics.is_empty());
 
-    let operations = ctx.db.operations();
+    let operations = compiler.db.operations(query_id);
     let get_product_op = operations
         .iter()
-        .find(|op| op.name() == Some("getProduct")).expect("getProduct query does not exist");
-    let op_fields = get_product_op.fields(&ctx.db);
+        .find(|op| op.name() == Some("getProduct"))
+        .expect("getProduct query does not exist");
+    let op_fields = get_product_op.fields(&compiler.db);
 
     let in_stock_field = op_fields
         .iter()
@@ -207,7 +213,7 @@ fn main() -> Result<()> {
         .selection_set()
         .field("inStock")
         .expect("inStock field does not exist")
-        .field_definition(&ctx.db)
+        .field_definition(&compiler.db)
         .expect("field definition does not exist");
     let in_stock_directive: Vec<&str> = in_stock_field
         .directives()
@@ -279,8 +285,10 @@ type Cat implements Pet {
 union CatOrDog = Cat | Dog
 "#;
 
-let ctx = ApolloCompiler::new(input);
-let diagnostics = ctx.validate();
+let mut compiler = ApolloCompiler::new();
+compiler.create_document(input, "document.graphql");
+
+let diagnostics = compiler.validate();
 for diagnostic in &diagnostics {
     println!("{}", diagnostic)
 }
diff --git a/crates/apollo-compiler/examples/extend_db.rs b/crates/apollo-compiler/examples/extend_db.rs
index 86a7eef..cd2f86f 100644
--- a/crates/apollo-compiler/examples/extend_db.rs
+++ b/crates/apollo-compiler/examples/extend_db.rs
@@ -1,24 +1,40 @@
-use std::fmt;
+use std::{fmt, path::Path, sync::Arc};
 
 use apollo_compiler::{
     database::{AstStorage, DocumentStorage, HirStorage, InputStorage},
-    AstDatabase, DocumentDatabase, HirDatabase, InputDatabase,
+    AstDatabase, DocumentDatabase, FileId, HirDatabase, InputDatabase, 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,
+    next_file_id: u32,
 }
 
 impl Linter {
     /// Create a new instance of Linter.
-    pub fn new(input: &str) -> Self {
-        let mut db = LinterDatabase::default();
-        let input = input.to_string();
-        db.set_input(input);
-        Self { db }
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    pub fn document(&mut self, input: &str, path: impl AsRef<Path>) -> FileId {
+        let id = FileId(self.next_file_id);
+        self.next_file_id += 1;
+
+        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.
@@ -93,13 +109,13 @@ fn capitalised_definitions(db: &dyn LintValidation) -> Vec<LintDiagnostic> {
         .iter()
         .filter_map(|def| {
             if !def.name()?.chars().next()?.is_uppercase() {
-                if let Some(node) = def.name_src()?.ast_node(db.upcast()) {
-                    let offset: usize = node.text_range().start().into();
-                    let len: usize = node.text_range().len().into();
+                if let Some(loc) = def.name_src()?.loc() {
+                    let offset = loc.offset();
+                    let len = loc.node_len();
 
                     Some(LintDiagnostic::CapitalisedDefinitions(
                         CapitalisedDefinitions {
-                            src: db.input(),
+                            src: db.source_code(loc.file_id()),
                             definition: (offset, len).into(),
                         },
                     ))
@@ -145,7 +161,7 @@ impl fmt::Display for LintDiagnostic {
 #[diagnostic(code("graphql linter diagnostic"))]
 pub struct CapitalisedDefinitions {
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label = "capitalise this definition"]
     pub definition: SourceSpan,
@@ -172,7 +188,8 @@ type user {
 scalar url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
     "#;
 
-    let linter = Linter::new(input);
+    let mut linter = Linter::new();
+    linter.document(input, "document.graphql");
     let lints = linter.lint();
 
     // Display lints.
diff --git a/crates/apollo-compiler/examples/hello_world.rs b/crates/apollo-compiler/examples/hello_world.rs
index 86bd0a9..7de98f2 100644
--- a/crates/apollo-compiler/examples/hello_world.rs
+++ b/crates/apollo-compiler/examples/hello_world.rs
@@ -6,13 +6,14 @@ fn compile_query() -> Option<hir::FragmentDefinition> {
     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 ctx = ApolloCompiler::new(&src);
+    let mut compiler = ApolloCompiler::new();
+    let document_id = compiler.create_document(&src, file);
     // let errors = ctx.validate();
 
-    let operations = ctx.db.operations();
+    let operations = compiler.db.operations(document_id);
     let operation_names: Vec<_> = operations.iter().filter_map(|op| op.name()).collect();
     assert_eq!(["ExampleQuery"], operation_names.as_slice());
-    let frags = ctx.db.fragments();
+    let frags = compiler.db.fragments(document_id);
     let fragments: Vec<_> = frags.iter().map(|frag| frag.name()).collect();
     assert_eq!(["vipCustomer"], fragments.as_slice());
 
@@ -25,8 +26,9 @@ fn compile_query() -> Option<hir::FragmentDefinition> {
         .collect();
 
     assert_eq!(operation_variables, ["definedVariable"]);
-    ctx.db
-        .fragments()
+    compiler
+        .db
+        .fragments(document_id)
         .iter()
         .find(|op| op.name() == "vipCustomer")
         .cloned()
diff --git a/crates/apollo-compiler/src/database/ast.rs b/crates/apollo-compiler/src/database/ast.rs
index 1968c12..87268de 100644
--- a/crates/apollo-compiler/src/database/ast.rs
+++ b/crates/apollo-compiler/src/database/ast.rs
@@ -1,20 +1,26 @@
 use apollo_parser::{Parser as ApolloParser, SyntaxTree};
 use rowan::GreenNode;
 
-use crate::{database::inputs::InputDatabase, diagnostics::SyntaxError, ApolloDiagnostic};
+use crate::{database::inputs::InputDatabase, diagnostics::SyntaxError, ApolloDiagnostic, FileId};
 
 #[salsa::query_group(AstStorage)]
 pub trait AstDatabase: InputDatabase {
-    fn ast(&self) -> SyntaxTree;
+    /// Get an AST for a particular file. Returns a `rowan` SyntaxTree.  The
+    /// SyntaxTree can be safely shared between threads as it's `Send` and
+    /// `Sync`.
+    fn ast(&self, file_id: FileId) -> SyntaxTree;
 
-    // root node
-    fn document(&self) -> GreenNode;
+    /// Get a file's GraphQL Document. Returns a `rowan` Green Node. This is the
+    /// top level document node that can be used when going between an
+    /// SyntaxNodePtr to an actual SyntaxNode.
+    fn document(&self, file_id: FileId) -> GreenNode;
 
+    /// Get syntax errors found in the compiler's manifest.
     fn syntax_errors(&self) -> Vec<ApolloDiagnostic>;
 }
 
-fn ast(db: &dyn AstDatabase) -> SyntaxTree {
-    let input = db.input();
+fn ast(db: &dyn AstDatabase, file_id: FileId) -> SyntaxTree {
+    let input = db.source_code(file_id);
 
     let parser = ApolloParser::new(&input);
     let parser = if let Some(limit) = db.recursion_limit() {
@@ -25,20 +31,25 @@ fn ast(db: &dyn AstDatabase) -> SyntaxTree {
     parser.parse()
 }
 
-fn document(db: &dyn AstDatabase) -> GreenNode {
-    db.ast().green()
+fn document(db: &dyn AstDatabase, file_id: FileId) -> GreenNode {
+    db.ast(file_id).green()
 }
 
 fn syntax_errors(db: &dyn AstDatabase) -> Vec<ApolloDiagnostic> {
-    db.ast()
-        .errors()
+    db.source_files()
         .into_iter()
-        .map(|err| {
-            ApolloDiagnostic::SyntaxError(SyntaxError {
-                src: db.input(),
-                span: (err.index(), err.data().len()).into(), // (offset, length of error token)
-                message: err.message().into(),
-            })
+        .flat_map(|file_id| {
+            db.ast(file_id)
+                .errors()
+                .into_iter()
+                .map(|err| {
+                    ApolloDiagnostic::SyntaxError(SyntaxError {
+                        src: db.source_code(file_id),
+                        span: (err.index(), err.data().len()).into(), // (offset, length of error token)
+                        message: err.message().into(),
+                    })
+                })
+                .collect::<Vec<ApolloDiagnostic>>()
         })
         .collect()
 }
@@ -57,9 +68,10 @@ mod tests {
           }
         }
         "#;
-        let compiler = ApolloCompiler::with_recursion_limit(schema, 1);
+        let mut compiler = ApolloCompiler::with_recursion_limit(1);
+        let doc_id = compiler.create_document(schema, "schema.graphql");
 
-        let ast = compiler.db.ast();
+        let ast = compiler.db.ast(doc_id);
 
         assert_eq!(ast.recursion_limit().high, 2);
         assert_eq!(ast.errors().len(), 1);
@@ -75,9 +87,10 @@ mod tests {
           }
         }
         "#;
-        let compiler = ApolloCompiler::with_recursion_limit(schema, 7);
+        let mut compiler = ApolloCompiler::with_recursion_limit(7);
+        let doc_id = compiler.create_document(schema, "schema.graphql");
 
-        let ast = compiler.db.ast();
+        let ast = compiler.db.ast(doc_id);
 
         assert_eq!(ast.recursion_limit().high, 4);
         assert_eq!(ast.errors().len(), 0);
diff --git a/crates/apollo-compiler/src/database/document.rs b/crates/apollo-compiler/src/database/document.rs
index 4bb29a0..2986d8c 100644
--- a/crates/apollo-compiler/src/database/document.rs
+++ b/crates/apollo-compiler/src/database/document.rs
@@ -5,17 +5,25 @@ use std::{
 
 use uuid::Uuid;
 
-use crate::{hir::*, AstDatabase, HirDatabase, InputDatabase};
+use crate::{hir::*, AstDatabase, FileId, HirDatabase, InputDatabase};
 
 #[salsa::query_group(DocumentStorage)]
 pub trait DocumentDatabase: InputDatabase + AstDatabase + HirDatabase {
     fn find_operation(&self, id: Uuid) -> Option<Arc<OperationDefinition>>;
 
-    fn find_operation_by_name(&self, name: String) -> Option<Arc<OperationDefinition>>;
+    fn find_operation_by_name(
+        &self,
+        file_id: FileId,
+        name: String,
+    ) -> Option<Arc<OperationDefinition>>;
 
     fn find_fragment(&self, id: Uuid) -> Option<Arc<FragmentDefinition>>;
 
-    fn find_fragment_by_name(&self, name: String) -> Option<Arc<FragmentDefinition>>;
+    fn find_fragment_by_name(
+        &self,
+        file_id: FileId,
+        name: String,
+    ) -> Option<Arc<FragmentDefinition>>;
 
     fn find_object_type(&self, id: Uuid) -> Option<Arc<ObjectTypeDefinition>>;
 
@@ -45,11 +53,11 @@ pub trait DocumentDatabase: InputDatabase + AstDatabase + HirDatabase {
 
     fn find_type_system_definition_by_name(&self, name: String) -> Option<Arc<Definition>>;
 
-    fn query_operations(&self) -> Arc<Vec<OperationDefinition>>;
+    fn query_operations(&self, file_id: FileId) -> Arc<Vec<OperationDefinition>>;
 
-    fn mutation_operations(&self) -> Arc<Vec<OperationDefinition>>;
+    fn mutation_operations(&self, file_id: FileId) -> Arc<Vec<OperationDefinition>>;
 
-    fn subscription_operations(&self) -> Arc<Vec<OperationDefinition>>;
+    fn subscription_operations(&self, file_id: FileId) -> Arc<Vec<OperationDefinition>>;
 
     fn operation_fields(&self, id: Uuid) -> Arc<Vec<Field>>;
 
@@ -103,19 +111,22 @@ fn find_type_system_definition_by_name(
 }
 
 fn find_operation(db: &dyn DocumentDatabase, id: Uuid) -> Option<Arc<OperationDefinition>> {
-    db.operations().iter().find_map(|op| {
-        if &id == op.id() {
-            return Some(Arc::new(op.clone()));
-        }
-        None
+    db.executable_definition_files().iter().find_map(|file_id| {
+        db.operations(*file_id).iter().find_map(|op| {
+            if &id == op.id() {
+                return Some(Arc::new(op.clone()));
+            }
+            None
+        })
     })
 }
 
 fn find_operation_by_name(
     db: &dyn DocumentDatabase,
+    file_id: FileId,
     name: String,
 ) -> Option<Arc<OperationDefinition>> {
-    db.operations().iter().find_map(|op| {
+    db.operations(file_id).iter().find_map(|op| {
         if let Some(n) = op.name() {
             if n == name {
                 return Some(Arc::new(op.clone()));
@@ -126,19 +137,22 @@ fn find_operation_by_name(
 }
 
 fn find_fragment(db: &dyn DocumentDatabase, id: Uuid) -> Option<Arc<FragmentDefinition>> {
-    db.fragments().iter().find_map(|fragment| {
-        if &id == fragment.id() {
-            return Some(Arc::new(fragment.clone()));
-        }
-        None
+    db.executable_definition_files().iter().find_map(|file_id| {
+        db.fragments(*file_id).iter().find_map(|fragment| {
+            if &id == fragment.id() {
+                return Some(Arc::new(fragment.clone()));
+            }
+            None
+        })
     })
 }
 
 fn find_fragment_by_name(
     db: &dyn DocumentDatabase,
+    file_id: FileId,
     name: String,
 ) -> Option<Arc<FragmentDefinition>> {
-    db.fragments().iter().find_map(|fragment| {
+    db.fragments(file_id).iter().find_map(|fragment| {
         if name == fragment.name() {
             return Some(Arc::new(fragment.clone()));
         }
@@ -271,27 +285,33 @@ fn find_input_object_by_name(
     })
 }
 
-fn query_operations(db: &dyn DocumentDatabase) -> Arc<Vec<OperationDefinition>> {
+fn query_operations(db: &dyn DocumentDatabase, file_id: FileId) -> Arc<Vec<OperationDefinition>> {
     let operations = db
-        .operations()
+        .operations(file_id)
         .iter()
         .filter_map(|op| op.operation_ty.is_query().then(|| op.clone()))
         .collect();
     Arc::new(operations)
 }
 
-fn subscription_operations(db: &dyn DocumentDatabase) -> Arc<Vec<OperationDefinition>> {
+fn subscription_operations(
+    db: &dyn DocumentDatabase,
+    file_id: FileId,
+) -> Arc<Vec<OperationDefinition>> {
     let operations = db
-        .operations()
+        .operations(file_id)
         .iter()
         .filter_map(|op| op.operation_ty.is_subscription().then(|| op.clone()))
         .collect();
     Arc::new(operations)
 }
 
-fn mutation_operations(db: &dyn DocumentDatabase) -> Arc<Vec<OperationDefinition>> {
+fn mutation_operations(
+    db: &dyn DocumentDatabase,
+    file_id: FileId,
+) -> Arc<Vec<OperationDefinition>> {
     let operations = db
-        .operations()
+        .operations(file_id)
         .iter()
         .filter_map(|op| op.operation_ty.is_mutation().then(|| op.clone()))
         .collect();
@@ -396,7 +416,7 @@ fn operation_definition_variables(db: &dyn DocumentDatabase, id: Uuid) -> Arc<Ha
             .iter()
             .map(|v| Variable {
                 name: v.name().to_owned(),
-                ast_ptr: v.ast_ptr().clone(),
+                loc: *v.loc(),
             })
             .collect(),
         None => HashSet::new(),
@@ -484,8 +504,12 @@ mod tests {
             }
         "#;
 
-        let ctx = ApolloCompiler::new(schema);
-        let key_definitions = ctx.db.find_definitions_with_directive(String::from("key"));
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(schema, "schema.graphql");
+
+        let key_definitions = compiler
+            .db
+            .find_definitions_with_directive(String::from("key"));
         let key_definition_names: Vec<&str> = key_definitions
             .iter()
             .filter_map(|def| def.name())
@@ -515,7 +539,9 @@ mod tests {
                 "#,
             );
             let schema = format!("{}\n{}", base_schema, schema);
-            ApolloCompiler::new(&schema)
+            let mut compiler = ApolloCompiler::new();
+            compiler.create_document(&schema, "schema.graphql");
+            compiler
         }
 
         fn gen_schema_interfaces(schema: &str) -> ApolloCompiler {
@@ -539,7 +565,9 @@ mod tests {
                 "#,
             );
             let schema = format!("{}\n{}", base_schema, schema);
-            ApolloCompiler::new(&schema)
+            let mut compiler = ApolloCompiler::new();
+            compiler.create_document(&schema, "schema.graphql");
+            compiler
         }
 
         let ctx = gen_schema_types("union UnionType = Foo | Bar | Baz");
diff --git a/crates/apollo-compiler/src/database/hir.rs b/crates/apollo-compiler/src/database/hir.rs
index 2024c65..514751a 100644
--- a/crates/apollo-compiler/src/database/hir.rs
+++ b/crates/apollo-compiler/src/database/hir.rs
@@ -1,14 +1,13 @@
 use std::sync::Arc;
 
-use apollo_parser::{
-    ast::{self, SyntaxNodePtr},
-    SyntaxNode,
-};
+use apollo_parser::ast;
 use ordered_float::{self, OrderedFloat};
 use uuid::Uuid;
 
 use crate::DocumentDatabase;
 
+use super::FileId;
+
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 pub enum Definition {
     OperationDefinition(OperationDefinition),
@@ -276,7 +275,7 @@ pub struct FragmentDefinition {
     pub(crate) type_condition: String,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) selection_set: SelectionSet,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 // NOTE @lrlna: all the getter methods here return the exact types that are
@@ -333,15 +332,9 @@ impl FragmentDefinition {
         db.find_type_system_definition_by_name(self.name().to_string())
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get fragment definition's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -353,7 +346,7 @@ pub struct OperationDefinition {
     pub(crate) variables: Arc<Vec<VariableDefinition>>,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) selection_set: SelectionSet,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl OperationDefinition {
@@ -413,23 +406,20 @@ impl OperationDefinition {
     //
     // We will need to figure out how to store operation definition id on its
     // fragment spreads and inline fragments to do this
+
+    /// Get all fields in an inline fragment.
     pub fn fields_in_inline_fragments(&self, db: &dyn DocumentDatabase) -> Arc<Vec<Field>> {
         db.operation_inline_fragment_fields(self.id)
     }
 
+    /// Get all fields in a fragment spread
     pub fn fields_in_fragment_spread(&self, db: &dyn DocumentDatabase) -> Arc<Vec<Field>> {
         db.operation_fragment_spread_fields(self.id)
     }
 
-    /// Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    /// Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get operation definition's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -506,7 +496,7 @@ pub struct VariableDefinition {
     pub(crate) ty: Type,
     pub(crate) default_value: Option<DefaultValue>,
     pub(crate) directives: Arc<Vec<Directive>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl VariableDefinition {
@@ -530,15 +520,9 @@ impl VariableDefinition {
         self.directives.as_ref()
     }
 
-    /// Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    /// Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get variable definition's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -546,15 +530,15 @@ impl VariableDefinition {
 pub enum Type {
     NonNull {
         ty: Box<Type>,
-        ast_ptr: Option<SyntaxNodePtr>,
+        loc: Option<HirNodeLocation>,
     },
     List {
         ty: Box<Type>,
-        ast_ptr: Option<SyntaxNodePtr>,
+        loc: Option<HirNodeLocation>,
     },
     Named {
         name: String,
-        ast_ptr: Option<SyntaxNodePtr>,
+        loc: Option<HirNodeLocation>,
     },
 }
 
@@ -616,12 +600,12 @@ impl Type {
         }
     }
 
-    /// Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> Option<&SyntaxNodePtr> {
+    /// Get a reference to location information of the current HIR node.
+    pub fn loc(&self) -> Option<&HirNodeLocation> {
         match self {
-            Type::NonNull { ty: _, ast_ptr } => ast_ptr.as_ref(),
-            Type::List { ty: _, ast_ptr } => ast_ptr.as_ref(),
-            Type::Named { name: _, ast_ptr } => ast_ptr.as_ref(),
+            Type::NonNull { ty: _, loc } => loc.as_ref(),
+            Type::List { ty: _, loc } => loc.as_ref(),
+            Type::Named { name: _, loc } => loc.as_ref(),
         }
     }
 
@@ -629,34 +613,28 @@ impl Type {
         db.find_definition_by_name(self.name())
     }
 
-    /// Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> Option<SyntaxNode> {
-        self.ast_ptr()
-            .map(|ptr| ptr.to_node(&rowan::SyntaxNode::new_root(db.document())))
-    }
-
     pub fn name(&self) -> String {
         match self {
-            Type::NonNull { ty, ast_ptr: _ } => get_name(*ty.clone()),
-            Type::List { ty, ast_ptr: _ } => get_name(*ty.clone()),
-            Type::Named { name, ast_ptr: _ } => name.to_owned(),
+            Type::NonNull { ty, loc: _ } => get_name(*ty.clone()),
+            Type::List { ty, loc: _ } => get_name(*ty.clone()),
+            Type::Named { name, loc: _ } => name.to_owned(),
         }
     }
 }
 
 fn get_name(ty: Type) -> String {
     match ty {
-        Type::NonNull { ty, ast_ptr: _ } => match *ty {
-            Type::NonNull { ty, ast_ptr: _ } => get_name(*ty),
-            Type::List { ty, ast_ptr: _ } => get_name(*ty),
-            Type::Named { name, ast_ptr: _ } => name,
+        Type::NonNull { ty, loc: _ } => match *ty {
+            Type::NonNull { ty, loc: _ } => get_name(*ty),
+            Type::List { ty, loc: _ } => get_name(*ty),
+            Type::Named { name, loc: _ } => name,
         },
-        Type::List { ty, ast_ptr: _ } => match *ty {
-            Type::NonNull { ty, ast_ptr: _ } => get_name(*ty),
-            Type::List { ty, ast_ptr: _ } => get_name(*ty),
-            Type::Named { name, ast_ptr: _ } => name,
+        Type::List { ty, loc: _ } => match *ty {
+            Type::NonNull { ty, loc: _ } => get_name(*ty),
+            Type::List { ty, loc: _ } => get_name(*ty),
+            Type::Named { name, loc: _ } => name,
         },
-        Type::Named { name, ast_ptr: _ } => name,
+        Type::Named { name, loc: _ } => name,
     }
 }
 
@@ -664,7 +642,7 @@ fn get_name(ty: Type) -> String {
 pub struct Directive {
     pub(crate) name: Name,
     pub(crate) arguments: Arc<Vec<Argument>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl Directive {
@@ -693,15 +671,9 @@ impl Directive {
         db.find_directive_definition_by_name(self.name().to_string())
     }
 
-    /// Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    /// Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get directive's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -713,7 +685,7 @@ pub struct DirectiveDefinition {
     pub(crate) arguments: ArgumentsDefinition,
     pub(crate) repeatable: bool,
     pub(crate) directive_locations: Arc<Vec<DirectiveLocation>>,
-    pub(crate) ast_ptr: Option<SyntaxNodePtr>,
+    pub(crate) loc: Option<HirNodeLocation>,
 }
 
 impl DirectiveDefinition {
@@ -752,15 +724,9 @@ impl DirectiveDefinition {
         self.repeatable
     }
 
-    /// Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> Option<&SyntaxNodePtr> {
-        self.ast_ptr.as_ref()
-    }
-
-    /// Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> Option<SyntaxNode> {
-        self.ast_ptr()
-            .map(|ptr| ptr.to_node(&rowan::SyntaxNode::new_root(db.document())))
+    /// Get directive definition's hir node location.
+    pub fn loc(&self) -> Option<&HirNodeLocation> {
+        self.loc.as_ref()
     }
 }
 
@@ -861,7 +827,7 @@ impl From<DirectiveLocation> for String {
 pub struct Argument {
     pub(crate) name: Name,
     pub(crate) value: Value,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl Argument {
@@ -875,15 +841,9 @@ impl Argument {
         self.name.src()
     }
 
-    /// Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    /// Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get argument's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -997,7 +957,7 @@ pub enum IntCoercionError {
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub struct Variable {
     pub(crate) name: String,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl Variable {
@@ -1006,15 +966,9 @@ impl Variable {
         self.name.as_ref()
     }
 
-    /// Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    /// Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get variable's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -1138,7 +1092,7 @@ pub struct Field {
     pub(crate) parent_obj: Option<String>,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) selection_set: SelectionSet,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl Field {
@@ -1155,7 +1109,7 @@ impl Field {
         self.name.src()
     }
 
-    // Get a reference to field's type.
+    /// Get a reference to field's type.
     pub fn ty(&self, db: &dyn DocumentDatabase) -> Option<Type> {
         let def = db
             .find_type_system_definition_by_name(self.parent_obj.as_ref()?.to_string())?
@@ -1165,7 +1119,7 @@ impl Field {
         Some(def)
     }
 
-    // Get field's original field definition.
+    /// Get field's original field definition.
     pub fn field_definition(&self, db: &dyn DocumentDatabase) -> Option<FieldDefinition> {
         db.find_object_type_by_name(self.parent_obj.as_ref()?.to_string())?
             .fields_definition()
@@ -1208,20 +1162,10 @@ impl Field {
         vars
     }
 
-    /// Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    /// Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get field's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
-
-    // pub fn field_definition(&self, db: &dyn Document) -> Option<Arc<FieldDefinition>> {
-    //     db.get
-    // }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
@@ -1229,7 +1173,7 @@ pub struct InlineFragment {
     pub(crate) type_condition: Option<Name>,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) selection_set: SelectionSet,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl InlineFragment {
@@ -1248,6 +1192,7 @@ impl InlineFragment {
         &self.selection_set
     }
 
+    /// Get variables in use in the inline fragment.
     pub fn variables(&self, db: &dyn DocumentDatabase) -> Vec<Variable> {
         let vars = self
             .selection_set
@@ -1258,15 +1203,9 @@ impl InlineFragment {
         vars
     }
 
-    /// Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    /// Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get inline fragment's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -1274,7 +1213,7 @@ impl InlineFragment {
 pub struct FragmentSpread {
     pub(crate) name: Name,
     pub(crate) directives: Arc<Vec<Directive>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl FragmentSpread {
@@ -1283,12 +1222,12 @@ impl FragmentSpread {
         self.name.src()
     }
 
-    // Get the fragment definition this fragment spread is referencing.
+    /// Get the fragment definition this fragment spread is referencing.
     pub fn fragment(&self, db: &dyn DocumentDatabase) -> Option<Arc<FragmentDefinition>> {
-        db.find_fragment_by_name(self.name().to_string())
+        db.find_fragment_by_name(self.loc.file_id(), self.name().to_string())
     }
 
-    // Get framgent spread's defined variables.
+    /// Get framgent spread's defined variables.
     pub fn variables(&self, db: &dyn DocumentDatabase) -> Vec<Variable> {
         let vars = match self.fragment(db) {
             Some(fragment) => fragment
@@ -1307,15 +1246,9 @@ impl FragmentSpread {
         self.directives.as_ref()
     }
 
-    /// Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    /// Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get fragment spread's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -1358,7 +1291,7 @@ pub struct SchemaDefinition {
     pub(crate) description: Option<String>,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) root_operation_type_definition: Arc<Vec<RootOperationTypeDefinition>>,
-    pub(crate) ast_ptr: Option<SyntaxNodePtr>,
+    pub(crate) loc: Option<HirNodeLocation>,
 }
 
 impl SchemaDefinition {
@@ -1379,15 +1312,9 @@ impl SchemaDefinition {
             .push(op)
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> Option<&SyntaxNodePtr> {
-        self.ast_ptr.as_ref()
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> Option<SyntaxNode> {
-        self.ast_ptr()
-            .map(|ptr| ptr.to_node(&rowan::SyntaxNode::new_root(db.document())))
+    /// Get schema definition's hir node location.
+    pub fn loc(&self) -> Option<&HirNodeLocation> {
+        self.loc.as_ref()
     }
 
     // NOTE(@lrlna): potentially have the following fns on the database itself
@@ -1431,7 +1358,7 @@ impl SchemaDefinition {
 pub struct RootOperationTypeDefinition {
     pub(crate) operation_type: OperationType,
     pub(crate) named_type: Type,
-    pub(crate) ast_ptr: Option<SyntaxNodePtr>,
+    pub(crate) loc: Option<HirNodeLocation>,
 }
 
 impl RootOperationTypeDefinition {
@@ -1453,15 +1380,9 @@ impl RootOperationTypeDefinition {
         self.object_type(db).map(|object_type| *object_type.id())
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> Option<&SyntaxNodePtr> {
-        self.ast_ptr.as_ref()
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> Option<SyntaxNode> {
-        self.ast_ptr()
-            .map(|ptr| ptr.to_node(&rowan::SyntaxNode::new_root(db.document())))
+    /// Get root operation type definition's hir node location.
+    pub fn loc(&self) -> Option<&HirNodeLocation> {
+        self.loc.as_ref()
     }
 }
 
@@ -1471,9 +1392,9 @@ impl Default for RootOperationTypeDefinition {
             operation_type: OperationType::Query,
             named_type: Type::Named {
                 name: "Query".to_string(),
-                ast_ptr: None,
+                loc: None,
             },
-            ast_ptr: None,
+            loc: None,
         }
     }
 }
@@ -1486,7 +1407,7 @@ pub struct ObjectTypeDefinition {
     pub(crate) implements_interfaces: Arc<Vec<ImplementsInterface>>,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) fields_definition: Arc<Vec<FieldDefinition>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl ObjectTypeDefinition {
@@ -1530,25 +1451,20 @@ impl ObjectTypeDefinition {
         self.implements_interfaces.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get object type definition's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub struct ImplementsInterface {
     pub(crate) interface: Name,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl ImplementsInterface {
+    /// Get the interface this implements interface is referencing.
     pub fn interface_definition(
         &self,
         db: &dyn DocumentDatabase,
@@ -1556,19 +1472,14 @@ impl ImplementsInterface {
         db.find_interface_by_name(self.interface().to_string())
     }
 
+    /// Get implements interfaces' interface name.
     pub fn interface(&self) -> &str {
         self.interface.src()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get implements interfaces's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -1579,7 +1490,7 @@ pub struct FieldDefinition {
     pub(crate) arguments: ArgumentsDefinition,
     pub(crate) ty: Type,
     pub(crate) directives: Arc<Vec<Directive>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl FieldDefinition {
@@ -1593,28 +1504,22 @@ impl FieldDefinition {
         self.description.as_deref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
     /// Get a reference to the field's directives.
     pub fn directives(&self) -> &[Directive] {
         self.directives.as_ref()
     }
 
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get field definition's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 
-    // Get a reference to field definition's type.
+    /// Get a reference to field definition's type.
     pub fn ty(&self) -> &Type {
         &self.ty
     }
 
-    // Get a reference to field definition's arguments
+    /// Get a reference to field definition's arguments
     pub fn arguments(&self) -> &ArgumentsDefinition {
         &self.arguments
     }
@@ -1623,7 +1528,7 @@ impl FieldDefinition {
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub struct ArgumentsDefinition {
     pub(crate) input_values: Arc<Vec<InputValueDefinition>>,
-    pub(crate) ast_ptr: Option<SyntaxNodePtr>,
+    pub(crate) loc: Option<HirNodeLocation>,
 }
 
 impl ArgumentsDefinition {
@@ -1632,15 +1537,9 @@ impl ArgumentsDefinition {
         self.input_values.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> Option<&SyntaxNodePtr> {
-        self.ast_ptr.as_ref()
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> Option<SyntaxNode> {
-        self.ast_ptr()
-            .map(|ptr| ptr.to_node(&rowan::SyntaxNode::new_root(db.document())))
+    /// Get arguments definition's hir node location.
+    pub fn loc(&self) -> Option<&HirNodeLocation> {
+        self.loc.as_ref()
     }
 }
 
@@ -1651,7 +1550,7 @@ pub struct InputValueDefinition {
     pub(crate) ty: Type,
     pub(crate) default_value: Option<DefaultValue>,
     pub(crate) directives: Arc<Vec<Directive>>,
-    pub(crate) ast_ptr: Option<SyntaxNodePtr>,
+    pub(crate) loc: Option<HirNodeLocation>,
 }
 
 impl InputValueDefinition {
@@ -1670,15 +1569,9 @@ impl InputValueDefinition {
         self.directives.as_ref()
     }
 
-    /// Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> Option<&SyntaxNodePtr> {
-        self.ast_ptr.as_ref()
-    }
-
-    /// Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> Option<SyntaxNode> {
-        self.ast_ptr()
-            .map(|ptr| ptr.to_node(&rowan::SyntaxNode::new_root(db.document())))
+    /// Get input value definition's hir node location.
+    pub fn loc(&self) -> Option<&HirNodeLocation> {
+        self.loc.as_ref()
     }
 
     /// Get a reference to input value definition's type.
@@ -1698,8 +1591,8 @@ pub struct ScalarTypeDefinition {
     pub(crate) description: Option<String>,
     pub(crate) name: Name,
     pub(crate) directives: Arc<Vec<Directive>>,
-    pub(crate) ast_ptr: Option<SyntaxNodePtr>,
     pub(crate) built_in: bool,
+    pub(crate) loc: Option<HirNodeLocation>,
 }
 
 impl ScalarTypeDefinition {
@@ -1728,21 +1621,15 @@ impl ScalarTypeDefinition {
         self.directives.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> Option<&SyntaxNodePtr> {
-        self.ast_ptr.as_ref()
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> Option<SyntaxNode> {
-        self.ast_ptr()
-            .map(|ptr| ptr.to_node(&rowan::SyntaxNode::new_root(db.document())))
-    }
-
     /// Returns true if the current scalar is a GraphQL built in.
     pub fn is_built_in(&self) -> bool {
         self.built_in
     }
+
+    /// Get scalar type definition's hir node location.
+    pub fn loc(&self) -> Option<&HirNodeLocation> {
+        self.loc.as_ref()
+    }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
@@ -1752,7 +1639,7 @@ pub struct EnumTypeDefinition {
     pub(crate) name: Name,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) enum_values_definition: Arc<Vec<EnumValueDefinition>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl EnumTypeDefinition {
@@ -1786,15 +1673,9 @@ impl EnumTypeDefinition {
         self.enum_values_definition.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get enum type definition's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -1803,7 +1684,7 @@ pub struct EnumValueDefinition {
     pub(crate) description: Option<String>,
     pub(crate) enum_value: Name,
     pub(crate) directives: Arc<Vec<Directive>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl EnumValueDefinition {
@@ -1817,15 +1698,9 @@ impl EnumValueDefinition {
         self.directives.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get enum value definition's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -1836,7 +1711,7 @@ pub struct UnionTypeDefinition {
     pub(crate) name: Name,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) union_members: Arc<Vec<UnionMember>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl UnionTypeDefinition {
@@ -1870,22 +1745,16 @@ impl UnionTypeDefinition {
         self.union_members.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get union type definition's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub struct UnionMember {
     pub(crate) name: Name,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl UnionMember {
@@ -1894,19 +1763,14 @@ impl UnionMember {
         self.name.src()
     }
 
+    /// Get the object definition this union member is referencing.
     pub fn object(&self, db: &dyn DocumentDatabase) -> Option<Arc<ObjectTypeDefinition>> {
         db.find_object_type_by_name(self.name().to_string())
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get union member's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -1918,7 +1782,7 @@ pub struct InterfaceTypeDefinition {
     pub(crate) implements_interfaces: Arc<Vec<ImplementsInterface>>,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) fields_definition: Arc<Vec<FieldDefinition>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl InterfaceTypeDefinition {
@@ -1962,15 +1826,9 @@ impl InterfaceTypeDefinition {
         self.fields_definition().iter().find(|f| f.name() == name)
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get interface type definition's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -1981,7 +1839,7 @@ pub struct InputObjectTypeDefinition {
     pub(crate) name: Name,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) input_fields_definition: Arc<Vec<InputValueDefinition>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl InputObjectTypeDefinition {
@@ -2010,42 +1868,32 @@ impl InputObjectTypeDefinition {
         self.directives.as_ref()
     }
 
+    /// Get a reference to input fields definitions.
     pub fn input_fields_definition(&self) -> &[InputValueDefinition] {
         self.input_fields_definition.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get input object type definition's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub struct Name {
     pub(crate) src: String,
-    pub(crate) ast_ptr: Option<SyntaxNodePtr>,
+    pub(crate) loc: Option<HirNodeLocation>,
 }
 
 impl Name {
+    /// Get a reference to the name itself.
     pub fn src(&self) -> &str {
         self.src.as_ref()
     }
 
-    /// Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> Option<&SyntaxNodePtr> {
-        self.ast_ptr.as_ref()
-    }
-
-    /// Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> Option<SyntaxNode> {
-        self.ast_ptr()
-            .map(|ptr| ptr.to_node(&rowan::SyntaxNode::new_root(db.document())))
+    /// Get input object type definition's hir node location.
+    pub fn loc(&self) -> Option<&HirNodeLocation> {
+        self.loc.as_ref()
     }
 }
 
@@ -2059,7 +1907,7 @@ impl From<String> for Name {
     fn from(name: String) -> Name {
         Name {
             src: name,
-            ast_ptr: None,
+            loc: None,
         }
     }
 }
@@ -2068,7 +1916,7 @@ impl From<String> for Name {
 pub struct SchemaExtension {
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) root_operation_type_definition: Arc<Vec<RootOperationTypeDefinition>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl SchemaExtension {
@@ -2082,15 +1930,9 @@ impl SchemaExtension {
         self.root_operation_type_definition.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        self.ast_ptr
-            .to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get schema extension's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -2098,7 +1940,7 @@ impl SchemaExtension {
 pub struct ScalarTypeExtension {
     pub(crate) name: Name,
     pub(crate) directives: Arc<Vec<Directive>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl ScalarTypeExtension {
@@ -2117,15 +1959,9 @@ impl ScalarTypeExtension {
         self.directives.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        self.ast_ptr
-            .to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get scalar type extension's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -2135,7 +1971,7 @@ pub struct ObjectTypeExtension {
     pub(crate) implements_interfaces: Arc<Vec<ImplementsInterface>>,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) fields_definition: Arc<Vec<FieldDefinition>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl ObjectTypeExtension {
@@ -2168,15 +2004,9 @@ impl ObjectTypeExtension {
         self.implements_interfaces.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get object type extension's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -2186,7 +2016,7 @@ pub struct InterfaceTypeExtension {
     pub(crate) implements_interfaces: Arc<Vec<ImplementsInterface>>,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) fields_definition: Arc<Vec<FieldDefinition>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl InterfaceTypeExtension {
@@ -2220,15 +2050,9 @@ impl InterfaceTypeExtension {
         self.fields_definition().iter().find(|f| f.name() == name)
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get interface type extension's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -2237,7 +2061,7 @@ pub struct UnionTypeExtension {
     pub(crate) name: Name,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) union_members: Arc<Vec<UnionMember>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl UnionTypeExtension {
@@ -2261,15 +2085,9 @@ impl UnionTypeExtension {
         self.union_members.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get union type extension's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -2278,7 +2096,7 @@ pub struct EnumTypeExtension {
     pub(crate) name: Name,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) enum_values_definition: Arc<Vec<EnumValueDefinition>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl EnumTypeExtension {
@@ -2302,15 +2120,9 @@ impl EnumTypeExtension {
         self.enum_values_definition.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
-    }
-
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+    /// Get enum type extension's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
 }
 
@@ -2319,7 +2131,7 @@ pub struct InputObjectTypeExtension {
     pub(crate) name: Name,
     pub(crate) directives: Arc<Vec<Directive>>,
     pub(crate) input_fields_definition: Arc<Vec<InputValueDefinition>>,
-    pub(crate) ast_ptr: SyntaxNodePtr,
+    pub(crate) loc: HirNodeLocation,
 }
 
 impl InputObjectTypeExtension {
@@ -2342,15 +2154,33 @@ impl InputObjectTypeExtension {
         self.input_fields_definition.as_ref()
     }
 
-    // Get a reference to SyntaxNodePtr of the current HIR node.
-    pub fn ast_ptr(&self) -> &SyntaxNodePtr {
-        &self.ast_ptr
+    /// Get input object type extension's hir node location.
+    pub fn loc(&self) -> &HirNodeLocation {
+        &self.loc
     }
+}
 
-    // Get current HIR node's AST node.
-    pub fn ast_node(&self, db: &dyn DocumentDatabase) -> SyntaxNode {
-        let syntax_node_ptr = self.ast_ptr();
-        syntax_node_ptr.to_node(&rowan::SyntaxNode::new_root(db.document()))
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
+pub struct HirNodeLocation {
+    pub(crate) offset: usize,
+    pub(crate) node_len: usize,
+    pub(crate) file_id: FileId,
+}
+
+impl HirNodeLocation {
+    /// Get file id of the current node.
+    pub fn file_id(&self) -> FileId {
+        self.file_id
+    }
+
+    /// Get source offset of the current node.
+    pub fn offset(&self) -> usize {
+        self.offset
+    }
+
+    /// Get node length.
+    pub fn node_len(&self) -> usize {
+        self.node_len
     }
 }
 
@@ -2361,14 +2191,17 @@ mod tests {
 
     #[test]
     fn huge_floats() {
-        let compiler = ApolloCompiler::new(
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_schema(
             "input HugeFloats {
                 a: Float = 9876543210
                 b: Float = 9876543210.0
                 c: Float = 98765432109876543210
                 d: Float = 98765432109876543210.0
             }",
+            "huge_floats.graphql",
         );
+
         let default_values: Vec<_> = compiler
             .db
             .find_input_object_by_name("HugeFloats".into())
@@ -2392,13 +2225,15 @@ mod tests {
 
     #[test]
     fn syntax_errors() {
-        let compiler = ApolloCompiler::new(
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_schema(
             "type Person {
                 id: ID!
                 name: String
                 appearedIn: [Film]s
                 directed: [Film]
             }",
+            "person.graphql",
         );
         let person = compiler
             .db
diff --git a/crates/apollo-compiler/src/database/hir_db.rs b/crates/apollo-compiler/src/database/hir_db.rs
index 7699210..537c0dd 100644
--- a/crates/apollo-compiler/src/database/hir_db.rs
+++ b/crates/apollo-compiler/src/database/hir_db.rs
@@ -1,9 +1,12 @@
 use std::sync::Arc;
 
-use apollo_parser::ast::{self, AstChildren, AstNode, SyntaxNodePtr};
+use apollo_parser::{
+    ast::{self, AstChildren, AstNode},
+    SyntaxNode,
+};
 use uuid::Uuid;
 
-use crate::{hir::*, AstDatabase, InputDatabase};
+use crate::{database::FileId, hir::*, AstDatabase, InputDatabase};
 
 // HIR creators *ignore* missing data entirely. *Only* missing data
 // as a result of parser errors should be ignored.
@@ -12,13 +15,21 @@ use crate::{hir::*, AstDatabase, InputDatabase};
 pub trait HirDatabase: InputDatabase + AstDatabase {
     // fn definitions(&self) -> Arc<Vec<ast::Definition>>;
 
+    /// Return all definitions known to the compiler.
+    ///
+    /// This includes all type system definitions and all executable definitions
+    /// from all files. If multiple executable documents are known to the compiler,
+    /// there may be duplicate executable definitions that are still valid.
     fn db_definitions(&self) -> Arc<Vec<Definition>>;
 
+    /// Return the type system definitions.
     fn type_system_definitions(&self) -> Arc<Vec<Definition>>;
 
-    fn operations(&self) -> Arc<Vec<OperationDefinition>>;
+    /// Return all the operations defined in a file.
+    fn operations(&self, file_id: FileId) -> Arc<Vec<OperationDefinition>>;
 
-    fn fragments(&self) -> Arc<Vec<FragmentDefinition>>;
+    /// Return all the fragments defined in a file.
+    fn fragments(&self, file_id: FileId) -> Arc<Vec<FragmentDefinition>>;
 
     fn schema(&self) -> Arc<SchemaDefinition>;
 
@@ -54,19 +65,19 @@ pub trait HirDatabase: InputDatabase + AstDatabase {
 fn db_definitions(db: &dyn HirDatabase) -> Arc<Vec<Definition>> {
     let mut definitions = Vec::clone(&*db.type_system_definitions());
 
-    let operations: Vec<Definition> = db
-        .operations()
-        .iter()
-        .map(|def| Definition::OperationDefinition(def.clone()))
-        .collect();
-    let fragments: Vec<Definition> = db
-        .fragments()
-        .iter()
-        .map(|def| Definition::FragmentDefinition(def.clone()))
-        .collect();
-
-    definitions.extend(operations);
-    definitions.extend(fragments);
+    // collect *all* executable definitions.
+    for file_id in db.executable_definition_files() {
+        definitions.extend(
+            db.operations(file_id)
+                .iter()
+                .map(|def| Definition::OperationDefinition(def.clone())),
+        );
+        definitions.extend(
+            db.fragments(file_id)
+                .iter()
+                .map(|def| Definition::FragmentDefinition(def.clone())),
+        );
+    }
 
     Arc::new(definitions)
 }
@@ -165,47 +176,66 @@ fn type_system_definitions(db: &dyn HirDatabase) -> Arc<Vec<Definition>> {
     Arc::new(definitions)
 }
 
-fn operations(db: &dyn HirDatabase) -> Arc<Vec<OperationDefinition>> {
+fn operations(db: &dyn HirDatabase, file_id: FileId) -> Arc<Vec<OperationDefinition>> {
     let operations = db
-        .ast()
+        .ast(file_id)
         .document()
         .definitions()
         .into_iter()
         .filter_map(|definition| match definition {
-            ast::Definition::OperationDefinition(op_def) => Some(operation_definition(db, op_def)),
+            ast::Definition::OperationDefinition(op_def) => {
+                Some(operation_definition(db, op_def, file_id))
+            }
             _ => None,
         })
-        .collect();
+        .collect::<Vec<OperationDefinition>>();
+
     Arc::new(operations)
 }
 
-fn fragments(db: &dyn HirDatabase) -> Arc<Vec<FragmentDefinition>> {
+fn fragments(db: &dyn HirDatabase, file_id: FileId) -> Arc<Vec<FragmentDefinition>> {
     let fragments = db
-        .ast()
+        .ast(file_id)
         .document()
         .definitions()
         .into_iter()
         .filter_map(|definition| match definition {
             ast::Definition::FragmentDefinition(fragment_def) => {
-                fragment_definition(db, fragment_def)
+                fragment_definition(db, fragment_def, file_id)
             }
             _ => None,
         })
-        .collect();
+        .collect::<Vec<FragmentDefinition>>();
+
     Arc::new(fragments)
 }
 
+/// Return an iterator over all type definition AST nodes in all type definition files.
+fn all_type_definitions(
+    db: &dyn HirDatabase,
+) -> impl Iterator<Item = (FileId, ast::Definition)> + '_ {
+    db.type_definition_files().into_iter().flat_map(|file_id| {
+        db.ast(file_id)
+            .document()
+            .definitions()
+            .map(move |definition| (file_id, definition))
+    })
+}
+
+// FIXME(@lrlna): if our compiler is composed of multiple documents that for
+// some reason have more than one schema definition, we should be raising an
+// error.
+//
+// This implementation currently just finds the first schema definition, which
+// means we can't really diagnose the "multiple schema definitions" errors.
 fn schema(db: &dyn HirDatabase) -> Arc<SchemaDefinition> {
-    let schema = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .find_map(|definition| match definition {
-            ast::Definition::SchemaDefinition(schema) => Some(schema),
+    let schema: Option<(FileId, ast::SchemaDefinition)> =
+        all_type_definitions(db).find_map(|(id, definition)| match definition {
+            ast::Definition::SchemaDefinition(schema) => Some((id, schema)),
             _ => None,
         });
-    let mut schema_def = schema.map_or(SchemaDefinition::default(), schema_definition);
+    let mut schema_def =
+        schema.map_or(SchemaDefinition::default(), |s| schema_definition(s.1, s.0));
 
     // NOTE(@lrlna):
     //
@@ -223,14 +253,43 @@ fn schema(db: &dyn HirDatabase) -> Arc<SchemaDefinition> {
     Arc::new(schema_def)
 }
 
+fn schema_extensions(db: &dyn HirDatabase) -> Arc<Vec<SchemaExtension>> {
+    let schema_ext = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
+            ast::Definition::SchemaExtension(def) => Some(SchemaExtension {
+                directives: directives(def.directives(), id),
+                root_operation_type_definition: root_operation_type_definition(
+                    def.root_operation_type_definitions(),
+                    id,
+                ),
+                loc: location(id, def.syntax()),
+            }),
+            _ => None,
+        })
+        .collect();
+    Arc::new(schema_ext)
+}
+
 fn object_types(db: &dyn HirDatabase) -> Arc<Vec<ObjectTypeDefinition>> {
-    let objects = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::ObjectTypeDefinition(obj_def) => object_type_definition(obj_def),
+    let objects = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
+            ast::Definition::ObjectTypeDefinition(obj_def) => object_type_definition(obj_def, id),
+            _ => None,
+        })
+        .collect();
+    Arc::new(objects)
+}
+
+fn object_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<ObjectTypeExtension>> {
+    let objects = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
+            ast::Definition::ObjectTypeExtension(def) => Some(ObjectTypeExtension {
+                directives: directives(def.directives(), id),
+                name: name(def.name(), id)?,
+                implements_interfaces: implements_interfaces(def.implements_interfaces(), id),
+                fields_definition: fields_definition(def.fields_definition(), id),
+                loc: location(id, def.syntax()),
+            }),
             _ => None,
         })
         .collect();
@@ -238,13 +297,9 @@ fn object_types(db: &dyn HirDatabase) -> Arc<Vec<ObjectTypeDefinition>> {
 }
 
 fn scalars(db: &dyn HirDatabase) -> Arc<Vec<ScalarTypeDefinition>> {
-    let scalars = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::ScalarTypeDefinition(scalar_def) => scalar_definition(scalar_def),
+    let scalars = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
+            ast::Definition::ScalarTypeDefinition(scalar_def) => scalar_definition(scalar_def, id),
             _ => None,
         })
         .collect();
@@ -253,14 +308,39 @@ fn scalars(db: &dyn HirDatabase) -> Arc<Vec<ScalarTypeDefinition>> {
     Arc::new(scalars)
 }
 
+fn scalar_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<ScalarTypeExtension>> {
+    let scalars = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
+            ast::Definition::ScalarTypeExtension(def) => Some(ScalarTypeExtension {
+                directives: directives(def.directives(), id),
+                name: name(def.name(), id)?,
+                loc: location(id, def.syntax()),
+            }),
+            _ => None,
+        })
+        .collect();
+    Arc::new(scalars)
+}
+
 fn enums(db: &dyn HirDatabase) -> Arc<Vec<EnumTypeDefinition>> {
-    let enums = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::EnumTypeDefinition(enum_def) => enum_definition(enum_def),
+    let enums = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
+            ast::Definition::EnumTypeDefinition(enum_def) => enum_definition(enum_def, id),
+            _ => None,
+        })
+        .collect();
+    Arc::new(enums)
+}
+
+fn enum_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<EnumTypeExtension>> {
+    let enums = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
+            ast::Definition::EnumTypeExtension(def) => Some(EnumTypeExtension {
+                directives: directives(def.directives(), id),
+                name: name(def.name(), id)?,
+                enum_values_definition: enum_values_definition(def.enum_values_definition(), id),
+                loc: location(id, def.syntax()),
+            }),
             _ => None,
         })
         .collect();
@@ -268,13 +348,24 @@ fn enums(db: &dyn HirDatabase) -> Arc<Vec<EnumTypeDefinition>> {
 }
 
 fn unions(db: &dyn HirDatabase) -> Arc<Vec<UnionTypeDefinition>> {
-    let unions = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::UnionTypeDefinition(union_def) => union_definition(union_def),
+    let unions = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
+            ast::Definition::UnionTypeDefinition(union_def) => union_definition(union_def, id),
+            _ => None,
+        })
+        .collect();
+    Arc::new(unions)
+}
+
+fn union_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<UnionTypeExtension>> {
+    let unions = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
+            ast::Definition::UnionTypeExtension(def) => Some(UnionTypeExtension {
+                directives: directives(def.directives(), id),
+                name: name(def.name(), id)?,
+                union_members: union_members(def.union_member_types(), id),
+                loc: location(id, def.syntax()),
+            }),
             _ => None,
         })
         .collect();
@@ -282,14 +373,10 @@ fn unions(db: &dyn HirDatabase) -> Arc<Vec<UnionTypeDefinition>> {
 }
 
 fn interfaces(db: &dyn HirDatabase) -> Arc<Vec<InterfaceTypeDefinition>> {
-    let interfaces = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
+    let interfaces = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
             ast::Definition::InterfaceTypeDefinition(interface_def) => {
-                interface_definition(interface_def)
+                interface_definition(interface_def, id)
             }
             _ => None,
         })
@@ -297,15 +384,27 @@ fn interfaces(db: &dyn HirDatabase) -> Arc<Vec<InterfaceTypeDefinition>> {
     Arc::new(interfaces)
 }
 
+fn interface_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<InterfaceTypeExtension>> {
+    let interfaces = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
+            ast::Definition::InterfaceTypeExtension(def) => Some(InterfaceTypeExtension {
+                directives: directives(def.directives(), id),
+                name: name(def.name(), id)?,
+                implements_interfaces: implements_interfaces(def.implements_interfaces(), id),
+                fields_definition: fields_definition(def.fields_definition(), id),
+                loc: location(id, def.syntax()),
+            }),
+            _ => None,
+        })
+        .collect();
+    Arc::new(interfaces)
+}
+
 fn directive_definitions(db: &dyn HirDatabase) -> Arc<Vec<DirectiveDefinition>> {
-    let directives = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
+    let directives = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
             ast::Definition::DirectiveDefinition(directive_def) => {
-                directive_definition(directive_def)
+                directive_definition(directive_def, id)
             }
             _ => None,
         })
@@ -317,32 +416,44 @@ fn directive_definitions(db: &dyn HirDatabase) -> Arc<Vec<DirectiveDefinition>>
 }
 
 fn input_objects(db: &dyn HirDatabase) -> Arc<Vec<InputObjectTypeDefinition>> {
-    let directives = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
+    let input_objs = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
             ast::Definition::InputObjectTypeDefinition(input_obj) => {
-                input_object_definition(input_obj)
+                input_object_definition(input_obj, id)
             }
             _ => None,
         })
         .collect();
 
-    Arc::new(directives)
+    Arc::new(input_objs)
+}
+
+fn input_object_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<InputObjectTypeExtension>> {
+    let input_objs = all_type_definitions(db)
+        .filter_map(|(id, definition)| match definition {
+            ast::Definition::InputObjectTypeExtension(def) => Some(InputObjectTypeExtension {
+                directives: directives(def.directives(), id),
+                name: name(def.name(), id)?,
+                input_fields_definition: input_fields_definition(def.input_fields_definition(), id),
+                loc: location(id, def.syntax()),
+            }),
+            _ => None,
+        })
+        .collect();
+    Arc::new(input_objs)
 }
 
 fn operation_definition(
     db: &dyn HirDatabase,
     op_def: ast::OperationDefinition,
+    file_id: FileId,
 ) -> OperationDefinition {
     // check if there are already operations
     // if there are operations, they must have names
     // if there are no names, an error must be raised that all operations must have a name
-    let name = op_def.name().map(name_hir_node);
+    let name = op_def.name().map(|n| name_hir_node(n, file_id));
     let ty = operation_type(op_def.operation_type());
-    let variables = variable_definitions(op_def.variable_definitions());
+    let variables = variable_definitions(op_def.variable_definitions(), file_id);
     let parent_object_ty = db
         .schema()
         .root_operation_type_definition()
@@ -354,9 +465,9 @@ fn operation_definition(
                 None
             }
         });
-    let selection_set = selection_set(db, op_def.selection_set(), parent_object_ty);
-    let directives = directives(op_def.directives());
-    let ast_ptr = SyntaxNodePtr::new(op_def.syntax());
+    let selection_set = selection_set(db, op_def.selection_set(), parent_object_ty, file_id);
+    let directives = directives(op_def.directives(), file_id);
+    let loc = location(file_id, op_def.syntax());
 
     OperationDefinition {
         id: Uuid::new_v4(),
@@ -365,15 +476,16 @@ fn operation_definition(
         variables,
         selection_set,
         directives,
-        ast_ptr,
+        loc,
     }
 }
 
 fn fragment_definition(
     db: &dyn HirDatabase,
     fragment_def: ast::FragmentDefinition,
+    file_id: FileId,
 ) -> Option<FragmentDefinition> {
-    let name = name(fragment_def.fragment_name()?.name())?;
+    let name = name(fragment_def.fragment_name()?.name(), file_id)?;
     let type_condition = fragment_def
         .type_condition()?
         .named_type()?
@@ -384,9 +496,10 @@ fn fragment_definition(
         db,
         fragment_def.selection_set(),
         Some(type_condition.clone()),
+        file_id,
     );
-    let directives = directives(fragment_def.directives());
-    let ast_ptr = SyntaxNodePtr::new(fragment_def.syntax());
+    let directives = directives(fragment_def.directives(), file_id);
+    let loc = location(file_id, fragment_def.syntax());
 
     Some(FragmentDefinition {
         id: Uuid::new_v4(),
@@ -394,33 +507,36 @@ fn fragment_definition(
         type_condition,
         selection_set,
         directives,
-        ast_ptr,
+        loc,
     })
 }
 
-fn schema_definition(schema_def: ast::SchemaDefinition) -> SchemaDefinition {
+fn schema_definition(schema_def: ast::SchemaDefinition, file_id: FileId) -> SchemaDefinition {
     let description = description(schema_def.description());
-    let directives = directives(schema_def.directives());
+    let directives = directives(schema_def.directives(), file_id);
     let root_operation_type_definition =
-        root_operation_type_definition(schema_def.root_operation_type_definitions());
-    let ast_ptr = SyntaxNodePtr::new(schema_def.syntax());
+        root_operation_type_definition(schema_def.root_operation_type_definitions(), file_id);
+    let loc = location(file_id, schema_def.syntax());
 
     SchemaDefinition {
         description,
         directives,
         root_operation_type_definition,
-        ast_ptr: Some(ast_ptr),
+        loc: Some(loc),
     }
 }
 
-fn object_type_definition(obj_def: ast::ObjectTypeDefinition) -> Option<ObjectTypeDefinition> {
+fn object_type_definition(
+    obj_def: ast::ObjectTypeDefinition,
+    file_id: FileId,
+) -> Option<ObjectTypeDefinition> {
     let id = Uuid::new_v4();
     let description = description(obj_def.description());
-    let name = name(obj_def.name())?;
-    let implements_interfaces = implements_interfaces(obj_def.implements_interfaces());
-    let directives = directives(obj_def.directives());
-    let fields_definition = fields_definition(obj_def.fields_definition());
-    let ast_ptr = SyntaxNodePtr::new(obj_def.syntax());
+    let name = name(obj_def.name(), file_id)?;
+    let implements_interfaces = implements_interfaces(obj_def.implements_interfaces(), file_id);
+    let directives = directives(obj_def.directives(), file_id);
+    let fields_definition = fields_definition(obj_def.fields_definition(), file_id);
+    let loc = location(file_id, obj_def.syntax());
 
     // TODO(@goto-bus-stop) when a name is missing on this,
     // we might still want to produce a HIR node, so we can validate other parts of the definition
@@ -431,16 +547,19 @@ fn object_type_definition(obj_def: ast::ObjectTypeDefinition) -> Option<ObjectTy
         implements_interfaces,
         directives,
         fields_definition,
-        ast_ptr,
+        loc,
     })
 }
 
-fn scalar_definition(scalar_def: ast::ScalarTypeDefinition) -> Option<ScalarTypeDefinition> {
+fn scalar_definition(
+    scalar_def: ast::ScalarTypeDefinition,
+    file_id: FileId,
+) -> Option<ScalarTypeDefinition> {
     let id = Uuid::new_v4();
     let description = description(scalar_def.description());
-    let name = name(scalar_def.name())?;
-    let directives = directives(scalar_def.directives());
-    let ast_ptr = SyntaxNodePtr::new(scalar_def.syntax());
+    let name = name(scalar_def.name(), file_id)?;
+    let directives = directives(scalar_def.directives(), file_id);
+    let loc = location(file_id, scalar_def.syntax());
 
     // TODO(@goto-bus-stop) when a name is missing on this,
     // we might still want to produce a HIR node, so we can validate other parts of the definition
@@ -449,18 +568,21 @@ fn scalar_definition(scalar_def: ast::ScalarTypeDefinition) -> Option<ScalarType
         description,
         name,
         directives,
-        ast_ptr: Some(ast_ptr),
+        loc: Some(loc),
         built_in: false,
     })
 }
 
-fn enum_definition(enum_def: ast::EnumTypeDefinition) -> Option<EnumTypeDefinition> {
+fn enum_definition(
+    enum_def: ast::EnumTypeDefinition,
+    file_id: FileId,
+) -> Option<EnumTypeDefinition> {
     let id = Uuid::new_v4();
     let description = description(enum_def.description());
-    let name = name(enum_def.name())?;
-    let directives = directives(enum_def.directives());
-    let enum_values_definition = enum_values_definition(enum_def.enum_values_definition());
-    let ast_ptr = SyntaxNodePtr::new(enum_def.syntax());
+    let name = name(enum_def.name(), file_id)?;
+    let directives = directives(enum_def.directives(), file_id);
+    let enum_values_definition = enum_values_definition(enum_def.enum_values_definition(), file_id);
+    let loc = location(file_id, enum_def.syntax());
 
     // TODO(@goto-bus-stop) when a name is missing on this,
     // we might still want to produce a HIR node, so we can validate other parts of the definition
@@ -470,19 +592,20 @@ fn enum_definition(enum_def: ast::EnumTypeDefinition) -> Option<EnumTypeDefiniti
         name,
         directives,
         enum_values_definition,
-        ast_ptr,
+        loc,
     })
 }
 
 fn enum_values_definition(
     enum_values_def: Option<ast::EnumValuesDefinition>,
+    file_id: FileId,
 ) -> Arc<Vec<EnumValueDefinition>> {
     match enum_values_def {
         Some(enum_values) => {
             let enum_values = enum_values
                 .enum_value_definitions()
                 .into_iter()
-                .filter_map(enum_value_definition)
+                .filter_map(|e| enum_value_definition(e, file_id))
                 .collect();
             Arc::new(enum_values)
         }
@@ -490,27 +613,33 @@ fn enum_values_definition(
     }
 }
 
-fn enum_value_definition(enum_value_def: ast::EnumValueDefinition) -> Option<EnumValueDefinition> {
+fn enum_value_definition(
+    enum_value_def: ast::EnumValueDefinition,
+    file_id: FileId,
+) -> Option<EnumValueDefinition> {
     let description = description(enum_value_def.description());
-    let enum_value = enum_value(enum_value_def.enum_value())?;
-    let directives = directives(enum_value_def.directives());
-    let ast_ptr = SyntaxNodePtr::new(enum_value_def.syntax());
+    let enum_value = enum_value(enum_value_def.enum_value(), file_id)?;
+    let directives = directives(enum_value_def.directives(), file_id);
+    let loc = location(file_id, enum_value_def.syntax());
 
     Some(EnumValueDefinition {
         description,
         enum_value,
         directives,
-        ast_ptr,
+        loc,
     })
 }
 
-fn union_definition(union_def: ast::UnionTypeDefinition) -> Option<UnionTypeDefinition> {
+fn union_definition(
+    union_def: ast::UnionTypeDefinition,
+    file_id: FileId,
+) -> Option<UnionTypeDefinition> {
     let id = Uuid::new_v4();
     let description = description(union_def.description());
-    let name = name(union_def.name())?;
-    let directives = directives(union_def.directives());
-    let union_members = union_members(union_def.union_member_types());
-    let ast_ptr = SyntaxNodePtr::new(union_def.syntax());
+    let name = name(union_def.name(), file_id)?;
+    let directives = directives(union_def.directives(), file_id);
+    let union_members = union_members(union_def.union_member_types(), file_id);
+    let loc = location(file_id, union_def.syntax());
 
     // TODO(@goto-bus-stop) when a name is missing on this,
     // we might still want to produce a HIR node, so we can validate other parts of the definition
@@ -520,17 +649,20 @@ fn union_definition(union_def: ast::UnionTypeDefinition) -> Option<UnionTypeDefi
         name,
         directives,
         union_members,
-        ast_ptr,
+        loc,
     })
 }
 
-fn union_members(union_members: Option<ast::UnionMemberTypes>) -> Arc<Vec<UnionMember>> {
+fn union_members(
+    union_members: Option<ast::UnionMemberTypes>,
+    file_id: FileId,
+) -> Arc<Vec<UnionMember>> {
     match union_members {
         Some(members) => {
             let mems = members
                 .named_types()
                 .into_iter()
-                .filter_map(union_member)
+                .filter_map(|u| union_member(u, file_id))
                 .collect();
             Arc::new(mems)
         }
@@ -538,23 +670,25 @@ fn union_members(union_members: Option<ast::UnionMemberTypes>) -> Arc<Vec<UnionM
     }
 }
 
-fn union_member(member: ast::NamedType) -> Option<UnionMember> {
-    let name = name(member.name())?;
-    let ast_ptr = SyntaxNodePtr::new(member.syntax());
+fn union_member(member: ast::NamedType, file_id: FileId) -> Option<UnionMember> {
+    let name = name(member.name(), file_id)?;
+    let loc = location(file_id, member.syntax());
 
-    Some(UnionMember { name, ast_ptr })
+    Some(UnionMember { name, loc })
 }
 
 fn interface_definition(
     interface_def: ast::InterfaceTypeDefinition,
+    file_id: FileId,
 ) -> Option<InterfaceTypeDefinition> {
     let id = Uuid::new_v4();
     let description = description(interface_def.description());
-    let name = name(interface_def.name())?;
-    let implements_interfaces = implements_interfaces(interface_def.implements_interfaces());
-    let directives = directives(interface_def.directives());
-    let fields_definition = fields_definition(interface_def.fields_definition());
-    let ast_ptr = SyntaxNodePtr::new(interface_def.syntax());
+    let name = name(interface_def.name(), file_id)?;
+    let implements_interfaces =
+        implements_interfaces(interface_def.implements_interfaces(), file_id);
+    let directives = directives(interface_def.directives(), file_id);
+    let fields_definition = fields_definition(interface_def.fields_definition(), file_id);
+    let loc = location(file_id, interface_def.syntax());
 
     // TODO(@goto-bus-stop) when a name is missing on this,
     // we might still want to produce a HIR node, so we can validate other parts of the definition
@@ -565,17 +699,20 @@ fn interface_definition(
         implements_interfaces,
         directives,
         fields_definition,
-        ast_ptr,
+        loc,
     })
 }
 
-fn directive_definition(directive_def: ast::DirectiveDefinition) -> Option<DirectiveDefinition> {
-    let name = name(directive_def.name())?;
+fn directive_definition(
+    directive_def: ast::DirectiveDefinition,
+    file_id: FileId,
+) -> Option<DirectiveDefinition> {
+    let name = name(directive_def.name(), file_id)?;
     let description = description(directive_def.description());
-    let arguments = arguments_definition(directive_def.arguments_definition());
+    let arguments = arguments_definition(directive_def.arguments_definition(), file_id);
     let repeatable = directive_def.repeatable_token().is_some();
     let directive_locations = directive_locations(directive_def.directive_locations());
-    let ast_ptr = SyntaxNodePtr::new(directive_def.syntax());
+    let loc = location(file_id, directive_def.syntax());
 
     // TODO(@goto-bus-stop) when a name is missing on this,
     // we might still want to produce a HIR node, so we can validate other parts of the definition
@@ -586,19 +723,21 @@ fn directive_definition(directive_def: ast::DirectiveDefinition) -> Option<Direc
         arguments,
         repeatable,
         directive_locations,
-        ast_ptr: Some(ast_ptr),
+        loc: Some(loc),
     })
 }
 
 fn input_object_definition(
     input_obj: ast::InputObjectTypeDefinition,
+    file_id: FileId,
 ) -> Option<InputObjectTypeDefinition> {
     let id = Uuid::new_v4();
     let description = description(input_obj.description());
-    let name = name(input_obj.name())?;
-    let directives = directives(input_obj.directives());
-    let input_fields_definition = input_fields_definition(input_obj.input_fields_definition());
-    let ast_ptr = SyntaxNodePtr::new(input_obj.syntax());
+    let name = name(input_obj.name(), file_id)?;
+    let directives = directives(input_obj.directives(), file_id);
+    let input_fields_definition =
+        input_fields_definition(input_obj.input_fields_definition(), file_id);
+    let loc = location(file_id, input_obj.syntax());
 
     // TODO(@goto-bus-stop) when a name is missing on this,
     // we might still want to produce a HIR node, so we can validate other parts of the definition
@@ -608,7 +747,7 @@ fn input_object_definition(
         name,
         directives,
         input_fields_definition,
-        ast_ptr,
+        loc,
     })
 }
 
@@ -631,9 +770,9 @@ fn add_object_type_id_to_schema(db: &dyn HirDatabase) -> Arc<Vec<RootOperationTy
                     operation_type,
                     named_type: Type::Named {
                         name: obj_name.to_string(),
-                        ast_ptr: None,
+                        loc: None,
                     },
-                    ast_ptr: None,
+                    loc: None,
                 })
             } else {
                 None
@@ -646,6 +785,7 @@ fn add_object_type_id_to_schema(db: &dyn HirDatabase) -> Arc<Vec<RootOperationTy
 
 fn implements_interfaces(
     implements_interfaces: Option<ast::ImplementsInterfaces>,
+    file_id: FileId,
 ) -> Arc<Vec<ImplementsInterface>> {
     let interfaces: Vec<ImplementsInterface> = implements_interfaces
         .iter()
@@ -655,8 +795,8 @@ fn implements_interfaces(
                 .filter_map(|n| {
                     let name = n.name()?;
                     Some(ImplementsInterface {
-                        interface: name_hir_node(name),
-                        ast_ptr: SyntaxNodePtr::new(n.syntax()),
+                        interface: name_hir_node(name, file_id),
+                        loc: location(file_id, n.syntax()),
                     })
                 })
                 .collect();
@@ -669,12 +809,13 @@ fn implements_interfaces(
 
 fn fields_definition(
     fields_definition: Option<ast::FieldsDefinition>,
+    file_id: FileId,
 ) -> Arc<Vec<FieldDefinition>> {
     match fields_definition {
         Some(fields_def) => {
             let fields: Vec<FieldDefinition> = fields_def
                 .field_definitions()
-                .filter_map(field_definition)
+                .filter_map(|f| field_definition(f, file_id))
                 .collect();
             Arc::new(fields)
         }
@@ -682,13 +823,13 @@ fn fields_definition(
     }
 }
 
-fn field_definition(field: ast::FieldDefinition) -> Option<FieldDefinition> {
+fn field_definition(field: ast::FieldDefinition, file_id: FileId) -> Option<FieldDefinition> {
     let description = description(field.description());
-    let name = name(field.name())?;
-    let arguments = arguments_definition(field.arguments_definition());
-    let ty = ty(field.ty()?)?;
-    let directives = directives(field.directives());
-    let ast_ptr = SyntaxNodePtr::new(field.syntax());
+    let name = name(field.name(), file_id)?;
+    let arguments = arguments_definition(field.arguments_definition(), file_id);
+    let ty = ty(field.ty()?, file_id)?;
+    let directives = directives(field.directives(), file_id);
+    let loc = location(file_id, field.syntax());
 
     Some(FieldDefinition {
         description,
@@ -696,50 +837,54 @@ fn field_definition(field: ast::FieldDefinition) -> Option<FieldDefinition> {
         arguments,
         ty,
         directives,
-        ast_ptr,
+        loc,
     })
 }
 
 fn arguments_definition(
     arguments_definition: Option<ast::ArgumentsDefinition>,
+    file_id: FileId,
 ) -> ArgumentsDefinition {
     match arguments_definition {
         Some(arguments) => {
-            let input_values = input_value_definitions(arguments.input_value_definitions());
-            let ast_ptr = SyntaxNodePtr::new(arguments.syntax());
+            let input_values =
+                input_value_definitions(arguments.input_value_definitions(), file_id);
+            let loc = location(file_id, arguments.syntax());
 
             ArgumentsDefinition {
                 input_values,
-                ast_ptr: Some(ast_ptr),
+                loc: Some(loc),
             }
         }
         None => ArgumentsDefinition {
             input_values: Arc::new(Vec::new()),
-            ast_ptr: None,
+            loc: None,
         },
     }
 }
 
 fn input_fields_definition(
     input_fields: Option<ast::InputFieldsDefinition>,
+    file_id: FileId,
 ) -> Arc<Vec<InputValueDefinition>> {
     match input_fields {
-        Some(fields) => input_value_definitions(fields.input_value_definitions()),
+        Some(fields) => input_value_definitions(fields.input_value_definitions(), file_id),
         None => Arc::new(Vec::new()),
     }
 }
 
 fn input_value_definitions(
     input_values: AstChildren<ast::InputValueDefinition>,
+    file_id: FileId,
 ) -> Arc<Vec<InputValueDefinition>> {
     let input_values: Vec<InputValueDefinition> = input_values
         .filter_map(|input| {
             let description = description(input.description());
-            let name = name(input.name())?;
-            let ty = ty(input.ty()?)?;
-            let default_value = default_value(input.default_value());
-            let directives = directives(input.directives());
-            let ast_ptr = SyntaxNodePtr::new(input.syntax());
+            let name = name(input.name(), file_id)?;
+            let ty = ty(input.ty()?, file_id)?;
+            let default_value = default_value(input.default_value(), file_id);
+            let directives = directives(input.directives(), file_id);
+            let loc = location(file_id, input.syntax());
 
             Some(InputValueDefinition {
                 description,
@@ -747,31 +892,37 @@ fn input_value_definitions(
                 ty,
                 default_value,
                 directives,
-                ast_ptr: Some(ast_ptr),
+                loc: Some(loc),
             })
         })
         .collect();
     Arc::new(input_values)
 }
 
-fn default_value(default_value: Option<ast::DefaultValue>) -> Option<DefaultValue> {
-    default_value.and_then(|val| val.value()).and_then(value)
+fn default_value(
+    default_value: Option<ast::DefaultValue>,
+    file_id: FileId,
+) -> Option<DefaultValue> {
+    default_value
+        .and_then(|val| val.value())
+        .and_then(|val| value(val, file_id))
 }
 
 fn root_operation_type_definition(
     root_type_def: AstChildren<ast::RootOperationTypeDefinition>,
+    file_id: FileId,
 ) -> Arc<Vec<RootOperationTypeDefinition>> {
     let type_defs: Vec<RootOperationTypeDefinition> = root_type_def
         .into_iter()
         .filter_map(|ty| {
             let operation_type = operation_type(ty.operation_type());
-            let named_type = named_type(ty.named_type()?.name()?);
-            let ast_ptr = SyntaxNodePtr::new(ty.syntax());
+            let named_type = named_type(ty.named_type()?.name()?, file_id);
+            let loc = location(file_id, ty.syntax());
 
             Some(RootOperationTypeDefinition {
                 operation_type,
                 named_type,
-                ast_ptr: Some(ast_ptr),
+                loc: Some(loc),
             })
         })
         .collect();
@@ -798,13 +949,14 @@ fn operation_type(op_type: Option<ast::OperationType>) -> OperationType {
 
 fn variable_definitions(
     variable_definitions: Option<ast::VariableDefinitions>,
+    file_id: FileId,
 ) -> Arc<Vec<VariableDefinition>> {
     match variable_definitions {
         Some(vars) => {
             let variable_definitions = vars
                 .variable_definitions()
                 .into_iter()
-                .filter_map(variable_definition)
+                .filter_map(|v| variable_definition(v, file_id))
                 .collect();
             Arc::new(variable_definitions)
         }
@@ -812,44 +964,47 @@ fn variable_definitions(
     }
 }
 
-fn variable_definition(var: ast::VariableDefinition) -> Option<VariableDefinition> {
-    let name = name(var.variable()?.name())?;
-    let directives = directives(var.directives());
-    let default_value = default_value(var.default_value());
-    let ty = ty(var.ty()?)?;
-    let ast_ptr = SyntaxNodePtr::new(var.syntax());
+fn variable_definition(
+    var: ast::VariableDefinition,
+    file_id: FileId,
+) -> Option<VariableDefinition> {
+    let name = name(var.variable()?.name(), file_id)?;
+    let directives = directives(var.directives(), file_id);
+    let default_value = default_value(var.default_value(), file_id);
+    let ty = ty(var.ty()?, file_id)?;
+    let loc = location(file_id, var.syntax());
 
     Some(VariableDefinition {
         name,
         directives,
         ty,
         default_value,
-        ast_ptr,
+        loc,
     })
 }
 
-fn ty(ty_: ast::Type) -> Option<Type> {
+fn ty(ty_: ast::Type, file_id: FileId) -> Option<Type> {
     match ty_ {
-        ast::Type::NamedType(name) => name.name().map(named_type),
+        ast::Type::NamedType(name) => name.name().map(|name| named_type(name, file_id)),
         ast::Type::ListType(list) => Some(Type::List {
-            ty: Box::new(ty(list.ty()?)?),
-            ast_ptr: Some(SyntaxNodePtr::new(list.syntax())),
+            ty: Box::new(ty(list.ty()?, file_id)?),
+            loc: Some(location(file_id, list.syntax())),
         }),
         ast::Type::NonNullType(non_null) => {
             if let Some(n) = non_null.named_type() {
-                let named_type = n.name().map(named_type)?;
+                let named_type = n.name().map(|name| named_type(name, file_id))?;
                 Some(Type::NonNull {
                     ty: Box::new(named_type),
-                    ast_ptr: Some(SyntaxNodePtr::new(n.syntax())),
+                    loc: Some(location(file_id, n.syntax())),
                 })
             } else if let Some(list) = non_null.list_type() {
                 let list_type = Type::List {
-                    ty: Box::new(ty(list.ty()?)?),
-                    ast_ptr: Some(SyntaxNodePtr::new(list.syntax())),
+                    ty: Box::new(ty(list.ty()?, file_id)?),
+                    loc: Some(location(file_id, list.syntax())),
                 };
                 Some(Type::NonNull {
                     ty: Box::new(list_type),
-                    ast_ptr: Some(SyntaxNodePtr::new(list.syntax())),
+                    loc: Some(location(file_id, list.syntax())),
                 })
             } else {
                 // TODO: parser should have caught an error if there wasn't
@@ -861,10 +1016,10 @@ fn ty(ty_: ast::Type) -> Option<Type> {
     }
 }
 
-fn named_type(name: ast::Name) -> Type {
+fn named_type(name: ast::Name, file_id: FileId) -> Type {
     Type::Named {
         name: name.text().to_string(),
-        ast_ptr: Some(SyntaxNodePtr::new(name.syntax())),
+        loc: Some(location(file_id, name.syntax())),
     }
 }
 
@@ -884,13 +1039,13 @@ fn directive_locations(
     }
 }
 
-fn directives(directives: Option<ast::Directives>) -> Arc<Vec<Directive>> {
+fn directives(directives: Option<ast::Directives>, file_id: FileId) -> Arc<Vec<Directive>> {
     match directives {
         Some(directives) => {
             let directives = directives
                 .directives()
                 .into_iter()
-                .filter_map(directive)
+                .filter_map(|d| directive(d, file_id))
                 .collect();
             Arc::new(directives)
         }
@@ -898,25 +1053,25 @@ fn directives(directives: Option<ast::Directives>) -> Arc<Vec<Directive>> {
     }
 }
 
-fn directive(directive: ast::Directive) -> Option<Directive> {
-    let name = name(directive.name())?;
-    let arguments = arguments(directive.arguments());
-    let ast_ptr = SyntaxNodePtr::new(directive.syntax());
+fn directive(directive: ast::Directive, file_id: FileId) -> Option<Directive> {
+    let name = name(directive.name(), file_id)?;
+    let arguments = arguments(directive.arguments(), file_id);
+    let loc = location(file_id, directive.syntax());
 
     Some(Directive {
         name,
         arguments,
-        ast_ptr,
+        loc,
     })
 }
 
-fn arguments(arguments: Option<ast::Arguments>) -> Arc<Vec<Argument>> {
+fn arguments(arguments: Option<ast::Arguments>, file_id: FileId) -> Arc<Vec<Argument>> {
     match arguments {
         Some(arguments) => {
             let arguments = arguments
                 .arguments()
                 .into_iter()
-                .filter_map(argument)
+                .filter_map(|a| argument(a, file_id))
                 .collect();
             Arc::new(arguments)
         }
@@ -924,23 +1079,19 @@ fn arguments(arguments: Option<ast::Arguments>) -> Arc<Vec<Argument>> {
     }
 }
 
-fn argument(argument: ast::Argument) -> Option<Argument> {
-    let name = name(argument.name())?;
-    let value = value(argument.value()?)?;
-    let ast_ptr = SyntaxNodePtr::new(argument.syntax());
+fn argument(argument: ast::Argument, file_id: FileId) -> Option<Argument> {
+    let name = name(argument.name(), file_id)?;
+    let value = value(argument.value()?, file_id)?;
+    let loc = location(file_id, argument.syntax());
 
-    Some(Argument {
-        name,
-        value,
-        ast_ptr,
-    })
+    Some(Argument { name, value, loc })
 }
 
-fn value(val: ast::Value) -> Option<Value> {
+fn value(val: ast::Value, file_id: FileId) -> Option<Value> {
     let hir_val = match val {
         ast::Value::Variable(var) => Value::Variable(Variable {
             name: var.name()?.text().to_string(),
-            ast_ptr: SyntaxNodePtr::new(var.syntax()),
+            loc: location(file_id, var.syntax()),
         }),
         ast::Value::StringValue(string_val) => Value::String(string_val.into()),
         // TODO(@goto-bus-stop) do not unwrap
@@ -948,17 +1099,17 @@ fn value(val: ast::Value) -> Option<Value> {
         ast::Value::IntValue(int) => Value::Int(Float::new(f64::try_from(int).unwrap())),
         ast::Value::BooleanValue(bool) => Value::Boolean(bool.try_into().unwrap()),
         ast::Value::NullValue(_) => Value::Null,
-        ast::Value::EnumValue(enum_) => Value::Enum(name(enum_.name())?),
+        ast::Value::EnumValue(enum_) => Value::Enum(name(enum_.name(), file_id)?),
         ast::Value::ListValue(list) => {
-            let list: Vec<Value> = list.values().filter_map(value).collect();
+            let list: Vec<Value> = list.values().filter_map(|v| value(v, file_id)).collect();
             Value::List(list)
         }
         ast::Value::ObjectValue(object) => {
             let object_values: Vec<(Name, Value)> = object
                 .object_fields()
                 .filter_map(|o| {
-                    let name = name(o.name())?;
-                    let value = value(o.value()?)?;
+                    let name = name(o.name(), file_id)?;
+                    let value = value(o.value()?, file_id)?;
                     Some((name, value))
                 })
                 .collect();
@@ -972,12 +1123,13 @@ fn selection_set(
     db: &dyn HirDatabase,
     selections: Option<ast::SelectionSet>,
     parent_obj_ty: Option<String>,
+    file_id: FileId,
 ) -> SelectionSet {
     let selection_set = match selections {
         Some(sel) => sel
             .selections()
             .into_iter()
-            .filter_map(|sel| selection(db, sel, parent_obj_ty.as_ref().cloned()))
+            .filter_map(|sel| selection(db, sel, parent_obj_ty.as_ref().cloned(), file_id))
             .collect(),
         None => Vec::new(),
     };
@@ -991,16 +1143,17 @@ fn selection(
     db: &dyn HirDatabase,
     selection: ast::Selection,
     parent_obj_ty: Option<String>,
+    file_id: FileId,
 ) -> Option<Selection> {
     match selection {
         ast::Selection::Field(sel_field) => {
-            field(db, sel_field, parent_obj_ty).map(Selection::Field)
+            field(db, sel_field, parent_obj_ty, file_id).map(Selection::Field)
         }
         ast::Selection::FragmentSpread(fragment) => {
-            fragment_spread(fragment).map(Selection::FragmentSpread)
+            fragment_spread(fragment, file_id).map(Selection::FragmentSpread)
         }
         ast::Selection::InlineFragment(fragment) => Some(Selection::InlineFragment(
-            inline_fragment(db, fragment, parent_obj_ty),
+            inline_fragment(db, fragment, parent_obj_ty, file_id),
         )),
     }
 }
@@ -1009,38 +1162,40 @@ fn inline_fragment(
     db: &dyn HirDatabase,
     fragment: ast::InlineFragment,
     parent_obj: Option<String>,
+    file_id: FileId,
 ) -> Arc<InlineFragment> {
     let type_condition = fragment.type_condition().and_then(|tc| {
         let tc = tc.named_type()?.name()?;
-        Some(name_hir_node(tc))
+        Some(name_hir_node(tc, file_id))
     });
-    let directives = directives(fragment.directives());
+    let directives = directives(fragment.directives(), file_id);
     let new_parent_obj = if let Some(type_condition) = type_condition.clone() {
         Some(type_condition.src().to_string())
     } else {
         parent_obj
     };
-    let selection_set: SelectionSet = selection_set(db, fragment.selection_set(), new_parent_obj);
-    let ast_ptr = SyntaxNodePtr::new(fragment.syntax());
+    let selection_set: SelectionSet =
+        selection_set(db, fragment.selection_set(), new_parent_obj, file_id);
+    let loc = location(file_id, fragment.syntax());
 
     let fragment_data = InlineFragment {
         type_condition,
         directives,
         selection_set,
-        ast_ptr,
+        loc,
     };
     Arc::new(fragment_data)
 }
 
-fn fragment_spread(fragment: ast::FragmentSpread) -> Option<Arc<FragmentSpread>> {
-    let name = name(fragment.fragment_name()?.name())?;
-    let directives = directives(fragment.directives());
-    let ast_ptr = SyntaxNodePtr::new(fragment.syntax());
+fn fragment_spread(fragment: ast::FragmentSpread, file_id: FileId) -> Option<Arc<FragmentSpread>> {
+    let name = name(fragment.fragment_name()?.name(), file_id)?;
+    let directives = directives(fragment.directives(), file_id);
+    let loc = location(file_id, fragment.syntax());
 
     let fragment_data = FragmentSpread {
         name,
         directives,
-        ast_ptr,
+        loc,
     };
     Some(Arc::new(fragment_data))
 }
@@ -1049,14 +1204,15 @@ fn field(
     db: &dyn HirDatabase,
     field: ast::Field,
     parent_obj: Option<String>,
+    file_id: FileId,
 ) -> Option<Arc<Field>> {
-    let name = name(field.name())?;
+    let name = name(field.name(), file_id)?;
     let alias = alias(field.alias());
     let new_parent_obj = parent_ty(db, name.src(), parent_obj.clone());
-    let selection_set = selection_set(db, field.selection_set(), new_parent_obj);
-    let directives = directives(field.directives());
-    let arguments = arguments(field.arguments());
-    let ast_ptr = SyntaxNodePtr::new(field.syntax());
+    let selection_set = selection_set(db, field.selection_set(), new_parent_obj, file_id);
+    let directives = directives(field.directives(), file_id);
+    let arguments = arguments(field.arguments(), file_id);
+    let loc = location(file_id, field.syntax());
 
     let field_data = Field {
         name,
@@ -1065,7 +1221,7 @@ fn field(
         parent_obj,
         directives,
         arguments,
-        ast_ptr,
+        loc,
     };
     Some(Arc::new(field_data))
 }
@@ -1085,20 +1241,20 @@ fn parent_ty(db: &dyn HirDatabase, field_name: &str, parent_obj: Option<String>)
     }
 }
 
-fn name(name: Option<ast::Name>) -> Option<Name> {
-    name.map(name_hir_node)
+fn name(name: Option<ast::Name>, file_id: FileId) -> Option<Name> {
+    name.map(|name| name_hir_node(name, file_id))
 }
 
-fn name_hir_node(name: ast::Name) -> Name {
+fn name_hir_node(name: ast::Name, file_id: FileId) -> Name {
     Name {
         src: name.text().to_string(),
-        ast_ptr: Some(SyntaxNodePtr::new(name.syntax())),
+        loc: Some(location(file_id, name.syntax())),
     }
 }
 
-fn enum_value(enum_value: Option<ast::EnumValue>) -> Option<Name> {
+fn enum_value(enum_value: Option<ast::EnumValue>, file_id: FileId) -> Option<Name> {
     let name = enum_value?.name()?;
-    Some(name_hir_node(name))
+    Some(name_hir_node(name, file_id))
 }
 
 fn description(description: Option<ast::Description>) -> Option<String> {
@@ -1113,6 +1269,16 @@ fn alias(alias: Option<ast::Alias>) -> Option<Arc<Alias>> {
     })
 }
 
+fn location(file_id: FileId, syntax_node: &SyntaxNode) -> HirNodeLocation {
+    let text_range = syntax_node.text_range();
+
+    HirNodeLocation {
+        offset: text_range.start().into(),
+        node_len: text_range.len().into(),
+        file_id,
+    }
+}
+
 //  Int, Float, String, Boolean, and ID
 fn built_in_scalars(mut scalars: Vec<ScalarTypeDefinition>) -> Vec<ScalarTypeDefinition> {
     scalars.push(int_scalar());
@@ -1130,7 +1296,7 @@ fn int_scalar() -> ScalarTypeDefinition {
         description: Some("The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.".into()),
         name: "Int".to_string().into(),
         directives: Arc::new(Vec::new()),
-        ast_ptr: None,
+        loc: None,
         built_in: true
     }
 }
@@ -1141,7 +1307,7 @@ fn float_scalar() -> ScalarTypeDefinition {
         description: Some("The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).".into()),
         name: "Float".to_string().into(),
         directives: Arc::new(Vec::new()),
-        ast_ptr: None,
+        loc: None,
         built_in: true
     }
 }
@@ -1152,7 +1318,7 @@ fn string_scalar() -> ScalarTypeDefinition {
         description: Some("The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.".into()),
         name: "String".to_string().into(),
         directives: Arc::new(Vec::new()),
-        ast_ptr: None,
+        loc: None,
         built_in: true
     }
 }
@@ -1163,7 +1329,7 @@ fn boolean_scalar() -> ScalarTypeDefinition {
         description: Some("The `Boolean` scalar type represents `true` or `false`.".into()),
         name: "Boolean".to_string().into(),
         directives: Arc::new(Vec::new()),
-        ast_ptr: None,
+        loc: None,
         built_in: true,
     }
 }
@@ -1174,7 +1340,7 @@ fn id_scalar() -> ScalarTypeDefinition {
         description: Some("The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.".into()),
         name: "ID".to_string().into(),
         directives: Arc::new(Vec::new()),
-        ast_ptr: None,
+        loc: None,
         built_in: true
     }
 }
@@ -1219,15 +1385,15 @@ fn skip_directive() -> DirectiveDefinition {
                 ty: Type::NonNull {
                     ty: Box::new(Type::Named {
                         name: "Boolean".into(),
-                        ast_ptr: None,
+                        loc: None,
                     }),
-                    ast_ptr: None,
+                    loc: None,
                 },
                 default_value: None,
                 directives: Arc::new(Vec::new()),
-                ast_ptr: None,
+                loc: None,
             }]),
-            ast_ptr: None,
+            loc: None,
         },
         repeatable: false,
         directive_locations: Arc::new(vec![
@@ -1235,7 +1401,7 @@ fn skip_directive() -> DirectiveDefinition {
             DirectiveLocation::FragmentSpread,
             DirectiveLocation::InlineFragment,
         ]),
-        ast_ptr: None,
+        loc: None,
     }
 }
 
@@ -1256,19 +1422,19 @@ fn specified_by_directive() -> DirectiveDefinition {
                 ty: Type::NonNull {
                     ty: Box::new(Type::Named {
                         name: "String".into(),
-                        ast_ptr: None,
+                        loc: None,
                     }),
-                    ast_ptr: None,
+                    loc: None,
                 },
                 default_value: None,
                 directives: Arc::new(Vec::new()),
-                ast_ptr: None,
+                loc: None,
             }]),
-            ast_ptr: None,
+            loc: None,
         },
         repeatable: false,
         directive_locations: Arc::new(vec![DirectiveLocation::Scalar]),
-        ast_ptr: None,
+        loc: None,
     }
 }
 
@@ -1290,25 +1456,25 @@ fn deprecated_directive() -> DirectiveDefinition {
         arguments: ArgumentsDefinition {
             input_values: Arc::new(vec![InputValueDefinition {
                 description: Some(
-                    "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/).".into(),
-                ),
-                name: "reason".to_string().into(),
-                ty: Type::Named {
-                    name: "String".into(),
-                    ast_ptr: None,
-                },
-                default_value: Some(DefaultValue::String("No longer supported".into())),
-                directives: Arc::new(Vec::new()),
-                ast_ptr: None
+                                 "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/).".into(),
+                                 ),
+                                 name: "reason".to_string().into(),
+                                 ty: Type::Named {
+                                     name: "String".into(),
+                                     loc: None,
+                                 },
+                                 default_value: Some(DefaultValue::String("No longer supported".into())),
+                                 directives: Arc::new(Vec::new()),
+                                 loc: None
             }]),
-            ast_ptr: None
+            loc: None
         },
         repeatable: false,
         directive_locations: Arc::new(vec![
-            DirectiveLocation::FieldDefinition,
-            DirectiveLocation::EnumValue
+                                      DirectiveLocation::FieldDefinition,
+                                      DirectiveLocation::EnumValue
         ]),
-        ast_ptr: None
+        loc: None
     }
 }
 
@@ -1325,163 +1491,28 @@ fn include_directive() -> DirectiveDefinition {
         arguments: ArgumentsDefinition {
             input_values: Arc::new(vec![InputValueDefinition {
                 description: Some(
-                    "Included when true.".into(),
-                ),
-                name: "if".to_string().into(),
-                ty: Type::NonNull {
-                    ty: Box::new(Type::Named {
-                        name: "Boolean".into(),
-                        ast_ptr: None,
-                    }),
-                    ast_ptr: None,
-                },
-                default_value: None,
-                directives: Arc::new(Vec::new()),
-                ast_ptr: None
+                                 "Included when true.".into(),
+                                 ),
+                                 name: "if".to_string().into(),
+                                 ty: Type::NonNull {
+                                     ty: Box::new(Type::Named {
+                                         name: "Boolean".into(),
+                                         loc: None,
+                                     }),
+                                     loc: None,
+                                 },
+                                 default_value: None,
+                                 directives: Arc::new(Vec::new()),
+                                 loc: None
             }]),
-            ast_ptr: None
+            loc: None
         },
         repeatable: false,
         directive_locations: Arc::new(vec![
-            DirectiveLocation::Field,
-            DirectiveLocation::FragmentSpread,
-            DirectiveLocation::InlineFragment,
+                                      DirectiveLocation::Field,
+                                      DirectiveLocation::FragmentSpread,
+                                      DirectiveLocation::InlineFragment,
         ]),
-        ast_ptr: None
+        loc: None
     }
 }
-
-fn schema_extensions(db: &dyn HirDatabase) -> Arc<Vec<SchemaExtension>> {
-    let objects = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::SchemaExtension(def) => Some(SchemaExtension {
-                directives: directives(def.directives()),
-                root_operation_type_definition: root_operation_type_definition(
-                    def.root_operation_type_definitions(),
-                ),
-                ast_ptr: SyntaxNodePtr::new(def.syntax()),
-            }),
-            _ => None,
-        })
-        .collect();
-    Arc::new(objects)
-}
-
-fn scalar_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<ScalarTypeExtension>> {
-    let objects = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::ScalarTypeExtension(def) => Some(ScalarTypeExtension {
-                directives: directives(def.directives()),
-                name: name(def.name())?,
-                ast_ptr: SyntaxNodePtr::new(def.syntax()),
-            }),
-            _ => None,
-        })
-        .collect();
-    Arc::new(objects)
-}
-
-fn object_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<ObjectTypeExtension>> {
-    let objects = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::ObjectTypeExtension(def) => Some(ObjectTypeExtension {
-                directives: directives(def.directives()),
-                name: name(def.name())?,
-                implements_interfaces: implements_interfaces(def.implements_interfaces()),
-                fields_definition: fields_definition(def.fields_definition()),
-                ast_ptr: SyntaxNodePtr::new(def.syntax()),
-            }),
-            _ => None,
-        })
-        .collect();
-    Arc::new(objects)
-}
-
-fn interface_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<InterfaceTypeExtension>> {
-    let objects = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::InterfaceTypeExtension(def) => Some(InterfaceTypeExtension {
-                directives: directives(def.directives()),
-                name: name(def.name())?,
-                implements_interfaces: implements_interfaces(def.implements_interfaces()),
-                fields_definition: fields_definition(def.fields_definition()),
-                ast_ptr: SyntaxNodePtr::new(def.syntax()),
-            }),
-            _ => None,
-        })
-        .collect();
-    Arc::new(objects)
-}
-
-fn union_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<UnionTypeExtension>> {
-    let objects = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::UnionTypeExtension(def) => Some(UnionTypeExtension {
-                directives: directives(def.directives()),
-                name: name(def.name())?,
-                union_members: union_members(def.union_member_types()),
-                ast_ptr: SyntaxNodePtr::new(def.syntax()),
-            }),
-            _ => None,
-        })
-        .collect();
-    Arc::new(objects)
-}
-
-fn enum_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<EnumTypeExtension>> {
-    let objects = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::EnumTypeExtension(def) => Some(EnumTypeExtension {
-                directives: directives(def.directives()),
-                name: name(def.name())?,
-                enum_values_definition: enum_values_definition(def.enum_values_definition()),
-                ast_ptr: SyntaxNodePtr::new(def.syntax()),
-            }),
-            _ => None,
-        })
-        .collect();
-    Arc::new(objects)
-}
-
-fn input_object_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<InputObjectTypeExtension>> {
-    let objects = db
-        .ast()
-        .document()
-        .definitions()
-        .into_iter()
-        .filter_map(|definition| match definition {
-            ast::Definition::InputObjectTypeExtension(def) => Some(InputObjectTypeExtension {
-                directives: directives(def.directives()),
-                name: name(def.name())?,
-                input_fields_definition: input_fields_definition(def.input_fields_definition()),
-                ast_ptr: SyntaxNodePtr::new(def.syntax()),
-            }),
-            _ => None,
-        })
-        .collect();
-    Arc::new(objects)
-}
diff --git a/crates/apollo-compiler/src/database/inputs.rs b/crates/apollo-compiler/src/database/inputs.rs
index e51265f..a4b2e21 100644
--- a/crates/apollo-compiler/src/database/inputs.rs
+++ b/crates/apollo-compiler/src/database/inputs.rs
@@ -1,50 +1,56 @@
+use super::sources::{FileId, Source, SourceType};
+use std::sync::Arc;
+
 #[salsa::query_group(InputStorage)]
 pub trait InputDatabase {
     #[salsa::input]
-    fn input(&self) -> String;
+    fn recursion_limit(&self) -> Option<usize>;
 
     #[salsa::input]
-    fn recursion_limit(&self) -> Option<usize>;
+    fn input(&self, file_id: FileId) -> Source;
+
+    /// Get the GraphQL source text for a file.
+    fn source_code(&self, file_id: FileId) -> Arc<str>;
+
+    /// Get the source type (document/schema/executable) for a file.
+    fn source_type(&self, file_id: FileId) -> SourceType;
+
+    /// Get all file ids currently in the compiler.
+    #[salsa::input]
+    fn source_files(&self) -> Vec<FileId>;
+
+    fn type_definition_files(&self) -> Vec<FileId>;
+    fn executable_definition_files(&self) -> Vec<FileId>;
 }
 
-// #[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
-// pub struct Sources {
-//     manifest: Vec<SourceInfo>,
-// }
-//
-// impl Sources {
-//     pub fn new(source: String) -> Self {
-//         Self {
-//             manifest: vec![SourceInfo::new(source)],
-//         }
-//     }
-//
-//     pub fn with_sources(sources: Vec<String>) -> Self {
-//         Self {
-//             manifest: sources.into_iter().map(|s| SourceInfo::new(s)).collect(),
-//         }
-//     }
-//
-//     pub(crate) fn manifest(&self, source: String) {
-//         self.manifest.push(SourceInfo::new(source))
-//     }
-// }
-//
-// #[derive(Clone, Debug, PartialEq, Eq, Hash)]
-// pub struct SourceInfo {
-//     pub(crate) file_id: FileId,
-//     pub(crate) name: String,
-// }
-//
-// impl SourceInfo {
-//     pub fn new(name: String) -> Self {
-//         Self {
-//             file_id: FileId(Uuid::new_v4()),
-//             name,
-//         }
-//     }
-// }
-//
-// #[derive(Clone, Debug, PartialEq, Eq, Hash)]
-// pub struct FileId(Uuid);
-//
+fn source_code(db: &dyn InputDatabase, file_id: FileId) -> Arc<str> {
+    db.input(file_id).text()
+}
+
+fn source_type(db: &dyn InputDatabase, file_id: FileId) -> SourceType {
+    db.input(file_id).source_type()
+}
+
+fn type_definition_files(db: &dyn InputDatabase) -> Vec<FileId> {
+    db.source_files()
+        .into_iter()
+        .filter(|source| {
+            matches!(
+                db.source_type(*source),
+                SourceType::Schema | SourceType::Document
+            )
+        })
+        .collect()
+}
+
+fn executable_definition_files(db: &dyn InputDatabase) -> Vec<FileId> {
+    db.source_files()
+        .into_iter()
+        .filter(|source| {
+            matches!(
+                db.source_type(*source),
+                SourceType::Query | SourceType::Document
+            )
+        })
+        .collect()
+}
diff --git a/crates/apollo-compiler/src/database/mod.rs b/crates/apollo-compiler/src/database/mod.rs
index bf62398..cd662c7 100644
--- a/crates/apollo-compiler/src/database/mod.rs
+++ b/crates/apollo-compiler/src/database/mod.rs
@@ -5,9 +5,11 @@ mod ast;
 mod document;
 mod hir_db;
 mod inputs;
+mod sources;
 
 pub use ast::{AstDatabase, AstStorage};
 pub use db::RootDatabase;
 pub use document::{DocumentDatabase, DocumentStorage};
 pub use hir_db::{HirDatabase, HirStorage};
 pub use inputs::{InputDatabase, InputStorage};
+pub use sources::{FileId, Source};
diff --git a/crates/apollo-compiler/src/database/sources.rs b/crates/apollo-compiler/src/database/sources.rs
new file mode 100644
index 0000000..837b395
--- /dev/null
+++ b/crates/apollo-compiler/src/database/sources.rs
@@ -0,0 +1,66 @@
+use std::{
+    path::{Path, PathBuf},
+    sync::Arc,
+};
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum SourceType {
+    Schema,
+    Query,
+    Document,
+}
+
+/// Represents a GraphQL source file.
+#[derive(Clone, Debug, Hash)]
+pub struct Source {
+    ty: SourceType,
+    filename: PathBuf,
+    text: Arc<str>,
+}
+
+impl Source {
+    /// Create a GraphQL schema source file.
+    pub fn schema(filename: PathBuf, text: impl Into<Arc<str>>) -> Self {
+        Self {
+            ty: SourceType::Schema,
+            filename,
+            text: text.into(),
+        }
+    }
+
+    /// Create a GraphQL executable source file.
+    pub fn executable(filename: PathBuf, text: impl Into<Arc<str>>) -> Self {
+        Self {
+            ty: SourceType::Query,
+            filename,
+            text: text.into(),
+        }
+    }
+
+    /// 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<Arc<str>>) -> Self {
+        Self {
+            ty: SourceType::Document,
+            filename,
+            text: text.into(),
+        }
+    }
+
+    pub fn filename(&self) -> &Path {
+        &self.filename
+    }
+
+    pub fn source_type(&self) -> SourceType {
+        self.ty
+    }
+
+    pub fn text(&self) -> Arc<str> {
+        Arc::clone(&self.text)
+    }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct FileId(pub u32);
diff --git a/crates/apollo-compiler/src/diagnostics.rs b/crates/apollo-compiler/src/diagnostics.rs
index 6625918..51ee33f 100644
--- a/crates/apollo-compiler/src/diagnostics.rs
+++ b/crates/apollo-compiler/src/diagnostics.rs
@@ -1,4 +1,4 @@
-use std::fmt;
+use std::{fmt, sync::Arc};
 
 use miette::{Diagnostic, Report, SourceSpan};
 use thiserror::Error;
@@ -100,7 +100,7 @@ impl fmt::Display for ApolloDiagnostic {
 #[diagnostic(code("apollo-compiler validation error"))]
 pub struct MissingIdent {
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label = "provide a name for this definition"]
     pub definition: SourceSpan,
@@ -117,7 +117,7 @@ pub struct MissingField {
     pub ty: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("`{}` was originally defined here", self.ty)]
     pub super_definition: SourceSpan,
@@ -140,7 +140,7 @@ pub struct UniqueDefinition {
     pub ty: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("previous definition of `{}` here", self.name)]
     pub original_definition: SourceSpan,
@@ -157,7 +157,7 @@ pub struct UniqueDefinition {
 #[diagnostic(code("apollo-compiler validation error"))]
 pub struct SingleRootField {
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     pub fields: usize,
 
@@ -176,7 +176,7 @@ pub struct UnsupportedOperation {
     pub ty: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("{} operation is not defined in the schema and is therefore not supported", self.ty)]
     pub operation: SourceSpan,
@@ -195,7 +195,7 @@ pub struct SyntaxError {
     pub message: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("{}", self.message)]
     pub span: SourceSpan,
@@ -209,7 +209,7 @@ pub struct UniqueField {
     pub field: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("previous definition of `{}` field here", self.field)]
     pub original_field: SourceSpan,
@@ -226,7 +226,7 @@ pub struct UniqueField {
 #[diagnostic(code("apollo-compiler validation error"))]
 pub struct RecursiveDefinition {
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("{}", self.definition_label)]
     pub definition: SourceSpan,
@@ -244,7 +244,7 @@ pub struct UndefinedDefinition {
     pub ty: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("not found in this scope")]
     pub definition: SourceSpan,
@@ -258,7 +258,7 @@ pub struct TransitiveImplementedInterfaces {
     pub missing_interface: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("{} must also be implemented here", self.missing_interface)]
     pub definition: SourceSpan,
@@ -269,7 +269,7 @@ pub struct TransitiveImplementedInterfaces {
 #[diagnostic(code("apollo-compiler validation error"))]
 pub struct QueryRootOperationType {
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("`query` root operation type must be defined here")]
     pub schema: SourceSpan,
@@ -280,7 +280,7 @@ pub struct QueryRootOperationType {
 #[diagnostic(code("apollo-compiler validation error"))]
 pub struct BuiltInScalarDefinition {
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("remove this scalar definition")]
     pub scalar: SourceSpan,
@@ -291,7 +291,7 @@ pub struct BuiltInScalarDefinition {
 #[diagnostic(code("apollo-compiler validation advice"), severity(advice))]
 pub struct ScalarSpecificationURL {
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("consider adding a @specifiedBy directive to this scalar definition")]
     pub scalar: SourceSpan,
@@ -304,7 +304,7 @@ pub struct CapitalizedValue {
     pub ty: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("consider capitalizing {}", self.ty)]
     pub value: SourceSpan,
@@ -317,7 +317,7 @@ pub struct UnusedVariable {
     pub ty: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("unused variable")]
     pub definition: SourceSpan,
@@ -336,7 +336,7 @@ pub struct OutputType {
     pub ty: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("this is of `{}` type", self.ty)]
     pub definition: SourceSpan,
@@ -355,7 +355,7 @@ pub struct ObjectType {
     pub ty: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("this is of `{}` type", self.ty)]
     pub definition: SourceSpan,
@@ -370,7 +370,7 @@ pub struct UndefinedField {
     pub field: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("`{}` field is not in scope", self.field)]
     pub definition: SourceSpan,
@@ -387,7 +387,7 @@ pub struct UniqueArgument {
     pub name: String,
 
     #[source_code]
-    pub src: String,
+    pub src: Arc<str>,
 
     #[label("previous definition of `{}` here", self.name)]
     pub original_definition: SourceSpan,
diff --git a/crates/apollo-compiler/src/lib.rs b/crates/apollo-compiler/src/lib.rs
index 59469a2..be93adc 100644
--- a/crates/apollo-compiler/src/lib.rs
+++ b/crates/apollo-compiler/src/lib.rs
@@ -6,14 +6,19 @@ pub mod diagnostics;
 mod tests;
 pub mod validation;
 
+use std::path::Path;
+
 use salsa::ParallelDatabase;
 use validation::ValidationDatabase;
 
-pub use database::{hir, AstDatabase, DocumentDatabase, HirDatabase, InputDatabase, RootDatabase};
+pub use database::{
+    hir, AstDatabase, DocumentDatabase, FileId, HirDatabase, InputDatabase, RootDatabase, Source,
+};
 pub use diagnostics::ApolloDiagnostic;
 
 pub struct ApolloCompiler {
     pub db: RootDatabase,
+    next_file_id: FileId,
 }
 
 /// Apollo compiler creates a context around your GraphQL. It creates refernces
@@ -53,8 +58,10 @@ pub struct ApolloCompiler {
 ///   }
 /// "#;
 ///
-/// let ctx = ApolloCompiler::new(input);
-/// let diagnostics = ctx.validate();
+/// let mut compiler = ApolloCompiler::new();
+/// compiler.create_schema(input, "schema.graphql");
+///
+/// let diagnostics = compiler.validate();
 /// for diagnostic in &diagnostics {
 ///     // this will pretty-print diagnostics using the miette crate.
 ///     println!("{}", diagnostic);
@@ -63,22 +70,94 @@ pub struct ApolloCompiler {
 /// ```
 impl ApolloCompiler {
     /// Create a new instance of Apollo Compiler.
-    pub fn new(input: &str) -> Self {
-        Self::with_opt_recursion_limit(input, None)
+    pub fn new() -> Self {
+        Default::default()
     }
 
     /// Create a new instance of Apollo Compiler,
     /// and configure the parser with the given recursion limit.
-    pub fn with_recursion_limit(input: &str, limit: usize) -> Self {
-        Self::with_opt_recursion_limit(input, Some(limit))
+    pub fn with_recursion_limit(limit: usize) -> Self {
+        let mut compiler = Self::new();
+        compiler.db.set_recursion_limit(Some(limit));
+        compiler
     }
 
-    fn with_opt_recursion_limit(input: &str, limit: Option<usize>) -> Self {
-        let mut db = RootDatabase::default();
-        let input = input.to_string();
-        db.set_input(input);
-        db.set_recursion_limit(limit);
-        Self { db }
+    fn add_input(&mut self, source: Source) -> FileId {
+        let next_file_id = FileId(self.next_file_id.0 + 1);
+        let file_id = std::mem::replace(&mut self.next_file_id, next_file_id);
+
+        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 create_document(&mut self, input: &str, path: impl AsRef<Path>) -> FileId {
+        let filename = path.as_ref().to_owned();
+        self.add_input(Source::document(filename, input))
+    }
+
+    /// Add a schema - 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 create_schema(&mut self, input: &str, path: impl AsRef<Path>) -> FileId {
+        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 create_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_schema(&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.
@@ -98,8 +177,10 @@ impl ApolloCompiler {
     /// }
     /// "#;
     ///
-    /// let ctx = ApolloCompiler::new(input);
-    /// let diagnostics = ctx.validate();
+    /// let mut compiler = ApolloCompiler::new();
+    /// compiler.create_document(input, "document.graphql");
+    ///
+    /// let diagnostics = compiler.validate();
     /// for diagnostic in &diagnostics {
     ///     println!("{}", diagnostic);
     /// }
@@ -110,12 +191,51 @@ impl ApolloCompiler {
     }
 }
 
+impl Default for ApolloCompiler {
+    fn default() -> 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_source_files(vec![]);
+
+        Self {
+            db,
+            next_file_id: FileId(0),
+        }
+    }
+}
+
 #[cfg(test)]
 mod test {
     use std::collections::HashMap;
 
     use crate::{hir::Definition, ApolloCompiler, DocumentDatabase, HirDatabase};
 
+    #[test]
+    fn it_creates_compiler_from_multiple_sources() {
+        let schema = r#"
+type Query {
+  name: String
+  price: Int
+  dimensions: Int
+  size: Int
+  weight: Int
+}"#;
+        let query = r#"
+query ExampleQuery {
+  name
+  price
+  dimensions
+  size
+  weight
+}
+      "#;
+
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(schema, "schema.graphql");
+        compiler.create_executable(query, "query.graphql");
+    }
+
     #[test]
     fn it_accesses_operation_definition_parts() {
         let input = r#"
@@ -151,18 +271,20 @@ type User {
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        let document_id = compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         assert!(diagnostics.is_empty());
 
-        let operations = ctx.db.operations();
+        let operations = compiler.db.operations(document_id);
         let operation_names: Vec<_> = operations.iter().filter_map(|op| op.name()).collect();
         assert_eq!(["ExampleQuery"], operation_names.as_slice());
 
-        let fragments = ctx.db.fragments();
+        let fragments = compiler.db.fragments(document_id);
         let fragment_names: Vec<_> = fragments.iter().map(|fragment| fragment.name()).collect();
         assert_eq!(["vipCustomer"], fragment_names.as_slice());
 
@@ -203,19 +325,21 @@ type Query {
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        let document_id = compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         assert!(diagnostics.is_empty());
 
-        let operations = ctx.db.operations();
+        let operations = compiler.db.operations(document_id);
         let fields = operations
             .iter()
             .find(|op| op.name() == Some("ExampleQuery"))
             .unwrap()
-            .fields(&ctx.db);
+            .fields(&compiler.db);
         let field_names: Vec<&str> = fields.iter().map(|f| f.name()).collect();
         assert_eq!(
             field_names,
@@ -260,23 +384,25 @@ type Concrete implements Interface {
 union Union = Concrete
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        let document_id = compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         assert!(diagnostics.is_empty());
 
-        let operations = ctx.db.operations();
+        let operations = compiler.db.operations(document_id);
         let fields = operations
             .iter()
             .find(|op| op.name() == Some("ExampleQuery"))
             .unwrap()
-            .fields(&ctx.db);
+            .fields(&compiler.db);
 
         let interface_field = fields.iter().find(|f| f.name() == "interface").unwrap();
 
         let interface_fields = interface_field.selection_set().fields();
         let interface_selection_fields_types: HashMap<_, _> = interface_fields
             .iter()
-            .map(|f| (f.name(), f.ty(&ctx.db).map(|f| f.name())))
+            .map(|f| (f.name(), f.ty(&compiler.db).map(|f| f.name())))
             .collect();
         assert_eq!(
             interface_selection_fields_types,
@@ -292,7 +418,7 @@ union Union = Concrete
         let inline_fragment_fields = inline_fragment.selection_set().fields();
         let inline_fragment_fields_types: HashMap<_, _> = inline_fragment_fields
             .iter()
-            .map(|f| (f.name(), f.ty(&ctx.db).map(|ty| ty.name())))
+            .map(|f| (f.name(), f.ty(&compiler.db).map(|ty| ty.name())))
             .collect();
         assert_eq!(
             inline_fragment_fields_types,
@@ -310,7 +436,7 @@ union Union = Concrete
         let union_inline_fragment_fields = union_inline_fragment.selection_set().fields();
         let union_inline_fragment_field_types: HashMap<_, _> = union_inline_fragment_fields
             .iter()
-            .map(|f| (f.name(), f.ty(&ctx.db).map(|ty| ty.name())))
+            .map(|f| (f.name(), f.ty(&compiler.db).map(|ty| ty.name())))
             .collect();
         assert_eq!(
             union_inline_fragment_field_types,
@@ -348,23 +474,25 @@ type Product {
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        let document_id = compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         assert!(diagnostics.is_empty());
 
         // Get the types of the two top level fields - topProducts and size
-        let operations = ctx.db.operations();
+        let operations = compiler.db.operations(document_id);
         let get_product_op = operations
             .iter()
             .find(|op| op.name() == Some("getProduct"))
             .unwrap();
-        let op_fields = get_product_op.fields(&ctx.db);
+        let op_fields = get_product_op.fields(&compiler.db);
         let name_field_def: Vec<String> = op_fields
             .iter()
-            .filter_map(|field| Some(field.ty(&ctx.db)?.name()))
+            .filter_map(|field| Some(field.ty(&compiler.db)?.name()))
             .collect();
         assert_eq!(name_field_def, ["Int", "Product"]);
 
@@ -378,7 +506,7 @@ type Product {
 
         let top_product_fields: Vec<String> = top_products
             .iter()
-            .filter_map(|f| Some(f.ty(&ctx.db)?.name()))
+            .filter_map(|f| Some(f.ty(&compiler.db)?.name()))
             .collect();
         assert_eq!(top_product_fields, ["String", "Boolean"]);
 
@@ -392,7 +520,7 @@ type Product {
             .selection_set()
             .field("inStock")
             .unwrap()
-            .field_definition(&ctx.db)
+            .field_definition(&compiler.db)
             .unwrap();
         let in_stock_directive: Vec<&str> = in_stock_field
             .directives()
@@ -403,21 +531,43 @@ type Product {
     }
 
     #[test]
-    fn it_accesses_fragment_definition_field_types() {
-        let input = r#"
-query getProduct {
-  topProducts {
-    type
-  }
-  ... vipCustomer
+    fn it_supports_multiple_independent_queries() {
+        let schema = r#"
+type Query {
+  topProducts: Product
+  customer: User
 }
 
-fragment vipCustomer on User {
-  id
-  name
-  profilePic(size: 50)
+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 product_query = r#"query getProduct { topProducts { type } }"#;
+        let customer_query = r#"{ customer }"#;
+        let colliding_query = r#"query getProduct { topProducts { type, price } }"#;
+
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_schema(schema, "schema.graphql");
+        compiler.create_executable(product_query, "query.graphql");
+        compiler.create_executable(customer_query, "query.graphql");
+        compiler.create_executable(colliding_query, "query.graphql");
+
+        assert_eq!(compiler.validate(), &[]);
+    }
+
+    #[test]
+    fn it_accesses_fragment_definition_field_types() {
+        let schema = r#"
 type Query {
   topProducts: Product
   customer: User
@@ -436,15 +586,34 @@ type User {
 
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
+        let query = r#"
+query getProduct {
+  topProducts {
+    type
+  }
+  ... vipCustomer
+}
+
+fragment vipCustomer on User {
+  id
+  name
+  profilePic(size: 50)
+}
+"#;
+
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_schema(schema, "schema.graphql");
+        let query_id = compiler.create_executable(query, "query.graphql");
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         assert!(diagnostics.is_empty());
 
-        let op = ctx.db.find_operation_by_name(String::from("getProduct"));
+        let op = compiler
+            .db
+            .find_operation_by_name(query_id, String::from("getProduct"));
         let fragment_in_op: Vec<crate::hir::FragmentDefinition> = op
             .unwrap()
             .selection_set()
@@ -452,7 +621,7 @@ scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
             .iter()
             .filter_map(|sel| match sel {
                 crate::hir::Selection::FragmentSpread(frag) => {
-                    Some(frag.fragment(&ctx.db)?.as_ref().clone())
+                    Some(frag.fragment(&compiler.db)?.as_ref().clone())
                 }
                 _ => None,
             })
@@ -463,7 +632,7 @@ scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
             .collect();
         let field_ty: Vec<String> = fragment_fields
             .iter()
-            .filter_map(|f| Some(f.ty(&ctx.db)?.name()))
+            .filter_map(|f| Some(f.ty(&compiler.db)?.name()))
             .collect();
         assert_eq!(field_ty, ["ID", "String", "URL"])
     }
@@ -493,8 +662,10 @@ type Result {
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
@@ -512,14 +683,16 @@ type Query {
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         assert!(diagnostics.is_empty());
 
-        let scalars = ctx.db.scalars();
+        let scalars = compiler.db.scalars();
 
         let directives: Vec<&str> = scalars
             .iter()
@@ -546,14 +719,16 @@ enum Pet {
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         assert!(diagnostics.is_empty());
 
-        let enums = ctx.db.enums();
+        let enums = compiler.db.enums();
         let enum_values: Vec<&str> = enums
             .iter()
             .find(|enum_def| enum_def.name() == "Pet")
@@ -589,14 +764,16 @@ type SearchQuery {
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         assert!(diagnostics.is_empty());
 
-        let unions = ctx.db.unions();
+        let unions = compiler.db.unions();
         let union_members: Vec<&str> = unions
             .iter()
             .find(|def| def.name() == "SearchResult")
@@ -615,7 +792,7 @@ type SearchQuery {
             .iter()
             .find(|mem| mem.name() == "Person")
             .unwrap()
-            .object(&ctx.db);
+            .object(&compiler.db);
 
         if let Some(photo) = photo_object {
             let fields: Vec<&str> = photo
@@ -641,14 +818,16 @@ type Book @delegateField(name: "pageCount") @delegateField(name: "author") {
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         assert!(diagnostics.is_empty());
 
-        let directives = ctx.db.directive_definitions();
+        let directives = compiler.db.directive_definitions();
         let locations: Vec<String> = directives
             .iter()
             .filter_map(|dir| {
@@ -685,14 +864,16 @@ input Point2D {
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         assert!(diagnostics.is_empty());
 
-        let input_objects = ctx.db.input_objects();
+        let input_objects = compiler.db.input_objects();
         let fields: Vec<&str> = input_objects
             .iter()
             .filter_map(|input| {
@@ -722,14 +903,19 @@ type Book @directiveA(name: "pageCount") @directiveB(name: "author") {
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         assert!(diagnostics.is_empty());
 
-        let book_obj = ctx.db.find_object_type_by_name("Book".to_string()).unwrap();
+        let book_obj = compiler
+            .db
+            .find_object_type_by_name("Book".to_string())
+            .unwrap();
 
         let directive_names: Vec<&str> = book_obj.directives().iter().map(|d| d.name()).collect();
         assert_eq!(directive_names, ["directiveA", "directiveB"]);
@@ -751,14 +937,16 @@ enum Number {
 scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         assert!(diagnostics.is_empty());
 
-        let person_obj = ctx.db.find_object_type_by_name("Person".to_string());
+        let person_obj = compiler.db.find_object_type_by_name("Person".to_string());
 
         if let Some(person) = person_obj {
             let field_ty_directive: Vec<String> = person
@@ -766,7 +954,7 @@ scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
                 .iter()
                 .filter_map(|f| {
                     // get access to the actual definition the field is using
-                    if let Some(field_ty) = f.ty().ty(&ctx.db) {
+                    if let Some(field_ty) = f.ty().ty(&compiler.db) {
                         match field_ty.as_ref() {
                             // get that definition's directives, for example
                             Definition::ScalarTypeDefinition(scalar) => {
@@ -795,7 +983,7 @@ scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
                         .input_values()
                         .iter()
                         .filter_map(|val| {
-                            if let Some(input_ty) = val.ty().ty(&ctx.db) {
+                            if let Some(input_ty) = val.ty().ty(&compiler.db) {
                                 match input_ty.as_ref() {
                                     // get that definition's directives, for example
                                     Definition::EnumTypeDefinition(enum_) => {
@@ -831,21 +1019,23 @@ input Person {
 scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         assert!(diagnostics.is_empty());
 
-        let person_obj = ctx.db.find_input_object_by_name("Person".to_string());
+        let person_obj = compiler.db.find_input_object_by_name("Person".to_string());
 
         if let Some(person) = person_obj {
             let field_ty_directive: Vec<String> = person
                 .input_fields_definition()
                 .iter()
                 .filter_map(|f| {
-                    if let Some(field_ty) = f.ty().ty(&ctx.db) {
+                    if let Some(field_ty) = f.ty().ty(&compiler.db) {
                         match field_ty.as_ref() {
                             Definition::ScalarTypeDefinition(scalar) => {
                                 let dir_names: Vec<String> = scalar
@@ -944,15 +1134,17 @@ type User
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
         // the scalar warning diagnostic
         assert_eq!(diagnostics.len(), 1);
 
-        let object_types = ctx.db.object_types();
+        let object_types = compiler.db.object_types();
         let object_names: Vec<_> = object_types.iter().map(|op| op.name()).collect();
         assert_eq!(
             ["Mutation", "Product", "Query", "Review", "User"],
@@ -971,16 +1163,18 @@ type Query {
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
 
         assert!(diagnostics.is_empty());
 
-        let snapshot = ctx.snapshot();
-        let snapshot2 = ctx.snapshot();
+        let snapshot = compiler.snapshot();
+        let snapshot2 = compiler.snapshot();
 
         let thread1 = std::thread::spawn(move || snapshot.find_object_type_by_name("Query".into()));
         let thread2 = std::thread::spawn(move || snapshot2.scalars());
@@ -988,4 +1182,37 @@ scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
         thread1.join().expect("object_type_by_name panicked");
         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.create_document(input, "document.graphql");
+
+        let object_type = compiler
+            .db
+            .find_object_type_by_name("Query".into())
+            .unwrap();
+        assert!(object_type.directives().is_empty());
+
+        let input = r#"
+type Query @withDirective {
+  website: URL,
+  amount: Int
+}
+"#;
+        compiler.update_document(input_id, input);
+
+        let object_type = compiler
+            .db
+            .find_object_type_by_name("Query".into())
+            .unwrap();
+        assert_eq!(object_type.directives().len(), 1);
+    }
 }
diff --git a/crates/apollo-compiler/src/validation/directive.rs b/crates/apollo-compiler/src/validation/directive.rs
index 83c4f75..b3cc12c 100644
--- a/crates/apollo-compiler/src/validation/directive.rs
+++ b/crates/apollo-compiler/src/validation/directive.rs
@@ -16,36 +16,16 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     for dir_def in db.directive_definitions().iter() {
         let name = dir_def.name();
         if let Some(prev_def) = seen.get(&name) {
-            if prev_def.ast_node(db.upcast()).is_some() && dir_def.ast_node(db.upcast()).is_some() {
-                let prev_offset: usize = prev_def
-                    .ast_node(db.upcast())
-                    .unwrap()
-                    .text_range()
-                    .start()
-                    .into();
-                let prev_node_len: usize = prev_def
-                    .ast_node(db.upcast())
-                    .unwrap()
-                    .text_range()
-                    .len()
-                    .into();
+            if prev_def.loc.is_some() && dir_def.loc.is_some() {
+                let prev_offset: usize = prev_def.loc().unwrap().offset();
+                let prev_node_len: usize = prev_def.loc().unwrap().node_len();
 
-                let current_offset: usize = dir_def
-                    .ast_node(db.upcast())
-                    .unwrap()
-                    .text_range()
-                    .start()
-                    .into();
-                let current_node_len: usize = dir_def
-                    .ast_node(db.upcast())
-                    .unwrap()
-                    .text_range()
-                    .len()
-                    .into();
+                let current_offset: usize = dir_def.loc().unwrap().offset();
+                let current_node_len: usize = dir_def.loc().unwrap().node_len();
                 errors.push(ApolloDiagnostic::UniqueDefinition(UniqueDefinition {
                     ty: "directive".into(),
                     name: name.into(),
-                    src: db.input(),
+                    src: db.source_code(prev_def.loc().unwrap().file_id()),
                     original_definition: (prev_offset, prev_node_len).into(),
                     redefined_definition: (current_offset, current_node_len).into(),
                     help: Some(format!(
@@ -68,12 +48,12 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             for directive in input_values.directives().iter() {
                 let directive_name = directive.name();
                 if name == directive_name {
-                    let offset = directive.ast_node(db.upcast()).text_range().start().into();
-                    let len: usize = directive.ast_node(db.upcast()).text_range().len().into();
+                    let offset = directive.loc().offset();
+                    let len: usize = directive.loc().node_len();
                     errors.push(ApolloDiagnostic::RecursiveDefinition(RecursiveDefinition {
                         message: format!("{} directive definition cannot reference itself", name),
                         definition: (offset, len).into(),
-                        src: db.input(),
+                        src: db.source_code(directive.loc().file_id()),
                         definition_label: "recursive directive definition".into(),
                     }));
                 }
diff --git a/crates/apollo-compiler/src/validation/enum_.rs b/crates/apollo-compiler/src/validation/enum_.rs
index 2860687..60ed526 100644
--- a/crates/apollo-compiler/src/validation/enum_.rs
+++ b/crates/apollo-compiler/src/validation/enum_.rs
@@ -17,17 +17,15 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
         for enum_value in enum_def.enum_values_definition().iter() {
             let value = enum_value.enum_value();
             if let Some(prev_def) = seen.get(&value) {
-                let prev_offset: usize = prev_def.ast_node(db.upcast()).text_range().start().into();
-                let prev_node_len: usize = prev_def.ast_node(db.upcast()).text_range().len().into();
+                let prev_offset = prev_def.loc().offset();
+                let prev_node_len = prev_def.loc().node_len();
 
-                let current_offset: usize =
-                    enum_value.ast_node(db.upcast()).text_range().start().into();
-                let current_node_len: usize =
-                    enum_value.ast_node(db.upcast()).text_range().len().into();
+                let current_offset = enum_value.loc().offset();
+                let current_node_len = enum_value.loc().node_len();
                 diagnostics.push(ApolloDiagnostic::UniqueDefinition(UniqueDefinition {
                     ty: "enum".into(),
                     name: value.into(),
-                    src: db.input(),
+                    src: db.source_code(prev_def.loc().file_id()),
                     original_definition: (prev_offset, prev_node_len).into(),
                     redefined_definition: (current_offset, current_node_len).into(),
                     help: Some(format!("{value} must only be defined once in this enum.")),
@@ -44,13 +42,13 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     for enum_def in db.enums().iter() {
         for enum_value in enum_def.enum_values_definition().iter() {
             let value = enum_value.enum_value();
-            let offset: usize = enum_value.ast_node(db.upcast()).text_range().start().into();
-            let len: usize = enum_value.ast_node(db.upcast()).text_range().len().into();
+            let offset = enum_value.loc().offset();
+            let len = enum_value.loc().node_len();
 
             if value.to_uppercase() != value {
                 diagnostics.push(ApolloDiagnostic::CapitalizedValue(CapitalizedValue {
                     ty: value.into(),
-                    src: db.input(),
+                    src: db.source_code(enum_value.loc().file_id()),
                     value: (offset, len).into(),
                 }));
             }
diff --git a/crates/apollo-compiler/src/validation/input_object.rs b/crates/apollo-compiler/src/validation/input_object.rs
index 9d73a54..9fd755e 100644
--- a/crates/apollo-compiler/src/validation/input_object.rs
+++ b/crates/apollo-compiler/src/validation/input_object.rs
@@ -16,20 +16,15 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     for input_object in db.input_objects().iter() {
         let name = input_object.name();
         if let Some(prev_def) = seen.get(name) {
-            let prev_offset: usize = prev_def.ast_node(db.upcast()).text_range().start().into();
-            let prev_node_len: usize = prev_def.ast_node(db.upcast()).text_range().len().into();
+            let prev_offset = prev_def.loc().offset();
+            let prev_node_len = prev_def.loc().node_len();
 
-            let current_offset: usize = input_object
-                .ast_node(db.upcast())
-                .text_range()
-                .start()
-                .into();
-            let current_node_len: usize =
-                input_object.ast_node(db.upcast()).text_range().len().into();
+            let current_offset = input_object.loc().offset();
+            let current_node_len = input_object.loc().node_len();
             diagnostics.push(ApolloDiagnostic::UniqueDefinition(UniqueDefinition {
                 ty: "input object".into(),
                 name: name.into(),
-                src: db.input(),
+                src: db.source_code(prev_def.loc().file_id()),
                 original_definition: (prev_offset, prev_node_len).into(),
                 redefined_definition: (current_offset, current_node_len).into(),
                 help: Some(format!(
@@ -52,38 +47,16 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
         for field in input_fields {
             let field_name = field.name();
             if let Some(prev_field) = seen.get(&field_name) {
-                if prev_field.ast_node(db.upcast()).is_some()
-                    && field.ast_node(db.upcast()).is_some()
-                {
-                    let prev_offset: usize = prev_field
-                        .ast_node(db.upcast())
-                        .unwrap()
-                        .text_range()
-                        .start()
-                        .into();
-                    let prev_node_len: usize = prev_field
-                        .ast_node(db.upcast())
-                        .unwrap()
-                        .text_range()
-                        .len()
-                        .into();
+                if prev_field.loc().is_some() && field.loc().is_some() {
+                    let prev_offset = prev_field.loc().unwrap().offset();
+                    let prev_node_len = prev_field.loc().unwrap().node_len();
 
-                    let current_offset: usize = field
-                        .ast_node(db.upcast())
-                        .unwrap()
-                        .text_range()
-                        .start()
-                        .into();
-                    let current_node_len: usize = field
-                        .ast_node(db.upcast())
-                        .unwrap()
-                        .text_range()
-                        .len()
-                        .into();
+                    let current_offset = field.loc().unwrap().offset();
+                    let current_node_len = field.loc().unwrap().node_len();
 
                     diagnostics.push(ApolloDiagnostic::UniqueField(UniqueField {
                         field: field_name.into(),
-                        src: db.input(),
+                        src: db.source_code(prev_field.loc().unwrap().file_id()),
                         original_field: (prev_offset, prev_node_len).into(),
                         redefined_field: (current_offset, current_node_len).into(),
                         help: Some(format!(
diff --git a/crates/apollo-compiler/src/validation/interface.rs b/crates/apollo-compiler/src/validation/interface.rs
index e8069d3..50828d3 100644
--- a/crates/apollo-compiler/src/validation/interface.rs
+++ b/crates/apollo-compiler/src/validation/interface.rs
@@ -20,15 +20,15 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     for interface in db.interfaces().iter() {
         let name = interface.name();
         if let Some(prev_def) = seen.get(&name) {
-            let prev_offset: usize = prev_def.ast_node(db.upcast()).text_range().start().into();
-            let prev_node_len: usize = prev_def.ast_node(db.upcast()).text_range().len().into();
+            let prev_offset = prev_def.loc().offset();
+            let prev_node_len = prev_def.loc().node_len();
 
-            let current_offset: usize = interface.ast_node(db.upcast()).text_range().start().into();
-            let current_node_len: usize = interface.ast_node(db.upcast()).text_range().len().into();
+            let current_offset = interface.loc().offset();
+            let current_node_len = interface.loc().node_len();
             diagnostics.push(ApolloDiagnostic::UniqueDefinition(UniqueDefinition {
                 ty: "interface".into(),
                 name: name.into(),
-                src: db.input(),
+                src: db.source_code(prev_def.loc().file_id()),
                 original_definition: (prev_offset, prev_node_len).into(),
                 redefined_definition: (current_offset, current_node_len).into(),
                 help: Some(format!(
@@ -61,20 +61,12 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             if let Some(interface) = implements_interface.interface_definition(db.upcast()) {
                 let i_name = (*interface.name()).to_string();
                 if name == i_name {
-                    let offset = implements_interface
-                        .ast_node(db.upcast())
-                        .text_range()
-                        .start()
-                        .into();
-                    let len: usize = implements_interface
-                        .ast_node(db.upcast())
-                        .text_range()
-                        .len()
-                        .into();
+                    let offset = implements_interface.loc().offset();
+                    let len = implements_interface.loc().node_len();
                     diagnostics.push(ApolloDiagnostic::RecursiveDefinition(RecursiveDefinition {
                         message: format!("{} interface cannot implement itself", i_name),
                         definition: (offset, len).into(),
-                        src: db.input(),
+                        src: db.source_code(implements_interface.loc().file_id()),
                         definition_label: "recursive implements interfaces".into(),
                     }));
                 }
@@ -93,18 +85,16 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             //
             // Returns Unique Value error.
             let field_name = field.name();
-            let offset: usize = field.ast_node(db.upcast()).text_range().start().into();
-            let len: usize = field.ast_node(db.upcast()).text_range().len().into();
+            let offset = field.loc().offset();
+            let len = field.loc().node_len();
 
             if let Some(prev_field) = seen.get(&field_name) {
-                let prev_offset: usize =
-                    prev_field.ast_node(db.upcast()).text_range().start().into();
-                let prev_node_len: usize =
-                    prev_field.ast_node(db.upcast()).text_range().len().into();
+                let prev_offset = prev_field.loc().offset();
+                let prev_node_len = prev_field.loc().node_len();
 
                 diagnostics.push(ApolloDiagnostic::UniqueField(UniqueField {
                     field: field_name.into(),
-                    src: db.input(),
+                    src: db.source_code(prev_field.loc().file_id()),
                     original_field: (prev_offset, prev_node_len).into(),
                     redefined_field: (offset, len).into(),
                     help: Some(format!(
@@ -121,22 +111,22 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                     diagnostics.push(ApolloDiagnostic::OutputType(OutputType {
                         name: field.name().into(),
                         ty: field_ty.ty(),
-                        src: db.input(),
+                        src: db.source_code(field.loc().file_id()),
                         definition: (offset, len).into(),
                     }))
                 }
-            } else if let Some(node) = field.ty().ast_node(db.upcast()) {
-                let field_ty_offset: usize = node.text_range().start().into();
-                let field_ty_len: usize = node.text_range().len().into();
+            } else if let Some(loc) = field.ty().loc() {
+                let field_ty_offset = loc.offset();
+                let field_ty_len = loc.node_len();
                 diagnostics.push(ApolloDiagnostic::UndefinedDefinition(UndefinedDefinition {
                     ty: field.ty().name(),
-                    src: db.input(),
+                    src: db.source_code(loc.file_id()),
                     definition: (field_ty_offset, field_ty_len).into(),
                 }))
             } else {
                 diagnostics.push(ApolloDiagnostic::UndefinedDefinition(UndefinedDefinition {
                     ty: field.ty().name(),
-                    src: db.input(),
+                    src: db.source_code(field.loc().file_id()),
                     definition: (offset, len).into(),
                 }))
             }
@@ -148,7 +138,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
         .iter()
         .map(|interface| ValidationSet {
             name: interface.name().to_owned(),
-            node: interface.ast_node(db.upcast()),
+            loc: *interface.loc(),
         })
         .collect();
     for interface_def in interfaces.iter() {
@@ -160,16 +150,16 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             .iter()
             .map(|interface| ValidationSet {
                 name: interface.interface().to_owned(),
-                node: interface.ast_node(db.upcast()),
+                loc: *interface.loc(),
             })
             .collect();
         let diff = implements_interfaces.difference(&defined_interfaces);
         for undefined in diff {
-            let offset = undefined.node.text_range().start().into();
-            let len: usize = undefined.node.text_range().len().into();
+            let offset = undefined.loc.offset();
+            let len: usize = undefined.loc.node_len();
             diagnostics.push(ApolloDiagnostic::UndefinedDefinition(UndefinedDefinition {
                 ty: undefined.name.clone(),
-                src: db.input(),
+                src: db.source_code(undefined.loc.file_id()),
                 definition: (offset, len).into(),
             }))
         }
@@ -188,7 +178,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                         .iter()
                         .map(|interface| ValidationSet {
                             name: interface.interface().to_owned(),
-                            node: implements_interface.ast_node(db.upcast()),
+                            loc: *implements_interface.loc(),
                         })
                         .collect();
                     Some(child_interfaces)
@@ -200,12 +190,12 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             .collect();
         let transitive_diff = transitive_interfaces.difference(&implements_interfaces);
         for undefined in transitive_diff {
-            let offset = undefined.node.text_range().start().into();
-            let len: usize = undefined.node.text_range().len().into();
+            let offset = undefined.loc.offset();
+            let len = undefined.loc.node_len();
             diagnostics.push(ApolloDiagnostic::TransitiveImplementedInterfaces(
                 TransitiveImplementedInterfaces {
                     missing_interface: undefined.name.clone(),
-                    src: db.input(),
+                    src: db.source_code(undefined.loc.file_id()),
                     definition: (offset, len).into(),
                 },
             ))
@@ -221,7 +211,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             .iter()
             .map(|field| ValidationSet {
                 name: field.name().into(),
-                node: field.ast_node(db.upcast()),
+                loc: *field.loc(),
             })
             .collect();
         for implements_interface in interface_def.implements_interfaces().iter() {
@@ -231,31 +221,22 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                     .iter()
                     .map(|field| ValidationSet {
                         name: field.name().into(),
-                        node: field.ast_node(db.upcast()),
+                        loc: *field.loc(),
                     })
                     .collect();
 
                 let field_diff = implements_interface_fields.difference(&fields);
 
                 for missing_field in field_diff {
-                    let current_offset: usize = interface_def
-                        .ast_node(db.upcast())
-                        .text_range()
-                        .start()
-                        .into();
-                    let current_len = interface_def
-                        .ast_node(db.upcast())
-                        .text_range()
-                        .len()
-                        .into();
-
-                    let super_offset = interface.ast_node(db.upcast()).text_range().start().into();
-                    let super_len: usize =
-                        interface.ast_node(db.upcast()).text_range().len().into();
+                    let current_offset = interface_def.loc().offset();
+                    let current_len = interface_def.loc().node_len();
+
+                    let super_offset = interface.loc().offset();
+                    let super_len = interface.loc().node_len();
 
                     diagnostics.push(ApolloDiagnostic::MissingField(MissingField {
                         ty: missing_field.name.clone(),
-                        src: db.input(),
+                        src: db.source_code(interface_def.loc.file_id()),
                         current_definition: (current_offset, current_len).into(),
                         super_definition: (super_offset, super_len).into(),
                         help: Some(
@@ -294,8 +275,10 @@ interface NamedEntity {
 
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic)
         }
@@ -326,8 +309,10 @@ interface NamedEntity {
 
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic)
         }
@@ -352,8 +337,10 @@ interface NamedEntity implements NamedEntity {
 
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic)
         }
@@ -371,8 +358,10 @@ interface NamedEntity implements NewEntity {
 
 scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic)
         }
@@ -400,8 +389,10 @@ interface Image implements Resource & Node {
   thumbnail: String
 }
 "#;
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic)
         }
@@ -461,8 +452,10 @@ input Point2D {
 
 scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic)
         }
diff --git a/crates/apollo-compiler/src/validation/mod.rs b/crates/apollo-compiler/src/validation/mod.rs
index febe77e..2009dc6 100644
--- a/crates/apollo-compiler/src/validation/mod.rs
+++ b/crates/apollo-compiler/src/validation/mod.rs
@@ -11,14 +11,14 @@ mod schema;
 mod union_;
 mod unused_variable;
 
-use apollo_parser::SyntaxNode;
-
 pub use validation_db::{ValidationDatabase, ValidationStorage};
 
+use crate::hir::HirNodeLocation;
+
 #[derive(Debug, Eq)]
 struct ValidationSet {
     name: String,
-    node: SyntaxNode,
+    loc: HirNodeLocation,
 }
 
 impl std::hash::Hash for ValidationSet {
diff --git a/crates/apollo-compiler/src/validation/object.rs b/crates/apollo-compiler/src/validation/object.rs
index c11a817..593b008 100644
--- a/crates/apollo-compiler/src/validation/object.rs
+++ b/crates/apollo-compiler/src/validation/object.rs
@@ -20,15 +20,15 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     for object in db.object_types().iter() {
         let name = object.name();
         if let Some(prev_def) = seen.get(&name) {
-            let prev_offset: usize = prev_def.ast_node(db.upcast()).text_range().start().into();
-            let prev_node_len: usize = prev_def.ast_node(db.upcast()).text_range().len().into();
+            let prev_offset = prev_def.loc().offset();
+            let prev_node_len = prev_def.loc().node_len();
 
-            let current_offset: usize = object.ast_node(db.upcast()).text_range().start().into();
-            let current_node_len: usize = object.ast_node(db.upcast()).text_range().len().into();
+            let current_offset = object.loc().offset();
+            let current_node_len = object.loc().node_len();
             diagnostics.push(ApolloDiagnostic::UniqueDefinition(UniqueDefinition {
                 ty: "object type".into(),
                 name: name.into(),
-                src: db.input(),
+                src: db.source_code(prev_def.loc().file_id()),
                 original_definition: (prev_offset, prev_node_len).into(),
                 redefined_definition: (current_offset, current_node_len).into(),
                 help: Some(format!(
@@ -50,18 +50,16 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             //
             // Returns Unique Value error.
             let field_name = field.name();
-            let offset: usize = field.ast_node(db.upcast()).text_range().start().into();
-            let len: usize = field.ast_node(db.upcast()).text_range().len().into();
+            let offset = field.loc().offset();
+            let len = field.loc().node_len();
 
             if let Some(prev_field) = seen.get(&field_name) {
-                let prev_offset: usize =
-                    prev_field.ast_node(db.upcast()).text_range().start().into();
-                let prev_node_len: usize =
-                    prev_field.ast_node(db.upcast()).text_range().len().into();
+                let prev_offset = prev_field.loc().offset();
+                let prev_node_len = prev_field.loc().node_len();
 
                 diagnostics.push(ApolloDiagnostic::UniqueField(UniqueField {
                     field: field_name.into(),
-                    src: db.input(),
+                    src: db.source_code(prev_field.loc().file_id()),
                     original_field: (prev_offset, prev_node_len).into(),
                     redefined_field: (offset, len).into(),
                     help: Some(format!(
@@ -78,22 +76,22 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                     diagnostics.push(ApolloDiagnostic::OutputType(OutputType {
                         name: field.name().into(),
                         ty: field_ty.ty(),
-                        src: db.input(),
+                        src: db.source_code(field.loc().file_id()),
                         definition: (offset, len).into(),
                     }))
                 }
-            } else if let Some(node) = field.ty().ast_node(db.upcast()) {
-                let field_ty_offset: usize = node.text_range().start().into();
-                let field_ty_len: usize = node.text_range().len().into();
+            } else if let Some(loc) = field.ty().loc() {
+                let field_ty_offset = loc.offset();
+                let field_ty_len = loc.node_len();
                 diagnostics.push(ApolloDiagnostic::UndefinedDefinition(UndefinedDefinition {
                     ty: field.ty().name(),
-                    src: db.input(),
+                    src: db.source_code(field.loc().file_id()),
                     definition: (field_ty_offset, field_ty_len).into(),
                 }))
             } else {
                 diagnostics.push(ApolloDiagnostic::UndefinedDefinition(UndefinedDefinition {
                     ty: field.ty().name(),
-                    src: db.input(),
+                    src: db.source_code(field.loc().file_id()),
                     definition: (offset, len).into(),
                 }))
             }
@@ -106,7 +104,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
         .iter()
         .map(|interface| ValidationSet {
             name: interface.name().to_owned(),
-            node: interface.ast_node(db.upcast()),
+            loc: *interface.loc(),
         })
         .collect();
     for object in objects.iter() {
@@ -118,16 +116,16 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             .iter()
             .map(|interface| ValidationSet {
                 name: interface.interface().to_owned(),
-                node: interface.ast_node(db.upcast()),
+                loc: *interface.loc(),
             })
             .collect();
         let diff = implements_interfaces.difference(&defined_interfaces);
         for undefined in diff {
-            let offset = undefined.node.text_range().start().into();
-            let len: usize = undefined.node.text_range().len().into();
+            let offset = undefined.loc.offset();
+            let len: usize = undefined.loc.node_len();
             diagnostics.push(ApolloDiagnostic::UndefinedDefinition(UndefinedDefinition {
                 ty: undefined.name.clone(),
-                src: db.input(),
+                src: db.source_code(undefined.loc.file_id()),
                 definition: (offset, len).into(),
             }))
         }
@@ -146,7 +144,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                         .iter()
                         .map(|interface| ValidationSet {
                             name: interface.interface().to_owned(),
-                            node: implements_interface.ast_node(db.upcast()),
+                            loc: *implements_interface.loc(),
                         })
                         .collect();
                     Some(child_interfaces)
@@ -158,12 +156,12 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             .collect();
         let transitive_diff = transitive_interfaces.difference(&implements_interfaces);
         for undefined in transitive_diff {
-            let offset = undefined.node.text_range().start().into();
-            let len: usize = undefined.node.text_range().len().into();
+            let offset = undefined.loc.offset();
+            let len = undefined.loc.node_len();
             diagnostics.push(ApolloDiagnostic::TransitiveImplementedInterfaces(
                 TransitiveImplementedInterfaces {
                     missing_interface: undefined.name.clone(),
-                    src: db.input(),
+                    src: db.source_code(undefined.loc.file_id()),
                     definition: (offset, len).into(),
                 },
             ))
@@ -179,7 +177,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             .iter()
             .map(|field| ValidationSet {
                 name: field.name().into(),
-                node: field.ast_node(db.upcast()),
+                loc: *field.loc(),
             })
             .collect();
         for implements_interface in object.implements_interfaces().iter() {
@@ -189,24 +187,22 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                     .iter()
                     .map(|field| ValidationSet {
                         name: field.name().into(),
-                        node: field.ast_node(db.upcast()),
+                        loc: *field.loc(),
                     })
                     .collect();
 
                 let field_diff = implements_interface_fields.difference(&fields);
 
                 for missing_field in field_diff {
-                    let current_offset: usize =
-                        object.ast_node(db.upcast()).text_range().start().into();
-                    let current_len = object.ast_node(db.upcast()).text_range().len().into();
+                    let current_offset = object.loc().offset();
+                    let current_len = object.loc().node_len();
 
-                    let super_offset = interface.ast_node(db.upcast()).text_range().start().into();
-                    let super_len: usize =
-                        interface.ast_node(db.upcast()).text_range().len().into();
+                    let super_offset = interface.loc().offset();
+                    let super_len = interface.loc().node_len();
 
                     diagnostics.push(ApolloDiagnostic::MissingField(MissingField {
                         ty: missing_field.name.clone(),
-                        src: db.input(),
+                        src: db.source_code(object.loc().file_id()),
                         current_definition: (current_offset, current_len).into(),
                         super_definition: (super_offset, super_len).into(),
                         help: Some(
@@ -277,8 +273,10 @@ input Point2D {
 
 scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
 "#;
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic)
         }
diff --git a/crates/apollo-compiler/src/validation/operation.rs b/crates/apollo-compiler/src/validation/operation.rs
index b0e91e7..0b00f1f 100644
--- a/crates/apollo-compiler/src/validation/operation.rs
+++ b/crates/apollo-compiler/src/validation/operation.rs
@@ -5,28 +5,32 @@ use crate::{
         MissingIdent, SingleRootField, UndefinedField, UniqueDefinition, UnsupportedOperation,
     },
     hir::{OperationDefinition, Selection},
-    ApolloDiagnostic, ValidationDatabase,
+    ApolloDiagnostic, FileId, ValidationDatabase,
 };
 // use crate::{diagnostics::ErrorDiagnostic, ApolloDiagnostic, Document};
 
-pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub fn check(db: &dyn ValidationDatabase, file_id: FileId) -> Vec<ApolloDiagnostic> {
     let mut diagnostics = Vec::new();
+    let operations = db.operations(file_id);
+    let subscription_operations = db.upcast().subscription_operations(file_id);
+    let query_operations = db.upcast().query_operations(file_id);
+    let mutation_operations = db.upcast().mutation_operations(file_id);
+
     // It is possible to have an unnamed (anonymous) operation definition only
     // if there is **one** operation definition.
     //
     // Return a Missing Indent error if there are multiple operations and one or
     // more are missing a name.
-    let op_len = db.operations().len();
+    let op_len = operations.len();
     if op_len > 1 {
-        let missing_ident: Vec<ApolloDiagnostic> = db
-            .operations()
+        let missing_ident: Vec<ApolloDiagnostic> = operations
             .iter()
             .filter_map(|op| {
                 if op.name().is_none() {
-                    let offset = op.ast_node(db.upcast()).text_range().start().into();
-                    let len: usize = op.ast_node(db.upcast()).text_range().len().into();
+                    let offset = op.loc().offset();
+                    let len= op.loc().node_len();
                     return Some(ApolloDiagnostic::MissingIdent(MissingIdent {
-                        src: db.input(),
+                        src: db.source_code(op.loc().file_id()),
                         definition: (offset, len).into(),
                         help: Some(format!("GraphQL allows a short-hand form for defining query operations when only that one operation exists in the document. There are {op_len} operations in this document."))
                     }));
@@ -41,18 +45,18 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     //
     // Return a Unique Operation Definition error in case of a duplicate name.
     let mut seen: HashMap<&str, &OperationDefinition> = HashMap::new();
-    for op in db.operations().iter() {
+    for op in operations.iter() {
         if let Some(name) = op.name() {
             if let Some(prev_def) = seen.get(&name) {
-                let prev_offset: usize = prev_def.ast_node(db.upcast()).text_range().start().into();
-                let prev_node_len: usize = prev_def.ast_node(db.upcast()).text_range().len().into();
+                let prev_offset = prev_def.loc().offset();
+                let prev_node_len = prev_def.loc().node_len();
 
-                let current_offset: usize = op.ast_node(db.upcast()).text_range().start().into();
-                let current_node_len: usize = op.ast_node(db.upcast()).text_range().len().into();
+                let current_offset = op.loc().offset();
+                let current_node_len = op.loc().node_len();
                 diagnostics.push(ApolloDiagnostic::UniqueDefinition(UniqueDefinition {
                     ty: "operation".into(),
                     name: name.into(),
-                    src: db.input(),
+                    src: db.source_code(prev_def.loc().file_id()),
                     original_definition: (prev_offset, prev_node_len).into(),
                     redefined_definition: (current_offset, current_node_len).into(),
                     help: Some(format!(
@@ -67,10 +71,8 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
 
     // A Subscription operation definition can only have **one** root level
     // field.
-    if db.upcast().subscription_operations().len() >= 1 {
-        let single_root_field: Vec<ApolloDiagnostic> = db
-            .upcast()
-            .subscription_operations()
+    if subscription_operations.len() >= 1 {
+        let single_root_field: Vec<ApolloDiagnostic> = subscription_operations
             .iter()
             .filter_map(|op| {
                 let mut fields = op.fields(db.upcast()).as_ref().clone();
@@ -78,11 +80,11 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                 fields.extend(op.fields_in_fragment_spread(db.upcast()).as_ref().clone());
                 if fields.len() > 1 {
                     let field_names: Vec<&str> = fields.iter().map(|f| f.name()).collect();
-                    let offset = op.ast_node(db.upcast()).text_range().start().into();
-                    let len: usize = op.ast_node(db.upcast()).text_range().len().into();
+                    let offset = op.loc().offset();
+                    let len = op.loc().node_len();
                     Some(ApolloDiagnostic::SingleRootField(SingleRootField {
                         fields: fields.len(),
-                        src: db.input(),
+                        src: db.source_code(op.loc().file_id()),
                         subscription: (offset, len).into(),
                         help: Some(format!(
                             "There are {} root fields: {}. This is not allowed.",
@@ -102,24 +104,20 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     // defined schema root operation types.
     //
     //   * subscription operation - subscription root operation
-    if db.upcast().subscription_operations().len() >= 1
-        && db.schema().subscription(db.upcast()).is_none()
-    {
-        let unsupported_ops: Vec<ApolloDiagnostic> = db
-            .upcast()
-            .subscription_operations()
+    if subscription_operations.len() >= 1 && db.schema().subscription(db.upcast()).is_none() {
+        let unsupported_ops: Vec<ApolloDiagnostic> = subscription_operations
             .iter()
             .map(|op| {
-                let op_offset: usize = op.ast_node(db.upcast()).text_range().start().into();
-                let op_len: usize = op.ast_node(db.upcast()).text_range().len().into();
+                let op_offset = op.loc().offset();
+                let op_len = op.loc().node_len();
 
-                if let Some(schema_node) = db.schema().ast_node(db.upcast()) {
-                    let schema_offset: usize = schema_node.text_range().start().into();
-                    let schema_len: usize = schema_node.text_range().len().into();
+                if let Some(loc) = db.schema().loc() {
+                    let schema_offset = loc.offset();
+                    let schema_len = loc.node_len();
                     ApolloDiagnostic::UnsupportedOperation(UnsupportedOperation {
                         ty: "Subscription".into(),
                         operation: (op_offset, op_len).into(),
-                        src: db.input(),
+                        src: db.source_code(loc.file_id()),
                         schema: Some((schema_offset, schema_len).into()),
                         help: None,
                     })
@@ -127,7 +125,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                     ApolloDiagnostic::UnsupportedOperation(UnsupportedOperation {
                         ty: "Subscription".into(),
                         operation: (op_offset, op_len).into(),
-                        src: db.input(),
+                        src: db.source_code(op.loc().file_id()),
                         schema: None,
                         help: Some(
                             "consider defining a `subscription` root operation type in your schema"
@@ -139,24 +137,22 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             .collect();
         diagnostics.extend(unsupported_ops)
     }
-    //
+
     //   * query operation - query root operation
-    if db.upcast().query_operations().len() >= 1 && db.schema().query(db.upcast()).is_none() {
-        let unsupported_ops: Vec<ApolloDiagnostic> = db
-            .upcast()
-            .query_operations()
+    if query_operations.len() >= 1 && db.schema().query(db.upcast()).is_none() {
+        let unsupported_ops: Vec<ApolloDiagnostic> = query_operations
             .iter()
             .map(|op| {
-                let op_offset: usize = op.ast_node(db.upcast()).text_range().start().into();
-                let op_len: usize = op.ast_node(db.upcast()).text_range().len().into();
+                let op_offset = op.loc().offset();
+                let op_len = op.loc().node_len();
 
-                if let Some(schema_node) = db.schema().ast_node(db.upcast()) {
-                    let schema_offset: usize = schema_node.text_range().start().into();
-                    let schema_len: usize = schema_node.text_range().len().into();
+                if let Some(loc) = db.schema().loc() {
+                    let schema_offset = loc.offset();
+                    let schema_len = loc.node_len();
                     ApolloDiagnostic::UnsupportedOperation(UnsupportedOperation {
                         ty: "Query".into(),
                         operation: (op_offset, op_len).into(),
-                        src: db.input(),
+                        src: db.source_code(loc.file_id()),
                         schema: Some((schema_offset, schema_len).into()),
                         help: None,
                     })
@@ -164,7 +160,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                     ApolloDiagnostic::UnsupportedOperation(UnsupportedOperation {
                         ty: "Query".into(),
                         operation: (op_offset, op_len).into(),
-                        src: db.input(),
+                        src: db.source_code(op.loc().file_id()),
                         schema: None,
                         help: Some(
                             "consider defining a `query` root operation type in your schema".into(),
@@ -177,22 +173,20 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     }
 
     //   * mutation operation - mutation root operation
-    if db.upcast().mutation_operations().len() >= 1 && db.schema().mutation(db.upcast()).is_none() {
-        let unsupported_ops: Vec<ApolloDiagnostic> = db
-            .upcast()
-            .mutation_operations()
+    if mutation_operations.len() >= 1 && db.schema().mutation(db.upcast()).is_none() {
+        let unsupported_ops: Vec<ApolloDiagnostic> = mutation_operations
             .iter()
             .map(|op| {
-                let op_offset: usize = op.ast_node(db.upcast()).text_range().start().into();
-                let op_len: usize = op.ast_node(db.upcast()).text_range().len().into();
+                let op_offset = op.loc().offset();
+                let op_len = op.loc().node_len();
 
-                if let Some(schema_node) = db.schema().ast_node(db.upcast()) {
-                    let schema_offset: usize = schema_node.text_range().start().into();
-                    let schema_len: usize = schema_node.text_range().len().into();
+                if let Some(loc) = db.schema().loc() {
+                    let schema_offset = loc.offset();
+                    let schema_len = loc.node_len();
                     ApolloDiagnostic::UnsupportedOperation(UnsupportedOperation {
                         ty: "Mutation".into(),
                         operation: (op_offset, op_len).into(),
-                        src: db.input(),
+                        src: db.source_code(loc.file_id()),
                         schema: Some((schema_offset, schema_len).into()),
                         help: None,
                     })
@@ -200,7 +194,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                     ApolloDiagnostic::UnsupportedOperation(UnsupportedOperation {
                         ty: "Mutation".into(),
                         operation: (op_offset, op_len).into(),
-                        src: db.input(),
+                        src: db.source_code(op.loc().file_id()),
                         schema: None,
                         help: Some(
                             "consider defining a `mutation` root operation type in your schema"
@@ -214,13 +208,13 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     }
 
     // Fields must exist on the type being queried.
-    for op in db.operations().iter() {
+    for op in operations.iter() {
         for selection in op.selection_set().selection() {
             let obj_name = op.object_type(db.upcast()).map(|obj| obj.name().to_owned());
             if let Selection::Field(field) = selection {
                 if field.ty(db.upcast()).is_none() {
-                    let offset: usize = field.ast_node(db.upcast()).text_range().start().into();
-                    let len: usize = field.ast_node(db.upcast()).text_range().len().into();
+                    let offset = field.loc().offset();
+                    let len = field.loc().node_len();
                     let field_name = field.name().into();
                     let help = if let Some(obj_type) = obj_name {
                         format!("`{}` is not defined on `{}` type", field_name, obj_type)
@@ -233,7 +227,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                     };
                     diagnostics.push(ApolloDiagnostic::UndefinedField(UndefinedField {
                         field: field_name,
-                        src: db.input(),
+                        src: db.source_code(field.loc().file_id()),
                         definition: (offset, len).into(),
                         help,
                     }))
@@ -306,8 +300,10 @@ type Cat implements Pet {
 
 union CatOrDog = Cat | Dog
 "#;
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic)
         }
@@ -353,8 +349,10 @@ type Cat implements Pet {
   meowVolume: Int
 }
 "#;
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic)
         }
@@ -400,8 +398,10 @@ type Cat implements Pet {
   meowVolume: Int
 }
 "#;
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
         assert!(diagnostics.is_empty());
     }
 
@@ -437,8 +437,10 @@ type Cat implements Pet {
   meowVolume: Int
 }
 "#;
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic)
         }
@@ -465,8 +467,10 @@ type Product {
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "schema.graphql");
+
+        let diagnostics = compiler.validate();
         for diagnostic in &diagnostics {
             println!("{}", diagnostic);
         }
diff --git a/crates/apollo-compiler/src/validation/scalar.rs b/crates/apollo-compiler/src/validation/scalar.rs
index 421a482..ab2654b 100644
--- a/crates/apollo-compiler/src/validation/scalar.rs
+++ b/crates/apollo-compiler/src/validation/scalar.rs
@@ -10,16 +10,16 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
 
     for scalar in db.scalars().iter() {
         let name = scalar.name();
-        if let Some(node) = scalar.ast_node(db.upcast()) {
-            let offset: usize = node.text_range().start().into();
-            let len: usize = node.text_range().len().into();
+        if let Some(loc) = scalar.loc() {
+            let offset = loc.offset();
+            let len = loc.node_len();
 
             // All built-in scalars must be omitted for brevity.
             if BUILT_IN_SCALARS.contains(&name) && !scalar.is_built_in() {
                 diagnostics.push(ApolloDiagnostic::BuiltInScalarDefinition(
                     BuiltInScalarDefinition {
                         scalar: (offset, len).into(),
-                        src: db.input(),
+                        src: db.source_code(loc.file_id()),
                     },
                 ));
             } else if !scalar.is_built_in() {
@@ -33,7 +33,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                     diagnostics.push(ApolloDiagnostic::ScalarSpecificationURL(
                         ScalarSpecificationURL {
                             scalar: (offset, len).into(),
-                            src: db.input(),
+                            src: db.source_code(loc.file_id()),
                         },
                     ))
                 }
diff --git a/crates/apollo-compiler/src/validation/schema.rs b/crates/apollo-compiler/src/validation/schema.rs
index 153ac83..2451bdd 100644
--- a/crates/apollo-compiler/src/validation/schema.rs
+++ b/crates/apollo-compiler/src/validation/schema.rs
@@ -11,12 +11,12 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
 
     // A GraphQL schema must have a Query root operation.
     if db.schema().query(db.upcast()).is_none() {
-        if let Some(node) = db.schema().ast_node(db.upcast()) {
-            let offset: usize = node.text_range().start().into();
-            let len: usize = node.text_range().len().into();
+        if let Some(loc) = db.schema().loc() {
+            let offset = loc.offset();
+            let len = loc.node_len();
             diagnostics.push(ApolloDiagnostic::QueryRootOperationType(
                 QueryRootOperationType {
-                    src: db.input(),
+                    src: db.source_code(loc.file_id()),
                     schema: (offset, len).into(),
                 },
             ));
@@ -30,36 +30,16 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     for op_type in db.schema().root_operation_type_definition().iter() {
         let name = op_type.named_type().name();
         if let Some(prev_def) = seen.get(&name) {
-            if prev_def.ast_node(db.upcast()).is_some() && op_type.ast_node(db.upcast()).is_some() {
-                let prev_offset: usize = prev_def
-                    .ast_node(db.upcast())
-                    .unwrap()
-                    .text_range()
-                    .start()
-                    .into();
-                let prev_node_len: usize = prev_def
-                    .ast_node(db.upcast())
-                    .unwrap()
-                    .text_range()
-                    .len()
-                    .into();
+            if prev_def.loc().is_some() && op_type.loc().is_some() {
+                let prev_offset = prev_def.loc().unwrap().offset();
+                let prev_node_len = prev_def.loc().unwrap().node_len();
 
-                let current_offset: usize = op_type
-                    .ast_node(db.upcast())
-                    .unwrap()
-                    .text_range()
-                    .start()
-                    .into();
-                let current_node_len: usize = op_type
-                    .ast_node(db.upcast())
-                    .unwrap()
-                    .text_range()
-                    .len()
-                    .into();
+                let current_offset = op_type.loc().unwrap().offset();
+                let current_node_len = op_type.loc().unwrap().node_len();
                 diagnostics.push(ApolloDiagnostic::UniqueDefinition(UniqueDefinition {
                     name: name.clone(),
                     ty: "root operation type definition".into(),
-                    src: db.input(),
+                    src: db.source_code(prev_def.loc().unwrap().file_id()),
                     original_definition: (prev_offset, prev_node_len).into(),
                     redefined_definition: (current_offset, current_node_len).into(),
                     help: Some(format!(
diff --git a/crates/apollo-compiler/src/validation/union_.rs b/crates/apollo-compiler/src/validation/union_.rs
index 37f2e33..b1efe45 100644
--- a/crates/apollo-compiler/src/validation/union_.rs
+++ b/crates/apollo-compiler/src/validation/union_.rs
@@ -13,23 +13,19 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
         let mut seen: HashMap<&str, &UnionMember> = HashMap::new();
         for union_member in union_def.union_members().iter() {
             let name = union_member.name();
-            let offset: usize = union_member
-                .ast_node(db.upcast())
-                .text_range()
-                .start()
-                .into();
-            let len: usize = union_member.ast_node(db.upcast()).text_range().len().into();
+            let offset = union_member.loc().offset();
+            let len = union_member.loc().node_len();
             // A Union type must include one or more unique member types.
             //
             // Return a Unique Value error in case of a duplicate member.
             if let Some(prev_def) = seen.get(&name) {
-                let prev_offset: usize = prev_def.ast_node(db.upcast()).text_range().start().into();
-                let prev_node_len: usize = prev_def.ast_node(db.upcast()).text_range().len().into();
+                let prev_offset = prev_def.loc().offset();
+                let prev_node_len = prev_def.loc().node_len();
 
                 diagnostics.push(ApolloDiagnostic::UniqueDefinition(UniqueDefinition {
                     name: name.into(),
                     ty: "union member".into(),
-                    src: db.input(),
+                    src: db.source_code(prev_def.loc().file_id()),
                     original_definition: (prev_offset, prev_node_len).into(),
                     redefined_definition: (offset, len).into(),
                     help: Some(format!(
@@ -47,7 +43,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             if union_member_type.is_none() {
                 diagnostics.push(ApolloDiagnostic::UndefinedDefinition(UndefinedDefinition {
                     ty: name.into(),
-                    src: db.input(),
+                    src: db.source_code(union_member.loc().file_id()),
                     definition: (offset, len).into(),
                 }))
             } else if let Some(ty) = union_member_type {
@@ -56,7 +52,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                     diagnostics.push(ApolloDiagnostic::ObjectType(ObjectType {
                         name: name.into(),
                         ty: ty.ty(),
-                        src: db.input(),
+                        src: db.source_code(union_member.loc().file_id()),
                         definition: (offset, len).into(),
                     }))
                 }
diff --git a/crates/apollo-compiler/src/validation/unused_variable.rs b/crates/apollo-compiler/src/validation/unused_variable.rs
index 7ed0f76..0541e15 100644
--- a/crates/apollo-compiler/src/validation/unused_variable.rs
+++ b/crates/apollo-compiler/src/validation/unused_variable.rs
@@ -3,14 +3,14 @@ use std::collections::HashSet;
 use crate::{
     diagnostics::{ApolloDiagnostic, UndefinedDefinition, UnusedVariable},
     validation::ValidationSet,
-    ValidationDatabase,
+    FileId, ValidationDatabase,
 };
 
 // check in scope
 // check in use
 // compare the two
-pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
-    db.operations()
+pub fn check(db: &dyn ValidationDatabase, file_id: FileId) -> Vec<ApolloDiagnostic> {
+    db.operations(file_id)
         .iter()
         .flat_map(|op| {
             let defined_vars: HashSet<ValidationSet> = op
@@ -18,7 +18,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                 .iter()
                 .map(|var| ValidationSet {
                     name: var.name().into(),
-                    node: var.ast_node(db.upcast()),
+                    loc: *var.loc(),
                 })
                 .collect();
             let used_vars: HashSet<ValidationSet> = op
@@ -32,7 +32,7 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
                         .iter()
                         .map(|var| ValidationSet {
                             name: var.name().into(),
-                            node: var.ast_node(db.upcast()),
+                            loc: *var.loc(),
                         })
                         .collect();
                     vars
@@ -41,11 +41,11 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
             let undefined_vars = used_vars.difference(&defined_vars);
             let mut diagnostics: Vec<ApolloDiagnostic> = undefined_vars
                 .map(|undefined_var| {
-                    let offset = undefined_var.node.text_range().start().into();
-                    let len: usize = undefined_var.node.text_range().len().into();
+                    let offset = undefined_var.loc.offset();
+                    let len = undefined_var.loc.node_len();
                     ApolloDiagnostic::UndefinedDefinition(UndefinedDefinition {
                         ty: undefined_var.name.clone(),
-                        src: db.input(),
+                        src: db.source_code(undefined_var.loc.file_id()),
                         definition: (offset, len).into(),
                     })
                 })
@@ -53,11 +53,11 @@ pub fn check(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
 
             let unused_vars = defined_vars.difference(&used_vars);
             let warnings = unused_vars.map(|unused_var| {
-                let offset = unused_var.node.text_range().start().into();
-                let len: usize = unused_var.node.text_range().len().into();
+                let offset = unused_var.loc.offset();
+                let len = unused_var.loc.node_len();
                 ApolloDiagnostic::UnusedVariable(UnusedVariable {
                     ty: unused_var.name.clone(),
-                    src: db.input(),
+                    src: db.source_code(unused_var.loc.file_id()),
                     definition: (offset, len).into(),
                 })
             });
@@ -100,8 +100,10 @@ type Products {
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
 
         for error in &diagnostics {
             println!("{}", error)
@@ -130,8 +132,10 @@ type Product {
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
 
         for error in diagnostics {
             println!("{}", error)
@@ -169,8 +173,10 @@ type Product {
 }
 "#;
 
-        let ctx = ApolloCompiler::new(input);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(input, "document.graphql");
+
+        let diagnostics = compiler.validate();
 
         for error in &diagnostics {
             println!("{}", error)
diff --git a/crates/apollo-compiler/src/validation/validation_db.rs b/crates/apollo-compiler/src/validation/validation_db.rs
index 8346a3c..4a4d5a1 100644
--- a/crates/apollo-compiler/src/validation/validation_db.rs
+++ b/crates/apollo-compiler/src/validation/validation_db.rs
@@ -8,24 +8,31 @@ use crate::{
         directive, enum_, input_object, interface, object, operation, scalar, schema, union_,
         unused_variable,
     },
-    ApolloDiagnostic, AstDatabase, DocumentDatabase, HirDatabase, InputDatabase,
+    ApolloDiagnostic, AstDatabase, DocumentDatabase, FileId, HirDatabase, InputDatabase,
 };
 
 #[salsa::query_group(ValidationStorage)]
 pub trait ValidationDatabase:
     Upcast<dyn DocumentDatabase> + InputDatabase + AstDatabase + HirDatabase
 {
+    /// Validate all documents.
     fn validate(&self) -> Vec<ApolloDiagnostic>;
+
+    /// Validate the schema, combined of all schema documents known to the compiler.
     fn validate_schema(&self) -> Vec<ApolloDiagnostic>;
-    fn validate_scalar(&self) -> Vec<ApolloDiagnostic>;
-    fn validate_enum(&self) -> Vec<ApolloDiagnostic>;
-    fn validate_union(&self) -> Vec<ApolloDiagnostic>;
-    fn validate_interface(&self) -> Vec<ApolloDiagnostic>;
-    fn validate_directive(&self) -> Vec<ApolloDiagnostic>;
-    fn validate_input_object(&self) -> Vec<ApolloDiagnostic>;
-    fn validate_object(&self) -> Vec<ApolloDiagnostic>;
-    fn validate_operation(&self) -> Vec<ApolloDiagnostic>;
-    fn validate_unused_variable(&self) -> Vec<ApolloDiagnostic>;
+    fn validate_schema_definition(&self) -> Vec<ApolloDiagnostic>;
+    fn validate_scalar_definitions(&self) -> Vec<ApolloDiagnostic>;
+    fn validate_enum_definitions(&self) -> Vec<ApolloDiagnostic>;
+    fn validate_union_definitions(&self) -> Vec<ApolloDiagnostic>;
+    fn validate_interface_definitions(&self) -> Vec<ApolloDiagnostic>;
+    fn validate_directive_definitions(&self) -> Vec<ApolloDiagnostic>;
+    fn validate_input_object_definitions(&self) -> Vec<ApolloDiagnostic>;
+    fn validate_object_type_definitions(&self) -> Vec<ApolloDiagnostic>;
+
+    /// Validate an executable document.
+    fn validate_executable(&self, file_id: FileId) -> Vec<ApolloDiagnostic>;
+    fn validate_operation_definitions(&self, file_id: FileId) -> Vec<ApolloDiagnostic>;
+    fn validate_unused_variable(&self, file_id: FileId) -> Vec<ApolloDiagnostic>;
 
     fn check_directive_definition(
         &self,
@@ -197,35 +204,15 @@ pub fn check_input_values(
     for input_value in input_values.iter() {
         let name = input_value.name();
         if let Some(prev_arg) = seen.get(name) {
-            let prev_offset: usize = prev_arg
-                .ast_node(db.upcast())
-                .unwrap()
-                .text_range()
-                .start()
-                .into();
-            let prev_node_len: usize = prev_arg
-                .ast_node(db.upcast())
-                .unwrap()
-                .text_range()
-                .len()
-                .into();
-
-            let current_offset: usize = input_value
-                .ast_node(db.upcast())
-                .unwrap()
-                .text_range()
-                .start()
-                .into();
-            let current_node_len: usize = input_value
-                .ast_node(db.upcast())
-                .unwrap()
-                .text_range()
-                .len()
-                .into();
+            let prev_offset = prev_arg.loc().unwrap().offset();
+            let prev_node_len = prev_arg.loc().unwrap().node_len();
+
+            let current_offset = input_value.loc().unwrap().offset();
+            let current_node_len = input_value.loc().unwrap().node_len();
 
             diagnostics.push(ApolloDiagnostic::UniqueArgument(UniqueArgument {
                 name: name.into(),
-                src: db.input(),
+                src: db.source_code(prev_arg.loc().unwrap().file_id()),
                 original_definition: (prev_offset, prev_node_len).into(),
                 redefined_definition: (current_offset, current_node_len).into(),
                 help: Some(format!("`{name}` argument must only be defined once.")),
@@ -325,15 +312,15 @@ pub fn check_arguments(
     for argument in &arguments {
         let name = argument.name();
         if let Some(prev_arg) = seen.get(name) {
-            let prev_offset: usize = prev_arg.ast_node(db.upcast()).text_range().start().into();
-            let prev_node_len: usize = prev_arg.ast_node(db.upcast()).text_range().len().into();
+            let prev_offset = prev_arg.loc().offset();
+            let prev_node_len = prev_arg.loc().node_len();
 
-            let current_offset: usize = argument.ast_node(db.upcast()).text_range().start().into();
-            let current_node_len: usize = argument.ast_node(db.upcast()).text_range().len().into();
+            let current_offset = argument.loc().offset();
+            let current_node_len = argument.loc().node_len();
 
             diagnostics.push(ApolloDiagnostic::UniqueArgument(UniqueArgument {
                 name: name.into(),
-                src: db.input(),
+                src: db.source_code(prev_arg.loc().file_id()),
                 original_definition: (prev_offset, prev_node_len).into(),
                 redefined_definition: (current_offset, current_node_len).into(),
                 help: Some(format!("`{name}` argument must only be provided once.")),
@@ -351,62 +338,85 @@ pub fn validate(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     diagnostics.extend(db.syntax_errors());
 
     diagnostics.extend(db.validate_schema());
+    diagnostics.extend(db.check_db_definitions(db.db_definitions()));
 
-    diagnostics.extend(db.validate_scalar());
-    diagnostics.extend(db.validate_enum());
-    diagnostics.extend(db.validate_union());
+    for file_id in db.executable_definition_files() {
+        diagnostics.extend(db.validate_executable(file_id));
+    }
 
-    diagnostics.extend(db.validate_interface());
-    diagnostics.extend(db.validate_directive());
-    diagnostics.extend(db.validate_input_object());
-    diagnostics.extend(db.validate_object());
-    diagnostics.extend(db.validate_operation());
+    diagnostics
+}
 
-    diagnostics.extend(db.validate_unused_variable());
+pub fn validate_schema(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+    let mut diagnostics = Vec::new();
 
-    diagnostics.extend(db.check_db_definitions(db.db_definitions()));
+    diagnostics.extend(db.validate_schema_definition());
+
+    diagnostics.extend(db.validate_scalar_definitions());
+    diagnostics.extend(db.validate_enum_definitions());
+    diagnostics.extend(db.validate_union_definitions());
+
+    diagnostics.extend(db.validate_interface_definitions());
+    diagnostics.extend(db.validate_directive_definitions());
+    diagnostics.extend(db.validate_input_object_definitions());
+    diagnostics.extend(db.validate_object_type_definitions());
 
     diagnostics
 }
 
-pub fn validate_schema(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub fn validate_executable(db: &dyn ValidationDatabase, file_id: FileId) -> Vec<ApolloDiagnostic> {
+    let mut diagnostics = Vec::new();
+
+    diagnostics.extend(db.validate_operation_definitions(file_id));
+    diagnostics.extend(db.validate_unused_variable(file_id));
+
+    diagnostics
+}
+
+pub fn validate_schema_definition(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     schema::check(db)
 }
 
-pub fn validate_scalar(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub fn validate_scalar_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     scalar::check(db)
 }
 
-pub fn validate_enum(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub fn validate_enum_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     enum_::check(db)
 }
 
-pub fn validate_union(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub fn validate_union_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     union_::check(db)
 }
 
-pub fn validate_interface(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub fn validate_interface_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     interface::check(db)
 }
 
-pub fn validate_directive(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub fn validate_directive_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     directive::check(db)
 }
 
-pub fn validate_input_object(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub fn validate_input_object_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     input_object::check(db)
 }
 
-pub fn validate_object(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
+pub fn validate_object_type_definitions(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
     object::check(db)
 }
 
-pub fn validate_operation(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
-    operation::check(db)
+pub fn validate_operation_definitions(
+    db: &dyn ValidationDatabase,
+    file_id: FileId,
+) -> Vec<ApolloDiagnostic> {
+    operation::check(db, file_id)
 }
 
-pub fn validate_unused_variable(db: &dyn ValidationDatabase) -> Vec<ApolloDiagnostic> {
-    unused_variable::check(db)
+pub fn validate_unused_variable(
+    db: &dyn ValidationDatabase,
+    file_id: FileId,
+) -> Vec<ApolloDiagnostic> {
+    unused_variable::check(db, file_id)
 }
 
 // #[salsa::query_group(ValidationStorage)]


Test patch:
diff --git a/crates/apollo-compiler/src/tests.rs b/crates/apollo-compiler/src/tests.rs
index fcf3b96..6036348 100644
--- a/crates/apollo-compiler/src/tests.rs
+++ b/crates/apollo-compiler/src/tests.rs
@@ -25,16 +25,20 @@ use crate::{ApolloCompiler, ApolloDiagnostic, AstDatabase};
 #[test]
 fn compiler_tests() {
     dir_tests(&test_data_dir(), &["ok"], "txt", |text, path| {
-        let ctx = ApolloCompiler::new(text);
-        let errors = ctx.validate();
-        let ast = ctx.db.ast();
+        let mut compiler = ApolloCompiler::new();
+        let file_id = compiler.create_document(text, path);
+
+        let errors = compiler.validate();
+        let ast = compiler.db.ast(file_id);
         assert_diagnostics_are_absent(&errors, path);
         format!("{:?}", ast)
     });
 
     dir_tests(&test_data_dir(), &["diagnostics"], "txt", |text, path| {
-        let ctx = ApolloCompiler::new(text);
-        let diagnostics = ctx.validate();
+        let mut compiler = ApolloCompiler::new();
+        compiler.create_document(text, path);
+
+        let diagnostics = compiler.validate();
         assert_diagnostics_are_present(&diagnostics, path);
         format!("{:#?}", diagnostics)
     });