Commit 1635e14e authored by Lucas Fernandes de Oliveira's avatar Lucas Fernandes de Oliveira
Browse files

Issue #29: Refactor postgres adapter


Signed-off-by: Lucas Fernandes de Oliveira's avatarLucas Fernandes de Oliveira <lfo14@inf.ufpr.br>
parent d61f49d6
Pipeline #11225 passed with stage
in 1 minute and 26 seconds
......@@ -22,16 +22,13 @@ import { Adapter } from "../core/adapter";
import { Metric } from "../core/metric";
import { Dimension } from "../core/dimension";
import { AggregationType, RelationType } from "../common/types";
import { View, ChildView } from "../core/view";
import { View } from "../core/view";
import { Pool, PoolConfig } from "pg";
interface ParsedChild {
query: string;
view: View;
dimensions: Dimension[];
metrics: Metric[];
alias: string;
};
interface DimInfo {
dim: Dimension;
views: View[];
}
export class PostgresAdapter extends Adapter {
private pool: Pool;
......@@ -42,7 +39,20 @@ export class PostgresAdapter extends Adapter {
}
public getDataFromView(view: View, cb: (error: Error, result?: any[]) => void): void {
// buildQueryFromView does not put the final ;, it need to be put apart
let query = this.buildQueryFromView(view, view.metrics, view.dimensions) + ";\n";
// let query = this.buildQueryFromView(view, view.metrics, view.dimensions) + ";\n";
const materialized = this.searchMaterializedViews(view).sort((a, b) => {
return (a.id < b.id) ? -1 : 1;
});
const unique = [materialized[0]];
for (let i = 1; i < materialized.length; ++i) {
if (materialized[i - 1].id !== materialized[i].id) {
unique.push(materialized[i]);
}
}
const query = this.buildQuery(view, unique);
this.pool.connect((err, client, done) => {
if (err) {
cb (err);
......@@ -60,136 +70,6 @@ export class PostgresAdapter extends Adapter {
return false;
}
private buildQueryFromView (view: View, metrics: Metric[], dimensions: Dimension[]): string {
/*
Reduce metrics and dimensions array to the intersection with the
view. So is possible only get useful data in the sub-querys.
*/
let strMetrics = metrics.map((metric) => {
let func = this.getAggregateFunction(metric.aggregation, view.origin);
let quotedName = "\"" + metric.name + "\"";
let extMetric = func + "(" + quotedName + ") AS " + quotedName;
return extMetric;
});
if (view.materialized) {
let strDimensions = dimensions.map((dimension) => "\"" + dimension.name + "\"");
let sql = "(SELECT " + strMetrics.concat(strDimensions).join(", ");
sql += " FROM " + "view_" + view.id;
if (strDimensions.length > 0 && strMetrics.length > 0) {
sql += " GROUP BY " + strDimensions.join(", ");
}
sql += ")";
return sql;
}
else {
let covered = new Map();
let matchable: any[] = [] ;
dimensions.forEach((item) => {
covered.set(item.name, "");
/*
For each dimension that must be covered
create a match in the array.
If a more than onde view match to the same
dimension, cretes a WHERE clause
*/
matchable.push({match: item.name, sub: item});
let dim = item;
/*
Sub dimensions also have parents that can match
with then too.
*/
while (dim.relation !== RelationType.NONE) {
dim = dim.parent;
matchable.push({
match: dim.name,
sub: item,
});
}
});
metrics.forEach((item) => covered.set(item.name, ""));
let elements: string[] = [];
let group: string[] = [];
let viewsQuery: string[] = [];
let selected: string[] = [];
let children: ParsedChild[] = view.childViews.map((item: ChildView) => {
let dims = item.view.dimensions.filter((dim) => {
return matchable.some((match) => match.match === dim.name);
});
let mets = item.metrics.filter((met) => {
return metrics.some((elem) => elem.name === met.name);
});
let query = "";
if (dims.length !== 0 || mets.length !== 0) {
query = this.buildQueryFromView(item.view, mets, dims);
}
return {
query: query,
view: item.view,
dimensions : dims,
metrics: mets,
alias: "alias_" + item.view.id
};
}).filter ((item) => item.query !== "");
children.forEach((child: ParsedChild) => {
child.view.dimensions.forEach((dimension: Dimension) => {
/*
Make selection. Search for dimensions, that are in
matchable array.
*/
matchable.filter((item) => {
return item.match === dimension.name;
})
.forEach((item) => {
// Expand the sub-dimension until match with a parent
let dim = item.sub;
let extDimension = child.alias + ".\"" + dimension.name + "\"";
while (dim.name !== item.match) {
extDimension = this.translateRelation(dim.relation, extDimension);
dim = dim.parent;
}
if (covered.get(item.sub.name) === "") {
elements.push(extDimension + " AS \"" + item.sub.name + "\"");
covered.set(item.sub.name, extDimension);
group.push(extDimension);
}
else {
selected.push(extDimension + " = " + covered.get(item.sub.name));
}
});
});
child.metrics.forEach((metric: Metric) => {
// Only materialized views can have origin as true
let func = this.getAggregateFunction(metric.aggregation, false);
let quotedName = "\"" + metric.name + "\"";
let extMetric = func + "(" + child.alias + "." + quotedName + ") AS " + quotedName;
elements.push(extMetric);
});
viewsQuery.push(child.query + " AS " + child.alias);
});
let projection = "SELECT " + elements.join(", ") + "\n";
let viewsFrom = "FROM " + viewsQuery.join(", ") + "\n";
let selection = (selected.length > 0) ? "WHERE " + selected.join(" AND ") + "\n" : "";
let grouping = "";
if (group.length > 0 && metrics.length > 0) {
grouping = "GROUP BY " + group.join(", ") + "\n";
}
return "(" + projection + viewsFrom + selection + grouping + ")";
}
}
private getAggregateFunction(aggrType: AggregationType, origin: boolean): string {
switch (aggrType) {
case AggregationType.SUM:
......@@ -237,4 +117,116 @@ export class PostgresAdapter extends Adapter {
*/
return name + "(" + args.map((item, idx) => item + values[idx]).join(",") + ")";
}
private searchMaterializedViews(view: View): View[] {
let r: View[] = [];
if (view.materialized) {
return [view];
}
else {
let children = view.childViews.map((item) => item.view);
for (let i = 0; i < children.length; ++i) {
r = r.concat(this.searchMaterializedViews(children[i]));
}
}
return r;
}
private buildQuery(target: View, views: View[]) {
const metrics = target.metrics;
const dimensions = target.dimensions;
let dimMap: {[key: string]: DimInfo} = {};
let metMap: {[key: string]: View[]} = {};
for (let i = 0; i < views.length; ++i) {
const mets = views[i].metrics;
const dims = views[i].dimensions;
for (let j = 0; j < mets.length; ++j) {
if (!metMap[mets[j].name]) {
metMap[mets[j].name] = [views[i]];
}
else {
metMap[mets[j].name].push(views[i]);
}
}
for (let j = 0; j < dims.length; ++j) {
if (!dimMap[dims[j].name]) {
dimMap[dims[j].name] = {
dim: dims[j],
views: [views[i]]
};
}
else {
dimMap[dims[j].name].views.push(views[i]);
}
}
}
const strMetrics = metrics.map((metric) => {
const view = metMap[metric.name][0];
let func = this.getAggregateFunction(metric.aggregation, view.origin);
let quotedName = "\"" + metric.name + "\"";
let extMetric = func + "(view_" + view.id + "." + quotedName + ")";
return extMetric + " AS " + quotedName;
});
const parsedDimensions = dimensions.map((dimension) => {
let dim = dimension;
while (!dimMap[dim.name]) {
// Checar exeção
dim = dim.parent;
}
const view = dimMap[dim.name].views[0];
const quotedDim = "\"" + dim.name + "\"";
const quotedName = "\"" + dimension.name + "\"";
let extDimension = "view_" + view.id + "." + quotedDim;
let aux = dimension;
while (aux.name !== dim.name) {
extDimension = this.translateRelation(aux.relation, extDimension);
aux = aux.parent;
}
return { aliased: extDimension + " AS " + quotedName, noalias: extDimension };
});
const strDimensions = parsedDimensions.map ((item) => item.aliased);
const grouped = parsedDimensions.map((item) => item.noalias);
const elements = strMetrics.concat(strDimensions);
let joins = [];
for (let i in dimMap) {
let remainViews = dimMap[i].views.slice();
let dim = dimMap[i].dim;
let leftSide = this.buildColumn(dim, remainViews.shift().id);
if (remainViews.length > 0) {
while (remainViews.length > 0) {
const id = remainViews.shift().id;
const rightSide = this.buildColumn(dim, id);
joins.push(leftSide + " = " + rightSide);
}
}
}
const projection = "SELECT " + elements.join(",");
const source = " FROM " + views.map((view) => "view_" + view.id).join(",");
const selection = (joins.length > 0) ? " WHERE " + joins.join(" AND ") : "";
let grouping = "";
if (grouped.length > 0) {
grouping = " GROUP BY " + grouped.join(",");
}
return projection + source + selection + grouping + ";";
}
private buildColumn (item: Metric|Dimension, id: string): string {
const quotedName = "\"" + item.name + "\"";
return "view_" + id + "." + quotedName;
}
}
......@@ -152,6 +152,11 @@ const aggrView = new View({
view: views[2],
metrics: [mets[6]],
dimensions: []
},
{
view: views[4],
metrics: [],
dimensions: []
}
]
});
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment