Skip to content
Snippets Groups Projects
Unverified Commit 3db6407f authored by Gustavo Saiani's avatar Gustavo Saiani Committed by GitHub
Browse files

Build datetime filters as datetime before question is saved (#26679)

parent b6e50930
No related branches found
No related tags found
No related merge requests found
...@@ -1264,6 +1264,10 @@ export class ExpressionDimension extends Dimension { ...@@ -1264,6 +1264,10 @@ export class ExpressionDimension extends Dimension {
base_type = "type/Boolean"; base_type = "type/Boolean";
break; break;
case MONOTYPE.DateTime:
base_type = "type/DateTime";
break;
// fallback // fallback
default: default:
base_type = "type/Float"; base_type = "type/Float";
......
...@@ -324,69 +324,69 @@ export const MBQL_CLAUSES = { ...@@ -324,69 +324,69 @@ export const MBQL_CLAUSES = {
"get-year": { "get-year": {
displayName: `year`, displayName: `year`,
type: "number", type: "number",
args: ["expression"], args: ["datetime"],
}, },
"get-quarter": { "get-quarter": {
displayName: `quarter`, displayName: `quarter`,
type: "number", type: "number",
args: ["expression"], args: ["datetime"],
}, },
"get-month": { "get-month": {
displayName: `month`, displayName: `month`,
type: "number", type: "number",
args: ["expression"], args: ["datetime"],
}, },
"get-week": { "get-week": {
displayName: `week`, displayName: `week`,
type: "number", type: "number",
args: ["expression"], args: ["datetime"],
hasOptions: true, // optional mode parameter hasOptions: true, // optional mode parameter
}, },
"get-day": { "get-day": {
displayName: `day`, displayName: `day`,
type: "number", type: "number",
args: ["expression"], args: ["datetime"],
}, },
"get-day-of-week": { "get-day-of-week": {
displayName: `weekday`, displayName: `weekday`,
type: "number", type: "number",
args: ["expression"], args: ["datetime"],
}, },
"get-hour": { "get-hour": {
displayName: `hour`, displayName: `hour`,
type: "number", type: "number",
args: ["expression"], args: ["datetime"],
}, },
"get-minute": { "get-minute": {
displayName: `minute`, displayName: `minute`,
type: "number", type: "number",
args: ["expression"], args: ["datetime"],
}, },
"get-second": { "get-second": {
displayName: `second`, displayName: `second`,
type: "number", type: "number",
args: ["expression"], args: ["datetime"],
}, },
"datetime-diff": { "datetime-diff": {
displayName: `datetimeDiff`, displayName: `datetimeDiff`,
type: "number", type: "number",
args: ["expression", "expression", "string"], args: ["datetime", "datetime", "string"],
requiresFeature: "datetime-diff", requiresFeature: "datetime-diff",
}, },
"datetime-add": { "datetime-add": {
displayName: `datetimeAdd`, displayName: `datetimeAdd`,
type: "expression", type: "datetime",
args: ["expression", "number", "string"], args: ["datetime", "number", "string"],
}, },
"datetime-subtract": { "datetime-subtract": {
displayName: `datetimeSubtract`, displayName: `datetimeSubtract`,
type: "expression", type: "datetime",
args: ["expression", "number", "string"], args: ["datetime", "number", "string"],
}, },
"convert-timezone": { "convert-timezone": {
displayName: `convertTimezone`, displayName: `convertTimezone`,
type: "expression", type: "datetime",
args: ["expression", "string"], args: ["datetime", "string"],
hasOptions: true, hasOptions: true,
requiresFeature: "convert-timezone", requiresFeature: "convert-timezone",
}, },
......
...@@ -38,11 +38,24 @@ function findMBQL(op) { ...@@ -38,11 +38,24 @@ function findMBQL(op) {
return clause; return clause;
} }
// a is the type of the argument expected,
// as defined in MBQL_CLAUSES,
// and b is the inferred type of the argument
const isCompatible = (a, b) => { const isCompatible = (a, b) => {
if (a === b) { if (a === b) {
return true; return true;
} }
if (a === "expression" && (b === "number" || b === "string")) {
// if b is a string, then it can be an arg to a function that expects a datetime argument.
// This allows datetime string literals to work as args for functions that expect datetime types.
// FIXME: By doing this we are allowing string columns to be arguments to functions, which isn’t valid MBQL.
if (a === "datetime" && b === "string") {
return true;
}
if (
a === "expression" &&
(b === "datetime" || b === "number" || b === "string")
) {
return true; return true;
} }
if (a === "aggregation" && b === "number") { if (a === "aggregation" && b === "number") {
......
...@@ -6,6 +6,7 @@ export const MONOTYPE = { ...@@ -6,6 +6,7 @@ export const MONOTYPE = {
Number: "number", Number: "number",
String: "string", String: "string",
Boolean: "boolean", Boolean: "boolean",
DateTime: "datetime",
}; };
export function infer(mbql, env) { export function infer(mbql, env) {
...@@ -37,9 +38,6 @@ export function infer(mbql, env) { ...@@ -37,9 +38,6 @@ export function infer(mbql, env) {
case "case": case "case":
return infer(mbql[1][0][1], env); return infer(mbql[1][0][1], env);
case "coalesce": case "coalesce":
case "convert-timezone":
case "datetime-add":
case "datetime-subtract":
return infer(mbql[1], env); return infer(mbql[1], env);
} }
...@@ -47,6 +45,8 @@ export function infer(mbql, env) { ...@@ -47,6 +45,8 @@ export function infer(mbql, env) {
if (func) { if (func) {
const returnType = func.type; const returnType = func.type;
switch (returnType) { switch (returnType) {
case "datetime":
return MONOTYPE.DateTime;
case "object": case "object":
return MONOTYPE.Undefined; return MONOTYPE.Undefined;
case "aggregation": case "aggregation":
......
...@@ -159,6 +159,66 @@ describe("metabase-lib/expressions/resolve", () => { ...@@ -159,6 +159,66 @@ describe("metabase-lib/expressions/resolve", () => {
it("should honor CONCAT's implicit casting", () => { it("should honor CONCAT's implicit casting", () => {
expect(() => expr(["concat", ["coalesce", "B", 1]])).not.toThrow(); expect(() => expr(["concat", ["coalesce", "B", 1]])).not.toThrow();
}); });
describe("datetime functions", () => {
it("should resolve unchained functions", () => {
expect(() => expr(["get-week", "2022-01-01"])).not.toThrow();
expect(() =>
expr(["datetime-add", "2022-01-01", 1, "month"]),
).not.toThrow();
// TODO: Implementation should be fine-tuned so that these throw
// as they are not really datetime
expect(() => expr(["get-day", A])).not.toThrow();
expect(() => expr(["get-day", "a"])).not.toThrow();
expect(() => expr(["get-day-of-week", A])).not.toThrow();
expect(() => expr(["get-day-of-week", "a"])).not.toThrow();
expect(() => expr(["get-week", A])).not.toThrow();
expect(() => expr(["get-week", "a"])).not.toThrow();
expect(() => expr(["get-month", A])).not.toThrow();
expect(() => expr(["get-month", "a"])).not.toThrow();
expect(() => expr(["get-quarter", A])).not.toThrow();
expect(() => expr(["get-quarter", "a"])).not.toThrow();
expect(() => expr(["get-year", A])).not.toThrow();
expect(() => expr(["get-year", "a"])).not.toThrow();
});
it("should resolve chained commmands", () => {
expect(() =>
expr([
"datetime-subtract",
["datetime-add", "2022-01-01", 1, "month"],
2,
"minute",
]),
).not.toThrow();
});
it("should chain datetime functions onto functions of compatible types", () => {
expect(() =>
expr([
"concat",
["datetime-add", "2022-01-01", 1, "month"],
"a string",
]),
).not.toThrow();
});
it("should throw if chaining datetime functions onto functions of incompatible types", () => {
expect(() =>
expr(["trim", ["datetime-add", "2022-01-01", 1, "month"]]),
).toThrow();
});
it("should throw if passing numbers as arguments expected to be datetime", () => {
expect(() => expr(["get-day", 15])).toThrow();
expect(() => expr(["get-day-of-week", 6])).toThrow();
expect(() => expr(["get-week", 52])).toThrow();
expect(() => expr(["get-month", 12])).toThrow();
expect(() => expr(["get-quarter", 3])).toThrow();
expect(() => expr(["get-year", 2025])).toThrow();
});
});
}); });
describe("for aggregations", () => { describe("for aggregations", () => {
......
...@@ -116,12 +116,19 @@ describe("metabase-lib/expressions/typeinferencer", () => { ...@@ -116,12 +116,19 @@ describe("metabase-lib/expressions/typeinferencer", () => {
}); });
it("should infer the result of datetimeAdd, datetimeSubtract", () => { it("should infer the result of datetimeAdd, datetimeSubtract", () => {
expect(type('datetimeAdd([CreatedAt], 2, "month")')).toEqual( expect(type('datetimeAdd([CreatedAt], 2, "month")')).toEqual("datetime");
"type/Datetime", expect(type('datetimeAdd("2022-01-01", 2, "month")')).toEqual("datetime");
);
expect(type('datetimeSubtract([CreatedAt], 2, "month")')).toEqual( expect(type('datetimeSubtract([CreatedAt], 2, "month")')).toEqual(
"type/Datetime", "datetime",
);
expect(type('datetimeSubtract("2022-01-01", 2, "month")')).toEqual(
"datetime",
); );
expect(
type(
'datetimeSubtract(datetimeAdd("2022-01-01", 2, "month"), 4, "minute")',
),
).toEqual("datetime");
}); });
it("should infer the result of datetimeExtract functions", () => { it("should infer the result of datetimeExtract functions", () => {
......
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