Compare commits

...

2 Commits

Author SHA1 Message Date
Aleksandr Lesnenko
942c64498a support bigints 2024-06-26 20:55:37 -04:00
Cam Saul
33922837f6 Remove large-int-id middleware 2024-06-26 20:55:15 +00:00
39 changed files with 153 additions and 281 deletions

View File

@@ -1041,6 +1041,24 @@ describe("scenarios > question > filter", () => {
});
});
it("should handle big integers", () => {
cy.createNativeQuestion(
{
name: "BigInt",
native: {
query:
"select 9123372036854000123 n union all select 9123372036854000124",
},
},
{ visitQuestion: true },
);
queryBuilderMain().within(() => {
cy.findByText("9123372036854000123");
cy.findByText("9123372036854000124");
});
});
// TODO: fixme!
it.skip("should render custom expression helper near the custom expression field", () => {
openReviewsTable({ mode: "notebook" });

View File

@@ -4,12 +4,13 @@ import type { Card } from "./card";
import type { DatabaseId } from "./database";
import type { FieldFingerprint, FieldId, FieldVisibilityType } from "./field";
import type { Insight } from "./insight";
import type { NumericValue } from "./number";
import type { ParameterOptions } from "./parameters";
import type { DownloadPermission } from "./permissions";
import type { DatasetQuery, DatetimeUnit, DimensionReference } from "./query";
import type { TableId } from "./table";
export type RowValue = string | number | null | boolean;
export type RowValue = string | NumericValue | null | boolean;
export type RowValues = RowValue[];
export type BinningMetadata = {

View File

@@ -0,0 +1 @@
export type NumericValue = number | bigint;

View File

@@ -1,3 +1,5 @@
import type { NumericValue } from "./number";
// SmartScalar (Trend Chart)
export type SmartScalarComparisonType =
| "anotherColumn"
@@ -21,7 +23,7 @@ export interface SmartScalarComparisonAnotherColumn
export interface SmartScalarComparisonPeriodsAgo
extends BaseSmartScalarComparison {
type: "periodsAgo";
value: number;
value: NumericValue;
}
export interface SmartScalarComparisonPreviousPeriod
@@ -37,7 +39,7 @@ export interface SmartScalarComparisonPreviousValue
export interface SmartScalarComparisonStaticNumber
extends BaseSmartScalarComparison {
type: "staticNumber";
value: number;
value: NumericValue;
label: string;
}

View File

@@ -1,6 +1,8 @@
import { t } from "ttag";
import { isFloat } from "metabase/lib/number";
import type * as Lib from "metabase-lib";
import type { NumericValue } from "metabase-types/api/number";
import { Table } from "../MetadataInfo.styled";
@@ -48,10 +50,14 @@ export function NumberFingerprint({
* @param num - a number value from the type/Number fingerprint; might not be a number
* @returns - a tuple, [isFormattedNumber, formattedNumber]
*/
function roundNumber(num: number | null | undefined): [boolean, string] {
function roundNumber(num: NumericValue | null | undefined): [boolean, string] {
if (num == null) {
return [false, ""];
}
return [true, Number.isInteger(num) ? num.toString() : num.toFixed(2)];
if (isFloat(num)) {
return [true, num.toFixed(2)];
}
return [true, num.toString()];
}

View File

@@ -4,6 +4,7 @@ import { forwardRef, useCallback } from "react";
import type { NumberInputProps } from "metabase/ui";
import { NumberInput } from "metabase/ui";
import type { NumericValue } from "metabase-types/api/number";
export interface FormNumberInputProps
extends Omit<NumberInputProps, "value" | "error"> {
@@ -19,7 +20,7 @@ export const FormNumberInput = forwardRef(function FormNumberInput(
useField(name);
const handleChange = useCallback(
(newValue: number | "") => {
(newValue: NumericValue | "") => {
if (newValue === "") {
setValue(nullable ? null : undefined);
} else {

View File

@@ -1,4 +1,5 @@
import EventEmitter from "events";
import JSONBigInt from "json-bigint-native";
import querystring from "querystring";
import { isTest } from "metabase/env";
@@ -119,7 +120,7 @@ export class Api extends EventEmitter {
if (options.hasBody) {
body = options.formData
? rawData.formData
: JSON.stringify(
: JSONBigInt.stringify(
options.bodyParamName != null
? data[options.bodyParamName]
: data,
@@ -209,7 +210,7 @@ export class Api extends EventEmitter {
let body = xhr.responseText;
if (options.json) {
try {
body = JSON.parse(body);
body = JSONBigInt.parse(body);
} catch (e) {}
}
let status = xhr.status;
@@ -270,7 +271,7 @@ export class Api extends EventEmitter {
return response.text().then(body => {
if (options.json) {
try {
body = JSON.parse(body);
body = JSONBigInt.parse(body);
} catch (e) {}
}

View File

@@ -1,3 +1,5 @@
import JSONBigInt from "json-bigint-native";
import Questions from "metabase/entities/questions";
import { b64hash_to_utf8, utf8_to_b64url } from "metabase/lib/encoding";
import { equals } from "metabase/lib/utils";
@@ -66,7 +68,7 @@ export function isEqualCard(card1, card2) {
// TODO Atte Keinänen 5/31/17 Deprecated, we should move tests to Questions.spec.js
export function serializeCardForUrl(card) {
return utf8_to_b64url(JSON.stringify(getCleanCard(card)));
return utf8_to_b64url(JSONBigInt.stringify(getCleanCard(card)));
}
export function deserializeCardFromUrl(serialized) {

View File

@@ -1,3 +1,5 @@
import type { NumericValue } from "metabase-types/api/number";
export function isPositiveInteger(value: any) {
return /^\d+$/.test(String(value));
}
@@ -11,3 +13,11 @@ export function parseNumberValue(value: any): number | null {
return null;
}
}
export function isInteger(num: unknown): num is NumericValue {
return Number.isInteger(num) || typeof num === "bigint";
}
export function isFloat(num: unknown): num is number {
return typeof Number.isFinite(num) && !Number.isInteger(num);
}

View File

@@ -1,3 +1,4 @@
import cloneDeep from "lodash.clonedeep";
import { t } from "ttag";
import _ from "underscore";
@@ -72,8 +73,7 @@ export function propertiesEqual(
}
export function copy(a: unknown) {
// FIXME: ugghhhhhhhhh
return JSON.parse(JSON.stringify(a));
return cloneDeep(a);
}
/**

View File

@@ -41,7 +41,9 @@ const VisualizationMock = ({
<div>
<div>
{rows[0].map((value, i) => (
<span key={i}>{value}</span>
<span key={i}>
<>{value}</>
</span>
))}
</div>
<div data-testid="settings">

View File

@@ -4,6 +4,7 @@ import { t } from "ttag";
import { Box, Button, Flex, Stack } from "metabase/ui";
import type * as Lib from "metabase-lib";
import type { NumericValue } from "metabase-types/api/number";
import { ExpressionWidgetHeader } from "../expressions/ExpressionWidgetHeader";
@@ -39,7 +40,7 @@ export const CompareAggregations = ({
const [aggregation, setAggregation] = useState<
Lib.AggregationClause | Lib.ExpressionClause | undefined
>(hasManyAggregations ? undefined : aggregations[0]);
const [offset, setOffset] = useState<number | "">(DEFAULT_OFFSET);
const [offset, setOffset] = useState<NumericValue | "">(DEFAULT_OFFSET);
const [columns, setColumns] = useState<ColumnType[]>(DEFAULT_COLUMNS);
const width = aggregation ? STEP_2_WIDTH : STEP_1_WIDTH;

View File

@@ -7,6 +7,7 @@ import type { NumberValue } from "metabase/querying/hooks/use-number-filter";
import { useNumberFilter } from "metabase/querying/hooks/use-number-filter";
import { Box, Flex, NumberInput, Text } from "metabase/ui";
import * as Lib from "metabase-lib";
import type { NumericValue } from "metabase-types/api/number";
import { NumberFilterValuePicker } from "../../FilterValuePicker";
import { FilterOperatorPicker } from "../FilterOperatorPicker";
@@ -151,13 +152,13 @@ function NumberValueInput({
value={values[0]}
placeholder={t`Min`}
autoFocus
onChange={(newValue: number) => onChange([newValue, values[1]])}
onChange={(newValue: NumericValue) => onChange([newValue, values[1]])}
/>
<Text mx="sm">{t`and`}</Text>
<NumberInput
value={values[1]}
placeholder={t`Max`}
onChange={(newValue: number) => onChange([values[0], newValue])}
onChange={(newValue: NumericValue) => onChange([values[0], newValue])}
/>
</Flex>
);

View File

@@ -6,7 +6,9 @@ import { t } from "ttag";
import { useGetFieldValuesQuery } from "metabase/api";
import { checkNotNull } from "metabase/lib/types";
import { Center, Loader } from "metabase/ui";
import { parseNumber } from "metabase/ui/utils/numbers";
import * as Lib from "metabase-lib";
import type { NumericValue } from "metabase-types/api/number";
import { ListValuePicker } from "./ListValuePicker";
import { SearchValuePicker } from "./SearchValuePicker";
@@ -135,10 +137,13 @@ export function NumberFilterValuePicker({
values,
onChange,
...props
}: FilterValuePickerProps<number>) {
}: FilterValuePickerProps<NumericValue>) {
const shouldCreate = (query: string, values: string[]) => {
const number = parseFloat(query);
return isFinite(number) && !values.includes(query);
const number = parseNumber(query);
return (
(typeof number === "bigint" || isFinite(number)) &&
!values.includes(query)
);
};
return (
@@ -148,7 +153,7 @@ export function NumberFilterValuePicker({
values={values.map(value => String(value))}
placeholder={isKeyColumn(column) ? t`Enter an ID` : t`Enter a number`}
shouldCreate={shouldCreate}
onChange={newValue => onChange(newValue.map(value => parseFloat(value)))}
onChange={newValue => onChange(newValue.map(parseNumber))}
/>
);
}

View File

@@ -1,4 +1,5 @@
import type { FilterOperatorOption } from "metabase/querying/utils/filters";
import type { NumericValue } from "metabase-types/api/number";
type CoordinatePickerOperator =
| "="
@@ -16,4 +17,4 @@ export interface OperatorOption
hasMultipleValues?: boolean;
}
export type NumberValue = number | "";
export type NumberValue = NumericValue | "";

View File

@@ -1,5 +1,6 @@
import type { FilterOperatorOption } from "metabase/querying/utils/filters";
import type * as Lib from "metabase-lib";
import type { NumericValue } from "metabase-types/api/number";
export interface OperatorOption
extends FilterOperatorOption<Lib.NumberFilterOperatorName> {
@@ -7,4 +8,4 @@ export interface OperatorOption
hasMultipleValues?: boolean;
}
export type NumberValue = number | "";
export type NumberValue = NumericValue | "";

View File

@@ -1,9 +1,19 @@
import type { NumberInputProps } from "@mantine/core";
import type { NumberInputProps as MantineNumberInputProps } from "@mantine/core";
import type { ChangeEvent, FocusEvent, Ref } from "react";
import { forwardRef, useLayoutEffect, useState } from "react";
import { formatNumber, parseNumber } from "metabase/ui/utils/numbers";
import type { NumericValue } from "metabase-types/api/number";
import { TextInput } from "../TextInput";
interface NumberInputProps
extends Omit<MantineNumberInputProps, "value" | "defaultValue" | "onChange"> {
value?: NumericValue | "";
defaultValue?: NumericValue | "";
onChange?: (value: NumericValue) => void;
}
export const NumberInput = forwardRef(function NumberInput(
{
value,
@@ -15,14 +25,16 @@ export const NumberInput = forwardRef(function NumberInput(
}: NumberInputProps,
ref: Ref<HTMLInputElement>,
) {
const [inputValue, setInputValue] = useState(formatValue(defaultValue ?? ""));
const [inputValue, setInputValue] = useState(
formatNumber(defaultValue ?? ""),
);
const [isFocused, setIsFocused] = useState(false);
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const newInputValue = event.target.value;
setInputValue(newInputValue);
const newValue = parseValue(newInputValue);
const newValue = parseNumber(newInputValue);
onChange?.(newValue);
};
@@ -38,7 +50,7 @@ export const NumberInput = forwardRef(function NumberInput(
useLayoutEffect(() => {
if (!isFocused) {
setInputValue(formatValue(value ?? parseValue(inputValue)));
setInputValue(formatNumber(value ?? parseNumber(inputValue)));
}
}, [value, inputValue, isFocused]);
@@ -53,12 +65,3 @@ export const NumberInput = forwardRef(function NumberInput(
/>
);
});
function parseValue(value: string) {
const number = parseFloat(value);
return Number.isNaN(number) ? "" : number;
}
function formatValue(value: number | "") {
return String(value);
}

View File

@@ -0,0 +1,17 @@
import type { NumericValue } from "metabase-types/api/number";
export function parseNumber(value: string): NumericValue {
const parsedFloatValue = parseFloat(value);
if (
Number.isInteger(parsedFloatValue) &&
!Number.isSafeInteger(parsedFloatValue)
) {
return BigInt(value);
}
return parseFloat(value);
}
export function formatNumber(value: NumericValue | "") {
return String(value);
}

View File

@@ -2,9 +2,11 @@ import type { KeyboardEvent, MouseEvent } from "react";
import { useCallback, useRef, useState } from "react";
import { t } from "ttag";
import { isInteger } from "metabase/lib/number";
import { rem, Group, Text, Box } from "metabase/ui";
import type { COMPARISON_TYPES } from "metabase/visualizations/visualizations/SmartScalar/constants";
import type { SmartScalarComparisonPeriodsAgo } from "metabase-types/api";
import type { NumericValue } from "metabase-types/api/number";
import { MenuItemStyled } from "./MenuItem.styled";
import { NumberInputStyled } from "./PeriodsAgoMenuOption.styled";
@@ -48,11 +50,15 @@ export function PeriodsAgoMenuOption({
const value = editedValue?.value ?? MIN_VALUE;
const handleInputChange = useCallback(
(value: number) => {
(value: NumericValue | "") => {
if (message) {
setMessage(null);
}
if (typeof value === "string") {
return;
}
if (value < 1) {
onChange({ type, value: MIN_VALUE });
reSelectInput();
@@ -68,7 +74,7 @@ export function PeriodsAgoMenuOption({
return;
}
if (!Number.isInteger(value)) {
if (!isInteger(value)) {
onChange({ type, value: Math.floor(value) ?? MIN_VALUE });
reSelectInput();
return;
@@ -102,7 +108,7 @@ export function PeriodsAgoMenuOption({
<NumberInputStyled
type="number"
value={value}
onChange={(value: number) => handleInputChange(value)}
onChange={(value: NumericValue | "") => handleInputChange(value)}
onKeyPress={handleInputEnter}
onClick={handleInputClick}
size="xs"

View File

@@ -10,6 +10,7 @@ import {
TextInput,
} from "metabase/ui";
import type { SmartScalarComparisonStaticNumber } from "metabase-types/api";
import type { NumericValue } from "metabase-types/api/number";
import { COMPARISON_TYPES } from "../constants";
@@ -27,7 +28,7 @@ export function StaticNumberForm({
onBack,
}: StaticNumberFormProps) {
const [label, setLabel] = useState(comparison?.label || "");
const [value, setValue] = useState(comparison?.value || 0);
const [value, setValue] = useState<NumericValue>(comparison?.value || 0);
const canSubmit = label.length > 0;
@@ -35,7 +36,7 @@ export function StaticNumberForm({
setLabel(e.target.value);
};
const handleChangeValue = (nextValue: number | "") => {
const handleChangeValue = (nextValue: NumericValue | "") => {
setValue(nextValue || 0);
};

View File

@@ -3,6 +3,7 @@ import { t } from "ttag";
import _ from "underscore";
import { formatDateTimeRangeWithUnit } from "metabase/lib/formatting/date";
import { isInteger } from "metabase/lib/number";
import { isEmpty } from "metabase/lib/validate";
import { computeChange } from "metabase/visualizations/lib/numeric";
import { COMPARISON_TYPES } from "metabase/visualizations/visualizations/SmartScalar/constants";
@@ -371,7 +372,7 @@ function computeTrendPeriodsAgo({
}
const { type, value } = comparison;
if (type === COMPARISON_TYPES.PERIODS_AGO && !Number.isInteger(value)) {
if (type === COMPARISON_TYPES.PERIODS_AGO && !isInteger(value)) {
throw Error("No integer value supplied for periods ago comparison.");
}
const dateUnitsAgo = value ?? 1;

View File

@@ -183,8 +183,7 @@
[{:type "date/all-options"
:value "past2hours"
:target ["dimension" ["template-tag" "date"]]
:id "2d7ce56a-2a66-5845-e9b9-e243c16965b8"}]
:middleware {:js-int-to-string? true}}]
:id "2d7ce56a-2a66-5845-e9b9-e243c16965b8"}]}]
(is (= [["Quentin Sören" "2014-10-03T17:30:00Z"]]
(mt/rows (qp/process-query query)))))))))

View File

@@ -59,12 +59,14 @@
"immer": "^9.0.17",
"inflection": "^1.7.1",
"js-cookie": "^2.1.2",
"json-bigint-native": "^1.2.0",
"jspdf": "^2.5.1",
"jsrsasign": "^11.0.0",
"kbar": "^0.1.0-beta.45",
"leaflet": "^1.2.0",
"leaflet-draw": "^0.4.9",
"leaflet.heat": "^0.2.0",
"lodash.clonedeep": "^4.5.0",
"lodash.debounce": "^4.0.8",
"lodash.orderby": "^4.6.0",
"moment-timezone": "^0.5.38",

View File

@@ -722,8 +722,7 @@
:middleware {:process-viz-settings? true
:skip-results-metadata? true
:ignore-cached-results? true
:format-rows? format_rows
:js-int-to-string? false}))
:format-rows? format_rows}))
;;; ----------------------------------------------- Sharing is Caring ------------------------------------------------

View File

@@ -1214,8 +1214,7 @@
:middleware {:process-viz-settings? true
:skip-results-metadata? true
:ignore-cached-results? true
:format-rows? format_rows
:js-int-to-string? false}})))
:format-rows? format_rows}})))
(api/defendpoint POST "/pivot/:dashboard-id/dashcard/:dashcard-id/card/:card-id/query"
"Run a pivot table query for a specific DashCard."

View File

@@ -88,9 +88,7 @@
[:as {{:keys [database] :as query} :body}]
{database [:maybe :int]}
(run-streaming-query
(-> query
(update-in [:middleware :js-int-to-string?] (fnil identity true))
qp/userland-query-with-default-constraints)))
(qp/userland-query-with-default-constraints query)))
;;; ----------------------------------- Downloading Query Results in Other Formats -----------------------------------
@@ -145,7 +143,7 @@
(assoc :viz-settings viz-settings)
(dissoc :constraints)
(update :middleware #(-> %
(dissoc :add-default-userland-constraints? :js-int-to-string?)
(dissoc :add-default-userland-constraints?)
(assoc :process-viz-settings? true
:skip-results-metadata? true
:format-rows? format_rows))))]

View File

@@ -88,7 +88,6 @@
(dissoc (m/map-keys keyword query-params) :format_rows)
:constraints nil
:middleware {:process-viz-settings? true
:js-int-to-string? false
:format-rows? format_rows}))
@@ -242,7 +241,6 @@
(m/map-keys keyword query-params)
:constraints nil
:middleware {:process-viz-settings? true
:js-int-to-string? false
:format-rows? format_rows}))

View File

@@ -191,7 +191,6 @@
(json/parse-string parameters keyword)
:constraints nil
:middleware {:process-viz-settings? true
:js-int-to-string? false
:format-rows? format_rows}))

View File

@@ -257,8 +257,7 @@
(qp/process-query
(qp/userland-query
(assoc query
:middleware {:process-viz-settings? true
:js-int-to-string? false})
:middleware {:process-viz-settings? true})
{:executed-by api/*current-user-id*
:context :pulse
:card-id card-id}))))

View File

@@ -29,8 +29,7 @@
(qp/process-query
(qp/userland-query-with-default-constraints
(assoc query :middleware {:skip-results-metadata? true
:process-viz-settings? true
:js-int-to-string? false})
:process-viz-settings? true})
(merge (cond-> {:executed-by pulse-creator-id
:context :pulse
:card-id card-id}
@@ -81,8 +80,7 @@
:context :dashboard-subscription
:export-format :api
:parameters parameters
:middleware {:process-viz-settings? true
:js-int-to-string? false}
:middleware {:process-viz-settings? true}
:make-run (fn make-run [qp _export-format]
(^:once fn* [query info]
(qp

View File

@@ -219,7 +219,7 @@
(update :viz-settings (fn [viz] (merge viz dash-viz)))
(update :middleware (fn [middleware]
(merge
{:js-int-to-string? true, :ignore-cached-results? ignore-cache}
{:ignore-cached-results? ignore-cache}
middleware))))
info (cond-> {:executed-by api/*current-user-id*
:context context

View File

@@ -1,68 +0,0 @@
(ns metabase.query-processor.middleware.large-int-id
"Middleware for handling conversion of IDs to strings for proper display of large numbers"
(:require
[metabase.lib.metadata.protocols :as lib.metadata.protocols]
[metabase.lib.util.match :as lib.util.match]
[metabase.query-processor.store :as qp.store]))
(defn- ->string [x]
(when x
(str x)))
(defn- result-int->string
[field-indexes rf]
((map (fn [row]
(reduce #(update (vec %1) %2 ->string) row field-indexes)))
rf))
(defn- should-convert-to-string? [field]
(and (or (isa? (:semantic-type field) :type/PK)
(isa? (:semantic-type field) :type/FK))
(or (isa? (:base-type field) :type/Integer)
(isa? (:base-type field) :type/Number))))
(defn- field-indexes [fields]
(not-empty
(keep-indexed
(fn [idx val]
;; TODO -- we could probably fix the rest of #5816 by adding support for
;; `:field` w/ name and removing the PK/FK requirements -- might break
;; the FE client tho.
(when-let [field (lib.util.match/match-one val
[:field (field-id :guard integer?) _]
;; TODO -- can't we use the QP store here? Seems like
;; we should be able to, but it doesn't work (not
;; initialized)
(lib.metadata.protocols/field (qp.store/metadata-provider) field-id))]
(when (should-convert-to-string? field)
idx)))
fields)))
(defn convert-id-to-string
"Converts any ID (:type/PK and :type/FK) in a result to a string to handle a number > 2^51
or < -2^51, the JavaScript float mantissa. This will allow proper display of large numbers,
like IDs from services like social media. All ID numbers are converted to avoid the performance
penalty of a comparison based on size. NULLs are converted to Clojure nil/JS null."
[{{:keys [js-int-to-string?] :or {js-int-to-string? false}} :middleware, :as query} rff]
;; currently, this excludes `:field` w/ name clauses, aggregations, etc.
;;
;; for a query like below, *no* conversion will occur
;;
;; (mt/mbql-query venues
;; {:source-query {:source-table $$venues
;; :aggregation [[:aggregation-options
;; [:avg $id]
;; {:name "some_generated_name", :display-name "My Cool Ag"}]]
;; :breakout [$price]}})
;;
;; when you run in this fashion, you lose the ability to determine if it's an ID - you get a `:fields` value like:
;;
;; [[:field "PRICE" {:base-type :type/Integer}] [:field "some_generated_name" {:base-type :type/BigInteger}]]
;;
;; so, short of turning all `:type/Integer` derived values into strings, this is the best approximation of a fix
;; that can be accomplished.
(let [rff' (when js-int-to-string?
(when-let [field-indexes (field-indexes (:fields (:query query)))]
(fn [metadata]
(result-int->string field-indexes (rff metadata)))))]
(or rff' rff)))

View File

@@ -10,7 +10,6 @@
[metabase.query-processor.middleware.enterprise :as qp.middleware.enterprise]
[metabase.query-processor.middleware.fetch-source-query :as fetch-source-query]
[metabase.query-processor.middleware.format-rows :as format-rows]
[metabase.query-processor.middleware.large-int-id :as large-int-id]
[metabase.query-processor.middleware.limit :as limit]
[metabase.query-processor.middleware.pivot-export :as pivot-export]
[metabase.query-processor.middleware.results-metadata :as results-metadata]
@@ -39,7 +38,6 @@
#'qp.add-dimension-projections/remap-results
#'pivot-export/add-data-for-pivot-export
#'format-rows/format-rows
#'large-int-id/convert-id-to-string
#'viz-settings/update-viz-settings
#'qp.cumulative-aggregations/sum-cumulative-aggregation-columns
#'annotate/add-column-info

View File

@@ -69,8 +69,7 @@
(recur (dec retries)))))))
(def ^:private query-defaults
{:middleware {:add-default-userland-constraints? true
:js-int-to-string? true}})
{:middleware {:add-default-userland-constraints? true}})
(deftest basic-query-test
(testing "POST /api/dataset"
@@ -144,8 +143,7 @@
[:native [:map
[:query [:= "foobar"]]]]
[:middleware [:map
[:add-default-userland-constraints? [:= true]]
[:js-int-to-string? [:= true]]]]]]
[:add-default-userland-constraints? [:= true]]]]]]
[:database_id [:= (mt/id)]]
[:state [:= "42000"]]
[:class [:= "class org.h2.jdbc.JdbcSQLSyntaxErrorException"]]]

View File

@@ -761,8 +761,7 @@
(let [query (assoc
(mt/mbql-query venues)
:constraints nil
:middleware {:js-int-to-string? false
:ignore-cached-results? false
:middleware {:ignore-cached-results? false
:process-viz-settings? true
:format-rows? false}
:viz-settings {}

View File

@@ -377,14 +377,13 @@
:card_id card-id
:dashboard_id dashboard-id}]
(testing "No parameters -- ignore Dashboard default (#20493, #20503)"
;; [[metabase.query-processor.middleware.large-int-id]] middleware is converting the IDs to strings I guess
(is (= [["1" "Rustic Paper Wallet" "Gizmo"]
["2" "Small Marble Shoes" "Doohickey"]]
(is (= [[1 "Rustic Paper Wallet" "Gizmo"]
[2 "Small Marble Shoes" "Doohickey"]]
(mt/rows
(run-query-for-dashcard dashboard-id card-id dashcard-id)))))
(testing "Request parameters with :default -- ignore these as well (#20516)"
(is (= [["1" "Rustic Paper Wallet" "Gizmo"]
["2" "Small Marble Shoes" "Doohickey"]]
(is (= [[1 "Rustic Paper Wallet" "Gizmo"]
[2 "Small Marble Shoes" "Doohickey"]]
(mt/rows
(run-query-for-dashcard dashboard-id card-id dashcard-id
:parameters [{:name "Category"
@@ -393,8 +392,8 @@
:type "category"
:default ["Gizmo"]}])))))
(testing "Request parameters with :default and nil value"
(is (= [["1" "Rustic Paper Wallet" "Gizmo"]
["2" "Small Marble Shoes" "Doohickey"]]
(is (= [[1 "Rustic Paper Wallet" "Gizmo"]
[2 "Small Marble Shoes" "Doohickey"]]
(mt/rows
(run-query-for-dashcard dashboard-id card-id dashcard-id
:parameters [{:name "Category"

View File

@@ -1,136 +0,0 @@
(ns metabase.query-processor.middleware.large-int-id-test
(:require
[clojure.test :refer :all]
[metabase.query-processor :as qp]
[metabase.query-processor.middleware.large-int-id :as large-int-id]
[metabase.query-processor.store :as qp.store]
[metabase.test :as mt]))
(set! *warn-on-reflection* true)
(deftest ^:parallel convert-ids
(let [query (mt/mbql-query users
{:order-by [[:asc $id]]
:limit 5})]
(testing "PKs become strings when middleware enabled"
(is (= [["1" "Plato Yeshua" "2014-04-01T08:30:00Z"]
["2" "Felipinho Asklepios" "2014-12-05T15:15:00Z"]
["3" "Kaneonuskatew Eiran" "2014-11-06T16:15:00Z"]
["4" "Simcha Yan" "2014-01-01T08:30:00Z"]
["5" "Quentin Sören" "2014-10-03T17:30:00Z"]]
(mt/rows
(qp/process-query (assoc query :middleware {:js-int-to-string? true}))))))
(testing "PKs are left alone when middleware disabled (default)"
(is (= [[1 "Plato Yeshua" "2014-04-01T08:30:00Z"]
[2 "Felipinho Asklepios" "2014-12-05T15:15:00Z"]
[3 "Kaneonuskatew Eiran" "2014-11-06T16:15:00Z"]
[4 "Simcha Yan" "2014-01-01T08:30:00Z"]
[5 "Quentin Sören" "2014-10-03T17:30:00Z"]]
(mt/rows
(qp/process-query (assoc query :middleware {}))))))))
(deftest ^:parallel convert-ids-2
(let [query (mt/mbql-query users
{:fields [$name]
:order-by [[:asc $name]]
:limit 5})]
(testing "handle when there are no ID columns in the query but the middleware is enabled"
(is (= [["Broen Olujimi"]
["Conchúr Tihomir"]
["Dwight Gresham"]
["Felipinho Asklepios"]
["Frans Hevel"]]
(mt/rows
(qp/process-query (assoc query :middleware {:js-int-to-string? true}))))))))
(deftest ^:parallel convert-ids-3
(let [query (mt/mbql-query venues
{:order-by [[:asc $id]]
:limit 5})]
(testing "FKs become strings when middleware enabled"
(is (= [["1" "Red Medicine" "4" 10.0646 -165.374 3]
["2" "Stout Burgers & Beers" "11" 34.0996 -118.329 2]
["3" "The Apple Pan" "11" 34.0406 -118.428 2]
["4" "Wurstküche" "29" 33.9997 -118.465 2]
["5" "Brite Spot Family Restaurant" "20" 34.0778 -118.261 2]]
(mt/rows
(qp/process-query (assoc query :middleware {:js-int-to-string? true}))))))
(testing "FKs are left alone when middleware disabled (default)"
(is (= [[1 "Red Medicine" 4 10.0646 -165.374 3]
[2 "Stout Burgers & Beers" 11 34.0996 -118.329 2]
[3 "The Apple Pan" 11 34.0406 -118.428 2]
[4 "Wurstküche" 29 33.9997 -118.465 2]
[5 "Brite Spot Family Restaurant" 20 34.0778 -118.261 2]]
(mt/rows
(qp/process-query (assoc query :middleware {}))))))))
(deftest ^:parallel convert-ids-4
(let [query (mt/mbql-query checkins
{:fields [$id $user_id->users.id $user_id->users.name $venue_id->venues.id $venue_id->venues.name]
:order-by [[:asc $id]]
:limit 5})]
(testing "joins work correctly"
(is (= [["1" "5" "Quentin Sören" "12" "The Misfit Restaurant + Bar"]
["2" "1" "Plato Yeshua" "31" "Bludso's BBQ"]
["3" "8" "Szymon Theutrich" "56" "Philippe the Original"]
["4" "5" "Quentin Sören" "4" "Wurstküche"]
["5" "3" "Kaneonuskatew Eiran" "49" "Hotel Biron"]]
(mt/rows
(qp/process-query (assoc query :middleware {:js-int-to-string? true}))))))))
(deftest ^:parallel convert-ids-5
(let [query (mt/mbql-query venues
{:source-query {:source-table $$venues
:aggregation [[:aggregation-options
[:avg $id]
{:name "some_generated_name", :display-name "My Cool Ag"}]]
:breakout [$price]}})]
;; see comment in [[metabase.query-processor.middleware.large-int-id/convert-id-to-string]]
;; for why this value does not change
(testing "aggregations are not converted to strings with middleware enabled"
(is (= [[1 55]
[2 48]
[3 47]
[4 61]]
(mt/formatted-rows [int int]
(qp/process-query (assoc query :middleware {:js-int-to-string? true}))))))
(testing "aggregation does not convert to strings with middleware disabled (default)"
(is (= [[1 55]
[2 48]
[3 47]
[4 61]]
(mt/formatted-rows [int int]
(qp/process-query (assoc query :middleware {}))))))))
(defn- convert-id-to-string [rows]
(qp.store/with-metadata-provider (mt/id)
(let [query {:type :query
:query {:fields [[:field (mt/id :venues :id) nil]]}
:middleware {:js-int-to-string? true}}
rff (large-int-id/convert-id-to-string query (constantly conj))
rf (rff nil)]
(transduce identity rf rows))))
(deftest ^:parallel different-row-types-test
(testing "Middleware should work regardless of the type of each row (#13475)"
(doseq [rows [[[1]
[Integer/MAX_VALUE]]
[(list 1)
(list Integer/MAX_VALUE)]
[(cons 1 nil)
(cons Integer/MAX_VALUE nil)]
[(lazy-seq [1])
(lazy-seq [Integer/MAX_VALUE])]]]
(testing (format "rows = ^%s %s" (.getCanonicalName (class rows)) (pr-str rows))
(is (= [["1"]
["2147483647"]]
(convert-id-to-string rows)))))))
(deftest ^:parallel null-ids-as-strings
(testing "Middleware should convert NULL IDs to nil (#13957)"
(is (= [["1"]
["2147483647"]
[nil]]
(convert-id-to-string [[1]
[Integer/MAX_VALUE]
[nil]])))))

View File

@@ -75,7 +75,7 @@
(let [byytes (if (= export-format :api)
(mt/user-real-request :crowberto :post "dataset"
{:request-options {:as :byte-array}}
(assoc-in query [:middleware :js-int-to-string?] false))
query)
(mt/user-real-request :crowberto :post (format "dataset/%s" (name export-format))
{:request-options {:as :byte-array}}
:query (json/generate-string query)))]

View File

@@ -15336,6 +15336,11 @@ jsesc@~0.5.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
json-bigint-native@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/json-bigint-native/-/json-bigint-native-1.2.0.tgz#69c05d5681989cdeb29c709f77962d9ed2523e9d"
integrity sha512-qC9EtJsyULhbwC2KEYoR8sRsC+PH7VwwPdxU6+CZTZxMtM23zlxCfhIa+6Sn74FQ4VqDqWUaHaBeU0bMUTU9jQ==
json-buffer@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
@@ -15881,6 +15886,11 @@ lodash._topath@^3.0.0:
dependencies:
lodash.isarray "^3.0.0"
lodash.clonedeep@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"