Advent of TypeQL: Day 12

A Christmassy data recovery adventure!

Joshua Send


Happy Advent!

Welcome to our Advent of TypeQL. Each day, we’ll post a small series of TypeQL challenges. There will be 14 days from December 11 to December 24, inclusive, so stay tuned!

It’ll be easiest to work through the days consecutively, since they are designed to incrementally build up an intuition of TypeQL. However, each day will also have a shortcut setup to just get a database for that day’s activity, allowing you to hop in without going through all the previous days challenges if you want.

You’ll want to use TypeDB Studio or TypeDB Console running against a TypeDB Cloud or local instance of TypeDB CE.

Background

Santa was preparing to release his Christmas plans. Finally done, he had just torn down his development and staging environments in preparation for a production launch of his grand Christmas plans…

Unfortunately at that precise moment, all of his Christmas plans, supposedly safely stashed in his git repository, got deleted by a pesky engineer with an overly permissive Github token and a mistyped command. Oh dear!

Santa contacted support and was able to recover some of his plans. However, they are incomplete. We’re going to help Santa get his plans back on track for Christmas, one day at a time!

Setup

If you’ve done Day 10 or Day 11, you can continue without setting anything up – just open up Studio or Console and get going!

If you’re new here, you’ll first want to spin up a TypeDB instance and connect with Studio or Console, and create a new database.

Then, you can get Santa’s recovered database schema including our subsequent changes here: schema, and load it by copying the linked text into a schema transaction’s query interface in Studio or Console, and then commit (note: by default, Studio auto-commits each query when set to “auto” mode).

Get the initial dataset plus subsequent days’ changes as a data file. Then load it by doing the same (follow link, copy text, paste into Studio or Console), but this time use a write transaction – and make sure you have committed.

At this point, you should have a database ready to go!

Day 12

We’re into the last stages of data recovery: we need to construct Santa’s distribution path for dropping presents in each country.

Here’s the part of the schema we need to work with now:

  entity santa-distribution-route,
    owns start-date @key,
    plays distribution-stop:route;

  relation distribution-stop,
    relates country @card(1),
    relates route @card(1),
    owns stop-number @card(1);
  
  entity country, plays distribution-stop:country;

Essentially, we need to:

  • construct a single santa-distribution-route
  • create a bunch of distribution-stop relations, one per country
  • order is enumerated with stop-number
  • we relate the relevant country, and the initial santa-distribution-route

The hard part is the enumeration, which we’ll start with! If you haven’t done Day 11, it could be useful – we’ve got recursive functions here too!

Heads up: due to the dataset being generated by LLMs, it contains a few duplicate countries.

Enumerating countries


To save a bit of time, here’s an optimized recursive function that enumerates countries by name and a counter. Feel free to study it:

fun enumerate_countries($country-name: name, $counter: integer) -> { name, integer }:
match
  {
    let $n, $c in enumerate_countries($country-name, $counter);
    let $next = next_country_name($n);
    let $id = $c + 1;
  } or {
    # trick to copy the country-name to a new variable for return
    $next isa name == $country-name;
    let $id = $counter;
  };
return { $next, $id };

This function accepts a country name, and a counter to start the enumeration from, and returns a stream of countries according to the next_country_name() function that we still have to provide. We’re using country names since they have a natural ordering.

For the first step, let’s try to implement this function in the simplest way possible: enumerate countries alphabetically! The function should have this signature:

fun next_country_name($country-name: name) -> name

So your function will have this signature:

Answer
with fun next_country_name($country-name: name) -> name:
  match
    $next isa country, has name $next-name;
    $next-name > $country-name;
  sort $next-name;
  return first $next-name;

Great! Let’s wire it up and test it out with both functions and an initial call to enumerate_countries() with the alphabetically first country and the counter 1.

Answer
with fun next_country_name($country-name: name) -> name:
  match
    $next isa country, has name $next-name;
    $next-name > $country-name;
  sort $next-name;
  return first $next-name;

