Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Newly failing migration in Easy EdgeDB book #6916

Open
raddevon opened this issue Feb 23, 2024 · 0 comments
Open

Newly failing migration in Easy EdgeDB book #6916

raddevon opened this issue Feb 23, 2024 · 0 comments
Labels

Comments

@raddevon
Copy link
Contributor

When attempting to follow along with the Easy EdgeDB book, the chapter 18 migrations to add an abstract type (HasMoney) and change an existing type (Person) to extend it now fail.

After answering the migration questions:

Error executing command: EdgeDB could not resolve migration with the provided answers. Please retry with different answers.

The user who discovered this started a discussion about it.

I tried to create a minimal reproduction by creating a type and then creating a new migration to create a new abstract type and updating the initial type to extend it, but that did not produce the same result. Instead, I discovered a bug in the migration tool.

  • EdgeDB Version: 4.1.0+a8fe4d7
  • EdgeDB CLI Version: 4.5+641a8f3
  • OS Version: macOS 14.0

Steps to Reproduce:

  1. Migrate to chapter 17 schema (below)
  2. Attempt to migrate to chapter 18 schema (below)

Schemas

Chapter 17
module default {

  # Scalar types

  scalar type Class extending enum<Rogue, Mystic, Merchant>;

  scalar type LotteryTicket extending enum <Nothing, WallChicken, ChainWhip, Crucifix, Garlic>;

  scalar type Mode extending enum<Info, Debug>;
  
  scalar type PCNumber extending sequence;

  scalar type Rank extending enum<Captain, FirstMate, SecondMate, Cook>;

  scalar type SleepState extending enum <Asleep, Awake>;

  # Globals and definitions

  required global tester_mode: Mode {
    default := Mode.Info;
  }

  global time := assert_single((select Time));

  abstract annotation warning;

  # Abstract object types

  abstract type HasNameAndCoffins {
    required coffins: int16 {
      default := 0;
    }
    required name: str {
      delegated constraint exclusive;
      constraint max_len_value(30);
    }
  }

  abstract type HasNumber {
    required number: int16;
  }

  abstract type Person {
    required name: str {
      delegated constraint exclusive;
    }
    multi places_visited: Place;
    multi lovers: Person;
    is_single := not exists .lovers;
    strength: int16;
    first_appearance: cal::local_date;
    last_appearance: cal::local_date;
    age: int16;
    title: str;
    degrees: array<str>;
    conversational_name := .title ++ ' ' 
      ++ .name if exists .title else .name;
    pen_name := .name ++ ', ' 
      ++ array_join(.degrees, ', ') if exists .degrees else .name;
  }

  abstract type Place extending HasNameAndCoffins {
    modern_name: str;
    multi important_places: Landmark;
  }

  # Object types

  type BookExcerpt {
    required date: cal::local_datetime;
    required excerpt: str;
    index on (.date);
    required author: Person;
  }

  type Castle extending Place {
    doors: array<int16>;
  }

  type City extending Place {
    annotation description := 'A place with 50 or more buildings. Anything else is an OtherPlace';
    population: int64;
    index on (.name ++ ': ' ++ <str>.population) {
      annotation title := 'Lists city name and population for display in Long Library stage';
    }
  }

  type Country extending Place;

  type Crewman extending HasNumber, Person {
    overloaded name: str {
      default := 'Crewman ' ++ <str>.number;
    }
  }

  type Event {
    required description: str;
    required start_time: cal::local_datetime;
    required end_time: cal::local_datetime;
    required multi place: Place;
    required multi people: Person;
    location: tuple<float64, float64>;
    index on (.location);
    ns_suffix := '_N_' if .location.0 > 0.0 else '_S_';
    ew_suffix := '_E' if .location.1 > 0.0 else '_W';
    url := get_url() 
      ++ <str>(math::abs(.location.0)) ++ .ns_suffix 
      ++ <str>(math::abs(.location.1)) ++ .ew_suffix;
  }

