Skip to content
Snippets Groups Projects
Unverified Commit 521a3cb4 authored by Nemanja Glumac's avatar Nemanja Glumac Committed by GitHub
Browse files

[MLv2] Migrate `cleanQuery` util [WIP] (#38505)

parent 103cc5a5
No related branches found
No related tags found
No related merge requests found
......@@ -5,7 +5,6 @@ import { copy } from "metabase/lib/utils";
import * as Lib from "metabase-lib";
import { normalizeParameterValue } from "metabase-lib/parameters/utils/parameter-values";
import { deriveFieldOperatorFromParameter } from "metabase-lib/parameters/utils/operators";
import * as Q_DEPRECATED from "metabase-lib/queries/utils"; // legacy
export function isNative(card) {
return card?.dataset_query?.type === "native";
......@@ -55,10 +54,6 @@ export function applyParameters(
parameterMappings = [],
) {
const datasetQuery = copy(card.dataset_query);
// clean the query
if (datasetQuery.type === "query") {
datasetQuery.query = Q_DEPRECATED.cleanQuery(datasetQuery.query);
}
datasetQuery.parameters = [];
for (const parameter of parameters || []) {
const value = parameterValues[parameter.id];
......
import _ from "underscore";
import * as QUERY from "./query";
import * as FieldRef from "./field-ref";
export * from "./query";
export * from "./field-ref";
// The backend won't return more than 2,000 rows so in cases where we
// need to communicate or use that, use this constant
export const HARD_ROW_LIMIT = 2000;
export function cleanQuery(query) {
if (!query) {
return query;
}
// it's possible the user left some half-done parts of the query on screen when they hit the run button, so find those
// things now and clear them out so that we have a nice clean set of valid clauses in our query
// aggregations
query.aggregation = QUERY.getAggregations(query);
if (query.aggregation.length === 0) {
delete query.aggregation;
}
// breakouts
query.breakout = QUERY.getBreakouts(query);
if (query.breakout.length === 0) {
delete query.breakout;
}
// filters
const filters = QUERY.getFilters(query).filter(filter =>
_.all(filter, a => a != null),
);
if (filters.length > 0) {
query.filter = QUERY.getFilterClause(filters);
} else {
delete query.filter;
}
if (query["order-by"]) {
query["order-by"] = query["order-by"]
.map(s => {
const [direction, field] = s;
// remove incomplete sorts
if (!FieldRef.isValidField(field) || direction == null) {
return null;
}
if (FieldRef.isAggregateField(field)) {
// remove aggregation sort if we can't sort by this aggregation
if (canSortByAggregateField(query, field[1])) {
return s;
}
} else if (hasValidBreakout(query)) {
const exactMatches = query.breakout.filter(b =>
FieldRef.isSameField(b, field, true),
);
if (exactMatches.length > 0) {
return s;
}
const targetMatches = query.breakout.filter(b =>
FieldRef.isSameField(b, field, false),
);
if (targetMatches.length > 0) {
// query processor expect the order-by clause to match the breakout's datetime-field unit or fk-> target,
// so just replace it with the one that matches the target field
// NOTE: if we have more than one breakout for the same target field this could match the wrong one
if (targetMatches.length > 1) {
console.warn(
"Sort clause matches more than one breakout field",
field,
targetMatches,
);
}
return [direction, targetMatches[0]];
}
} else if (QUERY.isBareRows(query)) {
return s;
}
// otherwise remove sort if it doesn't have a breakout but isn't a bare rows aggregation
return null;
})
.filter(s => s != null);
if (query["order-by"].length === 0) {
delete query["order-by"];
}
}
if (typeof query.limit !== "number") {
delete query.limit;
}
if (query.expressions) {
delete query.expressions[""];
} // delete any empty expressions
return query;
}
function hasValidBreakout(query) {
return (
query &&
query.breakout &&
query.breakout.length > 0 &&
query.breakout[0] !== null
);
}
function canSortByAggregateField(query, index) {
if (!hasValidBreakout(query)) {
return false;
}
const aggregations = QUERY.getAggregations(query);
return aggregations[index] && aggregations[index][0];
}
import { equals, copy } from "metabase/lib/utils";
import { equals } from "metabase/lib/utils";
import { b64hash_to_utf8, utf8_to_b64url } from "metabase/lib/encoding";
import Questions from "metabase/entities/questions";
import * as Q_DEPRECATED from "metabase-lib/queries/utils";
export function createCard(name = null) {
return {
......@@ -43,16 +42,11 @@ export async function loadCard(cardId, { dispatch, getState }) {
}
function getCleanCard(card) {
const dataset_query = copy(card.dataset_query);
if (dataset_query.query) {
dataset_query.query = Q_DEPRECATED.cleanQuery(dataset_query.query);
}
return {
name: card.name,
collectionId: card.collectionId,
description: card.description,
dataset_query: dataset_query,
dataset_query: card.dataset_query,
display: card.display,
displayIsLocked: card.displayIsLocked,
parameters: card.parameters,
......
import { copy } from "metabase/lib/utils";
import { createMockStructuredDatasetQuery } from "metabase-types/api/mocks";
import * as Q_DEPRECATED from "metabase-lib/queries/utils";
describe("Legacy Q_DEPRECATED library", () => {
describe("cleanQuery", () => {
it("should pass for a query created with metabase-lib", () => {
const datasetQuery = createMockStructuredDatasetQuery({
query: {
"source-table": 1,
aggregation: [["count"]],
},
});
// We have to take a copy because the original object isn't extensible
const copiedDatasetQuery = copy(datasetQuery);
Q_DEPRECATED.cleanQuery(copiedDatasetQuery);
expect(copiedDatasetQuery).toBeDefined();
});
it("should not remove complete sort clauses", () => {
const query = {
"source-table": 0,
"order-by": [["asc", ["field", 1, null]]],
};
Q_DEPRECATED.cleanQuery(query);
expect(query["order-by"]).toEqual([["asc", ["field", 1, null]]]);
});
it("should remove incomplete sort clauses", () => {
const query = {
"source-table": 0,
"order-by": [["asc", null]],
};
Q_DEPRECATED.cleanQuery(query);
expect(query["order-by"]).toEqual(undefined);
});
it("should not remove sort clauses on aggregations if that aggregation supports it", () => {
const query = {
"source-table": 0,
aggregation: [["count"]],
breakout: [["field", 1, null]],
"order-by": [["asc", ["aggregation", 0]]],
};
Q_DEPRECATED.cleanQuery(query);
expect(query["order-by"]).toEqual([["asc", ["aggregation", 0]]]);
});
it("should remove sort clauses on aggregations if that aggregation doesn't support it", () => {
const query = {
"source-table": 0,
"order-by": [["asc", ["aggregation", 0]]],
};
Q_DEPRECATED.cleanQuery(query);
expect(query["order-by"]).toEqual(undefined);
});
it("should not remove sort clauses on fields appearing in breakout", () => {
const query = {
"source-table": 0,
aggregation: [["count"]],
breakout: [["field", 1, null]],
"order-by": [["asc", ["field", 1, null]]],
};
Q_DEPRECATED.cleanQuery(query);
expect(query["order-by"]).toEqual([["asc", ["field", 1, null]]]);
});
it("should remove sort clauses on fields not appearing in breakout", () => {
const query = {
"source-table": 0,
aggregation: [["count"]],
"order-by": [["asc", ["field", 1, null]]],
};
Q_DEPRECATED.cleanQuery(query);
expect(query["order-by"]).toEqual(undefined);
});
it("should not remove sort clauses with foreign keys on fields appearing in breakout", () => {
const query = {
"source-table": 0,
aggregation: [["count"]],
breakout: [["field", 2, { "source-field": 1 }]],
"order-by": [["asc", ["field", 2, { "source-field": 1 }]]],
};
Q_DEPRECATED.cleanQuery(query);
expect(query["order-by"]).toEqual([
["asc", ["field", 2, { "source-field": 1 }]],
]);
});
it("should not remove sort clauses with datetime-fields on fields appearing in breakout", () => {
const query = {
"source-table": 0,
aggregation: [["count"]],
breakout: [["field", 1, { "temporal-unit": "week" }]],
"order-by": [["asc", ["field", 1, { "temporal-unit": "week" }]]],
};
Q_DEPRECATED.cleanQuery(query);
expect(query["order-by"]).toEqual([
["asc", ["field", 1, { "temporal-unit": "week" }]],
]);
});
it("should replace order-by clauses with the exact matching datetime-fields version in the breakout", () => {
const query = {
"source-table": 0,
aggregation: [["count"]],
breakout: [["field", 1, { "temporal-unit": "week" }]],
"order-by": [["asc", ["field", 1, null]]],
};
Q_DEPRECATED.cleanQuery(query);
expect(query["order-by"]).toEqual([
["asc", ["field", 1, { "temporal-unit": "week" }]],
]);
});
it("should replace order-by clauses with the exact matching fk version in the breakout", () => {
const query = {
"source-table": 0,
aggregation: [["count"]],
breakout: [["field", 2, { "source-field": 1 }]],
"order-by": [["asc", ["field", 2, null]]],
};
Q_DEPRECATED.cleanQuery(query);
expect(query["order-by"]).toEqual([
["asc", ["field", 2, { "source-field": 1 }]],
]);
});
});
describe("removeBreakout", () => {
it("should not mutate the query", () => {
const query = {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment