Introducing Records

Published by cce on 2012-03-15

Back in October of last year, we introduced hierarchical headers for our HTML and TEXT output, but they were implemented by convention and not broadly available to other formatters. With the forthcoming 2.3.1 release we add support for nested records to all formatters.

Background

Starting with the 2.3.1 release, we have completely rewritten our internal query output structure and corresponding formatters so that they provide support for nested records. Previously we represented query output as a pair of lists, one with headers and one with row tuples. At that time, most of our formatters followed this same tabular structure, with the exception of the HTML and TEXT formatters — they used an internal trick to present nested headers.

What motivates this update was a desire to have a more formalized mechanism to serialize records and support hierarchical headers. Early ruminations on this change can be read in an October 2011 blog entry, Discussion of JSON/CSV Headers.

A user now has explicit control over how their data is structured for output. A user could group fields together and assign decoration hints to individual fields.

The new /:raw formatter faithfully reproduces this output structure into JSON format so that visual tools could access this detail over HTTP.

Output Structure

Our output structure is a parallel hierarchy: "meta" is a nested data type declaration, while "data" has corresponding instance data. You could understand this by looking at the following trivial query:

{
  "meta": {
    "domain": {
      "type": "list",
      "item": {
        "domain": {
          "type": "integer"
        }
      }
    },
    "header": "1",
    "syntax": "\/1"
  },
  "data": [
    1
  ]
}

Since our syntax currently requires / to start every query, our top-level domain is always a list. Above, the structure is a list of integer items. In the example following, we have a list of string values instead.

{
  "meta": {
    "domain": {
      "type": "list",
      "item": {
        "domain": {
          "type": "text"
        }
      }
    },
    "header": "name",
    "path": "school.name",
    "syntax": "\/school.name",
    "tag": "name"
  },
  "data": [
    "School of Art & Design",
    "School of Business",
    "College of Education",
    "School of Engineering",
    "School of Arts and Humanities",
    "School of Music & Dance",
    "School of Natural Sciences",
    "Public Honorariums",
    "School of Continuing Studies"
  ]
}

The meta part has a "header" attribute that a formatter might use to display column headers. This header attribute can be changed using the :as decorator.

{
  "meta": {
    "domain": {
      "type": "list",
      "item": {
        "domain": {
          "type": "integer"
        }
      }
    },
    "header": "output",
    "syntax": "\/1 :as output",
    "tag": "output"
  },
  "data": [
    1
  ]
}

In this case, the header applies to the output as a whole, not to either the list nor the integer parts. Values themselves cannot be decorated in this manner. You could see this with the text formatter:

 | output |
-+--------+-
 |      1 |

Explicit Records

Nested records can now be explicitly marked using curly braces {}. For example, the next query returns a list of record having a single integer field:

Slots in a record can be decorated with presentation hints. For example, here we decorate the 1st slot in the record as "one" and the output as a whole "output":

This detail can be seen using a visual formatter:

 | output |
 +--------+
 | one    |
-+--------+-
 |      1 |

Querying a table produces a list of record items.

Nested Records

Records can be nested, providing very nice visual grouping properties. In this next example, we have a list of record with two slots. The first slot is a nested record having "name" and "campus" strings. The second slot is the department’s "name".

This can be seen using a visual formatter as:

school name
name campus
School of Business south Accounting
School of Arts and Humanities old Art History
School of Natural Sciences old Astronomy
School of Engineering north Bioengineering
Bursar’s Office

When a table link is used, an implicit record is created. For example, this request returns all columns from the department table and the entire corresponding school record.

school code name school_code
code name campus
bus School of Business south acc Accounting bus
la School of Arts and Humanities old arthis Art History la
ns School of Natural Sciences old astro Astronomy ns
eng School of Engineering north be Bioengineering eng
bursar Bursar’s Office

Notice in the text output how the nested school record is independently visualized. This query is syntax sugar for an equivalent expression using {*} to explicitly request all columns from a school record.

school code name school_code
code name campus
bus School of Business south acc Accounting bus
la School of Arts and Humanities old arthis Art History la
ns School of Natural Sciences old astro Astronomy ns
eng School of Engineering north be Bioengineering eng
bursar Bursar’s Office

Avoiding Records

Sometimes it is useful to expand a linked record’s columns inline without creating a nested record. This can be done by using the regular navigation operator:

campus name
south Accounting
old Art History
old Astronomy
north Bioengineering
Bursar’s Office

The wildcard (*) expansion when used in combination with the navigation operator (.) does not create a nested record and is instead expanded inline:

code name campus code name school_code
bus School of Business south acc Accounting bus
la School of Arts and Humanities old arthis Art History la
ns School of Natural Sciences old astro Astronomy ns
eng School of Engineering north be Bioengineering eng
bursar Bursar’s Office
blog comments powered by Disqus