  type Landmark {
    required name: str;
    multi context: str;
  }

  type Lord extending Person {
    constraint expression on (contains(__subject__.name, 'Lord')) {
      errmessage := "All lords need \'Lord\' in their name";
    }
  }

  type MinorVampire extending Person {
    former_self: Person;
    single master := assert_single(.<slaves[is Vampire]);
    master_name := .master.name;
  };

  type NPC extending Person {
    overloaded age: int16 {
      constraint max_value(120);
    }
  }

  type OtherPlace extending Place {
    annotation description := 'A place with under 50 buildings - hamlets, small villages, etc.';
    annotation warning := 'Castles and castle towns do not count! Use the Castle type for that';
  }

  type Party {
    name: str;
    members := .<party[is PC];
  }

  type PC extending Person {
    required class: Class;
    required created_at: datetime {
      default := datetime_of_statement();
    }
    required number: PCNumber {
      default := sequence_next(introspect PCNumber);
    }
    multi party: Party {
      on source delete delete target if orphan;
      on target delete allow;
    }
    overloaded required name: str {
      constraint max_len_value(30);
    }
    last_updated: datetime {
      rewrite insert, update using (datetime_of_statement());
    }
    bonus_item: LotteryTicket {
      rewrite insert, update using (get_ticket());
    }    
  }

  type Sailor extending Person {
    rank: Rank;
  }

  type Ship extending HasNameAndCoffins {
    multi sailors: Sailor;
    multi crew: Crewman;
  }

  type Time { 
    required clock: str; 
    clock_time := <cal::local_time>.clock; 
    hour := .clock[0:2]; 
    vampires_are := SleepState.Asleep if <int16>.hour > 7 and <int16>.hour < 19
      else SleepState.Awake;
  } 

  type Vampire extending Person {
    multi slaves: MinorVampire {
      on source delete delete target;
      property combined_strength := (Vampire.strength + .strength) / 2;
    }
    army_strength := sum(.slaves@combined_strength);
  }

  # Aliases

  alias AllNames := (
    distinct (HasNameAndCoffins.name union
    Place.modern_name union
    Landmark.name union 
    Person.name)
  );

  alias CrewmanInBulgaria := Crewman {
    name := 'Gospodin ' ++ .name,
    strength := .strength + <int16>1,
    original_name := .name,
  };

  alias GameInfo := (
    title := ( 
      en := "Dracula the Immortal",
      fr := "Dracula l'immortel",
      no := "Dracula den udødelige",
      ro := "Dracula, nemuritorul"
    ),
    country := "Norway",
    date_published := 2023,
    website := "www.draculatheimmortal.com"
  );

  # Functions

  function can_enter(person_name: str, place: str) -> optional str
    using (
      with 
        vampire := assert_single((select Person filter .name = person_name)),
        enter_place := assert_single((select HasNameAndCoffins filter .name = place))
        select vampire.name ++ ' can enter.' if enter_place.coffins > 0 else vampire.name ++ ' cannot enter.'
    );   

  function fight(one: Person, two: Person) -> str
  using (
    one.name ++ ' wins!' if (one.strength ?? 0) > (two.strength ?? 0)
    else two.name ++ ' wins!'
  );

  function fight(people_names: array<str>, opponent: Person) -> str
    using (
      with
          people := (select Person filter contains(people_names, .name)),
      select
          array_join(people_names, ', ') ++ ' win!'
          if sum(people.strength) > (opponent.strength ?? 0)
          else opponent.name ++ ' wins!'
  );

  function get_ticket() -> LotteryTicket 
    using (
      with rnd := <int16>(random() * 10),
        select(
        LotteryTicket.Nothing if rnd <= 6 else
        LotteryTicket.WallChicken if rnd = 7 else
        LotteryTicket.ChainWhip if rnd = 8 else
        LotteryTicket.Crucifix if rnd = 9 else
        LotteryTicket.Garlic)
    );

