Commit e934444a authored by Dian Fay's avatar Dian Fay
Browse files

feat: decompose with compound keys

parent f05aa55f
Loading
Loading
Loading
Loading
+10 −7
Changes for lib/util/decompose.js: 10 added lines, 7 removed lines.
Original line number Diff line number Diff line
'use strict';

const _ = require('lodash');
const murmurhash = require('murmurhash').v3;

/**
 * Collapses tabular result sets into a hierarchical object graph based on the
@@ -31,6 +32,7 @@ exports = module.exports = function (schema, data) {
    return [];
  }

  schema.pk = _.castArray(schema.pk);
  data = _.castArray(data);

  /* Generate a nested dictionary of id:entity in the form of the final
@@ -41,19 +43,20 @@ exports = module.exports = function (schema, data) {
   * Output: {1: {id: 1, name: 'hi', children: {111: {id: 111, name: 'ih'}}}
   */
  const mapping = data.reduce(function (acc, row) {
    if (row[schema.pk] === null) {
    if (schema.pk.every(c => row[c] === null)) {
      throw new Error('Attempted to decompose a row where the root object has a null PK. This can happen if tables in your SELECT list share column names. Ensure that all columns are aliased uniquely and update your decomposition schema if necessary.');
    }

    return (function build (obj, objSchema) {
      const id = row[objSchema.pk];
      objSchema.pk = _.castArray(objSchema.pk);

      // Use a string id instead of the (potentially numerical) row id as keys
      // in the mapping. This prevents Object.keys from reordering the
      // mapping in the "transform" step.
      const strid = '_' + id;
      const id = _.pick(row, objSchema.pk);

      if (id === null) {
      // Add a prefix to ensure strid doesn't get treated as a number, which
      // could lead to Object.keys reordering the mapping in the transform step
      const strid = '_' + murmurhash(JSON.stringify(id));

      if (_.every(id, v => v === null)) {
        // null id means this entity doesn't exist (eg outer join)
        return undefined;
      } else if (!Object.prototype.hasOwnProperty.call(obj, strid)) {
+5 −0
Changes for package-lock.json: 5 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -2578,6 +2578,11 @@
      "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
      "dev": true
    },
    "murmurhash": {
      "version": "0.0.2",
      "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-0.0.2.tgz",
      "integrity": "sha1-bwe9ihEF5wnCb8iUIMtZMMJFhf4="
    },
    "mute-stream": {
      "version": "0.0.7",
      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+2 −1
Changes for package.json: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -52,7 +52,8 @@
    "commander": "2.20.0",
    "glob": "7.1.4",
    "lodash": "4.17.15",
    "pg-promise": "8.7.5",
    "murmurhash": "0.0.2",
    "pg-promise": "8.7.4",
    "pg-query-stream": "2.0.0"
  },
  "devDependencies": {
+33 −8
Changes for test/util/decompose.js: 33 added lines, 8 removed lines.
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ describe('decompose', function () {
    }, [
      {parent_id: 1, parent_val: 'p1', children_id: 11, children_val: 'c1'},
      {parent_id: null, parent_val: null, children_id: null, children_val: null}
    ]));
    ]), 'Attempted to decompose a row where the root object has a null PK. This can happen if tables in your SELECT list share column names. Ensure that all columns are aliased uniquely and update your decomposition schema if necessary.');
  });

  it('should collapse simple tree structures', function () {
@@ -445,8 +445,8 @@ describe('decompose', function () {
    }]);
  });

  it.skip('should accept and use pk arrays', function () {
    const data = decompose({
  it('should throw if a row is encountered where all pk fields are null', function () {
    assert.throws(() => decompose({
      pk: ['parent_id_one', 'parent_id_two'],
      columns: {parent_id_one: 'id_one', parent_id_two: 'id_two', parent_val: 'val'},
      children1: {
@@ -459,8 +459,34 @@ describe('decompose', function () {
          array: true
        }
      }
    }, [
      {
    }, [{
      parent_id_one: null,
      parent_id_two: null,
      parent_val: 'p1',
      children1_id_one: 11,
      children1_id_two: 12,
      children1_val: 'c1',
      children1_children2_id_one: 21,
      children1_children2_id_two: 22,
      children1_children2_val: 'd1'
    }]), 'Attempted to decompose a row where the root object has a null PK. This can happen if tables in your SELECT list share column names. Ensure that all columns are aliased uniquely and update your decomposition schema if necessary.');
  });

  it('should accept and use pk arrays', function () {
    const data = decompose({
      pk: ['parent_id_one', 'parent_id_two'],
      columns: {parent_id_one: 'id_one', parent_id_two: 'id_two', parent_val: 'val'},
      children1: {
        pk: ['children1_id_one', 'children1_id_two'],
        columns: {children1_id_one: 'id_one', children1_id_two: 'id_two', children1_val: 'val'},
        array: true,
        children2: {
          pk: ['children1_children2_id_one', 'children1_children2_id_two'],
          columns: {children1_children2_id_one: 'id_one', children1_children2_id_two: 'id_two', children1_children2_val: 'val'},
          array: true
        }
      }
    }, [{
      parent_id_one: 1,
      parent_id_two: 2,
      parent_val: 'p1',
@@ -500,8 +526,7 @@ describe('decompose', function () {
      children1_children2_id_one: 27,
      children1_children2_id_two: 28,
      children1_children2_val: 'd4'
      }
    ]);
    }]);

    assert.deepEqual(data, [{
      id_one: 1,
@@ -529,7 +554,7 @@ describe('decompose', function () {
        id_one: 15,
        id_two: 16,
        val: 'c3',
        children2: [{id_one: 26, id_two: 28, val: 'd4'}]
        children2: [{id_one: 27, id_two: 28, val: 'd4'}]
      }]
    }]);
  });