Global

  • Capitalize keywords?
    • This style seems to have fallen out of favor, looking at newer parts of the documentation
  • Make sure this is workable from start to finish
  • Figure out a way that people can “pick up” wherever they want and get a starter with the code up to that point.
    • We already have ending code, so maybe re-cycling that as starting point code for the next chapter would be good.
  • Discontinue use of “crazy” (maybe “wild” instead as an easy 1-to-1 swap?)
  • Reduce use of “etc.”

Intro

Easy EdgeDB - The illustrated textbook | EdgeDB

  • Seems very apologetic for writing in plain English
    • This is a good thing, even for people who can read a denser text
    • This is harder than writing in a denser, more academic style, so we shouldn’t apologize for it
    • We basically say that it’s for babies, but it really isn’t. I don’t think we need to qualify it in this way.
    • Makes me think of the chronically mis-attributed quote about not having the time to write a shorter letter.

Chapter 1

Chapter 1 — Jonathan Harker travels to Transylvania | Easy EdgeDB | EdgeDB

  • Leaves a lot of ambiguity for people who might be following along
    • Are people intended to follow along?
    • Is this discouraged?
  • Many things a user following along would have to do are linked out instead of described inline.
    • Move them inline if we want readers to follow along
  • It’s difficult to understand what goes where in the migration
    • Show rather than tell
    • Actually, change this to a proper migration by moving schema to default.esdl
      • Does the book ask the user to create a project? If not, circle back and do that.
  • Heading says “Selecting,” but then we tell people about operators with the equals sign.
    • Maybe there’s a way to rework this so that we start the “Selecting” section by talking about selecting, but also deliver what we want to say about the operators couched within that context.
  • “…everything in EdgeDB is a set”
    • The link is great, but could we quickly explain why this is important and why we did it this way here?
  • “It’s also why EdgeDB doesn’t have null”
    • “This is why EdgeDB doesn’t have a null”
  • “Next, we’ll use := to assign a variable”
    • If I were reading this cold, I would wonder why you didn’t just do the first select to assign the value and then do another select that uses the value. Why involve a whole new keyword WITH if we can assign a value in SELECT?
    • I believe the reason is that a variable’s lifetime is the life of the query it’s in. Once you end the query with the semicolon, all the variables you’ve declared in that query are now dead.
    • WITH is explicitly for the purpose of assigning variables a value to later use within the same query.
  • “Soon we will actually do something with the variables we assign with :=.”
    • We did something with the variable just now.
    • I believe this means that we will do something we can’t accomplish more easily using a different method.
  • Call out the fact that Bistrița has a unicode character in it before the insert.
    • I typed it manually without noticing and didn’t figure it out until we started talking about why that value won’t work as a byte much later.
  • “Note that a comma at the end is optional”
    • At the end of what? At first, I thought this meant the comma at the end of the line, but I believe it means the comma after the last property
  • “…to something called multi link to the City type.”
    • Reads awkward. Maybe “to something called a link to the City type”
  • “As you can see, strs are fine with unicode letters like ț.”
    • Reads awkward. “As you can see, strings (str) are fine with unicode…”
  • “Also note that oh_and_by_the_way is of type str even though we didn’t have to tell it.”
    • “…even though we didn’t have to explicitly assign it the str type”
  • Doing the two Person inserts for Jonathan Parker will create a confusing situation where the database has two Persons with the same name, which could be confusing. We should indicate that you would not want to run the first query.
  • “We haven’t filtered anything, so it will put all the City types in there.”
    • This is unclear.
    • Rewrite: “By assigning places_visited the type of City, we have effectively linked to all the City objects we’ve created so far from this new person. That works for Jonathan and these cities because he did visit each of these. (More commonly, you’ll want to filter your links rather than linking to all objects of a type. We’ll see how to do that in the next chapter.)”
  • “Of course, Jonathan Harker has been inserted with a connection to every city in the database. Right now we only have three City objects, so this is no problem yet. But later on we will have more cities and won’t be able to just write places_visited := City for all the other characters. For that we will need FILTER, which we will learn to use in the next chapter.”
    • Rewrite: “Jonathan Harker has been insert with connections to each of the cities in the database. Right now, we have only three City objects and Jonathan did in fact visit them all, so this is not a problem. Later, we’ll have more City objects and want to insert new characters (Person objects) who have not visited every City. places_visited := City won’t work for these characters. For them, we will need filter which we’ll learn to use in the next chapter.”
  • Practice 2: Call out that the first character in “Istanbul” is not a simple capital letter “I”

