<commit_before>﻿/**
    Featherbone is a JavaScript based persistence framework for building object relational database applications
    
    Copyright (C) 2015  <NAME>
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

create or replace function load_fp() returns void as $$
/*global plv8: true, jsonpatch: true, featherbone: true, NOTICE: true, ERROR: true */
/*jslint nomen: true, plusplus: true, indent: 2, sloppy: true, todo: true, maxlen: 80*/
(function () {

  var _createView, _curry, _getKey, _getKeys, _isChildClass, _patch, _delete,
    _propagateViews, _relationColumn, _sanitize, _insert, _select, _update,
    _debug,
    _settings = {},
    _types = {
      object: {type: "json", defaultValue: {}},
      array: {type: "json", defaultValue: []},
      string: {type: "text", defaultValue: "''"},
      number: {type: "numeric", defaultValue: 0},
      date: {type: "timestamp with time zone", defaultValue: "minDate()"},
      boolean: {type: "boolean", defaultValue: "false"}
    };

  featherbone = {

    /**
      Return a unique identifier string.

      Moddified from https://github.com/google/closure-library
      @author <EMAIL> (<NAME>)
      http://www.apache.org/licenses/LICENSE-2.0

      @return {String}
    */
    createId: function () {
      var x = 2147483648,
        d = new Date(),
        result = Math.floor(Math.random() * x).toString(36) +
          Math.abs(Math.floor(Math.random() * x) ^ d).toString(36);

      return _getKey(result) ? featherbone.createId() : result;
    },

    /**
      Check to see if an etag is current.

      * @param {String} Object id
      * @param {String} Object etag
      * @return {String}
    */

    checkEtag: function (name, id, etag) {
      var sql = "SELECT etag FROM %I WHERE id = $1"
          .format([name.toSnakeCase()]),
        result = plv8.execute(sql, [id]);

      return result.length ? result[0].etag === etag : false;
    },

    /**
      Remove a class from the database.

      * @param {Object | Array} Object(s) describing object to remove.
      * @return {String}
    */
    deleteClass: function (specs) {
      specs = Array.isArray ? specs : [specs];

      var obj, table, catalog, sql, rels, i, props, view, type, key,
        o = 0;

      while (o < specs.length) {
        obj = specs[o];

        table = obj.name ? obj.name.toSnakeCase() : false;
        catalog = featherbone.getSettings('catalog');
        sql = "DROP VIEW %I; DROP TABLE %I;"
          .format(["_" + table, table]);
        rels = [];
        i = 0;

        if (!table || !catalog[obj.name]) {
          plv8.elog(ERROR, 'Class not found');
        }

        /* Drop views for composite types */
        props = catalog[obj.name].properties;
        for (key in props) {
          if (props.hasOwnProperty(key) &&
              typeof props[key].type === "object") {
            type = props[key].type;

            if (type.properties) {
              view = "_" + obj.name.toSnakeCase() + "$" + key.toSnakeCase();
              sql += "DROP VIEW %I;".format([view]);
            }

            if (type.childOf && catalog[type.relation]) {
              delete catalog[type.relation].properties[type.childOf];
              rels.push(type.relation);
            }
          }
        }

        /* Update catalog settings */
        delete catalog[obj.name];
        featherbone.saveSettings("catalog", catalog);

        /* Update views */
        while (i < rels.length) {
          _createView(rels[i], true);
          i++;
        }

        /* Drop table(s) */
        plv8.execute(sql);

        o++;
      }

      return true;
    },

    /**
      Return a class definition, including inherited properties.

      @param {String} Class name
      @param {Boolean} Include inherited or not. Defult = true.
      @return {String}
    */
    getClass: function (name, includeInherited) {
      var catalog = featherbone.getSettings('catalog'),
        appendParent = function (child, parent) {
          var klass = catalog[parent],
            klassProps = klass.properties,
            childProps = child.properties,
            key;

          if (parent !== "Object") {
            appendParent(child, klass.inherits || "Object");
          }

          for (key in klassProps) {
            if (klassProps.hasOwnProperty(key)) {
              if (childProps[key] === undefined) {
                childProps[key] = klassProps[key];
                childProps[key].inheritedFrom = parent;
              }
            }
          }

          return child;
        },
        result = {name: name, inherits: "Object"},
        resultProps,
        klassProps,
        key;

      if (!catalog[name]) { return false; }

      /* Add other attributes after name */
      for (key in catalog[name]) {
        if (catalog[name].hasOwnProperty(key)) {
          result[key] = catalog[name][key];
        }
      }

      /* Want inherited properites before class properties */
      if (includeInherited !== false && name !== "Object") {
        result.properties = {};
        result = appendParent(result, result.inherits);
      } else {
        delete result.inherits;
      }

      /* Now add local properties back in */
      klassProps = catalog[name].properties;
      resultProps = result.properties;
      for (key in klassProps) {
        if (klassProps.hasOwnProperty(key)) {
          resultProps[key] = klassProps[key];
        }
      }

      return result;
    },

    /**
      Return the current user.

      @return {String}
    */
    getCurrentUser: function () {
      return plv8.execute("SELECT CURRENT_USER AS user;")[0].user;
    },

    /**
      Return a date that is the lowest system date.

      @return {Date}
    */
    minDate: function () {
      return new Date(0);
    },

    /**
      Return a date that is the highest system date.

      @return {Date}
    */
    maxDate: function () {
      return new Date("2100-01-01T00:00:00.000Z");
    },


    /**
      Return a date that is the current time.

      @return {Date}
    */
    now: function () {
      return new Date();
    },

    /**
      Return settings.

      @param {String} Setting name
      @return {Object}
    */
    getSettings: function (name) {
      var result, rec, id, etag,
        sql = "SELECT data FROM \"$settings\" WHERE name = $1";

      if (_settings[name]) {
        id = _settings[name].id;
        etag = _settings[name].etag;
        if (featherbone.checkEtag("$settings", id, etag)) {
          return _settings[name];
        }
      }

      result = plv8.execute(sql, [name]);
      if (result.length) {
        rec = result[0];
        _settings[name] = {
          id: rec.id,
          etag: rec.etag,
          data: rec.data
        };
      }

      return _settings[name].data;
    },

    /**
      Request.

      Example payload:
          {
             "name": "Contact",
             "action": "POST",
             "data": {
               "id": "1f8c8akkptfe",
               "created": "2015-04-26T12:57:57.896Z",
               "createdBy": "admin",
               "updated": "2015-04-26T12:57:57.896Z",
               "updatedBy": "admin",
               "fullName": "<NAME>",
               "birthDate": "1970-01-01T00:00:00.000Z",
               "isMarried": true,
               "dependentes": 2
             }
          }

      @param {Object} Payload
      @return {Object | Array}
    */
    request: function (obj) {
      var prop = obj.name,
        result = {},
        args,
        fn;

      switch (obj.action) {
      case "GET":
        return _select(obj);
      case "POST":
        /* Handle if posting a function call */
        if (featherbone[prop] && typeof featherbone[prop] === "function") {
          args = Array.isArray(obj.data) ? obj.data : [obj.data];
          fn = _curry(featherbone[prop], args);
          result.value = fn();
          return result;
        }

        return _insert(obj);
      case "PATCH":
        return _patch(obj);
      case "DELETE":
        return _delete(obj);
      }
    },

    /**
      Create or update a persistence class. This function is idempotent. 
      Subsequent saves will automatically drop properties no longer present.

      Example payload:
       {
         "name": "Contact",
         "description": "Contact data about a person",
         "properties": {
           "fullName": {
             "description": "<NAME>",
             "type": "string"
          },
          "birthDate": {
            "description": "Birth date",
            "type": "date"
          },
          "isMarried": {
            "description": "Marriage status",
            "type": "boolean"
          },
          "dependents": {
            "description": "Number of dependents",
            "type": "number"
          }
        }
      }

     * @param {Object | Array} Class specification payload(s).
     * @return {Boolean}
    */
    saveClass: function (specs) {
      specs = Array.isArray(specs) ? specs : [specs];

      var table, inherits, klass, catalog, sql, sqlUpd, token, tokens, values,
        adds, args, fns, cols, defaultValue, props, key, recs, type, err, name,
        parent, obj, i, n, p, dropSql, changed,
        o = 0;

      while (o < specs.length) {
        obj = specs[o];
        table = obj.name ? obj.name.toSnakeCase() : false;
        inherits = (obj.inherits || "Object").toSnakeCase();
        klass = featherbone.getClass(obj.name, false);
        catalog = featherbone.getSettings("catalog");
        dropSql = "DROP VIEW IF EXISTS %I CASCADE;".format(["_" + table]);
        changed = false;
        sql = "";
        tokens = [];
        values = [];
        adds = [];
        args = [];
        fns = [];
        cols = [];
        i = 0;
        n = 0;
        p = 1;

        if (!table) { plv8.elog(ERROR, "No name defined"); }

        /* Create table if applicable */
        if (!klass) {
          sql = "CREATE TABLE %I( " +
            "CONSTRAINT %I PRIMARY KEY (_pk), " +
            "CONSTRAINT %I UNIQUE (id)) " +
            "INHERITS (%I);";
          tokens = tokens.concat([
            table,
            table + "_pkey",
            table + "_id_key",
            inherits
          ]);
        } else {
          /* Drop non-inherited columns not included in properties */
          props = klass.properties;
          for (key in props) {
            if (props.hasOwnProperty(key)) {
              if (obj.properties && !obj.properties[key] &&
                  !(typeof klass.properties[key].type === "object" &&
                  typeof klass.properties[key].type.parentOf)) {
                /* Drop views */
                if (!changed) {
                  sql += dropSql;
                  changed = true;
                }

                /* Handle relations */
                type = props[key].type;

                if (typeof type === "object" && type.properties) {
                  /* Drop associated view if applicable */
                  sql += "DROP VIEW %I;";
                  tokens = tokens.concat([
                    "_" + table + "_" + key.toSnakeCase(),
                    table,
                    _relationColumn(key, type.relation)
                  ]);
                } else {
                  tokens = tokens.concat([table, key.toSnakeCase()]);
                }

                sql += "ALTER TABLE %I DROP COLUMN %I;";

                /* Unrelate parent if applicable */
                if (type.childOf) {
                  parent = catalog[type.relation];
                  delete parent.properties[type.childOf];
                }
              }
            }
          }
        }

        /* Add table description */
        if (obj.description) {
          sql += "COMMENT ON TABLE %I IS %L;";
          tokens = tokens.concat([table, obj.description || ""]);
        }

        /* Add columns */
        obj.properties = obj.properties || {};
        props = obj.properties;
        for (key in props) {
          if (props.hasOwnProperty(key)) {
            type = typeof props[key].type === "string" ?
                _types[props[key].type] : props[key].type;

            if (type) {
              if (!klass || !klass.properties[key]) {
                /* Drop views */
                if (klass && !changed) {
                  sql += dropSql;
                }

                changed = true;

                sql += "ALTER TABLE %I ADD COLUMN %I ";

                /* Handle composite types */
                if (type.relation) {
                  sql += "integer;";
                  token = _relationColumn(key, type.relation);

                  /* Update parent class for children */
                  if (type.childOf) {
                    parent = catalog[type.relation];
                    if (!parent.properties[type.childOf]) {
                      parent.properties[type.childOf] = {
                        description: 'Parent of "' + key + '" on "' +
                          obj.name + '"',
                        type: {
                          relation: obj.name,
                          parentOf: key
                        }
                      };

                    } else {
                      err = 'Property "' + type.childOf +
                        '" already exists on "' + type.relation + '"';
                      plv8.elog(ERROR, err);
                    }

                  } else if (type.parentOf) {
                    err = 'Can not set parent directly for "' + key + '"';
                    plv8.elog(ERROR, err);

                  } else if (type.properties) {
                    cols = ["%I"];
                    name = "_" + table + "$" + key.toSnakeCase();
                    args = [name, "_pk"];

                    /* Always include "id" whether specified or not */
                    if (type.properties.indexOf("id") === -1) {
                      type.properties.unshift("id");
                    }

                    while (i < type.properties.length) {
                      cols.push("%I");
                      args.push(type.properties[i].toSnakeCase());
                      i++;
                    }

                    args.push(type.relation.toSnakeCase());
                    sql += ("CREATE VIEW %I AS SELECT " + cols.join(",") +
                      " FROM %I;").format(args);
                  }

                /* Handle standard types */
                } else {
                  sql += type.type + ";";
                  token = key.toSnakeCase();
                }
                adds.push(key);

                tokens = tokens.concat([table, token]);

                if (props[key].description) {
                  sql += "COMMENT ON COLUMN %I.%I IS %L;";
                  tokens = tokens.concat([
                    table,
                    token,
                    props[key].description || ""
                  ]);
                }
              }
            } else {
              err = 'Invalid type "' + props[key].type + '" for property "' +
                  key + '" on class "' + obj.name + '"';
              plv8.elog(ERROR, err);
            }
          }
        }<commit_msg>No deleted relations<commit_after>﻿/**
    Featherbone is a JavaScript based persistence framework for building object relational database applications
    
    Copyright (C) 2015  <NAME>
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

create or replace function load_fp() returns void as $$
/*global plv8: true, jsonpatch: true, featherbone: true, NOTICE: true, ERROR: true */
/*jslint nomen: true, plusplus: true, indent: 2, sloppy: true, todo: true, maxlen: 80*/
(function () {

  var _createView, _curry, _getKey, _getKeys, _isChildClass, _patch, _delete,
    _propagateViews, _relationColumn, _sanitize, _insert, _select, _update,
    _debug,
    _settings = {},
    _types = {
      object: {type: "json", defaultValue: {}},
      array: {type: "json", defaultValue: []},
      string: {type: "text", defaultValue: "''"},
      number: {type: "numeric", defaultValue: 0},
      date: {type: "timestamp with time zone", defaultValue: "minDate()"},
      boolean: {type: "boolean", defaultValue: "false"}
    };

  featherbone = {

    /**
      Return a unique identifier string.

      Moddified from https://github.com/google/closure-library
      @author <EMAIL> (<NAME>)
      http://www.apache.org/licenses/LICENSE-2.0

      @return {String}
    */
    createId: function () {
      var x = 2147483648,
        d = new Date(),
        result = Math.floor(Math.random() * x).toString(36) +
          Math.abs(Math.floor(Math.random() * x) ^ d).toString(36);

      return _getKey(result) ? featherbone.createId() : result;
    },

    /**
      Check to see if an etag is current.

      * @param {String} Object id
      * @param {String} Object etag
      * @return {String}
    */

    checkEtag: function (name, id, etag) {
      var sql = "SELECT etag FROM %I WHERE id = $1"
          .format([name.toSnakeCase()]),
        result = plv8.execute(sql, [id]);

      return result.length ? result[0].etag === etag : false;
    },

    /**
      Remove a class from the database.

      * @param {Object | Array} Object(s) describing object to remove.
      * @return {String}
    */
    deleteClass: function (specs) {
      specs = Array.isArray ? specs : [specs];

      var obj, table, catalog, sql, rels, i, props, view, type, key,
        o = 0;

      while (o < specs.length) {
        obj = specs[o];

        table = obj.name ? obj.name.toSnakeCase() : false;
        catalog = featherbone.getSettings('catalog');
        sql = "DROP VIEW %I; DROP TABLE %I;"
          .format(["_" + table, table]);
        rels = [];
        i = 0;

        if (!table || !catalog[obj.name]) {
          plv8.elog(ERROR, 'Class not found');
        }

        /* Drop views for composite types */
        props = catalog[obj.name].properties;
        for (key in props) {
          if (props.hasOwnProperty(key) &&
              typeof props[key].type === "object") {
            type = props[key].type;

            if (type.properties) {
              view = "_" + obj.name.toSnakeCase() + "$" + key.toSnakeCase();
              sql += "DROP VIEW %I;".format([view]);
            }

            if (type.childOf && catalog[type.relation]) {
              delete catalog[type.relation].properties[type.childOf];
              rels.push(type.relation);
            }
          }
        }

        /* Update catalog settings */
        delete catalog[obj.name];
        featherbone.saveSettings("catalog", catalog);

        /* Update views */
        while (i < rels.length) {
          _createView(rels[i], true);
          i++;
        }

        /* Drop table(s) */
        plv8.execute(sql);

        o++;
      }

      return true;
    },

    /**
      Return a class definition, including inherited properties.

      @param {String} Class name
      @param {Boolean} Include inherited or not. Defult = true.
      @return {String}
    */
    getClass: function (name, includeInherited) {
      var catalog = featherbone.getSettings('catalog'),
        appendParent = function (child, parent) {
          var klass = catalog[parent],
            klassProps = klass.properties,
            childProps = child.properties,
            key;

          if (parent !== "Object") {
            appendParent(child, klass.inherits || "Object");
          }

          for (key in klassProps) {
            if (klassProps.hasOwnProperty(key)) {
              if (childProps[key] === undefined) {
                childProps[key] = klassProps[key];
                childProps[key].inheritedFrom = parent;
              }
            }
          }

          return child;
        },
        result = {name: name, inherits: "Object"},
        resultProps,
        klassProps,
        key;

      if (!catalog[name]) { return false; }

      /* Add other attributes after name */
      for (key in catalog[name]) {
        if (catalog[name].hasOwnProperty(key)) {
          result[key] = catalog[name][key];
        }
      }

      /* Want inherited properites before class properties */
      if (includeInherited !== false && name !== "Object") {
        result.properties = {};
        result = appendParent(result, result.inherits);
      } else {
        delete result.inherits;
      }

      /* Now add local properties back in */
      klassProps = catalog[name].properties;
      resultProps = result.properties;
      for (key in klassProps) {
        if (klassProps.hasOwnProperty(key)) {
          resultProps[key] = klassProps[key];
        }
      }

      return result;
    },

    /**
      Return the current user.

      @return {String}
    */
    getCurrentUser: function () {
      return plv8.execute("SELECT CURRENT_USER AS user;")[0].user;
    },

    /**
      Return a date that is the lowest system date.

      @return {Date}
    */
    minDate: function () {
      return new Date(0);
    },

    /**
      Return a date that is the highest system date.

      @return {Date}
    */
    maxDate: function () {
      return new Date("2100-01-01T00:00:00.000Z");
    },


    /**
      Return a date that is the current time.

      @return {Date}
    */
    now: function () {
      return new Date();
    },

    /**
      Return settings.

      @param {String} Setting name
      @return {Object}
    */
    getSettings: function (name) {
      var result, rec, id, etag,
        sql = "SELECT data FROM \"$settings\" WHERE name = $1";

      if (_settings[name]) {
        id = _settings[name].id;
        etag = _settings[name].etag;
        if (featherbone.checkEtag("$settings", id, etag)) {
          return _settings[name];
        }
      }

      result = plv8.execute(sql, [name]);
      if (result.length) {
        rec = result[0];
        _settings[name] = {
          id: rec.id,
          etag: rec.etag,
          data: rec.data
        };
      }

      return _settings[name].data;
    },

    /**
      Request.

      Example payload:
          {
             "name": "Contact",
             "action": "POST",
             "data": {
               "id": "1f8c8akkptfe",
               "created": "2015-04-26T12:57:57.896Z",
               "createdBy": "admin",
               "updated": "2015-04-26T12:57:57.896Z",
               "updatedBy": "admin",
               "fullName": "<NAME>",
               "birthDate": "1970-01-01T00:00:00.000Z",
               "isMarried": true,
               "dependentes": 2
             }
          }

      @param {Object} Payload
      @return {Object | Array}
    */
    request: function (obj) {
      var prop = obj.name,
        result = {},
        args,
        fn;

      switch (obj.action) {
      case "GET":
        return _select(obj);
      case "POST":
        /* Handle if posting a function call */
        if (featherbone[prop] && typeof featherbone[prop] === "function") {
          args = Array.isArray(obj.data) ? obj.data : [obj.data];
          fn = _curry(featherbone[prop], args);
          result.value = fn();
          return result;
        }

        return _insert(obj);
      case "PATCH":
        return _patch(obj);
      case "DELETE":
        return _delete(obj);
      }
    },

    /**
      Create or update a persistence class. This function is idempotent. 
      Subsequent saves will automatically drop properties no longer present.

      Example payload:
       {
         "name": "Contact",
         "description": "Contact data about a person",
         "properties": {
           "fullName": {
             "description": "Full name",
             "type": "string"
          },
          "birthDate": {
            "description": "Birth date",
            "type": "date"
          },
          "isMarried": {
            "description": "Marriage status",
            "type": "boolean"
          },
          "dependents": {
            "description": "Number of dependents",
            "type": "number"
          }
        }
      }

     * @param {Object | Array} Class specification payload(s).
     * @return {Boolean}
    */
    saveClass: function (specs) {
      specs = Array.isArray(specs) ? specs : [specs];

      var table, inherits, klass, catalog, sql, sqlUpd, token, tokens, values,
        adds, args, fns, cols, defaultValue, props, key, recs, type, err, name,
        parent, obj, i, n, p, dropSql, changed,
        o = 0;

      while (o < specs.length) {
        obj = specs[o];
        table = obj.name ? obj.name.toSnakeCase() : false;
        inherits = (obj.inherits || "Object").toSnakeCase();
        klass = featherbone.getClass(obj.name, false);
        catalog = featherbone.getSettings("catalog");
        dropSql = "DROP VIEW IF EXISTS %I CASCADE;".format(["_" + table]);
        changed = false;
        sql = "";
        tokens = [];
        values = [];
        adds = [];
        args = [];
        fns = [];
        cols = [];
        i = 0;
        n = 0;
        p = 1;

        if (!table) { plv8.elog(ERROR, "No name defined"); }

        /* Create table if applicable */
        if (!klass) {
          sql = "CREATE TABLE %I( " +
            "CONSTRAINT %I PRIMARY KEY (_pk), " +
            "CONSTRAINT %I UNIQUE (id)) " +
            "INHERITS (%I);";
          tokens = tokens.concat([
            table,
            table + "_pkey",
            table + "_id_key",
            inherits
          ]);
        } else {
          /* Drop non-inherited columns not included in properties */
          props = klass.properties;
          for (key in props) {
            if (props.hasOwnProperty(key)) {
              if (obj.properties && !obj.properties[key] &&
                  !(typeof klass.properties[key].type === "object" &&
                  typeof klass.properties[key].type.parentOf)) {
                /* Drop views */
                if (!changed) {
                  sql += dropSql;
                  changed = true;
                }

                /* Handle relations */
                type = props[key].type;

                if (typeof type === "object" && type.properties) {
                  /* Drop associated view if applicable */
                  sql += "DROP VIEW %I;";
                  tokens = tokens.concat([
                    "_" + table + "_" + key.toSnakeCase(),
                    table,
                    _relationColumn(key, type.relation)
                  ]);
                } else {
                  tokens = tokens.concat([table, key.toSnakeCase()]);
                }

                sql += "ALTER TABLE %I DROP COLUMN %I;";

                /* Unrelate parent if applicable */
                if (type.childOf) {
                  parent = catalog[type.relation];
                  delete parent.properties[type.childOf];
                }
              }
            }
          }
        }

        /* Add table description */
        if (obj.description) {
          sql += "COMMENT ON TABLE %I IS %L;";
          tokens = tokens.concat([table, obj.description || ""]);
        }

        /* Add columns */
        obj.properties = obj.properties || {};
        props = obj.properties;
        for (key in props) {
          if (props.hasOwnProperty(key)) {
            type = typeof props[key].type === "string" ?
                _types[props[key].type] : props[key].type;

            if (type) {
              if (!klass || !klass.properties[key]) {
                /* Drop views */
                if (klass && !changed) {
                  sql += dropSql;
                }

                changed = true;

                sql += "ALTER TABLE %I ADD COLUMN %I ";

                /* Handle composite types */
                if (type.relation) {
                  sql += "integer;";
                  token = _relationColumn(key, type.relation);

                  /* Update parent class for children */
                  if (type.childOf) {
                    parent = catalog[type.relation];
                    if (!parent.properties[type.childOf]) {
                      parent.properties[type.childOf] = {
                        description: 'Parent of "' + key + '" on "' +
                          obj.name + '"',
                        type: {
                          relation: obj.name,
                          parentOf: key
                        }
                      };

                    } else {
                      err = 'Property "' + type.childOf +
                        '" already exists on "' + type.relation + '"';
                      plv8.elog(ERROR, err);
                    }

                  } else if (type.parentOf) {
                    err = 'Can not set parent directly for "' + key + '"';
                    plv8.elog(ERROR, err);

                  } else if (type.properties) {
                    cols = ["%I"];
                    name = "_" + table + "$" + key.toSnakeCase();
                    args = [name, "_pk"];

                    /* Always include "id" whether specified or not */
                    if (type.properties.indexOf("id") === -1) {
                      type.properties.unshift("id");
                    }

                    while (i < type.properties.length) {
                      cols.push("%I");
                      args.push(type.properties[i].toSnakeCase());
                      i++;
                    }

                    args.push(type.relation.toSnakeCase());
                    sql += ("CREATE VIEW %I AS SELECT " + cols.join(",") +
                      " FROM %I WHERE NOT is_deleted;").format(args);
                  }

                /* Handle standard types */
                } else {
                  sql += type.type + ";";
                  token = key.toSnakeCase();
                }
                adds.push(key);

                tokens = tokens.concat([table, token]);

                if (props[key].description) {
                  sql += "COMMENT ON COLUMN %I.%I IS %L;";
                  tokens = tokens.concat([
                    table,
                    token,
                    props[key].description || ""
                  ]);
                }
              }
            } else {
              err = 'Invalid type "' + props[key].type + '" for property "' +
                  key + '" on class "' + obj.name + '"';
              plv8.elog(ERROR, err);
            }
          }
        }
{'fraction_non_alphanumeric': 0.12957533339638702, 'fraction_numerical': 0.0075664428260663955, 'mean_word_length': 1.4564392472701928, 'pattern_counts': {'":': 62, '<': 22, '<?xml version=': 0, '>': 14, 'https://': 2, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '562973', 'n_tokens_mistral': 9021, 'n_tokens_neox': 8550, 'n_words': 2302}