  function get_url() -> str 
    using (
      <str>'https://geohack.toolforge.org/geohack.php?params='
    );

  function visited(person: str, city: str) -> bool 
    using (
      with person := (select Person filter .name = person),
      select city in person.places_visited.name
    );
}
Chapter 18
module default {

  # Scalar types

  scalar type Class extending enum<Rogue, Mystic, Merchant>;

  scalar type LotteryTicket extending enum <Nothing, WallChicken, ChainWhip, Crucifix, Garlic>;

  scalar type Mode extending enum<Info, Debug>;
  
  scalar type PCNumber extending sequence;

  scalar type Rank extending enum<Captain, FirstMate, SecondMate, Cook>;

  scalar type SleepState extending enum <Asleep, Awake>;

  # Globals and definitions

  required global tester_mode: Mode {
    default := Mode.Info;
  }

  global time := assert_single((select Time));

  abstract annotation warning;

  # Abstract object types

  abstract type HasNameAndCoffins {
    required coffins: int16 {
      default := 0;
    }
    required name: str {
      delegated constraint exclusive;
      constraint max_len_value(30);
    }
  }

  abstract type HasNumber {
    required number: int16;
  }

  abstract type Person extending HasMoney {
    required name: str {
      delegated constraint exclusive;
    }
    multi places_visited: Place;
    multi lovers: Person;
    is_single := not exists .lovers;
    strength: int16;
    first_appearance: cal::local_date;
    last_appearance: cal::local_date;
    age: int16;
    title: str;
    degrees: array<str>;
    conversational_name := .title ++ ' ' 
      ++ .name if exists .title else .name;
    pen_name := .name ++ ', ' 
      ++ array_join(.degrees, ', ') if exists .degrees else .name;
  }

  abstract type Place extending HasNameAndCoffins {
    modern_name: str;
    multi important_places: Landmark;
  }

  # Object types

  type BookExcerpt {
    required date: cal::local_datetime;
    required excerpt: str;
    index on (.date);
    required author: Person;
  }

  type Castle extending Place {
    doors: array<int16>;
  }

  type City extending Place {
    annotation description := 'A place with 50 or more buildings. Anything else is an OtherPlace';
    population: int64;
    index on (.name ++ ': ' ++ <str>.population) {
      annotation title := 'Lists city name and population for display in Long Library stage';
    }
  }

  type Country extending Place;

  type Crewman extending HasNumber, Person {
    overloaded name: str {
      default := 'Crewman ' ++ <str>.number;
    }
  }

  type Event {
    required description: str;
    required start_time: cal::local_datetime;
    required end_time: cal::local_datetime;
    required multi place: Place;
    required multi people: Person;
    location: tuple<float64, float64>;
    index on (.location);
    ns_suffix := '_N_' if .location.0 > 0.0 else '_S_';
    ew_suffix := '_E' if .location.1 > 0.0 else '_W';
    url := get_url() 
      ++ <str>(math::abs(.location.0)) ++ .ns_suffix 
      ++ <str>(math::abs(.location.1)) ++ .ew_suffix;
  }

  type Landmark {
    required name: str;
    multi context: str;
  }

  type Lord extending Person {
    constraint expression on (contains(__subject__.name, 'Lord')) {
      errmessage := "All lords need \'Lord\' in their name";
    }
  }

  type MinorVampire extending Person {
    former_self: Person;
    single master := assert_single(.<slaves[is Vampire]);
    master_name := .master.name;
  };

  type NPC extending Person {
    overloaded age: int16 {
      constraint max_value(120);
    }
  }

  type OtherPlace extending Place {
    annotation description := 'A place with under 50 buildings - hamlets, small villages, etc.';
    annotation warning := 'Castles and castle towns do not count! Use the Castle type for that';
  }

  type Party {
    name: str;
    members := .<party[is PC];
  }