Chapter 2

Chapter 2 — At the Hotel in Bistritz | Easy EdgeDB | EdgeDB

  • Update schema changes to use CLI migration tool
  • Inserting a city with the same name will result in two cities of the same name.
    • Introduce updating earlier?
  • “…is about making one choice between options.”
    • ”… is about choosing one of several options”
  • “The variants of the enum…”
    • “The options of the enum”
    • Keep consistent with the nomenclature
  • Making this schema change requires three steps:
    1. Adding the new person types extending the original person
    default {
        type City {
            modern_name -> std::str;
            property name -> std::str;
            important_places -> array<str>;
        }
        type Person {
          required property name -> std::str;
          multi link places_visited -> City
        };
        type PC extending Person {
          required property transport -> Transport;
        };
        type NPC extending Person {
        };
        scalar type Transport extending enum<Feet, Train, HorseDrawnCarriage>;
        }
    };
    
    1. Inserting NPCs for each existing person
    with persons := (select Person {name,places_visited}),
    for person in persons union (insert NPC {
        name:= person.name,
        places_visited := person.places_visited
    });
    
    1. Deleting existing persons that are not NPCs
    delete Person filter Person is not NPC;
    
    1. Changing the Person to an abstract type
    module default {
        type City {
            property modern_name -> std::str;
            required property name -> std::str;
            property important_places -> array<str>;
        };
        abstract type Person {
            required property name -> std::str;
            multi link places_visited -> City;
        };
        type PC extending Person {
            required property transport -> Transport;
        };
        type NPC extending Person {
        };
        scalar type Transport extending enum<Feet, Train, HorseDrawnCarriage>;
        }
    };
    
  • “Finally, let’s learn how to FILTER before we’re done Chapter 2.”
    • “Finally, let’s learn how to filter before we’re done with Chapter 2.”
  • “Finally, did you notice that we wrote a comment with # just now?”
    • “Did you notice in that previous select query, we added a comment using #?”
  • Too many “finally”s
  • Split paragraph under “Capturing a select expression” and generally rewrite. Hard to parse.
  • Since parentheses are a type of bracket but are not usually referred to as “brackets” like other types (e.g., square brackets), call them “parentheses” instead of “brackets” to avoid suggesting any kind of bracket can be used for grouping around a sub-query.

Chapter 4

Chapter 4 — “What a strange man this Count Dracula is.” | Easy EdgeDB | EdgeDB

  • About a third of the way into the chapter, the result of a Person select includes Count Dracula even though we deleted him back in chapter 3 and never added him back.
  • In the next query that limits the results, it declares that the result is the Dracula Person. One paragraph later, it says that you might not get that one. We should tell the reader this before we show the outcome so they don’t believe something is wrong.
  • Call out example with the is_single computed property that was just for demonstration purposes.

Chapter 5

