alasql
Version:
AlaSQL.js - JavaScript SQL database library for relational and graph data manipulation with support of localStorage, IndexedDB, and Excel
558 lines (486 loc) • 16.7 kB
JavaScript
// Main query procedure
function queryfn(query,oldscope,cb, A,B) {
// console.log(query.queriesfn);
var ms;
query.sourceslen = query.sources.length;
var slen = query.sourceslen;
query.query = query; // TODO Remove to prevent memory leaks
query.A = A;
query.B = B;
// console.log(arguments);
query.cb = cb;
query.oldscope = oldscope;
// Run all subqueries before main statement
if(query.queriesfn) {
query.sourceslen += query.queriesfn.length;
slen += query.queriesfn.length;
query.queriesdata = [];
// console.log(8);
query.queriesfn.forEach(function(q,idx){
// if(query.explain) ms = Date.now();
//console.log(18,idx);
// var res = flatArray(q(query.params,null,queryfn2,(-idx-1),query));
// var res = flatArray(queryfn(q.query,null,queryfn2,(-idx-1),query));
// console.log(A,B);
// console.log(q);
q.query.params = query.params;
// query.queriesdata[idx] =
if(false) {
queryfn(q.query,query.oldscope,queryfn2,(-idx-1),query);
} else {
queryfn2([],(-idx-1),query);
}
// console.log(27,q);
// query.explaination.push({explid: query.explid++, description:'Query '+idx,ms:Date.now()-ms});
// query.queriesdata[idx] = res;
// return res;
});
// console.log(9,query.queriesdata.length);
// console.log(query.queriesdata[0]);
}
var scope;
if(!oldscope) scope = {};
else scope = cloneDeep(oldscope);
query.scope = scope;
// First - refresh data sources
var result;
query.sources.forEach(function(source, idx){
// source.data = query.database.tables[source.tableid].data;
// console.log(666,idx);
source.query = query;
var rs = source.datafn(query, query.params, queryfn2, idx, alasql);
// console.log(333,rs);
if(typeof rs != undefined) {
// TODO - this is a hack: check if result is array - check all cases and
// make it more logical
if((query.intofn || query.intoallfn) && rs instanceof Array) rs = rs.length;
result = rs;
}
// console.log(444,result);
//
// Ugly hack to use in query.wherefn and source.srcwherefns functions
// constructions like this.queriesdata['test'].
// I can elimite it with source.srcwherefn.bind(this)()
// but it may be slow.
//
source.queriesdata = query.queriesdata;
});
if(slen == 0) result = queryfn3(query);
return result;
};
function queryfn2(data,idx,query) {
//console.log(56,arguments);
// console.log(78,data, idx,query);
//console.trace();
if(idx>=0) {
var source = query.sources[idx];
source.data = data;
if(typeof source.data == 'function') {
source.getfn = source.data;
source.dontcache = source.getfn.dontcache;
// var prevsource = query.sources[h-1];
if(source.joinmode == 'OUTER' || source.joinmode == 'RIGHT' || source.joinmode == 'ANTI') {
source.dontcache = false;
}
source.data = {};
}
} else {
// subqueries
// console.log("queriesdata",data, flatArray(data));
query.queriesdata[-idx-1] = flatArray(data);
// console.log(98,query.queriesdata);
// console.log(79,query.queriesdata);
}
query.sourceslen--;
if(query.sourceslen>0) return;
return queryfn3(query);
};
function queryfn3(query) {
//console.log(55,query);
var scope = query.scope;
// Preindexation of data sources
// if(!oldscope) {
preIndex(query);
// }
// query.sources.forEach(function(source) {
// console.log(source.data);
// });
// Prepare variables
query.data = [];
query.xgroups = {};
query.groups = [];
// Level of Joins
var h = 0;
// Start walking over data
doJoin(query, scope, h);
//console.log(85,query.data[0]);
// If groupping, then filter groups with HAVING function
// console.log(query.havingfns);
if(query.groupfn) {
query.data = [];
if(query.groups.length == 0) {
var g = {};
if(query.selectGroup.length>0) {
// console.log(query.selectGroup);
query.selectGroup.forEach(function(sg){
if(sg.aggregatorid == "COUNT" || sg.aggregatorid == "SUM") {
g[sg.nick] = 0;
} else {
g[sg.nick] = undefined;
}
});
};
query.groups = [g];
// console.log();
};
// console.log('EMPTY',query.groups);
// debugger;
// if(false && (query.groups.length == 1) && (Object.keys(query.groups[0]).length == 0)) {
// console.log('EMPTY',query.groups);
// } else {
for(var i=0,ilen=query.groups.length;i<ilen;i++) {
// console.log(query.groups[i]);
var g = query.groups[i];
if((!query.havingfn) || query.havingfn(g,query.params,alasql)) {
// console.log(g);
var d = query.selectgfn(g,query.params,alasql);
query.data.push(d);
};
};
// }
// query.groups = query.groups.filter();
};
// Remove distinct values
doDistinct(query);
// UNION / UNION ALL
if(query.unionallfn) {
// TODO Simplify this part of program
if(query.corresponding) {
if(!query.unionallfn.query.modifier) query.unionallfn.query.modifier = 'ARRAY';
var ud = query.unionallfn(query.params);
} else {
if(!query.unionallfn.query.modifier) query.unionallfn.query.modifier = 'RECORDSET';
var nd = query.unionallfn(query.params);
var ud = [];
for(var i=0,ilen=nd.data.length;i<ilen;i++) {
var r = {};
for(var j=0,jlen=Math.min(query.columns.length,nd.columns.length);j<jlen;j++) {
r[query.columns[j].columnid] = nd.data[i][nd.columns[j].columnid];
}
ud.push(r);
}
}
query.data = query.data.concat(ud);
} else if(query.unionfn) {
if(query.corresponding) {
if(!query.unionfn.query.modifier) query.unionfn.query.modifier = 'ARRAY';
var ud = query.unionfn(query.params);
} else {
if(!query.unionfn.query.modifier) query.unionfn.query.modifier = 'RECORDSET';
var nd = query.unionfn(query.params);
var ud = [];
for(var i=0,ilen=nd.data.length;i<ilen;i++) {
var r = {};
for(var j=0,jlen=Math.min(query.columns.length,nd.columns.length);j<jlen;j++) {
r[query.columns[j].columnid] = nd.data[i][nd.columns[j].columnid];
}
ud.push(r);
}
}
query.data = arrayUnionDeep(query.data, ud);
} else if(query.exceptfn) {
if(query.corresponding) {
if(!query.exceptfn.query.modifier) query.exceptfn.query.modifier = 'ARRAY';
var ud = query.exceptfn(query.params);
} else {
if(!query.exceptfn.query.modifier) query.exceptfn.query.modifier = 'RECORDSET';
var nd = query.exceptfn(query.params);
var ud = [];
for(var i=0,ilen=nd.data.length;i<ilen;i++) {
var r = {};
for(var j=0,jlen=Math.min(query.columns.length,nd.columns.length);j<jlen;j++) {
r[query.columns[j].columnid] = nd.data[i][nd.columns[j].columnid];
}
ud.push(r);
}
}
query.data = arrayExceptDeep(query.data, ud);
} else if(query.intersectfn) {
if(query.corresponding) {
if(!query.intersectfn.query.modifier) query.intersectfn.query.modifier = 'ARRAY';
var ud = query.intersectfn(query.params);
} else {
if(!query.intersectfn.query.modifier) query.intersectfn.query.modifier = 'RECORDSET';
var nd = query.intersectfn(query.params);
var ud = [];
for(var i=0,ilen=nd.data.length;i<ilen;i++) {
var r = {};
for(var j=0,jlen=Math.min(query.columns.length,nd.columns.length);j<jlen;j++) {
r[query.columns[j].columnid] = nd.data[i][nd.columns[j].columnid];
}
ud.push(r);
}
}
query.data = arrayIntersectDeep(query.data, ud);
};
// Ordering
if(query.orderfn) {
if(query.explain) var ms = Date.now();
query.data = query.data.sort(query.orderfn);
if(query.explain) {
query.explaination.push({explid: query.explid++, description:'QUERY BY',ms:Date.now()-ms});
}
};
// Reduce to limit and offset
doLimit(query);
// Remove Angular.js artifacts and other unnecessary columns
// Issue #25
// console.log('removeKeys:',query.removeKeys);
// TODO: Check what artefacts rest from Angular.js
if(typeof angular != "undefined") {
query.removeKeys.push('$$hashKey');
}
if(query.removeKeys.length > 0) {
var removeKeys = query.removeKeys;
// Remove from data
var jlen = removeKeys.length;
if(jlen > 0) {
for(var i=0,ilen=query.data.length;i<ilen;i++) {
for(var j=0; j<jlen;j++) {
delete query.data[i][removeKeys[j]];
}
}
};
// Remove from columns list
if(query.columns.length > 0) {
query.columns = query.columns.filter(function(column){
var found = false;
removeKeys.forEach(function(key){
if(column.columnid == key) found = true;
});
return !found;
});
}
}
if(typeof query.removeLikeKeys != 'undefined' && query.removeLikeKeys.length > 0) {
var removeLikeKeys = query.removeLikeKeys;
// Remove unused columns
// SELECT * REMOVE COLUMNS LIKE "%b"
for(var i=0,ilen=query.data.length;i<ilen;i++) {
var r = query.data[i];
for(var k in r) {
for(var j=0;j<query.removeLikeKeys.length;j++) {
if(k.match(query.removeLikeKeys[j])) {
delete r[k];
}
}
};
}
if(query.columns.length > 0) {
query.columns = query.columns.filter(function(column){
var found = false;
removeLikeKeys.forEach(function(key){
if(column.columnid.match(key)) found = true;
});
return !found;
});
}
}
// console.log(query.intoallfns);
// if(query.explain) {
// if(query.cb) query.cb(query.explaination,query.A, query.B);
// return query.explaination;
// } else
//console.log(190,query.intofns);
if(query.intoallfn) {
// console.log(161);
// var res = query.intoallfn(query.columns,query.cb,query.A, query.B, alasql);
var res = query.intoallfn(query.columns,query.cb,query.params,query.alasql);
// console.log(1163,res);
// if(query.cb) res = query.cb(res,query.A, query.B);
// console.log(1165,res);
// debugger;
return res;
} else if(query.intofn) {
for(var i=0,ilen=query.data.length;i<ilen;i++){
query.intofn(query.data[i],i,query.params,query.alasql);
}
// console.log(query.intofn);
if(query.cb) query.cb(query.data.length,query.A, query.B);
return query.data.length;
} else {
// console.log(111,query.cb,query.data);
var res = query.data;
if(query.cb) res = query.cb(query.data,query.A, query.B);
// console.log(777,res)
return res;
}
// That's all
};
// Limiting
function doLimit (query) {
// console.log(query.limit, query.offset)
if(query.limit) {
var offset = 0;
if(query.offset) offset = ((query.offset|0)-1)||0;
var limit;
if(query.percent) {
limit = ((query.data.length*query.limit/100)| 0)+offset;
} else {
limit = (query.limit|0) + offset;
};
query.data = query.data.slice(offset,limit);
}
}
// Distinct
function doDistinct (query) {
if(query.distinct) {
var uniq = {};
// TODO: Speedup, because Object.keys is slow
// TODO: Problem with DISTINCT on objects
for(var i=0,ilen=query.data.length;i<ilen;i++) {
var uix = Object.keys(query.data[i]).map(function(k){return query.data[i][k]}).join('`');
uniq[uix] = query.data[i];
};
query.data = [];
for(var key in uniq) query.data.push(uniq[key]);
}
};
// Optimization: preliminary indexation of joins
preIndex = function(query) {
// console.log(query);
// Loop over all sources
for(var k=0, klen = query.sources.length;k<klen;k++) {
var source = query.sources[k];
// If there is indexation rule
//console.log('preIndex', source);
//console.log(source);
if(k > 0 && source.optimization == 'ix' && source.onleftfn && source.onrightfn) {
// If there is no table.indices - create it
if(source.databaseid && alasql.databases[source.databaseid].tables[source.tableid]) {
if(!alasql.databases[source.databaseid].tables[source.tableid].indices) query.database.tables[source.tableid].indices = {};
// Check if index already exists
var ixx = alasql.databases[source.databaseid].tables[source.tableid].indices[hash(source.onrightfns+'`'+source.srcwherefns)];
if( !alasql.databases[source.databaseid].tables[source.tableid].dirty && ixx) {
source.ix = ixx;
}
};
if(!source.ix) {
source.ix = {};
// Walking over source data
var scope = {};
var i = 0;
var ilen = source.data.length;
var dataw;
// while(source.getfn i<ilen) {
while((dataw = source.data[i]) || (source.getfn && (dataw = source.getfn(i))) || (i<ilen)) {
if(source.getfn && !source.dontcache) source.data[i] = dataw;
// scope[tableid] = dataw;
// for(var i=0, ilen=source.data.length; i<ilen; i++) {
// Prepare scope for indexation
scope[source.alias || source.tableid] = dataw;
// Check if it apply to where function
if(source.srcwherefn(scope, query.params, alasql)) {
// Create index entry for each address
var addr = source.onrightfn(scope, query.params, alasql);
var group = source.ix [addr];
if(!group) {
group = source.ix [addr] = [];
}
group.push(dataw);
}
i++;
}
if(source.databaseid && alasql.databases[source.databaseid].tables[source.tableid]){
// Save index to original table
alasql.databases[source.databaseid].tables[source.tableid].indices[hash(source.onrightfns+'`'+source.srcwherefns)] = source.ix;
};
}
//console.log(38,274,source.ix);
// Optimization for WHERE column = expression
} else if (source.wxleftfn) {
if(!alasql.databases[source.databaseid].engineid) {
// Check if index exists
var ixx = alasql.databases[source.databaseid].tables[source.tableid].indices[hash(source.wxleftfns+'`')];
}
if( !alasql.databases[source.databaseid].tables[source.tableid].dirty && ixx) {
// Use old index if exists
source.ix = ixx;
// Reduce data (apply filter)
source.data = source.ix[source.wxrightfn(null, query.params, alasql)];
} else {
// Create new index
source.ix = {};
// Prepare scope
var scope = {};
// Walking on each source line
var i = 0;
var ilen = source.data.length;
var dataw;
// while(source.getfn i<ilen) {
while((dataw = source.data[i]) || (source.getfn && (dataw = source.getfn(i))) || (i<ilen)) {
if(source.getfn && !source.dontcache) source.data[i] = dataw;
// for(var i=0, ilen=source.data.length; i<ilen; i++) {
scope[source.alias || source.tableid] = source.data[i];
// Create index entry
var addr = source.wxleftfn(scope, query.params, alasql);
var group = source.ix [addr];
if(!group) {
group = source.ix [addr] = [];
}
group.push(source.data[i]);
i++;
}
// query.database.tables[source.tableid].indices[hash(source.wxleftfns+'`'+source.onwherefns)] = source.ix;
if(!alasql.databases[source.databaseid].engineid) {
alasql.databases[source.databaseid].tables[source.tableid].indices[hash(source.wxleftfns+'`')] = source.ix;
}
}
// Apply where filter to reduces rows
if(source.srcwherefns) {
if(source.data) {
var scope = {};
source.data = source.data.filter(function(r) {
scope[source.alias] = r;
return source.srcwherefn(scope, query.params, alasql);
});
} else {
source.data = [];
}
}
// }
// If there is no any optimization than apply srcwhere filter
} else if(source.srcwherefns && !source.dontcache) {
if(source.data) {
var scope = {};
// TODO!!!!! Data as Function
source.data = source.data.filter(function(r) {
scope[source.alias] = r;
// console.log(288,source);
return source.srcwherefn(scope, query.params, alasql);
});
var scope = {};
var i = 0;
var ilen = source.data.length;
var dataw;
var res = [];
// while(source.getfn i<ilen) {
while((dataw = source.data[i]) || (source.getfn && (dataw = source.getfn(i))) || (i<ilen)) {
if(source.getfn && !source.dontcache) source.data[i] = dataw;
scope[source.alias] = dataw;
if(source.srcwherefn(scope, query.params, alasql)) res.push(dataw);
i++;
}
source.data = res;
} else {
source.data = [];
};
}
// Change this to another place (this is a wrong)
if(source.databaseid && alasql.databases[source.databaseid].tables[source.tableid]) {
//query.database.tables[source.tableid].dirty = false;
} else {
// this is a subquery?
}
}
}