with fun enumerate_countries($country-name: name, $counter: integer) -> { name, integer }:
  match
    {
      let $n, $c in enumerate_countries($country-name, $counter);
      let $next = next_country_name($n);
      let $id = $c + 1;
    } or {
      # trick to copy the country-name to a new variable for return
      $next isa name == $country-name;
      let $id = $counter;
    };
  return { $next, $id };

match
  $start isa country, has name $start-name;
sort $start-name;
limit 1;
match
  let $n, $i in enumerate_countries($start-name, 1);

That’s enough for us to proceed, so let’s start wiring up Santa’s distribution route! If you want a challenge: try to change this query so it enumerates by country population instead.

Creating Santa’s route

The first thing we need is a santa-distribution-route. Create one for December 25, 2025 at 00:00 in ISO 8601 format: YYYY-MM-DDTHH:MM:SS.

Answer
insert
  $route isa santa-distribution-route,
    has start-date 2025-12-25T00:00;

Now, let’s use this inserted route in combination with our country enumeration query to match countries by the enumerated name, and then add a distribution-stop relation for them.

Answer
with fun next_country_name($country-name: name) -> name:
  match
    $next isa country, has name $next-name;
    $next-name > $country-name;
  sort $next-name;
  return first $next-name;

with fun enumerate_countries($country-name: name, $counter: integer) -> { name, integer }:
  match
    {
      let $n, $c in enumerate_countries($country-name, $counter);
      let $next = next_country_name($n);
      let $id = $c + 1;
    } or {
      # trick to copy the country-name to a new variable for return
      $next isa name == $country-name;
      let $id = $counter;
    };
  return { $next, $id };

match
  $start isa country, has name $start-name;
sort $start-name;
limit 1;
match
  $route isa santa-distribution-route, 
    has start-date 2025-12-25T00:00;
  let $n, $i in enumerate_countries($start-name, 1);
  $country isa country, has name $n;
insert
  distribution-stop (country: $country, route: $route), has stop-number == $i;

This is a complex, deep example demonstrating the combined power of functions, recursion, and multi-operation query composition all at once! Let’s write a query verify we haven’t accidentally made a stop in Santaland on Mars…

Answer
match
  $country isa country, has name "Santaland";
  distribution-stop (country: $country);


If you got a match, let’s delete all the distribution stops and try again…

Answer
match $stop isa distribution-stop;
delete $stop;

Alright: this time, we’ll only include countries in our next_country_name that are on Earth! Modify the long query to do just that, and we’ll be golden.

Answer
with fun next_country_name($country-name: name) -> name:
  match
    $planet isa planet, has name "Earth";
    $continent isa continent;
    location-contains (parent: $planet, child: $continent);
    $next isa country, has name $next-name;
    location-contains (parent: $continent, child: $next);
    $next-name > $country-name;
  sort $next-name;
  return first $next-name;

with fun enumerate_countries($country-name: name, $counter: integer) -> { name, integer }:
  match
    {
      let $n, $c in enumerate_countries($country-name, $counter);
      let $next = next_country_name($n);
      let $id = $c + 1;
    } or {
      # trick to copy the country-name to a new variable for return
      $next isa name == $country-name;
      let $id = $counter;
    };
  return { $next, $id };

match
  $start isa country, has name $start-name;
sort $start-name;
limit 1;
match
  $route isa santa-distribution-route, 
    has start-date 2025-12-25T00:00;
  let $n, $i in enumerate_countries($start-name, 1);
  $country isa country, has name $n;
insert
  distribution-stop (country: $country, route: $route), has stop-number == $i;


Re-verify, and if you got no matches – great job!

See you soon!

We’re almost at the end of our adventure. We’ve got all the elves situated, and Santa’s route set up. Tomorrow is Day 13!

If you encounter any issues, want to chat, or anything else – feel free to post in our Discord or feel free to email me directly.

Share this article

TypeDB Newsletter

Stay up to date with the latest TypeDB announcements and events.

Subscribe to newsletter

Further Learning

Feedback