Chapter 5 — Jonathan tries to leave the castle | Easy EdgeDB | EdgeDB

  • If we’re going to show the signatures second time around (at about 1/3 of the chapter), we may as well show them the first time rather than (or in addition to?) linking them.
  • Should we use story year to avoid dating the content?
  • About 1/2 way (when we insert the minor vampire and talk about the reason for assert_single might be a good place to talk about and link to “cardinality inference” if we end up with documentation around this concept.
  • First practice question: Link to function signatures in “check the function signatures above…”

Chapter 6

Chapter 6 — Still no escape | Easy EdgeDB | EdgeDB

  • After creating the OtherPlace type, we are inserting Jonathan again even though he is already in the database. This will cause a duplicate person with the changes described here.
  • Insert of Jonathan filtering for a set of places (about 1/4 down) filters for places with a name ‘Castle Dracula.’ This place doesn’t exist. Intentional?
  • Tell readers not to run the update that sets all Person’s places_visited to Place (i.e., all places)
  • About half way down, we insert Count Dracula again, even though he should still be in the database. We can do this since there’s no exclusive constraint, but it would be confusing at the least, especially since the output from a later query shows only one of them.
  • Is there a word we can use besides “slaves” for the minor vampires and “master” for the major one? leader/followers?
  • First practice: “Pleased to meet you. I’m…”
    • Terminate the sentence with a period.

Chapter 7

Chapter 7 — Jonathan finally “leaves” the castle | Easy EdgeDB | EdgeDB

  • Opening paragraph uses “he” in several instances where it is ambiguous whether it’s referring to Dracula or Jonathan.
  • The exclusive constraints cannot be applied if the reader has followed along because they will have inserted the same name twice in a few cases.
  • Practice 5: “…only the Person types…”
    • “…only the Person type objects…”
    • This is repeated elsewhere.

Chapter 8

Chapter 8 — Dracula takes the boat to England | Easy EdgeDB | EdgeDB

  • Blockquote at the beginning should be unbroken until the end.
    • Empty line should be inside the quote.
  • “The answer is…it depends.”
    • Space after ellipsis
  • When comparies array-like options for properties: “multi property vs. objects”
    • Be clear that we’re talking about object linking in this bullet: “multi property vs. objects (via link)”
  • “Objects are easier to migrate if you need to have more than one value with each.”
    • What does this mean?

Chapter 9

Chapter 9 — Strange events in England | Easy EdgeDB | EdgeDB

  • Doesn’t tell you exactly how to update the schema for the first_appearance and last_appearance dates
…..     abstract type Person {
         property name -> std::str {
             delegated constraint exclusive;
         };
         multi link places_visited -> Place;
         link lover -> Person;
         property strength -> int16;
         property first_appearance -> cal::local_date;
         property last_appearance -> cal::local_date;
     };
     type OtherPlace extending Place;
     type Castle extending Place {
         property doors -> array<int16>;
     };
     type PC extending Person {
         required property transport -> Transport;
     };
     type NPC extending Person {
         property age -> HumanAge;
     };
     type Vampire extending Person {
         property age -> int16;
         multi link slaves -> MinorVampire;
     };
     type Time {
         required property clock -> str;
         property clock_time := <cal::local_time>.clock;
         property hour := .clock[0:2];
         property awake := 'asleep' if <int16>.hour > 7 and <int16>.hour < 19 else 'awake';
     };
     type MinorVampire extending Person {
     };
     type Crewman extending HasNumber, Person {
     };
     type Sailor extending Person {
         property rank -> Rank;
     };
     type Ship {
         required property name -> str;
         multi link sailors -> Sailor;
         multi link crew -> Crewman;
     };
     scalar type Transport extending enum<Feet, Train, HorseDrawnCarriage>;
     scalar type HumanAge extending int16 {
         constraint max_value(120);
     };
     scalar type Rank extending enum<Captain, FirstMate, SecondMate, Cook>;
 }};
  • We use to_local_date to insert a new Crewman before introducing it.
  • Better warning about schema that should not be applied (adding post_date to Place)
  • When introducing for: ”… from now on in our code their insert will look like this:”
    • I guess this is talking about the code we’re giving the user at the end of each chapter, but this is weird. What good is that code if they’ve already done it?
  • “the order to follow” is a weird link to dump the reader on the for documentation. I would expect it to go to something specifically about the order.
  • Doesn’t show you how to change the lovers link to a multi link for Lucy
  • Alternative for the Lucy update to add her other lovers?
update NPC filter .name = 'Lucy Westenra'
set {
lover := (
select Person filter NPC in .lover
)};
  • When I try to apply the new schema, dropping the HumanAge scalar, moving age back to the Person type instead of having it on the NPC and Vampire types, and then overloading it on the NPC to add the constraint, I get an error:
SchemaError: cannot drop inherited property 'age' of object type 'default::NPC'
  Detail: property 'age' of object type 'default::NPC' is inherited from:
- object type 'default::Person'
  • I am able to do it in two steps, by first dropping the age altogether in one schema migration and by then adding it on the Person with the overloaded property on NPC in a separate migration.
  • Practice 4: show alternative solution using cal::to_local_date(1887, 9, 11) since that’s the most common method of creating a local date we’ve been using so far.
  • Practice 5: changes from like to ilike without any explanation. (Probably unintentional)

Chapter 10