  type PC extending Person {
    required class: Class;
    required created_at: datetime {
      default := datetime_of_statement();
    }
    required number: PCNumber {
      default := sequence_next(introspect PCNumber);
    }
    multi party: Party {
      on source delete delete target if orphan;
      on target delete allow;
    }
    overloaded required name: str {
      constraint max_len_value(30);
    }
    last_updated: datetime {
      rewrite insert, update using (datetime_of_statement());
    }
    bonus_item: LotteryTicket {
      rewrite insert, update using (get_ticket());
    }    
  }

  type Sailor extending Person {
    rank: Rank;
  }

  type Ship extending HasNameAndCoffins {
    multi sailors: Sailor;
    multi crew: Crewman;
  }

  type Time { 
    required clock: str; 
    clock_time := <cal::local_time>.clock; 
    hour := .clock[0:2]; 
    vampires_are := SleepState.Asleep if <int16>.hour > 7 and <int16>.hour < 19
      else SleepState.Awake;
  } 

  type Vampire extending Person {
    multi slaves: MinorVampire {
      on source delete delete target;
      property combined_strength := (Vampire.strength + .strength) / 2;
    }
    army_strength := sum(.slaves@combined_strength);
  }

  # Aliases

  alias AllNames := (
    distinct (HasNameAndCoffins.name union
    Place.modern_name union
    Landmark.name union 
    Person.name)
  );

  alias CrewmanInBulgaria := Crewman {
    name := 'Gospodin ' ++ .name,
    strength := .strength + <int16>1,
    original_name := .name,
  };

  alias GameInfo := (
    title := ( 
      en := "Dracula the Immortal",
      fr := "Dracula l'immortel",
      no := "Dracula den udødelige",
      ro := "Dracula, nemuritorul"
    ),
    country := "Norway",
    date_published := 2023,
    website := "www.draculatheimmortal.com"
  );

  # Functions

  function can_enter(person_name: str, place: str) -> optional str
    using (
      with 
        vampire := assert_single((select Person filter .name = person_name)),
        enter_place := assert_single((select HasNameAndCoffins filter .name = place))
        select vampire.name ++ ' can enter.' if enter_place.coffins > 0 else vampire.name ++ ' cannot enter.'
    );   

  function fight(one: Person, two: Person) -> str
  using (
    one.name ++ ' wins!' if (one.strength ?? 0) > (two.strength ?? 0)
    else two.name ++ ' wins!'
  );

  function fight(people_names: array<str>, opponent: Person) -> str
    using (
      with
          people := (select Person filter contains(people_names, .name)),
      select
          array_join(people_names, ', ') ++ ' win!'
          if sum(people.strength) > (opponent.strength ?? 0)
          else opponent.name ++ ' wins!'
  );

  function get_ticket() -> LotteryTicket 
    using (
      with rnd := <int16>(random() * 10),
        select(
        LotteryTicket.Nothing if rnd <= 6 else
        LotteryTicket.WallChicken if rnd = 7 else
        LotteryTicket.ChainWhip if rnd = 8 else
        LotteryTicket.Crucifix if rnd = 9 else
        LotteryTicket.Garlic)
    );

  function get_url() -> str 
    using (
      <str>'https://geohack.toolforge.org/geohack.php?params='
    );

  function visited(person: str, city: str) -> bool 
    using (
      with person := (select Person filter .name = person),
      select city in person.places_visited.name
    );

  scalar type Money extending int64 {
    constraint min_value(0);
  }

  abstract type HasMoney {
    required pounds: Money {
      default := 0;
    }
    required shillings: Money {
      default := 0;
    }
    required pence: Money {
      default := 0;
    }    
    required cents: Money {
      default := 0;
    }
    required dollars: Money {
      default := 0;
    }
    total_pence := .pounds * 240 + .shillings * 20 + .pence;
    total_cents := .dollars * 100 + .cents;
    approx_wealth_in_pounds := <int64>.total_pence / 240 + .total_cents / 800;
  }
}
```</details>
@raddevon raddevon added the bug label Feb 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant