* @param {Array} Parameters - optional
* @returns {Object}
*/
- buildClauseOptimized: function (nameSpace, type, parameters, orderBy) {
+ buildClause: function (nameSpace, type, parameters, orderBy) {
parameters = parameters || [];
var that = this,
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) {
} 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.splice(index, 0, 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);
}
}
});
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(
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);
/*
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);
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.
*
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),
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)
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());
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 " +
" 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;
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.
*