Chapter 10 — Terrible events in Whitby | Easy EdgeDB | EdgeDB

  • In story at top: “Dr. Seward does an examination on Lucy, who is pale and weak but he doesn’t know why.”
    • Comma before “but” and independent clause
  • “By the way, here’s the approximate population for our three cities at the time of the book.”
    • “By the way, here are the approximate populations for our three cities at the time of the book.”
  • Output of the tuple casting example does not reflect actual REPL output
    • Should be {(Json("\"London\""), 3500000)}
    • Output of the following query should be {(Json("\"London\""), 388888.8888888889)}
  • Change escapes link text from “here” to something else. Also, the link doesn’t go to the section on escapes. Maybe that ref has been deleted?
  • Scalar time documentation link label should not be “here”
  • unless conflict on example: “UNLESS CONFLICT ON is probably easier to explain through an example. Here’s one that shows what we can do to either insert a City with its population, and to UPDATE the population if it already exists in the database.”
    • “Here’s an example to show how you might try to insert a city with its population and use unless conflict on to instead update the population if the city’s name already exists”
  • “But here’s the important part: we don’t write ELSE INSERT, because we are already in the middle of an INSERT. What we write instead is UPDATE for the type we are inserting, so UPDATE City. We are basically taking the failed data from the insert and updating it to try again. So we’ll do this instead:”
    • This doesn’t make any sense to me. Probably needs a full rewrite
    • I believe at the point we reach the conflict update, City will reference the object that had the conflict rather than the type as it does initially in the insert.

Chapter 11

Chapter 11 — What’s wrong with Lucy? | Easy EdgeDB | EdgeDB

  • Function needs create keyword in DDL (so, to create via the console)
    • without create, the function must be created in the schema
  • Current function cannot be created: InvalidFunctionDefinitionError: return cardinality mismatch in function declared to return exactly one value
create function fight(one: Person, two: Person) -> str
    using (
        ((one.name ?? 'Fighter 1') ++ ' wins!' IF (one.strength ?? 0) > (two.strength ?? 0) ELSE (two.name ?? 'Fighter 2') ++ ' wins!')
    );
  • Write alt text for cartesian multiplication chart

Chapter 12

Chapter 12 — From bad to worse | Easy EdgeDB | EdgeDB

  • This goes about setting strengths for all the people without acknowledging we did so in chapter 11 by setting them all to 5.
  • The update query updates only people who don’t have a strength, which is currently no one sense we set all strengths in the previous chapter.
    • We should probably set them all randomly and then circle back to each one that needs a specific value
  • The overloaded function will not work for the same reason that the chapter 11 function won’t
create function fight(people_names: array<str>, opponent: Person) -> str
using (
    with
        people := (select Person filter .name in array_unpack(people_names)),
        names := array_join(array_agg(people.name), ', '),
    select (names ?? 'Fighters') ++ ' win!'
        if (sum(people.strength) ?? 0) > (opponent.strength ?? 0)
        else (opponent.name ?? 'Opponent') ++ ' wins!');
  • “The documentation for creating functions is here.”
    • “For more detail on creating functions, check out our function documentation.”
  • Make it clear the reader shouldn’t create the say_hi function
    • or drop it when we talk about dropping functions in the next section
  • On dropping: “The -> str part isn’t needed.”
    • It’s not just that this isn’t needed. The drop will error if you include it.
    • “Leave out the -> str part or the drop will fail.”
  • Does it make sense to move the part about array_agg and array_join up to introduce them before I use them in the function? If not, maybe provide a brief explanation inline where the function is created and mention that we will go into more detail below?
  • The two examples for ?= seem very arbitrary. They don’t seem practical. Come up with better examples for sets you might want to compare.

Chapter 13

Chapter 13 — Meet the new Lucy | Easy EdgeDB | EdgeDB

  • In the initial story: “(crucifixes have that power over vampires)”
    • Make this a parenthetical sentence on its own
  • Use correct codeblock types in the type union section so that the code is copy/pastable
  • “Let’s say for example there are other Vampire objects in the game, and one Vampire that is extremely powerful can control the other.”
    • “Let’s say for example there are other Vampire objects in the game, and one Vampire that is extremely powerful can control another less powerful vampire.”
  • “Right now though Vampires can only control MinorVampires:”
    • Find another way to pluralize
  • “So if you wanted to have all the MinorVampire types automatically deleted when their Vampire dies, you would add a link from MinorVampire to the Vampire type. Then you would add on target delete delete source to this: Vampire is the target of the link, and MinorVampire is the source that gets deleted.”
    • Show what this looks like.
  • When talking about using distinct, make it clear that the results shown in the book will be different if you’re working along since the values were generated randomly.
  • Practice 2: “How readable is this introspect query?”
    • “How readable is the result of this query?”

Chapter 14

