Intermediary Representation In Dynamo-Browse Expressions

One other thing I did in Dynamo-Browse is change how the query AST produced the actual DynamoDB call.

Previously, the AST produced the DynamoDB call directly. For example, if we were to use the expression pk = "something" and sk ^= "prefix", the generated AST may look something like the following:

AST from the parse expression

The AST will then be traversed to determine whether this could be handled by either running a query or a scan. This is called “planning” and the results of this will determine which DynamoDB API endpoint will be called to produce the result. This expression may produce a call to DynamoDB that would look like this:

client.Query(&dynamodb.QueryInput{
    TableName: "my-table-name",
    KeyConditionExpression: "#0 = :0 and beings_with(#1, :1)",
    ExpressionAttributeNames: map[string]string{
        "#0": "pk",
        "#1": "sk",
    },
    ExpressionAttributeValues: map[string]types.AttributeValue{
         ":0": &types.StringAttributeValue{ Value: "something" },
         ":1": &types.StringAttributeValue{ Value: "prefix" },
    },
})

Now, instead of determining the various calls to DynamoDB itself, the AST will generate an intermediary representation, something similar to the following:

Intermediary representation tree

The planning traversal will now happen off this tree, much like it did over the AST.

For such a simple expression, the benefits of this extra step may not be so obvious. But there are some major advantages that I can see from doing this.

First, it simplifies the planning logic quite substantially. If you compare the first tree with the second, notice how the nodes below the “and” node are both of type “binOp”. This type of node represents a binary operation, which can either be = or ^=, plus all the other binary operators that may come along. Because so many operators are represented by this single node type, the logic of determining whether this part of the expression can be represented as a query will need to look something like the following:

  • First determine whether the operation is either = or ^= (or whatever else)
  • If it’s = and the field on the left is either a partition or sort key, this can be represented as a query
  • If it’s ^=, first determine whether the operand is a string, (if not, fail) and then determine whether the field on the left is a sort key. If so, then this can be query.
  • Otherwise, it will have to be a scan.

This is mixing various stages of the compilation phase in a single traversal: determining what the operator is, determining whether the operands are valid (^= must have a string operand), and working out how we can run this as a query, if at all. You can imagine the code to do this being large and fiddly.

With the IR tree, the logic can be much simpler. The work surrounding the operand is done when the AST tree is traverse. This is trivial: if it’s = then produce a “fieldEq”; if it’s ^= then produce a “fieldBeginsWith”, and so on. Once we have the IR tree, we know that when we encounter a “fieldEq” node, this attribute can be represented as a query if the field name is either the partition or sort key. And when we encounter the “fieldBeginsWith” node, we know we can use a query if the field name is the sort key.

Second, it allows the AST to be richer and not tied to how the actual call is planned out. You won’t find the ^= operator in any of the expressions DynamoDB supports: this was added to Dynamo-Browse’s expression language to make it easier to write. But if we were to add the “official” function for this as well β€” begins_with() β€” and we weren’t using the IR, we would need to have the planning logic for this in two places. With an IR, we can simply have both operations produce a “fieldBeginsWith” node. Yes, there could be more code encapsulated by this IR node (there’s actually less) but it’s being leverage by two separate parts of the AST.

And since expressions are not directly mappable to DynamoDB expression types, we can theoretically do things like add arithmetic operations or a nice suite of utility functions. Provided that these produce a single result, these parts of the expression can be evaluated while the IR is being built, and the literal value returned that can be used directly.

It felt like a few other things went right with this decision. I was expecting this to take a few days, but I was actually able to get it built in a single evening. I’m also happy about how maintainable the code turned out to be. Although there are two separate tree-like types that need to be managed, both have logic which is much simpler than what we were dealing with before.

All in all, I’m quite happy with this decision.

Did a cleanup of all the half-finished posts I had lying around as drafts. Many of them have been cluttering up the Draft section for a few months now.

The original goal was to have a “bank” of posts that I could publish on days I had nothing else to say. I guess that didn’t quite pan out as expected. Since starting this idea, I think there were two banked micro-posts that I eventually published. The rest turned into ambitious, half-finished pieces of writing that became larger over time as I rewrote sections and added additional threads of thought. Eventually they became so large and overworked β€” without being anywhere close to finished β€” that it became too much of a hassle to keep them around.

I guess when it comes to posting stuff, I need to either publish it immediately, or at most a few days after it’s written. Keeping it around with the belief that I can publish it “later” means they’ll be little chance that it’ll see the light of day.

Dinner at The Mint. This is my table, which is the same table I sat at last time I was here, in January 2020. Feels like a bit of a bookend in a way.

Along the boundary of Melbourne Port.

Railway tracks with a road on the left and the Melbourne Port fence on the right.

πŸŽ™ Dithering: 13 Sept 22 - Discord AI

Enjoyed the discussion on Midjourney and AI images, but it was the final two minutes on the UIs of Slack and Discord that I found to be the most interesting part of this episode.

Sunny day today. Might need to bring out the hat once again.

Really tired this morning. Work up last night due to a production incident. Must say that nothing goes out to prod quicker than a 3 AM change you hope will just shut the damn PagerDuty alerts up.

Letting Queries Actually Be Queries In Dynamo-Browse

I spent some more time working on dynamo-browse over the weekend (I have to now that I’ve got a user-base πŸ˜„).

