tg-knex-query-resolver
Version:
TeselaGen's Knex based query resolver
209 lines (185 loc) • 8.03 kB
JavaScript
const _ = require('lodash');
module.exports = function mapFields(filterQuery, tableMap, currentEntity, opts){
var modelMap = convertTableMapToModelMap(tableMap);
return mapFieldNames(_.cloneDeep(filterQuery), modelMap, currentEntity, opts);
function getColumnName(modelName, attrName){
try{
var columnName = modelMap[modelName].attributes[attrName].columnName;
if(columnName != null) return columnName;
}catch(e){
//this should be blank here
}
if (attrName === 'query') {
throw new Error("You probably forgot to call .toJSON() on the qb object. Original error: Unrecognized attribute name: " + attrName + " for model: " + modelName + '');
} else {
throw new Error("Unrecognized attribute name: " + attrName + " for model: " + modelName);
}
}
function mapFieldNames(filterQuery, modelMap, currentModelName, opts){
if(filterQuery.type === 'root'){
currentModelName = filterQuery.entity;
filterQuery.entity = getTableName(currentModelName);
filterQuery.filters = filterQuery.filters.map(function(filter) {
return mapFieldNames(filter, modelMap, currentModelName, opts);
});
}else if(filterQuery.type === 'group'){
filterQuery.filters = filterQuery.filters.map(function(filter) {
return mapFieldNames(filter, modelMap, currentModelName, opts);
});
}else if(filterQuery.type === 'subquery'){
filterQuery.foreignKey = getColumnName(currentModelName, filterQuery.foreignKey);
currentModelName = filterQuery.entity;
filterQuery.entity = getTableName(currentModelName);
filterQuery.key = getColumnName(currentModelName, filterQuery.key);
filterQuery.filters = filterQuery.filters.map(function(filter) {
return mapFieldNames(filter, modelMap, currentModelName, opts);
});
}else if(filterQuery.type === 'expression'){
if(filterQuery.field.indexOf('.') > 1){
filterQuery = convertPathArgToSubquery(filterQuery.field, filterQuery, currentModelName)
} else {
filterQuery.field = getColumnName(currentModelName, filterQuery.field);
filterQuery.args.forEach((arg) => {
if(arg.__objectType === 'field'){
arg.field = getColumnName(currentModelName, arg.field);
}
});
}
}else if(filterQuery.type === 'where'){
var renamedArgs = {};
var subqueries = [];
_.each(filterQuery.args, (value, name) => {
if(name.indexOf('.') > 1){
subqueries.push(convertPathArgToSubquery(name, value, currentModelName));
}else{
renamedArgs[getColumnName(currentModelName, name)] = value;
}
});
filterQuery.args = renamedArgs;
if(subqueries.length > 0){
var clonedWhere = clone(filterQuery);
var groupFilter = convertWhereToGroupFilter(filterQuery);
groupFilter.filters = subqueries.concat(clonedWhere);
}
}else{
throw new Error("Unable to map fields for unknown query filter type:" + filterQuery.type);
}
return filterQuery;
function getTableName(modelName){
try{
var tableName = modelMap[modelName].tableName;
if(tableName != null) return tableName;
}catch(e){
//this should be blank here
}
throw new Error("Unrecognized model name in query: " + modelName);
}
}
function convertPathArgToSubquery(name, value, currentModelName, nested){
let filterQuery
if(name.indexOf('.') > 1){
var relAttrDef = getAttributeDefinitionForRelationAttributeName(modelMap, currentModelName, name);
var currentEntityName = modelMap[currentModelName].entityName;
var relatedModelName = tableMap[relAttrDef.def.referenceName].modelName;
filterQuery = {
"type": "subquery",
"key": "key on related model",
"entity": "related model",
"foreignKey": "key on current model",
"filters": []
};
if(relAttrDef.type === "association"){
filterQuery.foreignKey = tableMap[currentEntityName].primaryKeyField;
filterQuery.entity = relatedModelName;
filterQuery.key = tableMap[relAttrDef.def.referenceName].attributes[relAttrDef.def.referenceKey].name;
}else{
filterQuery.foreignKey = relAttrDef.def.name;
filterQuery.entity = relatedModelName;
filterQuery.key = tableMap[relAttrDef.def.referenceName].attributes[relAttrDef.def.referenceKey].name;
}
var newPath = name.split('.');
newPath.shift();
newPath = newPath.join('.');
let valueToUse = value
if (value && value.type === 'expression') {
valueToUse = _.assign({}, value, {field: newPath})
}
var subFilter = convertPathArgToSubquery(newPath, valueToUse, relatedModelName, true);
filterQuery.filters = [subFilter];
if (nested) { // if nested don't call mapFieldNames because mapFieldNames will handle all
return filterQuery
}
} else if (value && value.type === 'expression') {
value.field = getColumnName(currentModelName, value.field);
value.args.forEach((arg) => {
if(arg.__objectType === 'field'){
arg.field = getColumnName(currentModelName, arg.field);
}
});
filterQuery = value
} else {
filterQuery = {
type: "where",
args: {
[name]: value
}
};
}
return mapFieldNames(filterQuery, modelMap, currentModelName, opts);
}
}
function getAttributeDefinitionForRelationAttributeName(modelMap, modelName, relAttrName){
relAttrName = relAttrName.split('.')[0]
var relAttrDef;
_.each(modelMap[modelName].attributes, (attrDef) => {
if(attrDef.relAttrName === relAttrName) {
relAttrDef = attrDef;
return false;
}
});
if(!relAttrDef){
_.each(modelMap[modelName].associations, (assocDef) => {
if(assocDef.name === relAttrName) {
relAttrDef = assocDef;
return false;
}
});
if (!relAttrDef) {
throw new Error('Model ' + modelName + ' has no attribute/relation named: ' + relAttrName +'. This is probably due to a malformed "path" within a datatable schema.')
}
return {
type: "association",
def: relAttrDef
}
}
return {
type: "relation",
def: relAttrDef
}
}
function convertTableMapToModelMap(tableMap){
var modelMap = {};
_.each(tableMap, (model, entityName) => {
var attrs = {};
_.each(model.attributes, (attrDef) => {
attrs[attrDef.name] = attrDef;
});
modelMap[model.modelName] = {
tableName: model.tableName,
attributes: attrs,
associations: model.associations,
entityName
};
})
return modelMap;
}
function convertWhereToGroupFilter(filterQuery){
delete filterQuery.args;
filterQuery.type = "group";
filterQuery.filters = [];
filterQuery.operator = "and"; //wheres should always be ands!
return filterQuery;
}
function clone(obj){
return JSON.parse(JSON.stringify(obj));
}