Chapter 14 — A ray of hope | Easy EdgeDB | EdgeDB

  • Chapter says we will have 24 people, but I have 29 right now
    • The chapter points this out later, but I still have a discrepancy because of the practice exercises I’ve done. Might be good to call that out.
  • Practice 2: “…all the Place types (plus their names) that have an o in the name…”
    • should be “all the Place objects”

Chapter 15

Chapter 15 — The vampire hunt begins | Easy EdgeDB | EdgeDB

  • “This chapter they learned something about vampires”
    • “In this chapter, they learned something about vampires”
  • “Feel free because the night has just begun, start moving away from the safety of the coffins to find victims. May use a horse-driven carriage at 25 kph to do so.”
    • “Feel a sense of freedom because the night has just begun, and start moving away…”
  • “So the part between 8 pm and 1 am is when the vampire is free to move away, and at 25 kph we get an activity radius of about 100 km around a coffin.”
    • “That means that the vampire is free to move about between 8pm and 1am. At a speed of 25kph, we get an activity radius of about 100km from each coffin.”
  • “With a more complex game we could imagine that vampire terrorism is worse in the winter (activity radius = about 150 km), but we won’t get into those details.”
    • “If we were building a more complex game, we might imagine that vampire activity is worse in the winter, when the activity radius might increase to about 150km due to fewer hours of sunlight, but we won’t get that detailed here.”
  • can_enter function does not work
    • Alternative:
create function can_enter(person_name: str, place: HasCoffins) -> str
  using (
    with vampire := assert_single(
        (select Person filter .name = person_name)
    ),
    vampire_name := vampire.name ?? 'The vampire',
  select (vampire_name) ++ ' can enter.' if place.coffins > 0 else vampire_name ++ ' cannot enter.'
  );
  • This chapter calls out and links to the first appearance of the coalescing operator. If I mention that earlier, I will need to change this.
  • “One other area where you need to trust the user of the function is seen in the return type, which is just -> str. Beyond just returning a string, this return type also means that the function won’t be called if the input is empty. So what if you want it to be called anyway? If you want it to be called no matter what, you can change the return type to -> OPTIONAL str. The documentation explains it like this: the function is called normally when the corresponding argument is empty. And: A notable example of a function that gets called on empty input is the coalescing operator.
    • As far as I can tell, making the return type optional does not allow for calling the function with no inputs (which I take to mean “arguments”).
    • It does allow me to use values within the function that could be the empty set without coalescing with a fallback value.
      • This might be a way to make the functions look cleaner when we write them and to avoid having to coalesce every property that isn’t required.
    • This needs a rewrite aside from the technical inaccuracy because it’s very hard to parse.
    • The quotes cited here are no longer in the function documentation
  • In “more constraints”, start from the need rather than the mechanism we use to satisfy. Talk about the need for players to choose short names, and then show the approach of limiting with constraints.
  • Tell readers we’re not going to add the title and month constraints before showing the code
  • “One particularly flexible constraint is called expression on, which lets us add any expression we want. After expression on you add the expression (in brackets) that must be true to create the type. In other words: “Create this type as long as (insert expression here)”.”
    • “If you want to make constraints that are not covered by the built-in constraints, you can use an expression constraint instead. Start the constraint with constraint on and finish with an expression producing true or false. When a new object of the type is inserted or an existing object is updated, the operation fails if the constraint expression is false. If it’s true and all the other constraints are satisfied, the operation succeeds.
  • = true is not necessary in the expression constraint example. Alternative:
type Lord extending Person {
  constraint expression on (
    contains(__subject__.name, 'Lord')
  );
}
  • When talking about backlinks, We should note in the paragraph before the example where we add the computed property with the backlink that we are going to also add a string for the master name. Otherwise, it’s confusing to have that show up out of nowhere.
  • “But then again, if we want this master_name shortcut we can now just use the master link to do it. Let’s change it from required single property master_name -> str to property master_name := .master.name:”
    • “Since we have a link to the master, we can refactor the master_name shortcut to pull the name from the linked master instead of having to manually enter it.”
  • Make sure the Billy, Bob, and Kain objects get removed, or else clarify the reader shouldn’t actually run those queries.
  • Practice 4: “How would you make a function called display_coffins that pulls up all the HasCoffins types with more than 0 coffins?”
    • “How would you make a function called display_coffins that pulls up all the HasCoffins objects with more than 0 coffins?”
  • Practice 5: “How would you make it without touching the schema?”
    • “How would you make the function without creating a new migration?”