No real changes to scripting yet. It’s still only me that’s using it at the moment, and I’m hoping to keep it this way until I’m happy enough with the API. I think we getting close though. I haven’t made the changes discussed in the previous post about including the builtin plugin object. I’m thinking that instead of a builtin object, I’ll use another module instead, maybe something like the following:

const ext = require("audax:ext");

ext.registerCommand("thing", () => console.log("Do the thing"));

The idea is that the ext module will provide access to the extension points that the script can implement, such as registering new commands, etc.

At this stage, I’m not sure if I want to add the concept of namespaces to the ext module name. Could help in making it explicit that the user is accessing the hooks of “this” extension, as opposed to others. It may leave some room for some nice abilities, like provide a way to send messages to other extensions:

// com.example.showTables
// This extension will handle events which will prompt to show tables. 
const ext = require("audax:ext/this");

ext.on("show-tables", () => {
    ui.showTablePrompt();
});

// com.example.useShowTables
// This extension will use the "show-tables" message "com.example.showTables"
const showTables = require("audax:ext/com.example.showTables");

showTables.post("show-tables");

Then again, this might be unnecessary given that the JavaScript module facilities are there.

Anyway, what I actually did was start work on making queries actually run as queries against the DynamoDB table. In the released version, running a query (i.e. pressing ? and entering a query expression) will actually perform a table scan. Nothing against scans: they do what they need to do. But hardly the quickest way to get rows from DynamoDB if you know the partition and sort key.

So that’s what I’m working on now. Running a query of the form pk = "something" and sk = "else" where pk and sk are the partition and sort keys of the table will now call the dynamodb.Query API. This also works with the “begins with” operator: pk = "something" and sk ^= "prefix". Since sk is the sort key, this will be executed as part of a query to DynamoDB.

This also works if you were to swap the keys around, as in sk = "else" and pk = "something". Surprisingly the Go SDK that I’m using does not support allow expressions like this: you must have the partition key before the sort key if you’re using KeyAnd. This touches on one of the design goals I have for queries: the user shouldn’t need to care how the expression actually produces the result. If it can do so by running an actual query against the table, then it will; if not, it will do a scan. Generally, the user shouldn’t care either way: just get me the results in the most efficient way you can think of!

That said, it might be necessary for the user to control this to an extent, such as requiring the use of a scan if the planner would normally go for a query. I may add some control to this in the expression language to support this. But these should be, as a rule, very rarely used.

Anyway, that’s the biggest change that’s happening. There is something else regarding expressions that I’m in the process of working on now. I’ll touch on that in another blog post.

I miss working on monoliths. Micro-services are just so painful to test. So many moving parts you need to get working before you can even start.

Then you’ve got serverless stuff. Go to test those, and wow, you’ll wish you were testing micro-services. 😟

Saw someone at work use Numi to show some maths so I’m giving it a try. Only just started using it but I already like it. Feels very similar to Tot and Boop: a small MacOS utility that fits nicely in that middle-ground between Calculator and a spreadsheet.

Screenshot of Numi

Absolutely incredible seeing how far AI image creation has come. I know this was years in the making, but with all the recent launches, it feels like the change has happen over mere months. Will need to give it a try.

At least one photo will be taken at this event (well technically the setup of the event).

Sign that says 'Photo and video will be taken at this event'

Discovered that I can use a URL shortener to grab a link from my Android phone and open it on my iPad. I guess that means I’m finally up to speed on the awesome ideas of 2007. πŸ˜€

My whimsical side might be developing a little. I’m starting to come round to the wiggly seek bar in Android.

Rail works.

The Australian Republic Question

With the passing of Queen Elisabeth II, the talk of whether Australia should become a republic will probably start making the rounds once more. I don’t consider myself a royalist, and when the last referendum on the issue came around, I voted in favour of becoming a republic. The idea of having the British Royal Family as the head of state of a country halfway around the world seem anachronistic to me, and I was disappointed when the referendum failed.

Since then, my position has been become slightly more nuanced. I still believe in the ideals of becoming a republic β€” in being a country that is more-or-less completely self governing. But after reading this article from Vox.com, I’ve come to see some benefits of having a head of state that is removed from the day-to-day politics of government. Sure, the stability from such a figurehead may not have been wholly constant, but that “lack [of] semblance of legitimacy” that comes from the royals being the head of state does provide some reassurance. One less divisive thing for people to think about when that position changes hands.

So if the referendum was held today, which way would I go? I’d probably still vote “yes”, but it would have a “can we make it such that the office is not in any way marred in the politics of the day?” qualifier attached to it.

Then again, I talking about the terms of a theoretical referendum where the proposed system is more than just replace-the-monarchy-with-a-president. Such concerns regarding the division of power might already be settled within the constitution. I really don’t know: might be worth looking up if the question were to come up again.

Either way, we’ll see which way the winds blow.

Flags at half mast at the school today to mark the passing of Queen Elizabeth II.

Australian and Aboriginal flags at half mast

Those worried about video-centric podcasts ruining the format can allay their fears. The killer feature of podcasts is that you can listen to them without having to pay attention to anything visual. Video may fill a nitch, but I can’t see it taking over the entire format.

I’ve been tinkering around with Pico-8 game development recently. I like how limiting it is. Expectations are not too high, which helps given that I’m hopeless at the non-coding aspects of game design, like artwork.

It’s quite a joy. Reminds me a lot of making games in QBasic back in the day.

And while we’re on the subject of tests: make it easy for me to launch tests quickly from the IDE. Refusing to run a test because a particular environment variable is not set is really annoying, especially when a suitable default value would work 90% of the time.