Decode fetch result with single column from association

Hi,

I'm trying to fetch a single column from an association, but it fails to decode. It does decode correctly when I fetch the whole association.
My helper type:

struct ElementWithApertureData: Decodable, FetchableRecord {
    let element: Element
    let polygonData: Data?
}

And the request:

        let request = Element.including(optional: Element.aperture.select(Column("polygonData")))

The row looks like this in de debugger:

  ▿ 106 : 
    ▿ [id:107 type:4 lineNumber:780 imageId:1 layerId:1 parentId:NULL xRepeat:NULL yRepeat:NULL xStep:NULL yStep:NULL clearPolarity:0 polarityIndex:0 rotation:0.0 scale:1.0 x1:NULL x2:NULL x3:NULL y1:NULL y2:NULL y3:NULL xMirror:0 yMirror:0 apertureId:NULL width:NULL height:NULL]
  unadapted: [id:107 type:4 lineNumber:780 imageId:1 layerId:1 parentId:NULL xRepeat:NULL yRepeat:NULL xStep:NULL yStep:NULL clearPolarity:0 polarityIndex:0 rotation:0.0 scale:1.0 x1:NULL x2:NULL x3:NULL y1:NULL y2:NULL y3:NULL xMirror:0 yMirror:0 apertureId:NULL width:NULL height:NULL polygonData:NULL]
  - aperture: [polygonData:NULL]

No mater how I played with forKey() or other constructs, I can't get it to decode the single column association. I should add that my Aperture.init(for row:) has indexed subscriptions, as I have a feeling that that also messes things up here.

Is it possible to decode the single column into its own variable?

Thanks in advance.

Kind regards,

Remco Poelstra

Hello @remcopoelstra,

This becomes a frequently asked question, it's high time the documentation addresses it as clearly as possible.

My answer is in two steps:

  1. Why ElementWithApertureData does not fit the request.
  2. How to rewrite the request so that it can feed ElementWithApertureData.

The request, as written, can decode a struct like this:

struct ElementWithApertureData: Decodable, FetchableRecord {
    struct RestrictedAperture: Decodable {
        var polygonData: Data
    }
    let element: Element
    let aperture: RestrictedAperture?
}

let request = Element
    .including(optional: Element.aperture.select(Column("polygonData")))

To understand why, consider that the select method in including(association.select(...)) accepts several columns: those are naturally gathered in their own struct. The one-column case is no different.

And that's why you don't see the polygonData column in the row in the debugger. You only see it in the "unadapted" (all columns) and "aperture" scope, hidden from the row exposed to ElementWithApertureData.


But you want a single column from the association, and avoid this nested struct.

The solution is to flatten joined columns with annotated(with:):

struct ElementWithApertureData: Decodable, FetchableRecord {
    let element: Element
    let polygonData: Data?
}

let apertureAlias = TableAlias()
let request = Element
    .annotated(with: apertureAlias[Column("polygonData")])
    .joining(optional: Element.aperture.aliased(apertureAlias))

By using annotated(with:), the extra columns are made available for your top-level record.

By using a TableAlias, you can refer to tables used elsewhere in the request, and pick their columns.

By replacing including by joining, you do not append the default selected columns of Aperture to your request. The columns are selected by annotated(with:).

Thanks! Works like a charm!

1 Like