Chapter 16

Chapter 16 — Is Renfield telling the truth? | Easy EdgeDB | EdgeDB

  • Continue into blockquote across paragraphs
  • “Fortunately, the original book Dracula is all organized into letters, diaries, etc. that begin with the date and sometimes the time.”
    • “Fortunately, the original text of Dracula is organized into letters, diary entries, etc. that begin with the date and sometimes the time.”
  • “They all start out in this sort of way:”
    • “They start out like this:”
  • (excerpt = part of a book)
    • (an “excerpt” meaning a part of a larger text)
  • “Note: index is good in limited quantities, but you don’t want to index everything. Here is why:”
    • Make this an actual .. note::
  • “This is probably not surprising, because you can see that index is a choice that the user needs to make. If using index was the best idea in every case, then EdgeDB would just do it automatically.”
    • “If there were no downside to indexing, EdgeDB would just index everything for you by default. Since there is a downside, indexing only happens when you say so.”
  • “Now the ns are all gone:”
    • “Now, the names have been split into arrays at each instance of n:”
  • “You can’t see it but from the point of view of the computer every new line has a \n in it.”
    • “Even though they’re invisible when the text is rendered on the screen, every new line has a \n in it which you could use for splitting:”
  • “will split it by line and give the following array:”
    • “That will split your text by line and give you an array of the lines:”
  • “so it gives:”
    • The re_match_all function returns all the instances of the various permutations of “tonight:”
  • “And to match anything, you can use the wildcard character: .
    • Feels out of place. Remove.
  • Move the notes on index on up to where we’re still talking about indexing
  • Practice 1: “How would you split all the Person names into two strings if they have two words, and ignore any that don’t have exactly two words?”
    • “How would you write a query to show all the Person names split into an array of two strings they have two names and ignored if they don’t have exactly two names?”
  • Practice 4: “Hint: the std::str_repeat() function could help (though there is more than one way to do it)”
    • Could it help? I can’t see how it would help since it repeats a string a number of times without passing it into anything else.
    • Frederick doesn’t think so either. The hint needs to go.
  • Practice 5: “How would you use re_match_all() to display all the Person.names with Crewman in the name? e.g. Crewman 1, Crewman 2, etc.”
    • The prescribed solution technically tells how to find all names with Crewman in the name (Crewman followed by a space). The answer should probably change to match with the ask

Chapter 17

Chapter 17 — Poor Renfield. Poor Mina. | Easy EdgeDB | EdgeDB

  • Continuous blockquote in intro
  • Tenses are oscillating in the intro
  • “Imagine that for some reason we would like a CrewmanInBulgaria alias as well, because Bulgarians call each other ‘Gospodin’ instead of Mr. and our game needs to reflect that. Our Crewman types will get called “Gospodin (name)” whenever they are there. Let’s also add a current_location computed link that makes a link to Place types with the name Bulgaria. Here’s how to do that:”
    • “Imagine that we would like a CrewmanInBulgaria type as well, because Bulgarians call each other ‘Gospodin’ (the way an English-speaker might use “Mister”) and our game needs to reflect that. Our crewmen will be called “Gospodin (name)” whenever they are in Bulgaria. Everything else about the type should be the same which allows us to use an alias rather than extending. Let’s also add a current_location computed link that makes a link to the Place object with the name Bulgaria. Here’s how to do that:”
  • The part about aliases needs a rewrite. It does a poor job of explaining what aliases are and why to use them.
    • Aliases:
      • Not for inserts because it is not a type
      • Allow you to access existing objects by querying the alias instead of their actual type
      • Can overlay a new shape on top of the existing type that will be returned when you query by the alias, along with the original type’s shape
  • “The documentation on aliases mentions that they let you use “the full power of EdgeQL (expressions, aggregate functions, backwards link navigation) from GraphQL”, so keep aliases in mind if you use GraphQL a lot.”
    • The subtext here is that there are parts of EdgeQL that you can’t get to from GraphQL, but it doesn’t explain what and how this helps
      • This goes for both the book and the documentation referenced here
  • “Try to imagine what the output will be.”
    • “What do you think the output will be?”
  • The output of the fight_2 function will be different if you’ve been following along since you will also have Billy and Bob from Chapter 15
  • They have Lucy winning all her fights. I do not see that in my REPL. Was Lucy’s power set at some point that I missed?
  • When running the king of London query, it will also return Kain as king of London if you inserted him in chapter 15
    • Change this query to return only Dracula or make sure Kain is deleted after insertion in chapter 15

