It occurred to me tonight (after rewriting my Tax File Number generation script in JavaScript, so it works on Text Expander Touch), that it might be nice to do TFN validation in Postgres.
You can write a function that validates a TFN (this is not the first version I wrote, this one is a bit more capable in terms of the range of values it accepts):
However, we might want to convert our data into a canonical “format”: in this case, always store it as XXX-XXX-XXX. We can write a function that does this:
But how can we make our data that we insert always stored in this format?
Postgres triggers to the rescue. We can’t do it as part of a Domain (although we probably could do it as part of a scalar type, but that’s a whole other kettle of fish).
Let’s set up a table to store our tax declarations in:
Sometimes, your ORM just seems to be out to get you.
For instance, I’ve been investigating a technique for the most important data structure in a system to be essentially immuatable.
That is, instead of updating an existing instance of the object, we always create a new instance.
This requires a handful of things to be useful (and useful for querying).
We probably want to have a self-relation so we can see which object supersedes another. A series of objects that supersede one another is called a lifecycle.
We want to have a timestamp on each object, so we can view a snapshot at a given time: that is, which phase of the lifecycle was active at that point.
We should have a column that unique per-lifecycle: this makes for querying all objects of a lifecycle much simpler (although we can use a recursive query for that).
There must be a facility to prevent multiple heads on a lifecycle: that is, at most one phase of a lifecycle may be non-superseded.
The lifecycle phases needn’t be in the same order, or really have any differentiating features (like status). In practice they may, but for the purposes of this, they are just “what it was like at that time”.
I’m not sure these ideas will ever get into a released product, but the work behind them was fun (and all my private work).
The basic model structure might look something like:
classPhase(models.Model):phase_id=models.AutoField(primary_key=True)lifecycle_id=models.AutoField(primary_key=False,editable=False)superseded_by=models.OneToOneField('self',related_name='supersedes',null=True,blank=True,editable=False)timestamp=models.DateTimeField(auto_now_add=True)# Any other fields you might want...objects=PhaseQuerySet.as_manager()
So, that looks nice and simple.
Our second AutoField will have a sequence generated for it, and the database will give us a unique value from a sequence when we try to create a row in the database without providing this column in the query.
However, there is one problem: Django will not let us have a second AutoField in a model. And, even if it did, there would still be some problems. For instance, every time we attempt to create a new instance, every AutoField is not sent to the database. Which breaks our ability to keep the lifecycle_id between phases.
So, we will need a custom field. Luckily, all we really need is the SERIAL database type: that creates the sequence for us automatically.
This now ensures each time we save our object, a new instance is created. The lifecycle_id will stay the same.
Still not totally done though. We currently aren’t handling a newly created lifecycle (which should be handled by the associated postgres sequence), nor are we marking the previous instance as superseded.
It’s possible, using some black magic, to get the default value for a database column, and, in this case, execute a query with that default to get the next value. However, that’s pretty horrid: not to mention it also runs an extra two queries.
Similarly, we want to get the phase_id of the newly created instance, and set that as the superseded_by of the old instance. This would require yet another query, after the INSERT, but also has the sinister side-effect of making us unable to apply the not-superseded-by-per-lifecycle requirement.
As an aside, we can investigate storing the self-relation on the other end - this would enable us to just do:
However, this turns out to be less useful when querying: we are much more likely to be interested in phases that are not superseded, as they are the “current” phase of each lifecycle. Although we could query, it would be running sub-queries for each row.
Our two issues: setting the lifecycle, and storing the superseding data, can be done with one Postgres BEFORE UPDATE trigger function:
So, we have most of the db-level code written. How do we use our model? We can write some nice queryset methods to make getting the various bits easier:
(That was why I used the lifecycle_id as the column).
Whilst building this prototype, I came across a couple of things that were also interesting. The first was a mechanism to get the default value for a column:
defdatabase_default(table,column):cursor=connection.cursor()QUERY="""SELECT d.adsrc AS default_value
FROM pg_catalog.pg_attribute a
LEFT JOIN pg_catalog.pg_attrdef d ON (a.attrelid, a.attnum)
= (d.adrelid, d.adnum)
WHERE NOT a.attisdropped -- no dropped (dead) columns
AND a.attnum > 0 -- no system columns
AND a.attrelid = %s::regclass
AND a.attname = %s"""cursor.execute(QUERY,[table,column])cursor.execute('SELECT {}'.format(*cursor.fetchone()))returncursor.fetchone()[0]
You can probably see why I didn’t want to use this. Other than the aforementioned two extra queries, it’s executing a query with data that comes back from the database. It may be possible to inject a default value into a table that causes it to do Very Bad Things™. We could sanitise it, perhaps ensure it matches a regular expression:
However, the trigger-based approach is nicer in every way.
The other thing I discovered, and this one is really nice, is a way to create an exclusion constraint that only applies if a column is NULL. For instance, ensure that no two classes for a given student overlap, but only if they are not superseded (or deleted).
I’ve got lots of code I’d do a different way if I were to start over, but often, we have to live with what we have.
One situation I would seriously reconsider is the structure I use for storing data related to how I interact with external systems. I have an Application object, and I create instances of this for each external system I interact with. Each new Application gets a UUID, and is created as part of a migration. Code in the system uses this UUID to determine if something is for that system.
But that’s not the worst of it.
I also have an AppConfig object, and other related objects that store a relation to an Application. This was fine initially, but as my code got more complex, I hit upon the idea of using Django’s Proxy models, and using the related Application to determine the subclass. So, I have AppConfig subclasses for a range of systems. This is nice: we can even ensure that we only get the right instances (using a lookup to the application to get the discriminator, which I’d probably do a different way next time).
However, we also have other bits of information that we need to store, that has a relation to this AppConfig object.
And here is where we run into problems. Eventually, I had the need to subclass these other objects, and deal with them. That gives a similar benefit to above for fetching filtered lists of objects, however when we try to follow the relations between these, something annoying happens.
Instead of getting the subclass of AppConfig, that we probably want to use because the business logic hangs off that, we instead get the actual AppConfig instances. So, in order to get the subclass, we have to fetch the object again, or swizzle the __class__. And, going back the other way would have the same problem.
Python is a dynamic language, so we should be able to do better.
In theory, all we have to do is replace the attributes on the two classes with ones that will do what we want them to do. In practice, we need to muck around a little bit more to make sure it all works out right.
It would be nice to be able to decorate the declaration of the overridden field, but that’s not valid python syntax:
>>> class Foo(object):
... @override
... bar = object()
File "<stdin>", line 3
bar = object()
So, we’ll have to do one of two things: alter the class after it has been defined, or leverage the metaclass magic Django already does.
classFoo(models.Model):bar=models.ForeignKey('bar.Bar')classFooProxy(models.Model):bar=ProxyForeignKey('bar.BarProxy')# Note the proxy classclassMeta:proxy=True
However, we can’t just use the contribute_to_class(cls, name) method as-is, as the Proxy model attributes get dealt with before the parent model. So, we need to register a signal, and get the
framework to run our code after the class has been prepared:
We need to use the passed-in related_name if one was provided in the new field definition, else we want to use what the original field’s related name was. However, if not explicitly set, then neither field will actually have a related_name attribute.
We cannot allow an override of a foreign key to a non-proxy model: that would hijack the original model’s related queryset.
Similarly, we can only allow a single proxy to override-relate to another proxy: any subsequent override-relations would likewise hijack the related object queryset.
For non-related fields, we can only allow an override if the field is compatible. What that means I’m not completely sure just yet, but for now, we will only allow the same field class (or a subclass). Things that would require a db change would be verboten.
So, we need to guard our override_model_field somewhat:
defoverride_model_field(model,name,field):original_field=model._meta.get_field(name)ifnotisinstance(field,original_field.__class__):raiseTypeError('...')# Must do these checks before the `add_to_class`, otherwise it breaks tests.iffield.rel:ifnotfield.rel.to._meta.proxy:raiseTypeError('...')related_name=getattr(field,'related_name',original_field.related.get_accessor_name())related_model=getattr(field.rel.to,related_name).related.model# Do we already have an overridden relation to this model?ifrelated_model._meta.proxy:raiseTypeError('...')model.add_to_class(name,field)iffield.rel:field.rel.to.add_to_class(related_name,ForeignRelatedObjectsDescriptor(field.related))
There’s a nice article at Bowled Over by SQL Window Functions by a chap called Dwain Camps. It’s written from the perspective of T-SQL, which has some differences to Postgres’s DDL and querying. I’ve reworked his stuff into what feels nice for me from a Postgressy perspective.
I’ll recap a couple of things he mentions, but you’ll probably want to head there and read that first.
Strike: all pins knocked down on the first ball of a frame. Scores 10 (from this frame), plus the total of whatever the next two balls knock down.
Spare: all pins knocked down on the second ball of a frame. Scores 10 (from this frame), plus how ever many pins you knock down on the next ball.
Open: at least one pin remains standing at the end of the frame. Score is how ever many pins you knocked down.
By convention, only the running tally is shown on the scoresheet. I’ve kept the frame score as the score for this frame only, and the total will contain the running total.
The first thing I do a bit differently is to use Postgres’ DOMAIN structures, which enables us to remove some of the check constraints, and simplify some others:
Not much different to the original, other than using those fresh new domain types.
The other approach I’ve used is to use more constraints, but make them simpler. I’m also relying on the fact that X + NULL => NULL, which means we can leave off a heap of the constraint clauses.
We’ll start by preventing the (non-final) frames from exceeding the number of available pins. In the case of final frame, we still only allow a spare unless we have a strike already.
This is as simple as it can be. Because ball 2 may be null but ball 1 may not, and each ball must be no greater than 10, this is enough to encapsulate the requirement. There is one slightly incorrect circumstance: a value of (10, 0) would be valid, which is strictly incorrect (ball 2 was never bowled). In the case of the calculations it’s all correct, but if a 0 was bowled immediately after a strike, it may be possible to insert that as ball 2, which would be misleading.
A follow-up to the previous constraint: we only get to bowl ball 3 if we have bowled ball 2, and scored a strike or spare already. Note, we may have a strike on the first ball, which means we might have more than 10.
Finally, we have some specific allowable conditions for that last ball. We already know that we must have scored a strike or spare with the first two balls, but we need to know how many pins are available to us.
If we scored a spare or two strikes, then we may have any number up to 10 to score now.
Otherwise, we have however many pins were left by ball 2 (which means ball 1 must have been a strike).
I find those constraints much easier to read that the original ones.
I’ve written a view, that uses a couple of Common Table Expressions (CTEs), as well as the window functions Dwain discussed.
CREATEORREPLACEVIEWbowling.frame_scoreAS(WITHpin_countsAS(SELECTgame_id,player_id,frame,ball1,ball2,ball3,-- Get the first ball from the next frame.
-- Used by strike and spare.
LEAD(ball1,1)OVER(PARTITIONBYgame_id,player_idORDERBYframe)ASnext_ball_1,-- Get the second ball from the next frame.
-- Used by strike.
LEAD(ball2,1)OVER(PARTITIONBYgame_id,player_idORDERBYframe)ASnext_ball_2,-- Get the first ball from the next next frame.
-- Used by double-strike.
LEAD(ball1,2)OVER(PARTITIONBYgame_id,player_idORDERBYframe)ASnext_next_ball_1FROMbowling.frame),frame_countsAS(SELECTgame_id,player_id,frame,ball1,ball2,ball3,CASE-- We will start with frame 10: when we have a strike
-- or spare, we get all three balls.
WHENframe=10ANDball1+ball2>=10THENball1+ball2+ball3-- On a strike, we get the next two balls. This could
-- be from the next frame, or include the first ball
-- of the frame after that. Note that in frame 9, we will
-- also look at the second ball from fram 10, rather than
-- looking for a non-existent frame 11.
WHENball1=10THENball1+next_ball_1+(CASEWHENnext_ball_1=10ANDframe<9THENnext_next_ball_1ELSEnext_ball_2END)-- In the case of a spare, grab the next ball. We already
-- handled a spare on frame 10 above.
WHENball1+ball2=10THENball1+ball2+next_ball_1-- Otherwise, it's just the two balls we bowled in this frame.
ELSEball1+ball2ENDASscoreFROMpin_counts)-- We have everything we need in the previous CTE, except that
-- shows us the frame score, rather than the running tally.
-- We need to do that in another window function here, but
-- only calculate a value when the frame's wave function has
-- collapsed (ie, it's score is known).
SELECTframe_counts.*,CASEWHENscoreISNOTNULLTHENSUM(score)OVER(PARTITIONBYgame_id,player_idORDERBYframeROWSUNBOUNDEDPRECEDING)ELSENULLENDAStotalFROMframe_counts);
Again, I think this is a simpler query, and easier to read. But, I guess I wrote it.
We can insert the same data as used there, and look at our results:
-- Game 1
INSERTINTObowling.frameVALUES(1,1,1,7,2,NULL),(1,1,2,3,7,NULL),(1,1,3,6,4,NULL),(1,1,4,10,NULL,NULL),(1,1,5,10,NULL,NULL),(1,1,6,10,NULL,NULL),(1,1,7,9,1,NULL),(1,1,8,10,NULL,NULL),(1,1,9,8,1,NULL),(1,1,10,6,3,NULL);-- Game 2
INSERTINTObowling.frameVALUES(2,1,1,10,NULL,NULL),(2,1,2,3,7,NULL),(2,1,3,10,NULL,NULL),(2,1,4,6,4,NULL),(2,1,5,10,NULL,NULL),(2,1,6,9,1,NULL),(2,1,7,10,NULL,NULL),(2,1,8,8,2,NULL),(2,1,9,10,NULL,NULL),(2,1,10,7,3,10);-- Game 3
INSERTINTObowling.frameVALUES(3,1,1,10,NULL,NULL),(3,1,2,10,NULL,NULL),(3,1,3,10,NULL,NULL),(3,1,4,10,NULL,NULL),(3,1,5,10,NULL,NULL),(3,1,6,10,NULL,NULL),(3,1,7,10,NULL,NULL),(3,1,8,10,NULL,NULL),(3,1,9,10,NULL,NULL),(3,1,10,10,10,10);
player_id | average
-----------+----------------------
1 | 227.0000000000000000
(1 row)
I’m fairly sure I’ve rewritten the constraints correctly, but may have missed some. Here are some of the condition tests that show invalid constraints:
I recently bought a Humax 4tune (hms1000t). I’m rather liking in so far: 4 tuners, a reasonable UI.
I would like to be able to get it to do a bit more, so I’ve started investigating it. It shows the following ports as being open:
Port Scan has started…
Port Scanning host: 10.0.1.30
Open TCP Port: 21 ftp
Open TCP Port: 139 netbios-ssn
Open TCP Port: 445 microsoft-ds
Open TCP Port: 9000 cslistener
Open TCP Port: 9251
Open TCP Port: 9252
Open TCP Port: 36666
Open TCP Port: 36667
Open TCP Port: 48629
Open TCP Port: 50001
Open TCP Port: 55200
Open TCP Port: 56789
Open TCP Port: 56790
Open TCP Port: 58816
Open TCP Port: 60001
Port Scan has completed…
Obviously, I’ve turned on the FTP and Samba services.
Port 9000 appears to be an RTSP service:
$ curl http://<ip-address>:9000
RTSP
Ports 9251 and 9252 respond with:
{"errorCode": 404, "errorDescription":"Specified WID not found"}
The latter requires HTTPS, but uses a certificate for the domain name htvdev.humaxtvportal.com. The humaxtvportal.com domain is registered.
Port 36666 responds with:
RTSP/1.0 200 OK
CSeq: (null)
Apple-Jack-Status: connected; type=analog
Port 58816 rejects connections, but port 60001 is an interesting one:
$ telnet [address] 60001
Trying [address]...
Connected to [address].
Escape character is '^]'.
root:> help
* help [root:]
** registered process list
corsair homma prism obama
sama ipepg
** helpall : display all command set
root:> helpall
* help all
[corsair]
[mem]
printmem : print memory usage to outputfile
memconf : configuration of printmem
memcheck : check memory corruption - if corrupted, log in corrupted.log
currsec : print the current second from the boot
[RLIB]
print_rsc : print Resource Info
[OAPI]
oapi_output_vid : oapi_output_video debug api call
oapi_output_hdm : oapi_output_hdmi debug api call
[homma]
[mem]
printmem : print memory usage to outputfile
memconf : configuration of printmem
memcheck : check memory corruption - if corrupted, log in corrupted.log
currsec : print the current second from the boot
[RLIB]
print_rsc : print Resource Info
[prism]
[mem]
printmem : print memory usage to outputfile
memconf : configuration of printmem
memcheck : check memory corruption - if corrupted, log in corrupted.log
currsec : print the current second from the boot
[ONDK]
hello : help..
vkcmd : vkcmd [mem/sem/task/timer]
timerlist : timerlist
gwmlist : gwmlist
[OAPI]
oapi_output_hdm : oapi_output_hdmi debug api call
[obama]
[mem]
printmem : print memory usage to outputfile
memconf : configuration of printmem
memcheck : check memory corruption - if corrupted, log in corrupted.log
currsec : print the current second from the boot
[pal_hdmi]
hdcp_enable : HDCP on/off
status : Print HDMI status.
capa : Print HDMI TV capability.
hdcp_event : Send HDCP event to appkit
output : Enable/Disable HDMI output.
avinfo : Set AVI infoframe.
[OM]
om_output_hdmi : om_output_hdmi debug api call
om_output_video : om_output_video debug api call
[PAL]
pal_scl : pal scaler debug api call
pal_scart : pal scart debug api call
pal_out : pal out debug api call
pal_video : pal video debug api call
pal_audio : pal audio debug api call
[RLIB]
print_rsc : print Resource Info
[SVC]
svc_av : tv service /AV debug api call
svc_out : svc out debug api call
[sama]
[mem]
printmem : print memory usage to outputfile
memconf : configuration of printmem
memcheck : check memory corruption - if corrupted, log in corrupted.log
currsec : print the current second from the boot
[RLIB]
print_rsc : print Resource Info
[SamaCmd]
print_systime : print system time
print_rpc : print SAMA rpc
print_slot : print SAMA slot status
print_sch : print SAMA Schedules
print_timer : print SAMA Timer
make_sch : make SAMA Schedule
del_sch : delete SAMA Schedule
make_ransch : make SAMA Schedule (TBR)
del_sch_all : delete ALL SAMA Schedule
[ipepg]
[mem]
printmem : print memory usage to outputfile
memconf : configuration of printmem
memcheck : check memory corruption - if corrupted, log in corrupted.log
currsec : print the current second from the boot
[RLIB]
print_rsc : print Resource Info
root:>
This is the second post in an ongoing series dealing with storing Hierarchical or Tree data structures in Postgres. You should read the Introduction if you haven’t already.
This post contains the queries that illustrate how an Adjacency List model can be used to represent a Hierarchical set of data, including data definitions, and the various operations that have been defined in the aforementioned introduction.
They can be modelled with most ORMs without any extra infrastructure
Many of the operations are non-complex
Recursive queries allow us to perform the complex queries in reasonable time
To help build suspense (but more because I haven’t yet come up with a way to generate a nice reproducible, yet complex tree), this post may discuss the complexity of the queries, but will not contain any measurements.
Initial tree
Before each operation, our data will look like this (where parents point to children):
We will assume reversion back to this structure after each operation: we could do this using a TRUNCATE followed by an INSERT; or we could run the operation in a transaction and rollback.
There may be a post which shows the effects of each of the queries below in a graphical form.
Table structure
Adjacency Lists are dead simple. Each node simply contains a reference to it’s parent node.
Inserting a single leaf node is simple. To insert a single node as a child of node 13, for example:
INSERTINTOnodes(parent_id)VALUES(13);
Inserting a new root node is slightly more complicated. In most ORMs, we would probably do it in two queries: one to create the new node, and a second to update the other root nodes to point to this one. We’ll see that below.
We can do slightly better in raw SQL: UPDATE our table with the result of an INSERT ... RETURNING that occurs inside a Common Table Expression (CTE, or WITH query).
And our last insert, adding a child node, that gets it’s siblings as children. For instance, adding a new node under node 12, which gets all of node 12’s children as it’s children.
All of these queries should perform relatively well: the CTE will be very simple (as it is no different to the single leaf insert), and the UPDATE should likewise be fairly simple: it needs to filter out which existing nodes do not need to be updated; and then it needs to update the remainder of the rows with the value pulled from the CTE.
This theoretically is only marginally more complex than just a simple UPDATE foo SET bar=baz WHERE quux IS NULL style query.
If we were using an ORM, we might need to do this in two queries: something like this (in Django):
# Insert new root node: all other root nodes now have this as a parentnew_node=Node.objects.create()Node.objects.filter(parent=None).exclude(pk=new_node.pk).update(parent=new_node)# Could possibly do as:Node.objects.filter(parent=None).update(parent=Node.objects.create().pk)# Insert new node, with a single child as it's child (and that child's previous# parent as it's parent)new_node=Node.objects.create(parent=old_node.parent)old_node.parent=new_nodeold_node.save()# Insert new node, with children of that node's parent now children of the node.new_node=parent_node.children.create()parent_node.children.exclude(pk=new_node.pk).update(parent=new_node)# Again, may be able to do:parent_node.children.update(parent=parent_node.children.create().pk)
Note the required exclusion of the newly created node: we don’t have to do this in the CTE versions, as that doesn’t “exist” at the time the other part of the query runs.
Removals
Removing a single leaf node is no different than removing a row from a normal table. Removing node 9, for instance:
DELETEFROMnodesWHEREnode_id=9;
Because the information about parent-child relationships is stored in the child, we do not need to do anything else to maintain the tree.
To remove a single root node (in this case, node 1), and promote all children to root nodes themselves, we can do two queries:
The same pattern can be used for removing a node, and attaching it’s children to it’s parent. Here, we will remove node 2, and attach it’s children (4 and 5) as children of it’s parent, node 1:
Righto, now we are up to the traditionally “hard” things for an Adjacency List to perform. Dealing with removing an arbitrary depth of (sub)tree.
We’ll need to create a recursive CTE, and delete according to that. Let’s just select from that initially, so we can see what the CTE data will look like:
Coolio. So, on to our operations. Let’s remove the subtree starting with node 2. I’ll hide the CTE, since it will be the same for quite a few of these operations:
Because the relationship is stored purely on the child element, moving around trees and subtrees is very easy. We can start with moving subtree starting with 3 to underneath node 4:
UPDATEnodesSETparent_id=4WHEREnode_id=3;
Nothing surprising there. Similarly, the query is identical for moving a leaf to a different parent, a root node into a tree, and turning a subtree into a full tree (making that node a root node).
The final move: all of node’s children to a different node is almost as simple:
UPDATEnodesSETparent_id=5WHEREparent_id=12;
This seems to be a situation where Adjacency Lists are really good. None of these queries are any more complex than the simplest UPDATE you could think of.
Fetches
Using the same CTE, we can perform our fetches. We may need to extend it to deal with depths, but since the ancestors column contains ancestors starting with the root node, we could count stuff in there. Let’s see how it goes.
Descendants
Fetching all descendants of a given node just means we want to see if the node occurs at all in each row’s ancestors. To get all of node 10’s descendants:
Obviously, this is far less generic, but it is also significantly less complex. For starters, it only builds up the part of the tree we care about, and then just returns the node ids, rather than building up the whole tree, and then discarding the parts that are not required.
The same code can be used for determining the number of descendants, but with a COUNT(node_id) in the final query.
To get our depth-limited query, we can approach from two directions. To get the subtree to depth 2 from above:
To do the equivalent of the hand-built CTE, we would need to start with the node, and build back the other way. It’s getting late here, so I can’t think of a way to do this right now that doesn’t get stuck doing infinite recursion.
The count query is an interesting one: we can just remove the need to unnest, and take the cardinality:
The depth query is a little trickier. We want to know the ancestors of node 15, up to a depth of 2. If our ancestors array was in the reverse order, we should be able to unnest and limit.
We can do this because a node only has one parent: so limiting the number of ancestors (when sorted nearest ancestor first) is the same as limiting the depth.
Special queries
Fetching all leaf nodes is just a matter of excluding those that have a relationship to another node as it’s parent:
Well, that’s our operations. Most of them are really simple. For anything that requires us to fetch or delete a whole subtree, we needed to revert to recursive CTEs, and for some of the other operations, using a CTE makes it simpler (and easier to understand).
Next, we will look at an alternative to the CTE operations, using a recursive view. From there, we should be able to look at a trigger-based approach that materializes our tree (node, ancestors) data in a table, and keeps it up to date. That’s, as I hinted, getting close to a Materialized Path approach, but keeps the conceptual simplicity of the Adjacency List, and hopefully prevents possible issues relating to referential integrity.
I’ve written before about using Adjacency Lists, and even done some performance tests on querying them. Whilst reading a post today, it occurred to me that it might be worthwhile to do a comparison of the various methods of storing hierarchical data in Postgres, and the costs of the same operations on each of those.
This post is just an introduction, with an outline of what I plan to do to run these tests. Please feel free to suggest things that I have missed, or that might be an oversight at my end.
Tree Models
There are four methods of storing the relationships that might form a tree. This analysis will be limited to actual tree, rather than graph structures (no cycles). I plan to detail the data structures in a series of posts, one per method. In each case, where there are multiple ways to store the data, I will attempt to examine each of these.
Adjacency Lists, being the simplest to understand (and the ones I have spent more time on recently), will be discussed first.
Path Enumerations will be next, with a comparison of storing the data using the ltree extension, and using an ARRAY column.
Following this, I’ll make an attempt at using the Closure Table model: where each ancestor-descendant relationship is stored, rather than just the parent-child relationship.
Finally, I’ll have a crack at the Nested Set model. I’m not solidly behind this model for the types of data I’ve had to deal with, but it is a valid mechanism for storing and retrieving this data. Besides, it will be an interesting exercise to implement.
My plan to handle all of these is that all tree manipulation should be “automatic”, that is, adding a node (or removing one, or whatever) should not require explicit updating of the various metadata. This should all be handled by trigger functions on the tables themselves. Whether this turns out to be reasonable we shall see.
Operations
I plan to perform the same set of operations (on the same data, rather than randomly generated data) in all models, and compare the complexity and run-time of the various queries. I’m hoping to cover all of the operations that might be performed on a tree structure, so please add any more to the comments.
The data stored in the table will contain more than one tree: this means we can perform operations which add/remove root nodes/whole trees.
Insertions
Insert a single leaf node
Insert a single root node (all existing root nodes will then point to this)
Insert a single node partway through the tree, with the “replaced” node becoming a child of this (and this one keeps it’s children)
Insert a single node partway through the tree, with this node’s parent’s existing children all becoming children of the new node.
Removals
Remove a single leaf node
Remove a single root node (all children of this are promoted to root nodes)
Remove a single node partway through the tree: all children of this node then have their grand-parent as their parent.
Remove a subtree (a single non-root node and it’s descendants)
Remove a whole tree (a single root node and all descendants)
Remove all descendants of a specific node (but not the node itself)
Moves
Move a subtree from one parent to another
Move a single leaf node to a different parent
Move a root node into a tree
Make a subtree into a tree (turn a node into a root node).
Move all children of a node to a different parent
Fetches
Fetch all descendants of a given node
Fetch the number of descendants of a given node
Fetch descendants of a given node to a given depth
Fetch the number of descendants of a given node to a given depth
Fetch all ancestors of a given node
Fetch the number of ancestors of a given node
Fetch ancestors of a given node to a given depth
Fetch the number of ancestors of a given node to a given depth I don’t think this makes any sense.
Since we don’t have a nice range type in Python, we will just use a tuple that contains a lower and upper bound. We will assume that this is a canonical form: the lower bound is inclusive, the upper bound is non-inclusive. We will also assume (for simplicity) that the 0-th item is always less than the 1-th item in the tuple.
Given a list of these range-tuples, we want to do the two things we saw in the previous post.
Aggregate them into the simplest possible array of range-tuples, using a union operation.
Luckily, None sorts before any integer in python, so we will just be able to use the normal sort.
defaggregate_union(data):ifnotdata:returnNonesorted_data=sorted(data)result=sorted_data[0]forlower,upperinsorted_data[1:]:# If we ever find result[1] is None, we know it covers any# other possible values, so we can stop at that point.ifresult[1]isNone:breakiflower>result[1]:returnNone# Or raise an exception, if you want.result=(result[0],max(upper,result[1]))returnresult
The alternative, missing_ranges(data) takes cues from the SQL version too.
defmissing_ranges(data):ifnotdata:return(None,None)result=[]# We do a little fancy stuff here: append an extra item that# mimics what we were able to use lead for, but in a different# way so we can use [i + 1] later.sorted_data=sorted(data)+[(None,None)]ifsorted_data[0][0]isnotNone:# Note: the upper bound here is not quite correct!result.append((None,sorted_data[0][1]))fori,(lower,upper)inenumerate(sorted_data):# Grab the next item in our sorted list. Normally, we would check# to see if we are at the end, but we will rely on the break later# on: we can always stop processing when the upper bound of our# next item is `None`next=sorted_data[i+1]ifupper<next[0]or(next[0]isNoneandupperisnotNone):result.append((upper,next[0]))# Now, exit before we ever get to a bounds error on getting the next item.ifnext[1]isNone:breakreturnresult
However, there is a problem here that is nicely solved by the Range types within postgres:
>>> missing_ranges(data)
[(None, 3), (12, None)]
We need to subtract from the first object’s upper bound. Which is easy with integer tuple-ranges (basically, any discrete range type), but not so much with continuous ranges.
Postgres’ range types are very cool. You can use them to store, as you may guess, a value that is a range. There are several included range types, and it’s possible to create your own range type. For now, we’ll just look at using a simple int4range: although everything in principle could be applied to any range type.
Firstly, a quick discussion of how the range types work.
There is a literal form, and then a functional form:
The first thing you’ll notice is that postgres converts them to canonical form. We can provide the bound-types: inclusive or exclusive upper and lower values. This is the same notation you might see if you have done some mathematics.
You can also omit the upper and/or lower bounds to get a range object that goes to ±Infinity. Finally, you can also have empty ranges.
There are several operations that can be performed on a range. The most interesting one from the perspective of this article is the UNION operation.
You’ll get an error if your union does not result in a contiguous range. There is no way to store a discontinuous range in postgres (but you could store them in an array of int4range[], for instance).
What about if we want to get the aggregate range from a set of ranges?
This actually works to some degree: but only if the ranges in the query are already sorted, as each iteration of the aggregation function must result in a valid range:
Instead, we have to either collect all of the items in an array, sort this, and then attempt to aggregate, or, at each step, aggregate as much as possible, and add the current element to the array if we cannot perform a union.
The simpler of these (but will take more memory) is to stick them all into an array, and then sort and apply the union. We only need to define one new function to do it this way:
But what about the other case? What if we care more about what data is missing?
After spending way too many hours on playing around with this, I hit on the idea of using a window function, lead to get the data from the “next” row in the query.
This gets us most of the way. We can see the ones with upper >= lead indicate there is no gap between the ranges, so we can filter. However, we need to do this with a subquery to be able to get access to the columns correctly:
iOS 8’s HealthKit is a really nice feature. Prior to this, I was using Azumio’s Argus app for “life-tracking”, but it was really not providing me with much. It showed my my daily steps, included my exercise from my Garmin Forerunner 610, and helped me track my water, coffee and alcohol intake. It integrated really well with a Heart Rate recording app that they also make, but there was no real way to do anything else with this data. I was able to take photographs of my food, but that’s really it.
Along comes HealthKit.
All of a sudden, I’m no longer tied to a single application, or suite of applications that are engineered to work together. Instead, I can use Strava for my run tracking (still via my Garmin, or directly on the phone if I left it at home). These workouts, including calorie estimations, are available in HealthKit.
Similarly, step counts directly from my iPhone are also available, as are Heart Rate readings, either from the app I was using before, or an alternative. I can also add in extra values, for instance my max HR from a workout. As an aside, it would be really nice if Strava could push this value, removing the need for me to manually do it.
Even better for me would be the ability for Strava to send step counts for a workout: I usually run with my FootPod, which gives cadence information. From this, it’s possible to infer the number of steps I took in that workout. Because of this missing feature, I wound up taking my iPhone when I run, a less than optimal situation.
A couple of weeks ago, a co-worker introduced me to myfitnesspal. This is a calorie tracking app and website. Since I have (had?) about 10kg to lose, and something that makes getting relatively accurate calorie counts on the food I’m eating (although lots of the data in there is really, really bad: I’m forever looking at the nutritional breakdown of a suggested match and in some cases adding my own versions), in conjuction with the calorie usage of my excercise has become simple. Even fun.
I’m less concerned about trying out different apps on the iPhone, simply because the data (or most of it) just goes into HealthKit, which means if a better alternative comes along, I can just switch. I’m unsure if HealthKit data is restored from a backup - that’s something that I’ll find out eventually when I upgrade my phone, I guess.
Using my iPhone for step tracking means I need to have it in my pocket all of the time. This is actually really bad, as I find myself taking it out and using it more than I (or my family) would like. So, last night, I bought a Misfit Shine.
To be honest, the main reason is so that I get my phone out less, but I’m also looking forward to not taking my phone running. However, I’ve become quite attached to listening to podcasts whilst running. May have to find the little iPod nano (the little square one) I have floating around: but I’ll want to investigate the podcast syncing, since the other place I listen to them is in the car (via Bluetooth).
Having had the Misfit Shine for less than 24 hours, it’s still too early to discuss if it’s going to work for me. I do have some early comments though.
It’s pretty. The nice matt grey finish is unobtrusive and classy.
The magnet is nice and strong. I haven’t clipped it on anywhere yet, but it feels like it may stay attached while running. I’ve only used the watchband style fastener. I
Syncing is not automatic, even with “Automatic Sync” enabled. Indeed, I have found syncing somewhat intermittent. I’m not sure, but I think I need to tap the Shine as well as the iPhone to get it to sync. That’s a bit shit. Even if the syncing was done through a Notification Area widget, that would be an improvement. If you are only using the Shine itself for your tracking, it would not be so necessary, but since I use that data within myfitnesspal (and other HealthKit related apps), I want it updated as frequently as possible. Every minute would be great, hourly would be acceptable.
(It seems that you need to put the Shine on the phone to sync. That would explain my issues with it, but that’s even shitter than I thought. I’m not really prepared to take a device off to sync it. Hopefully this is something that improves, although I doubt it.)
Sleep tracking seems reasonable, but it should be possible to trim sleep without removing the “sleep quality” data. I was using an app-based sleep tracker, but that made my phone really, really hot. And it kept falling off the bed.
Since then, I’ve been using “Lark”, which really drives me batty with it’s cute dialog, but seems to be the only thing that will use device motion to guess when you have been sleeping (assuming you put your phone down immediately before sleep, and move it as soon as you wake). Unfortunately, I’ll need to keep that going, or manually transfer data from Misfit to HealthKit.
Step count data is pushed to HealthKit. No other HealthKit integration is present. It really needs Sleep data to be pushed; Weight and Dietary Calorie ideally should be fetched from HealthKit too. There is integration with myfitnesspal, but I’d like to be able to switch Dietary Calorie tracker if necessary in the future. I suspect that HealthKit is something that really could be improved in software, fairly easily.
The “clock” feature is kind-of cool. Except the “hour” light does not change when the time is after half-past. I kept thinking the time was quarter to nine, when it was quarter to ten. Luckily, it’s Saturday.
It’s also waterproof, which means I don’t have to take it off to give the boys a bath, which is nice.
There’s a fairly reasonable review from over a year ago at A weekend with Misfit’s Shine: this makes me suspect that the syncing may not improve much.
I’m not unhappy with the device so far. I’d really like to get more integration with HealthKit, so hopefully that will be forthcoming.