replace all occurrences of changed column name
[xtuple] / lib / orm / source / xt / javascript / data.sql
index 4c7bd92..ada5e1b 100644 (file)
@@ -33,7 +33,7 @@ select xt.install_js('XT','Data','xtuple', $$
      * @param {Array} Parameters - optional
      * @returns {Object}
      */
-    buildClauseOptimized: function (nameSpace, type, parameters, orderBy) {
+    buildClause: function (nameSpace, type, parameters, orderBy) {
       parameters = parameters || [];
 
       var that = this,
@@ -102,16 +102,48 @@ select xt.install_js('XT','Data','xtuple', $$
           attributes = attributeIsString ? [parameter.attribute] : parameter.attribute;
 
         attributes.map(function (attribute) {
-          var prop = XT.Orm.getProperty(orm, attribute),
+          var rootAttribute = (attribute.indexOf('.') < 0) ? attribute : attribute.split(".")[0],
+            prop = XT.Orm.getProperty(orm, rootAttribute),
             propName = prop.name,
             childOrm,
             naturalKey,
-            index;
+            index,
+            walkPath = function (pathParts, currentOrm, pathIndex) {
+              var currentAttributeIsString = typeof pathParts[pathIndex] === 'string',
+                currentProp = XT.Orm.getProperty(currentOrm, pathParts[pathIndex]),
+                subChildOrm,
+                naturalKey;
+
+              if ((currentProp.toOne || currentProp.toMany)) {
+                if (currentProp.toOne && currentProp.toOne.type) {
+                  subChildOrm = that.fetchOrm(nameSpace, currentProp.toOne.type);
+                } else if (currentProp.toMany && currentProp.toMany.type) {
+                  subChildOrm = that.fetchOrm(nameSpace, currentProp.toMany.type);
+                } else {
+                  plv8.elog(ERROR, "toOne or toMany property is missing it's 'type': " + currentProp.name);
+                }
 
-          if ((prop.toOne || prop.toMany) && attribute.indexOf('.') < 0) {
+                if (pathIndex < pathParts.length - 1) {
+                  /* Recurse. */
+                  walkPath(pathParts, subChildOrm, pathIndex + 1);
+                } else {
+                  /* This is the end of the path. */
+                  naturalKey = XT.Orm.naturalKey(subChildOrm);
+                  if (currentAttributeIsString) {
+                    /* add the natural key to the end of the requested attribute */
+                    parameter.attribute = attribute + "." + naturalKey;
+                  } else {
+                    /* swap out the attribute in the array for the one with the prepended natural key */
+                    index = parameter.attribute.indexOf(attribute);
+                    parameter.attribute.splice(index, 1);
+                    parameter.attribute.splice(index, 0, attribute + "."  + naturalKey);
+                  }
+                }
+              }
+            }
+
+          if ((prop.toOne || prop.toMany)) {
             /* Someone is querying on a toOne without using a path */
-            /* TODO: even if there's a path x.y, it's possible that it's still not
-              correct because the correct path maybe is x.y.naturalKeyOfY */
             if (prop.toOne && prop.toOne.type) {
               childOrm = that.fetchOrm(nameSpace, prop.toOne.type);
             } else if (prop.toMany && prop.toMany.type) {
@@ -119,15 +151,22 @@ select xt.install_js('XT','Data','xtuple', $$
             } else {
               plv8.elog(ERROR, "toOne or toMany property is missing it's 'type': " + prop.name);
             }
-            naturalKey = XT.Orm.naturalKey(childOrm);
-            if (attributeIsString) {
-              /* add the natural key to the end of the requested attribute */
-              parameter.attribute = attribute + "." + naturalKey;
+
+            if (attribute.indexOf('.') < 0) {
+              naturalKey = XT.Orm.naturalKey(childOrm);
+              if (attributeIsString) {
+                /* add the natural key to the end of the requested attribute */
+                parameter.attribute = attribute + "." + naturalKey;
+              } else {
+                /* swap out the attribute in the array for the one with the prepended natural key */
+                index = parameter.attribute.indexOf(attribute);
+                parameter.attribute.splice(index, 1);
+                parameter.attribute.splice(index, 0, attribute + "."  + naturalKey);
+              }
             } else {
-              /* swap out the attribute in the array for the one with the prepended natural key */
-              index = parameter.attribute.indexOf(attribute);
-              parameter.attribute.splice(index, 1);
-              parameter.attribute.push(attribute + "." + naturalKey);
+              /* Even if there's a path x.y, it's possible that it's still not
+                correct because the correct path maybe is x.y.naturalKeyOfY */
+              walkPath(attribute.split("."), orm, 0);
             }
           }
         });
@@ -276,8 +315,8 @@ select xt.install_js('XT','Data','xtuple', $$
                 plv8.elog(ERROR, 'Attribute not found in object map: ' + param.attribute);
               }
 
-              identifiers.push(pertinentExtension.isChild || pertinentExtension.isExtension ? 
-                "jt" + (joins.length - 1) : 
+              identifiers.push(pertinentExtension.isChild || pertinentExtension.isExtension ?
+                "jt" + (joins.length - 1) :
                 "t1");
               identifiers.push(prop.attr.column);
               pgType = this.getPgTypeFromOrmType(
@@ -348,6 +387,20 @@ select xt.install_js('XT','Data','xtuple', $$
                       childOrm = this.fetchOrm(nameSpace, prop.toOne.type);
                     }
                   } else {
+                    pertinentExtension = XT.Orm.getProperty(childOrm, parts[n], true);
+                    var isExtension = pertinentExtension.isChild || pertinentExtension.isExtension;
+                    if(isExtension) {
+                      /* We'll need to join this orm extension */
+                      fromKeyProp = XT.Orm.getProperty(orm, pertinentExtension.relations[0].inverse);
+                      joinIdentifiers.push(
+                        this.getNamespaceFromNamespacedTable(pertinentExtension.table),
+                        this.getTableFromNamespacedTable(pertinentExtension.table),
+                        fromKeyProp.attr.column,
+                        pertinentExtension.relations[0].column);
+                      joins.push("left join %" + (joinIdentifiers.length - 3) + "$I.%" + (joinIdentifiers.length - 2)
+                        + "$I jt" + joins.length + " on t1.%"
+                        + (joinIdentifiers.length - 1) + "$I = jt" + joins.length + ".%" + joinIdentifiers.length + "$I");
+                    }
                     /* Build path, e.g. table_name.column_name */
                     if (n === parts.length - 1) {
                       identifiers.push("jt" + (joins.length - 1));
@@ -357,7 +410,7 @@ select xt.install_js('XT','Data','xtuple', $$
                         params[pcount] = "lower(" + params[pcount] + ")";
                       }
                     } else {
-                      sourceTableAlias = n === 0 ? "t1" : "jt" + (joins.length - 1);
+                      sourceTableAlias = n === 0 && !isExtension ? "t1" : "jt" + (joins.length - 1);
                       if (prop.toOne && prop.toOne.type) {
                         childOrm = this.fetchOrm(nameSpace, prop.toOne.type);
                         joinIdentifiers.push(
@@ -402,8 +455,8 @@ select xt.install_js('XT','Data','xtuple', $$
                   plv8.elog(ERROR, 'Attribute not found in object map: ' + param.attribute[c]);
                 }
 
-                identifiers.push(pertinentExtension.isChild || pertinentExtension.isExtension ? 
-                  "jt" + (joins.length - 1) : 
+                identifiers.push(pertinentExtension.isChild || pertinentExtension.isExtension ?
+                  "jt" + (joins.length - 1) :
                   "t1");
                 identifiers.push(prop.attr.column);
 
@@ -514,7 +567,7 @@ select xt.install_js('XT','Data','xtuple', $$
             /*
               We might need to look at toOne if the client is asking for a toOne without specifying
               the path. Unfortunately, if they do specify the path, then sql2 will fail. So this does
-              work, although we're really sorting by the primary key of the toOne, whereas the 
+              work, although we're really sorting by the primary key of the toOne, whereas the
               user probably wants us to sort by the natural key TODO
             */
             orderByColumnIdentifiers.push(prop.attr ? prop.attr.column : prop.toOne.column);
@@ -1674,6 +1727,31 @@ select xt.install_js('XT','Data','xtuple', $$
       return ret;
     },
 
+    /**
+     * Get the current database server version.
+     * If the optional precision argument is passed, return the first prec
+     * fields of the full version number.
+     *
+     * @example
+     * var x   = getPgVersion(1),       // '9'
+     *     xy  = getPgVersion(2),       // '9.1'
+     *     xyz = getPgVersion(3),       // '9.1.3'
+     *     all = getPgVersion();        // '9.1.3'
+     *
+     * @param   {Number} proc - optional precision
+     * @returns {String} X[.Y[.Z]]
+     */
+    getPgVersion: function (prec) {
+      var q = plv8.execute("select setting from pg_settings " +
+                           "where name='server_version';"),
+          ret;
+      ret = q[0].setting;
+      if (typeof prec === 'number') {
+        ret = ret.split(".").slice(0,prec).join(".");
+      }
+      return ret;
+    },
+
     /**
      * Get the oid for a given table name.
      *
@@ -1863,7 +1941,7 @@ select xt.install_js('XT','Data','xtuple', $$
         table,
         tableNamespace,
         parameters = query.parameters,
-        clause = this.buildClauseOptimized(nameSpace, type, parameters, orderBy),
+        clause = this.buildClause(nameSpace, type, parameters, orderBy),
         i,
         pkey = XT.Orm.primaryKey(orm),
         pkeyColumn = XT.Orm.primaryKey(orm, true),
@@ -1907,6 +1985,13 @@ select xt.install_js('XT','Data','xtuple', $$
         return ret;
       }
 
+      /* Because we query views of views, you can get inconsistent results */
+      /* when doing limit and offest queries without an order by. Add a default. */
+      if (limit && offset && (!orderBy || !orderBy.length) && !clause.orderByColumns) {
+        /* We only want this on sql1, not sql2's clause.orderBy. */
+        clause.orderByColumns = XT.format('order by t1.%1$I', [pkeyColumn]);
+      }
+
       /* Query the model. */
       sql1 = XT.format(sql1, [tableNamespace.decamelize(), table.decamelize(), pkeyColumn]);
       sql1 = sql1.replace('{joins}', clause.joins)
@@ -2206,6 +2291,7 @@ select xt.install_js('XT','Data','xtuple', $$
           if (printFormat && !prop.toOne && !prop.toMany) {
             switch(prop.attr.type) {
               case "Date":
+              case "DueDate":
                 preOffsetDate = item[itemAttr];
                 offsetDate = preOffsetDate &&
                   new Date(preOffsetDate.valueOf() + 60000 * preOffsetDate.getTimezoneOffset());
@@ -2338,12 +2424,14 @@ select xt.install_js('XT','Data','xtuple', $$
         lockExp,
         oid,
         pcheck,
+        pgver = 0 + XT.Data.getPgVersion(2),
         pid = options.pid || null,
-        pidSql = "select usename, procpid " +
+        pidcol = (pgver < 9.2) ? "procpid" : "pid",
+        pidSql = "select usename, {pidcol} " +
                  "from pg_stat_activity " +
                  "where datname=current_database() " +
                  " and usename=$1 " +
-                 " and procpid=$2;",
+                 " and {pidcol}=$2;",
         query,
         selectSql = "select * " +
                     "from xt.lock " +
@@ -2351,6 +2439,8 @@ select xt.install_js('XT','Data','xtuple', $$
                     " and lock_record_id = $2;",
         username = XT.username;
 
+      pidSql = pidSql.replace(/{pidcol}/g, pidcol);
+
       /* If passed a table name, look up the oid. */
       oid = typeof table === "string" ? this.getTableOid(table) : table;
 
@@ -2458,346 +2548,6 @@ select xt.install_js('XT','Data','xtuple', $$
       return true;
     },
 
-    /* This deprecated function is still used by three dispatch functions. We should delete
-    this as soon as we refactor those, and then rename buildClauseOptimized to buildClause */
-    buildClause: function (nameSpace, type, parameters, orderBy) {
-      parameters = parameters || [];
-
-      var arrayIdentifiers = [],
-        arrayParams,
-        charSql,
-        childOrm,
-        clauses = [],
-        count = 1,
-        identifiers = [],
-        list = [],
-        isArray = false,
-        op,
-        orClause,
-        orderByIdentifiers = [],
-        orderByParams = [],
-        orm = this.fetchOrm(nameSpace, type),
-        param,
-        params = [],
-        parts,
-        pcount,
-        prevOrm,
-        privileges = orm.privileges,
-        prop,
-        ret = {};
-
-      ret.conditions = "";
-      ret.parameters = [];
-
-      /* Handle privileges. */
-      if (orm.isNestedOnly) { plv8.elog(ERROR, 'Access Denied'); }
-      if (privileges &&
-          (!privileges.all ||
-            (privileges.all &&
-              (!this.checkPrivilege(privileges.all.read) &&
-              !this.checkPrivilege(privileges.all.update)))
-          ) &&
-          privileges.personal &&
-          (this.checkPrivilege(privileges.personal.read) ||
-            this.checkPrivilege(privileges.personal.update))
-        ) {
-
-        parameters.push({
-          attribute: privileges.personal.properties,
-          isLower: true,
-          isUsernamePrivFilter: true,
-          value: XT.username
-        });
-      }
-
-      /* Handle parameters. */
-      if (parameters.length) {
-        for (var i = 0; i < parameters.length; i++) {
-          orClause = [];
-          param = parameters[i];
-          op = param.operator || '=';
-          switch (op) {
-          case '=':
-          case '>':
-          case '<':
-          case '>=':
-          case '<=':
-          case '!=':
-            break;
-          case 'BEGINS_WITH':
-            op = '~^';
-            break;
-          case 'ENDS_WITH':
-            op = '~?';
-            break;
-          case 'MATCHES':
-            op = '~*';
-            break;
-          case 'ANY':
-            op = '<@';
-            for (var c = 0; c < param.value.length; c++) {
-              ret.parameters.push(param.value[c]);
-              param.value[c] = '$' + count;
-              count++;
-            }
-            break;
-          case 'NOT ANY':
-            op = '!<@';
-            for (var c = 0; c < param.value.length; c++) {
-              ret.parameters.push(param.value[c]);
-              param.value[c] = '$' + count;
-              count++;
-            }
-            break;
-          default:
-            plv8.elog(ERROR, 'Invalid operator: ' + op);
-          }
-
-          /* Handle characteristics. This is very specific to xTuple,
-             and highly dependant on certain table structures and naming conventions,
-             but otherwise way too much work to refactor in an abstract manner right now. */
-          if (param.isCharacteristic) {
-            /* Handle array. */
-            if (op === '<@') {
-              param.value = ' ARRAY[' + param.value.join(',') + ']';
-            }
-
-            /* Booleans are stored as strings. */
-            if (param.value === true) {
-              param.value = 't';
-            } else if (param.value === false) {
-              param.value = 'f';
-            }
-
-            /* Yeah, it depends on a property called 'characteristics'... */
-            prop = XT.Orm.getProperty(orm, 'characteristics');
-
-            /* Build the characteristics query clause. */
-            identifiers.push(prop.toMany.inverse);
-            identifiers.push(orm.nameSpace.toLowerCase());
-            identifiers.push(prop.toMany.type.decamelize());
-            identifiers.push(param.attribute);
-            identifiers.push(param.value);
-
-            charSql = 'id in (' +
-                      '  select %' + (identifiers.length - 4) + '$I '+
-                      '  from %' + (identifiers.length - 3) + '$I.%' + (identifiers.length - 2) + '$I ' +
-                      '    join char on (char_name = characteristic)' +
-                      '  where 1=1 ' +
-                      /* Note: Not using $i for these. L = literal here. These is not identifiers. */
-                      '    and char_name = %' + (identifiers.length - 1) + '$L ' +
-                      '    and value ' + op + ' %' + (identifiers.length) + '$L ' +
-                      ')';
-
-            clauses.push(charSql);
-
-          /* Array comparisons handle another way. e.g. %1$I !<@ ARRAY[$1,$2] */
-          } else if (op === '<@' || op === '!<@') {
-            /* Handle paths if applicable. */
-            if (param.attribute.indexOf('.') > -1) {
-              parts = param.attribute.split('.');
-              childOrm = this.fetchOrm(nameSpace, type);
-              params.push("");
-              pcount = params.length - 1;
-
-              for (var n = 0; n < parts.length; n++) {
-                /* Validate attribute. */
-                prop = XT.Orm.getProperty(childOrm, parts[n]);
-                if (!prop) {
-                  plv8.elog(ERROR, 'Attribute not found in object map: ' + parts[n]);
-                }
-
-                /* Build path. e.g. ((%1$I).%2$I).%3$I */
-                identifiers.push(parts[n]);
-                params[pcount] += "%" + identifiers.length + "$I";
-                if (n < parts.length - 1) {
-                  params[pcount] = "(" + params[pcount] + ").";
-                  childOrm = this.fetchOrm(nameSpace, prop.toOne.type);
-                } else {
-                  params[pcount] += op + ' ARRAY[' + param.value.join(',') + ']';
-                }
-              }
-            } else {
-              identifiers.push(param.attribute);
-              params.push("%" + identifiers.length + "$I " + op + ' ARRAY[' + param.value.join(',') + ']');
-              pcount = params.length - 1;
-            }
-            clauses.push(params[pcount]);
-
-          /* Everything else handle another. */
-          } else {
-            if (XT.typeOf(param.attribute) !== 'array') {
-              param.attribute = [param.attribute];
-            }
-
-            for (var c = 0; c < param.attribute.length; c++) {
-              /* Handle paths if applicable. */
-              if (param.attribute[c].indexOf('.') > -1) {
-                parts = param.attribute[c].split('.');
-                childOrm = this.fetchOrm(nameSpace, type);
-                params.push("");
-                pcount = params.length - 1;
-                isArray = false;
-
-                /* Check if last part is an Array. */
-                for (var m = 0; m < parts.length; m++) {
-                  /* Validate attribute. */
-                  prop = XT.Orm.getProperty(childOrm, parts[m]);
-                  if (!prop) {
-                    plv8.elog(ERROR, 'Attribute not found in object map: ' + parts[m]);
-                  }
-
-                  if (m < parts.length - 1) {
-                    childOrm = this.fetchOrm(nameSpace, prop.toOne.type);
-                  } else if (prop.attr && prop.attr.type === 'Array') {
-                    /* The last property in the path is an array. */
-                    isArray = true;
-                    params[pcount] = '$' + count;
-                  }
-                }
-
-                /* Reset the childOrm to parent. */
-                childOrm = this.fetchOrm(nameSpace, type);
-
-                for (var n = 0; n < parts.length; n++) {
-                  /* Validate attribute. */
-                  prop = XT.Orm.getProperty(childOrm, parts[n]);
-                  if (!prop) {
-                    plv8.elog(ERROR, 'Attribute not found in object map: ' + parts[n]);
-                  }
-
-                  /* Do a persional privs array search e.g. 'admin' = ANY (usernames_array). */
-                  if (param.isUsernamePrivFilter && isArray) {
-                    identifiers.push(parts[n]);
-                    arrayIdentifiers.push(identifiers.length);
-
-                    if (n < parts.length - 1) {
-                      childOrm = this.fetchOrm(nameSpace, prop.toOne.type);
-                    }
-                  } else {
-                    /* Build path. e.g. ((%1$I).%2$I).%3$I */
-                    identifiers.push(parts[n]);
-                    params[pcount] += "%" + identifiers.length + "$I";
-
-                    if (n < parts.length - 1) {
-                      params[pcount] = "(" + params[pcount] + ").";
-                      childOrm = this.fetchOrm(nameSpace, prop.toOne.type);
-                    } else if (param.isLower) {
-                      params[pcount] = "lower(" + params[pcount] + ")";
-                    }
-                  }
-                }
-              } else {
-                /* Validate attribute. */
-                prop = XT.Orm.getProperty(orm, param.attribute[c]);
-                if (!prop) {
-                  plv8.elog(ERROR, 'Attribute not found in object map: ' + param.attribute[c]);
-                }
-
-                identifiers.push(param.attribute[c]);
-
-                /* Do a persional privs array search e.g. 'admin' = ANY (usernames_array). */
-                if (param.isUsernamePrivFilter && ((prop.toMany && !prop.isNested) ||
-                  (prop.attr && prop.attr.type === 'Array'))) {
-
-                  params.push('$' + count);
-                  pcount = params.length - 1;
-                  arrayIdentifiers.push(identifiers.length);
-                } else {
-                  params.push("%" + identifiers.length + "$I");
-                  pcount = params.length - 1;
-                }
-              }
-
-              /* Add persional privs array search. */
-              if (param.isUsernamePrivFilter && ((prop.toMany && !prop.isNested)
-                || (prop.attr && prop.attr.type === 'Array') || isArray)) {
-
-                /* e.g. 'admin' = ANY (usernames_array) */
-                arrayParams = "";
-                params[pcount] += ' ' + op + ' ANY (';
-
-                /* Build path. e.g. ((%1$I).%2$I).%3$I */
-                for (var f =0; f < arrayIdentifiers.length; f++) {
-                  arrayParams += '%' + arrayIdentifiers[f] + '$I';
-                  if (f < arrayIdentifiers.length - 1) {
-                    arrayParams = "(" + arrayParams + ").";
-                  }
-                }
-                params[pcount] += arrayParams + ')';
-
-              /* Add optional is null clause. */
-              } else if (parameters[i].includeNull) {
-                /* e.g. %1$I = $1 or %1$I is null */
-                params[pcount] = params[pcount] + " " + op + ' $' + count + ' or ' + params[pcount] + ' is null';
-              } else {
-                /* e.g. %1$I = $1 */
-                params[pcount] += " " + op + ' $' + count;
-              }
-
-              orClause.push(params[pcount]);
-            }
-
-            /* If more than one clause we'll get: (%1$I = $1 or %1$I = $2 or %1$I = $3) */
-            clauses.push('(' + orClause.join(' or ') + ')');
-            count++;
-            ret.parameters.push(param.value);
-          }
-        }
-      }
-
-      ret.conditions = (clauses.length ? '(' + XT.format(clauses.join(' and '), identifiers) + ')' : ret.conditions) || true;
-
-      /* Massage ordeBy with quoted identifiers. */
-      if (orderBy) {
-        for (var i = 0; i < orderBy.length; i++) {
-          /* Handle path case. */
-          if (orderBy[i].attribute.indexOf('.') > -1) {
-            parts = orderBy[i].attribute.split('.');
-            prevOrm = orm;
-            orderByParams.push("");
-            pcount = orderByParams.length - 1;
-
-            for (var n = 0; n < parts.length; n++) {
-              prop = XT.Orm.getProperty(orm, parts[n]);
-              if (!prop) {
-                plv8.elog(ERROR, 'Attribute not found in map: ' + parts[n]);
-              }
-              orderByIdentifiers.push(parts[n]);
-              orderByParams[pcount] += "%" + orderByIdentifiers.length + "$I";
-
-              if (n < parts.length - 1) {
-                orderByParams[pcount] = "(" + orderByParams[pcount] + ").";
-                orm = this.fetchOrm(nameSpace, prop.toOne.type);
-              }
-            }
-            orm = prevOrm;
-          /* Normal case. */
-          } else {
-            prop = XT.Orm.getProperty(orm, orderBy[i].attribute);
-            if (!prop) {
-              plv8.elog(ERROR, 'Attribute not found in map: ' + orderBy[i].attribute);
-            }
-            orderByIdentifiers.push(orderBy[i].attribute);
-            orderByParams.push("%" + orderByIdentifiers.length + "$I");
-            pcount = orderByParams.length - 1;
-          }
-
-          if (orderBy[i].isEmpty) {
-            orderByParams[pcount] = "length(" + orderByParams[pcount] + ")=0";
-          }
-          if (orderBy[i].descending) {
-            orderByParams[pcount] += " desc";
-          }
-
-          list.push(orderByParams[pcount])
-        }
-      }
-
-      ret.orderBy = list.length ? XT.format('order by ' + list.join(','), orderByIdentifiers) : '';
-
-      return ret;
-    },
     /**
      * Renew a lock. Defaults to rewing the lock for 30 seconds.
      *