Chapter 18

Chapter 18 — Using Dracula’s own weapon against him | Easy EdgeDB | EdgeDB

  • Add Oxford comma when listing out countries characters have been to
  • “And complicated things like credit and negative money we can probably just ignore for now.”
    • “Credit and negative balances are more than we want to mess with right now.”
  • Currency name as a type that represents a quantity of that currency is a bit odd. I also find the mixing of the amounts with the conversions strange. Maybe it needs another layer of abstraction for <Currency>Quantity?
  • “He has this many:”
    • “Based on the results of the query, he has about 2503 pounds.”
    • Move the query result above this sentence.
  • “Not that we need this Dollar type in our game: in our schema it would be type Dollar extending Currency.”
    • “Dollars won’t be used in this game, but if they were, we’d create it with type Dollar extending Currency
  • “One final note: our total_money…”
    • “One final note: the dollar’s total_money…”
  • “We are nearing the end of the book, and should probably start to clean up the schema and inserts a bit.”
    • This is a weird way to think of it. The inserts have already happened. That’s done. We can’t “clean them up” because the data is already in. (That seems to be the weird disconnect carried throughout the book.)
    • “We’ve come a long way since we were first setting up our database and doing our early object inserts. Let’s look back and see how things might change if we were starting again, knowing what we now know.”

Chapter 19

Chapter 19 — Dracula escapes | Easy EdgeDB | EdgeDB

  • Unbroken quote
  • hypnotises hypnotizes
  • “Jonathan Harker just sharpens his knife, and looks like a different person now.”
    • remove comma
  • “Every day they wait…and then one day, they get a message”
    • space after ellipses
  • The reverse lookup query is really hard to read and probably needs to be broken down better for the reader’s understanding.

Chapter 20

Chapter 20 — The final battle | Easy EdgeDB | EdgeDB

  • “The sun is almost down, it is snowing, and the need to hurry to catch him.”
    • “…they need…”
    • Separate with semi-colons since they are independent clauses? Too pedantic?
  • The minor vampire update query will also catch Billy and Bob
  • In “Reviewing the schema”, change the notes about the manual migrations to reflect that the workalong now instructs the reader to use the CLI migration tool.
  • When talking about first and last name fields as an alternative, consider complicating factors around those designations.
  • “The two links are multi links, without which a link is to only one object.”
  • “But if we wanted to, we could extend the game back to more historical times…”
    • “If we wanted, we could extend the game back before the events of the book…”
  • Should awake not be a boolean?
  • “The NPC type is where we first saw the overloaded keyword, which lets us use properties, links, functions etc. in different ways than the default.”
    • “The NPC type is where we first saw the overloaded keyword, which lets us use properties, links, functions etc. in different ways than in the type’s parent type.”
  • “The description property is for users of the database with descriptions like 'The Demeter arrives at Whitby, crashing on the beach' that are used to find events when we need to.”
    • “The description property makes it easy for users of the database to find events. It might contain something like 'The Demeter arrives at Whitby, crashing on the beach'.
  • “Now that you have reached the end of the book, you will certainly start looking at the EdgeDB documentation.”
    • “Now that you have reached the end of the book, you’ll be referencing our documentation to refresh what you know and to learn more.”
  • Link to something that helps people learn to read the syntax? Describe it in more detail here?
    • What do quotes mean?
    • What do square braces mean?
    • What does a pipe mean?
  • “Using edgedb migration tools makes it possible to work with the schema using only SDL.”
    • We will have done it this way all along, so this won’t make as much sense.
  • “You might want to take a look at or bookmark this page for reference during your projects.”
  • Getting help should probably mention the Discord first.
  • “…on this very page”
    • What does this mean? This page doesn’t seem to have a way for me to open an issue on the book.
  • “We hope you enjoyed learning EdgeDB through a story, and are now familiar enough with it to implement it for your own projects.”
    • remove comma
  • “Ironically, if we wrote the book with enough detail to answer all your questions then we might never see you on the forums!”
    • “We’d love to say hi sometimes, but if we wrote the book with enough detail to answer all your questions then we might never see you on our Discord!”
  • “See you, or not see you, however things turn out! Thanks again for reading.”
    • This could be stronger.